fix: BIG-206 Reconcilate credit note and vendor credit with invoices should amount to credit be max the invoice remaining amount.

This commit is contained in:
a.bouhuolia
2021-12-30 15:39:22 +02:00
parent 0bd11419bb
commit 0173630e80
6 changed files with 105 additions and 13 deletions

View File

@@ -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 (
<ReconcileCreditNoteEditableTable

View File

@@ -58,7 +58,6 @@ function ReconcileCreditNoteForm({
...values,
entries: entries,
};
// Handle the request success.
const onSuccess = (response) => {
AppToaster.show({
@@ -68,7 +67,6 @@ function ReconcileCreditNoteForm({
setSubmitting(false);
closeDialog(dialogName);
};
// Handle the request error.
const onError = ({
response: {

View File

@@ -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);

View File

@@ -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 (
<div className={Classes.DIALOG_BODY}>
<Callout intent={Intent.PRIMARY}>
<p>
<T id={'reconcile_credit_note.alert.there_is_no_open_sale_invoices'} />
<T
id={'reconcile_credit_note.alert.there_is_no_open_sale_invoices'}
/>
</p>
</Callout>
</div>
@@ -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 = () => {
},
],
[],
)
}
);
};
/**
* 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)
: '',
};
});
});

View File

@@ -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 (
<ReconcileVendorCreditEditableTable
columns={columns}

View File

@@ -66,3 +66,13 @@ export const useReconcileVendorCreditTableColumns = () => {
[],
);
};
/**
* 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) : '',
}));
};