diff --git a/src/containers/Dialogs/ReconcileCreditNoteDialog/ReconcileCreditNoteEntriesTable.js b/src/containers/Dialogs/ReconcileCreditNoteDialog/ReconcileCreditNoteEntriesTable.js index 22d86f362..b9ae7cb86 100644 --- a/src/containers/Dialogs/ReconcileCreditNoteDialog/ReconcileCreditNoteEntriesTable.js +++ b/src/containers/Dialogs/ReconcileCreditNoteDialog/ReconcileCreditNoteEntriesTable.js @@ -1,9 +1,16 @@ import React from 'react'; import styled from 'styled-components'; +import { defaultTo } from 'lodash'; import { DataTableEditable } from 'components'; import { compose, updateTableCell } from 'utils'; -import { useReconcileCreditNoteTableColumns } from './utils'; +import { useDeepCompareEffect } from 'hooks/utils'; +import { + useReconcileCreditNoteTableColumns, + maxAmountCreditFromRemaining, + maxCreditNoteAmountEntries, +} from './utils'; +import { useReconcileCreditNoteContext } from './ReconcileCreditNoteFormProvider'; /** * Reconcile credit note entries table. @@ -16,6 +23,11 @@ export default function ReconcileCreditNoteEntriesTable({ // Retrieve the reconcile credit note table columns. const columns = useReconcileCreditNoteTableColumns(); + // Reconcile credit note context provider. + const { + creditNote: { credits_remaining }, + } = useReconcileCreditNoteContext(); + // Handle update data. const handleUpdateData = React.useCallback( (rowIndex, columnId, value) => { @@ -26,6 +38,15 @@ export default function ReconcileCreditNoteEntriesTable({ }, [onUpdateData, entries], ); + // Deep compare entries to modify new entries. + useDeepCompareEffect(() => { + const newRows = compose( + maxCreditNoteAmountEntries(defaultTo(credits_remaining, 0)), + maxAmountCreditFromRemaining, + )(entries); + + onUpdateData(newRows); + }, [entries]); return ( { AppToaster.show({ @@ -68,7 +67,6 @@ function ReconcileCreditNoteForm({ setSubmitting(false); closeDialog(dialogName); }; - // Handle the request error. const onError = ({ response: { diff --git a/src/containers/Dialogs/ReconcileCreditNoteDialog/ReconcileCreditNoteFormFields.js b/src/containers/Dialogs/ReconcileCreditNoteDialog/ReconcileCreditNoteFormFields.js index df1b34c51..8820c75dd 100644 --- a/src/containers/Dialogs/ReconcileCreditNoteDialog/ReconcileCreditNoteFormFields.js +++ b/src/containers/Dialogs/ReconcileCreditNoteDialog/ReconcileCreditNoteFormFields.js @@ -57,10 +57,12 @@ export default function ReconcileCreditNoteFormFields() { * @returns {React.JSX} */ function ReconcileCreditNoteTotalLines() { + // Reconcile credit note context. const { creditNote: { credits_remaining, currency_code }, } = useReconcileCreditNoteContext(); + // Formik form context. const { values } = useFormikContext(); // Calculate the total amount of credit entries. @@ -68,7 +70,6 @@ function ReconcileCreditNoteTotalLines() { () => getEntriesTotal(values.entries), [values.entries], ); - // Calculate the total amount of credit remaining. const creditsRemaining = subtract(credits_remaining, totalAmount); diff --git a/src/containers/Dialogs/ReconcileCreditNoteDialog/utils.js b/src/containers/Dialogs/ReconcileCreditNoteDialog/utils.js index 9b9837412..100322f1b 100644 --- a/src/containers/Dialogs/ReconcileCreditNoteDialog/utils.js +++ b/src/containers/Dialogs/ReconcileCreditNoteDialog/utils.js @@ -1,35 +1,41 @@ import React from 'react'; import intl from 'react-intl-universal'; import { Callout, Intent, Classes } from '@blueprintjs/core'; +import * as R from 'ramda'; import clsx from 'classnames'; import { CLASSES } from 'common/classes'; -import { MoneyFieldCell, FormatDateCell, AppToaster, T } from 'components'; +import { MoneyFieldCell, FormatDateCell, AppToaster, T } from 'components'; export const transformErrors = (errors, { setErrors }) => { if (errors.some((e) => e.type === 'INVOICES_HAS_NO_REMAINING_AMOUNT')) { AppToaster.show({ - message: 'INVOICES_HAS_NO_REMAINING_AMOUNT', + message: 'The amount credit from the given invoice has no remaining amount.', intent: Intent.DANGER, }); } - if ( errors.find((error) => error.type === 'CREDIT_NOTE_HAS_NO_REMAINING_AMOUNT') ) { AppToaster.show({ - message: 'CREDIT_NOTE_HAS_NO_REMAINING_AMOUNT', + message: 'The total amount bigger than from remaining credit note amount', intent: Intent.DANGER, }); } }; +/** + * Empty status callout. + * @returns {React.JSX} + */ export function EmptyStatuCallout() { return (

- +

@@ -38,7 +44,7 @@ export function EmptyStatuCallout() { /** * Retrieves reconcile credit note table columns. - * @returns + * @returns */ export const useReconcileCreditNoteTableColumns = () => { return React.useMemo( @@ -80,5 +86,34 @@ export const useReconcileCreditNoteTableColumns = () => { }, ], [], - ) -} \ No newline at end of file + ); +}; + +/** + * Sets max credit amount from sale invoicue balance. + */ +export const maxAmountCreditFromRemaining = (entries) => { + return entries.map((entry) => ({ + ...entry, + amount: entry.amount ? Math.min(entry.balance, entry.amount) : '', + })); +}; + +/** + * Adjusts entries amount based on the given total. + */ +export const maxCreditNoteAmountEntries = R.curry((total, entries) => { + let balance = total; + + return entries.map((entry) => { + const oldBalance = balance; + balance -= entry.amount ? entry.amount : 0; + + return { + ...entry, + amount: entry.amount + ? Math.max(Math.min(entry.amount, oldBalance), 0) + : '', + }; + }); +}); diff --git a/src/containers/Dialogs/ReconcileVendorCreditDialog/ReconcileVendorCreditEntriesTable.js b/src/containers/Dialogs/ReconcileVendorCreditDialog/ReconcileVendorCreditEntriesTable.js index 07965a5aa..0155374ff 100644 --- a/src/containers/Dialogs/ReconcileVendorCreditDialog/ReconcileVendorCreditEntriesTable.js +++ b/src/containers/Dialogs/ReconcileVendorCreditDialog/ReconcileVendorCreditEntriesTable.js @@ -1,10 +1,22 @@ import React from 'react'; import styled from 'styled-components'; +import * as R from 'ramda'; +import { defaultTo } from 'lodash'; + +import { useDeepCompareEffect } from 'hooks/utils'; import { DataTableEditable } from 'components'; import { compose, updateTableCell } from 'utils'; -import { useReconcileVendorCreditTableColumns } from './utils'; +import { + useReconcileVendorCreditTableColumns, + maxAmountCreditFromRemaining, +} from './utils'; +import { maxCreditNoteAmountEntries } from '../ReconcileCreditNoteDialog/utils'; +import { useReconcileVendorCreditContext } from './ReconcileVendorCreditFormProvider'; +/** + * Reconcile vendor credit entries table. + */ export default function ReconcileVendorCreditEntriesTable({ onUpdateData, entries, @@ -13,6 +25,11 @@ export default function ReconcileVendorCreditEntriesTable({ // Reconcile vendor credit table columns. const columns = useReconcileVendorCreditTableColumns(); + // Reconcile vendor credit context. + const { + vendorCredit: { credits_remaining }, + } = useReconcileVendorCreditContext(); + // Handle update data. const handleUpdateData = React.useCallback( (rowIndex, columnId, value) => { @@ -24,6 +41,16 @@ export default function ReconcileVendorCreditEntriesTable({ [onUpdateData, entries], ); + // Watches deeply entries to compose a new entries. + useDeepCompareEffect(() => { + const newEntries = R.compose( + maxCreditNoteAmountEntries(defaultTo(credits_remaining, 0)), + maxAmountCreditFromRemaining, + )(entries); + + onUpdateData(newEntries); + }, [entries]); + return ( { [], ); }; + +/** + * Sets max amount credit from purchase due amount. + */ +export const maxAmountCreditFromRemaining = (entries) => { + return entries.map((entry) => ({ + ...entry, + amount: entry.amount ? Math.min(entry.due_amount, entry.amount) : '', + })); +};