feat: logic of excess amount confirmation

This commit is contained in:
Ahmed Bouhuolia
2024-07-24 22:33:26 +02:00
parent f3af3843dd
commit 3e2997d745
13 changed files with 185 additions and 51 deletions

View File

@@ -2,7 +2,7 @@
import React, { useMemo } from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { Formik, Form } from 'formik';
import { Formik, Form, FormikHelpers } from 'formik';
import { Intent } from '@blueprintjs/core';
import { sumBy, defaultTo } from 'lodash';
import { useHistory } from 'react-router-dom';
@@ -33,6 +33,7 @@ import {
transformToEditForm,
transformErrors,
transformFormToRequest,
getPaymentExcessAmountFromValues,
} from './utils';
/**
@@ -59,6 +60,7 @@ function PaymentMadeForm({
submitPayload,
createPaymentMadeMutate,
editPaymentMadeMutate,
isExcessConfirmed,
} = usePaymentMadeFormContext();
// Form initial values.
@@ -81,13 +83,11 @@ function PaymentMadeForm({
// Handle the form submit.
const handleSubmitForm = (
values,
{ setSubmitting, resetForm, setFieldError },
{ setSubmitting, resetForm, setFieldError }: FormikHelpers<any>,
) => {
setSubmitting(true);
// Total payment amount of entries.
const totalPaymentAmount = sumBy(values.entries, 'payment_amount');
if (totalPaymentAmount <= 0) {
if (values.amount <= 0) {
AppToaster.show({
message: intl.get('you_cannot_make_payment_with_zero_total_amount'),
intent: Intent.DANGER,
@@ -95,6 +95,16 @@ function PaymentMadeForm({
setSubmitting(false);
return;
}
const excessAmount = getPaymentExcessAmountFromValues(values);
// Show the confirmation popup if the excess amount bigger than zero and
// has not been confirmed yet.
if (excessAmount > 0 && !isExcessConfirmed) {
openDialog('payment-made-excessed-payment');
setSubmitting(false);
return;
}
// Transformes the form values to request body.
const form = transformFormToRequest(values);
@@ -124,11 +134,12 @@ function PaymentMadeForm({
}
setSubmitting(false);
};
if (!isNewMode) {
editPaymentMadeMutate([paymentMadeId, form]).then(onSaved).catch(onError);
return editPaymentMadeMutate([paymentMadeId, form])
.then(onSaved)
.catch(onError);
} else {
createPaymentMadeMutate(form).then(onSaved).catch(onError);
return createPaymentMadeMutate(form).then(onSaved).catch(onError);
}
};

View File

@@ -1,17 +1,23 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import { useFormikContext } from 'formik';
import {
T,
TotalLines,
TotalLine,
TotalLineBorderStyle,
TotalLineTextStyle,
FormatNumber,
} from '@/components';
import { usePaymentMadeTotals } from './utils';
import { usePaymentMadeExcessAmount, usePaymentMadeTotals } from './utils';
export function PaymentMadeFormFooterRight() {
const { formattedSubtotal, formattedTotal } = usePaymentMadeTotals();
const excessAmount = usePaymentMadeExcessAmount();
const {
values: { currency_code: currencyCode },
} = useFormikContext();
return (
<PaymentMadeTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
@@ -25,6 +31,11 @@ export function PaymentMadeFormFooterRight() {
value={formattedTotal}
textStyle={TotalLineTextStyle.Bold}
/>
<TotalLine
title={'Excess Amount'}
value={<FormatNumber value={excessAmount} currency={currencyCode} />}
textStyle={TotalLineTextStyle.Regular}
/>
</PaymentMadeTotalLines>
);
}

View File

@@ -1,12 +1,12 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import React from 'react';
import classNames from 'classnames';
import { useFormikContext } from 'formik';
import { sumBy } from 'lodash';
import { CLASSES } from '@/constants/classes';
import { Money, FormattedMessage as T } from '@/components';
import PaymentMadeFormHeaderFields from './PaymentMadeFormHeaderFields';
import { usePaymentmadeTotalAmount } from './utils';
/**
* Payment made header form.
@@ -14,11 +14,10 @@ import PaymentMadeFormHeaderFields from './PaymentMadeFormHeaderFields';
function PaymentMadeFormHeader() {
// Formik form context.
const {
values: { entries, currency_code },
values: { currency_code },
} = useFormikContext();
// Calculate the payment amount of the entries.
const amountPaid = useMemo(() => sumBy(entries, 'payment_amount'), [entries]);
const totalAmount = usePaymentmadeTotalAmount();
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
@@ -31,7 +30,7 @@ function PaymentMadeFormHeader() {
<T id={'amount_received'} />
</span>
<h1 class="big-amount__number">
<Money amount={amountPaid} currency={currency_code} />
<Money amount={totalAmount} currency={currency_code} />
</h1>
</div>
</div>

View File

@@ -68,7 +68,7 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) {
const fullAmount = safeSumBy(newEntries, 'payment_amount');
setFieldValue('entries', newEntries);
setFieldValue('full_amount', fullAmount);
setFieldValue('amount', fullAmount);
};
// Handles the full-amount field blur.
@@ -129,14 +129,14 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) {
className={('form-group--full-amount', Classes.FILL)}
intent={inputIntent({ error, touched })}
labelInfo={<Hint />}
helperText={<ErrorMessage name="full_amount" />}
helperText={<ErrorMessage name="amount" />}
>
<ControlGroup>
<InputPrependText text={currency_code} />
<MoneyInputGroup
value={value}
onChange={(value) => {
setFieldValue('full_amount', value);
setFieldValue('amount', value);
}}
onBlurValue={onFullAmountBlur}
/>

View File

@@ -1,5 +1,5 @@
// @ts-nocheck
import React, { createContext, useContext } from 'react';
import React, { createContext, useContext, useState } from 'react';
import { Features } from '@/constants';
import { useFeatureCan } from '@/hooks/state';
import {
@@ -71,6 +71,8 @@ function PaymentMadeFormProvider({ query, paymentMadeId, ...props }) {
const isFeatureLoading = isBranchesLoading;
const [isExcessConfirmed, setIsExcessConfirmed] = useState<boolean>(false);
// Provider payload.
const provider = {
paymentMadeId,
@@ -98,6 +100,9 @@ function PaymentMadeFormProvider({ query, paymentMadeId, ...props }) {
setSubmitPayload,
setPaymentVendorId,
isExcessConfirmed,
setIsExcessConfirmed,
};
return (

View File

@@ -23,14 +23,24 @@ const Schema = Yup.object().shape({
const DEFAULT_ACCOUNT_SLUG = 'depreciation-expense';
function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) {
const { setFieldValue } = useFormikContext();
const {
setFieldValue,
submitForm,
} = useFormikContext();
const { setIsExcessConfirmed } = usePaymentMadeFormContext();
const handleSubmit = (
values: ExcessPaymentValues,
{ setSubmitting }: FormikHelpers<ExcessPaymentValues>,
) => {
closeDialog(dialogName);
setFieldValue(values.accountId);
setSubmitting(true);
setIsExcessConfirmed(true);
return submitForm().then(() => {
setSubmitting(false);
closeDialog(dialogName);
});
};
// Handle close button click.
@@ -66,7 +76,7 @@ interface ExcessPaymentDialogContentFormProps {
function ExcessPaymentDialogContentForm({
onClose,
}: ExcessPaymentDialogContentFormProps) {
const { submitForm } = useFormikContext();
const { submitForm, isSubmitting } = useFormikContext();
const { accounts } = usePaymentMadeFormContext();
const handleCloseBtn = () => {
@@ -94,7 +104,11 @@ function ExcessPaymentDialogContentForm({
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button intent={Intent.PRIMARY} onClick={() => submitForm()}>
<Button
intent={Intent.PRIMARY}
loading={isSubmitting}
onClick={() => submitForm()}
>
Continue to Payment
</Button>
<Button onClick={handleCloseBtn}>Cancel</Button>

View File

@@ -37,7 +37,7 @@ export const defaultPaymentMadeEntry = {
// Default initial values of payment made.
export const defaultPaymentMade = {
full_amount: '',
amount: '',
vendor_id: '',
payment_account_id: '',
payment_date: moment(new Date()).format('YYYY-MM-DD'),
@@ -53,10 +53,10 @@ export const defaultPaymentMade = {
export const transformToEditForm = (paymentMade, paymentMadeEntries) => {
const attachments = transformAttachmentsToForm(paymentMade);
const appliedAmount = safeSumBy(paymentMadeEntries, 'payment_amount');
return {
...transformToForm(paymentMade, defaultPaymentMade),
full_amount: safeSumBy(paymentMadeEntries, 'payment_amount'),
entries: [
...paymentMadeEntries.map((paymentMadeEntry) => ({
...transformToForm(paymentMadeEntry, defaultPaymentMadeEntry),
@@ -177,6 +177,30 @@ export const usePaymentMadeTotals = () => {
};
};
export const usePaymentmadeTotalAmount = () => {
const {
values: { amount },
} = useFormikContext();
return amount;
};
export const usePaymentMadeAppliedAmount = () => {
const {
values: { entries },
} = useFormikContext();
// Retrieves the invoice entries total.
return React.useMemo(() => sumBy(entries, 'payment_amount'), [entries]);
};
export const usePaymentMadeExcessAmount = () => {
const appliedAmount = usePaymentMadeAppliedAmount();
const totalAmount = usePaymentmadeTotalAmount();
return Math.abs(totalAmount - appliedAmount);
};
/**
* Detarmines whether the bill has foreign customer.
* @returns {boolean}
@@ -191,3 +215,10 @@ export const usePaymentMadeIsForeignCustomer = () => {
);
return isForeignCustomer;
};
export const getPaymentExcessAmountFromValues = (values) => {
const appliedAmount = sumBy(values.entries, 'payment_amount');
const totalAmount = values.amount;
return Math.abs(totalAmount - appliedAmount);
};

View File

@@ -1,5 +1,5 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import React, { useMemo, useRef } from 'react';
import { sumBy, isEmpty, defaultTo } from 'lodash';
import intl from 'react-intl-universal';
import classNames from 'classnames';
@@ -37,6 +37,7 @@ import {
transformFormToRequest,
transformErrors,
resetFormState,
getExceededAmountFromValues,
} from './utils';
import { PaymentReceiveSyncIncrementSettingsToForm } from './components';
@@ -54,7 +55,7 @@ function PaymentReceiveForm({
organization: { base_currency },
// #withDialogActions
openDialog
openDialog,
}) {
const history = useHistory();
@@ -67,6 +68,7 @@ function PaymentReceiveForm({
submitPayload,
editPaymentReceiveMutate,
createPaymentReceiveMutate,
isExcessConfirmed,
} = usePaymentReceiveFormContext();
// Payment receive number.
@@ -98,19 +100,21 @@ function PaymentReceiveForm({
preferredDepositAccount,
],
);
// Handle form submit.
const handleSubmitForm = (
values,
{ setSubmitting, resetForm, setFieldError },
) => {
setSubmitting(true);
const exceededAmount = getExceededAmountFromValues(values);
if (true) {
// Show the confirm popup if the excessed amount bigger than zero and
// excess confirmation has not been confirmed yet.
if (exceededAmount > 0 && !isExcessConfirmed) {
setSubmitting(false);
openDialog('payment-received-excessed-payment');
return;
}
// Transformes the form values to request body.
const form = transformFormToRequest(values);
@@ -146,11 +150,11 @@ function PaymentReceiveForm({
};
if (paymentReceiveId) {
editPaymentReceiveMutate([paymentReceiveId, form])
return editPaymentReceiveMutate([paymentReceiveId, form])
.then(onSaved)
.catch(onError);
} else {
createPaymentReceiveMutate(form).then(onSaved).catch(onError);
return createPaymentReceiveMutate(form).then(onSaved).catch(onError);
}
};
return (
@@ -200,5 +204,5 @@ export default compose(
preferredDepositAccount: paymentReceiveSettings?.preferredDepositAccount,
})),
withCurrentOrganization(),
withDialogActions
withDialogActions,
)(PaymentReceiveForm);

View File

@@ -7,11 +7,16 @@ import {
TotalLine,
TotalLineBorderStyle,
TotalLineTextStyle,
FormatNumber,
} from '@/components';
import { usePaymentReceiveTotals } from './utils';
import {
usePaymentReceiveTotals,
usePaymentReceivedTotalExceededAmount,
} from './utils';
export function PaymentReceiveFormFootetRight() {
const { formattedSubtotal, formattedTotal } = usePaymentReceiveTotals();
const exceededAmount = usePaymentReceivedTotalExceededAmount();
return (
<PaymentReceiveTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
@@ -25,6 +30,11 @@ export function PaymentReceiveFormFootetRight() {
value={formattedTotal}
textStyle={TotalLineTextStyle.Bold}
/>
<TotalLine
title={'Exceeded Amount'}
value={<FormatNumber value={exceededAmount} />}
textStyle={TotalLineTextStyle.Regular}
/>
</PaymentReceiveTotalLines>
);
}

View File

@@ -30,15 +30,9 @@ function PaymentReceiveFormHeader() {
function PaymentReceiveFormBigTotal() {
// Formik form context.
const {
values: { currency_code, entries },
values: { currency_code, amount },
} = useFormikContext();
// Calculates the total payment amount from due amount.
const paymentFullAmount = useMemo(
() => sumBy(entries, 'payment_amount'),
[entries],
);
return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_BIG_NUMBERS)}>
<div class="big-amount">
@@ -46,7 +40,7 @@ function PaymentReceiveFormBigTotal() {
<T id={'amount_received'} />
</span>
<h1 class="big-amount__number">
<Money amount={paymentFullAmount} currency={currency_code} />
<Money amount={amount} currency={currency_code} />
</h1>
</div>
</div>

View File

@@ -1,5 +1,5 @@
// @ts-nocheck
import React, { createContext, useContext } from 'react';
import React, { createContext, useContext, useState } from 'react';
import { Features } from '@/constants';
import { useFeatureCan } from '@/hooks/state';
import { DashboardInsider } from '@/components';
@@ -74,6 +74,8 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
const { mutateAsync: editPaymentReceiveMutate } = useEditPaymentReceive();
const { mutateAsync: createPaymentReceiveMutate } = useCreatePaymentReceive();
const [isExcessConfirmed, setIsExcessConfirmed] = useState<boolean>(false);
// Provider payload.
const provider = {
paymentReceiveId,
@@ -97,6 +99,9 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
editPaymentReceiveMutate,
createPaymentReceiveMutate,
isExcessConfirmed,
setIsExcessConfirmed,
};
return (

View File

@@ -7,6 +7,7 @@ import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
import { AccountsSelect, FFormGroup } from '@/components';
import { usePaymentReceiveFormContext } from '../../PaymentReceiveFormProvider';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { usePaymentReceivedTotalExceededAmount } from '../../utils';
interface ExcessPaymentValues {
accountId: string;
@@ -23,15 +24,23 @@ const Schema = Yup.object().shape({
const DEFAULT_ACCOUNT_SLUG = 'depreciation-expense';
export function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) {
const { setFieldValue } = useFormikContext();
const { setFieldValue, submitForm } = useFormikContext();
const { setIsExcessConfirmed } = usePaymentReceiveFormContext();
const initialAccountId = useDefaultExcessPaymentDeposit();
const exceededAmount = usePaymentReceivedTotalExceededAmount();
const handleSubmit = (
values: ExcessPaymentValues,
{ setSubmitting }: FormikHelpers<ExcessPaymentValues>,
) => {
closeDialog(dialogName);
setSubmitting(true);
setIsExcessConfirmed(true);
setFieldValue('unearned_revenue_account_id', values.accountId);
submitForm().then(() => {
closeDialog(dialogName);
setSubmitting(false);
});
};
const handleClose = () => {
closeDialog(dialogName);
@@ -47,7 +56,10 @@ export function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) {
onSubmit={handleSubmit}
>
<Form>
<ExcessPaymentDialogContentForm onClose={handleClose} />
<ExcessPaymentDialogContentForm
exceededAmount={exceededAmount}
onClose={handleClose}
/>
</Form>
</Formik>
);
@@ -57,9 +69,9 @@ export const ExcessPaymentDialogContent = R.compose(withDialogActions)(
ExcessPaymentDialogContentRoot,
);
function ExcessPaymentDialogContentForm({ onClose }) {
function ExcessPaymentDialogContentForm({ onClose, exceededAmount }) {
const { accounts } = usePaymentReceiveFormContext();
const { submitForm } = useFormikContext();
const { submitForm, isSubmitting } = useFormikContext();
const handleCloseBtn = () => {
onClose && onClose();
@@ -69,8 +81,9 @@ function ExcessPaymentDialogContentForm({ onClose }) {
<>
<div className={Classes.DIALOG_BODY}>
<p>
Would you like to record the excess amount of $1000 as advanced
payment from the customer.
Would you like to record the excess amount of{' '}
<strong>{exceededAmount}</strong> as advanced payment from the
customer.
</p>
<FFormGroup
@@ -87,7 +100,12 @@ function ExcessPaymentDialogContentForm({ onClose }) {
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button intent={Intent.PRIMARY} onClick={() => submitForm()}>
<Button
intent={Intent.PRIMARY}
loading={isSubmitting}
disabled={isSubmitting}
onClick={() => submitForm()}
>
Continue to Payment
</Button>
<Button onClick={handleCloseBtn}>Cancel</Button>

View File

@@ -250,6 +250,30 @@ export const usePaymentReceiveTotals = () => {
};
};
export const usePaymentReceivedTotalAppliedAmount = () => {
const {
values: { entries },
} = useFormikContext();
// Retrieves the invoice entries total.
return React.useMemo(() => sumBy(entries, 'payment_amount'), [entries]);
};
export const usePaymentReceivedTotalAmount = () => {
const {
values: { amount },
} = useFormikContext();
return amount;
};
export const usePaymentReceivedTotalExceededAmount = () => {
const totalAmount = usePaymentReceivedTotalAmount();
const totalApplied = usePaymentReceivedTotalAppliedAmount();
return Math.abs(totalAmount - totalApplied);
};
/**
* Detarmines whether the payment has foreign customer.
* @returns {boolean}
@@ -274,3 +298,11 @@ export const resetFormState = ({ initialValues, values, resetForm }) => {
},
});
};
export const getExceededAmountFromValues = (values) => {
const totalApplied = sumBy(values.entries, 'payment_amount');
const totalAmount = values.amount;
return totalAmount - totalApplied;
}