mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
: '',
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) : '',
|
||||
}));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user