mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
feat: add discount fields in sale and purchase forms
This commit is contained in:
@@ -5,10 +5,14 @@ import {
|
||||
TotalLine,
|
||||
TotalLineBorderStyle,
|
||||
TotalLineTextStyle,
|
||||
FFormGroup,
|
||||
FInputGroup,
|
||||
FSelect,
|
||||
} from '@/components';
|
||||
import { useBillAggregatedTaxRates, useBillTotals } from './utils';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { TaxType } from '@/interfaces/TaxRates';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
|
||||
export function BillFormFooterRight() {
|
||||
const {
|
||||
@@ -37,6 +41,34 @@ export function BillFormFooterRight() {
|
||||
value={formattedSubtotal}
|
||||
borderStyle={TotalLineBorderStyle.None}
|
||||
/>
|
||||
|
||||
{/* ----------- Discount ----------- */}
|
||||
<FFormGroup name={'discount'} label={'Discount'} inline>
|
||||
<FInputGroup
|
||||
name={'discount'}
|
||||
rightElement={
|
||||
<FSelect
|
||||
name={'discount_type'}
|
||||
items={[
|
||||
{ text: 'USD', value: 'amount' },
|
||||
{ text: '%', value: 'percentage' },
|
||||
]}
|
||||
input={({ text }) => (
|
||||
<Button small minimal>
|
||||
{text}
|
||||
</Button>
|
||||
)}
|
||||
filterable={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
{/* ----------- Adjustment ----------- */}
|
||||
<FFormGroup name={'adjustment'} label={'Adjustment'} inline>
|
||||
<FInputGroup name={'adjustment'} />
|
||||
</FFormGroup>
|
||||
|
||||
{taxEntries.map((tax, index) => (
|
||||
<TotalLine
|
||||
key={index}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
repeatValue,
|
||||
orderingLinesIndexes,
|
||||
formattedAmount,
|
||||
toSafeNumber,
|
||||
} from '@/utils';
|
||||
import {
|
||||
updateItemsEntriesTotal,
|
||||
@@ -364,7 +365,27 @@ export const useBillSubtotal = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreives the bill total tax amount.
|
||||
* Retrieves the bill discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useBillDiscountAmount = () => {
|
||||
const { values } = useFormikContext();
|
||||
|
||||
return toSafeNumber(values.discount);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the bill adjustment amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useBillAdjustmentAmount = () => {
|
||||
const { values } = useFormikContext();
|
||||
|
||||
return toSafeNumber(values.adjustment);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the bill total tax amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useBillTotalTaxAmount = () => {
|
||||
@@ -389,15 +410,19 @@ export const useIsBillTaxExclusive = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreives the bill total.
|
||||
* Retrieves the bill total.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useBillTotal = () => {
|
||||
const subtotal = useBillSubtotal();
|
||||
const totalTaxAmount = useBillTotalTaxAmount();
|
||||
const isExclusiveTax = useIsBillTaxExclusive();
|
||||
const discountAmount = useBillDiscountAmount();
|
||||
const adjustmentAmount = useBillAdjustmentAmount();
|
||||
|
||||
return R.compose(R.when(R.always(isExclusiveTax), R.add(totalTaxAmount)))(
|
||||
subtotal,
|
||||
);
|
||||
return R.compose(
|
||||
R.when(R.always(isExclusiveTax), R.add(totalTaxAmount)),
|
||||
R.subtract(R.__, discountAmount),
|
||||
R.subtract(R.__, adjustmentAmount),
|
||||
)(subtotal);
|
||||
};
|
||||
|
||||
@@ -53,6 +53,9 @@ export const defaultVendorsCreditNote = {
|
||||
currency_code: '',
|
||||
entries: [...repeatValue(defaultCreditNoteEntry, MIN_LINES_NUMBER)],
|
||||
attachments: [],
|
||||
discount: '',
|
||||
discount_type: 'amount',
|
||||
adjustment: '',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -207,6 +210,75 @@ export const useVendorCrditNoteTotals = () => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the vendor credit subtotal.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useVendorCreditSubtotal = () => {
|
||||
const {
|
||||
values: { entries },
|
||||
} = useFormikContext();
|
||||
|
||||
// Retrieves the invoice entries total.
|
||||
const total = React.useMemo(() => getEntriesTotal(entries), [entries]);
|
||||
|
||||
return total;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the vendor credit discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useVendorCreditDiscountAmount = () => {
|
||||
const { values } = useFormikContext();
|
||||
|
||||
return toSafeNumber(values.discount);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the vendor credit adjustment amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useVendorCreditAdjustment = () => {
|
||||
const { values } = useFormikContext();
|
||||
|
||||
return toSafeNumber(values.adjustment);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the vendor credit total.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useVendorCreditTotal = () => {
|
||||
const subtotal = useVendorCreditSubtotal();
|
||||
const discountAmount = useVendorCreditDiscountAmount();
|
||||
const adjustment = useVendorCreditAdjustment();
|
||||
|
||||
return subtotal - discountAmount - adjustment;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the vendor credit formatted total.
|
||||
* @returns {string}
|
||||
*/
|
||||
export const useVendorCreditFormattedTotal = () => {
|
||||
const total = useVendorCreditTotal();
|
||||
const currencyCode = useCurrentOrganizationCurrencyCode();
|
||||
|
||||
return formattedAmount(total, currencyCode);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the vendor credit formatted subtotal.
|
||||
* @returns {string}
|
||||
*/
|
||||
export const useVendorCreditFormattedSubtotal = () => {
|
||||
const subtotal = useVendorCreditSubtotal();
|
||||
const currencyCode = useCurrentOrganizationCurrencyCode();
|
||||
|
||||
return formattedAmount(subtotal, currencyCode);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether the vendor note has foreign customer.
|
||||
* @returns {boolean}
|
||||
|
||||
@@ -1,28 +1,61 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
import {
|
||||
T,
|
||||
TotalLines,
|
||||
TotalLine,
|
||||
TotalLineBorderStyle,
|
||||
TotalLineTextStyle,
|
||||
FInputGroup,
|
||||
FFormGroup,
|
||||
FSelect,
|
||||
} from '@/components';
|
||||
import { useCreditNoteTotals } from './utils';
|
||||
import {
|
||||
useCreditNoteSubtotalFormatted,
|
||||
useCreditNoteTotalFormatted,
|
||||
} from './utils';
|
||||
|
||||
export function CreditNoteFormFooterRight() {
|
||||
const { formattedSubtotal, formattedTotal } = useCreditNoteTotals();
|
||||
const subtotalFormatted = useCreditNoteSubtotalFormatted();
|
||||
const totalFormatted = useCreditNoteTotalFormatted();
|
||||
|
||||
return (
|
||||
<CreditNoteTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
|
||||
<TotalLine
|
||||
title={<T id={'credit_note.label_subtotal'} />}
|
||||
value={formattedSubtotal}
|
||||
value={subtotalFormatted}
|
||||
borderStyle={TotalLineBorderStyle.None}
|
||||
/>
|
||||
<FFormGroup name={'discount'} label={'Discount'} inline>
|
||||
<FInputGroup
|
||||
name={'discount'}
|
||||
rightElement={
|
||||
<FSelect
|
||||
name={'discount_type'}
|
||||
items={[
|
||||
{ text: 'USD', value: 'amount' },
|
||||
{ text: '%', value: 'percentage' },
|
||||
]}
|
||||
input={({ text }) => (
|
||||
<Button small minimal>
|
||||
{text}
|
||||
</Button>
|
||||
)}
|
||||
filterable={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup name={'adjustment'} label={'Adjustment'} inline>
|
||||
<FInputGroup name={'adjustment'} />
|
||||
</FFormGroup>
|
||||
|
||||
<TotalLine
|
||||
title={<T id={'credit_note.label_total'} />}
|
||||
value={formattedTotal}
|
||||
value={totalFormatted}
|
||||
textStyle={TotalLineTextStyle.Bold}
|
||||
/>
|
||||
</CreditNoteTotalLines>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useFormikContext } from 'formik';
|
||||
import * as R from 'ramda';
|
||||
import { ExchangeRateInputGroup } from '@/components';
|
||||
import { useCurrentOrganization } from '@/hooks/state';
|
||||
import { useCreditNoteIsForeignCustomer, useCreditNoteTotals } from './utils';
|
||||
import { useCreditNoteIsForeignCustomer, useCreditNoteSubtotal } from './utils';
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
import { transactionNumber } from '@/utils';
|
||||
import {
|
||||
@@ -78,13 +78,13 @@ export const CreditNoteSyncIncrementSettingsToForm = R.compose(
|
||||
*/
|
||||
export const CreditNoteExchangeRateSync = R.compose(withDialogActions)(
|
||||
({ openDialog }) => {
|
||||
const { total } = useCreditNoteTotals();
|
||||
const subtotal = useCreditNoteSubtotal();
|
||||
const timeout = useRef();
|
||||
|
||||
useSyncExRateToForm({
|
||||
onSynced: () => {
|
||||
// If the total bigger then zero show alert to the user after adjusting entries.
|
||||
if (total > 0) {
|
||||
if (subtotal > 0) {
|
||||
clearTimeout(timeout.current);
|
||||
timeout.current = setTimeout(() => {
|
||||
openDialog(DialogsName.InvoiceExchangeRateChangeNotice);
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
repeatValue,
|
||||
formattedAmount,
|
||||
orderingLinesIndexes,
|
||||
toSafeNumber,
|
||||
} from '@/utils';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { useCreditNoteFormContext } from './CreditNoteFormProvider';
|
||||
@@ -57,6 +58,9 @@ export const defaultCreditNote = {
|
||||
entries: [...repeatValue(defaultCreditNoteEntry, MIN_LINES_NUMBER)],
|
||||
attachments: [],
|
||||
pdf_template_id: '',
|
||||
discount: '',
|
||||
discount_type: 'amount',
|
||||
adjustment: '',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -174,32 +178,74 @@ export const useSetPrimaryWarehouseToForm = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreives the credit note totals.
|
||||
* Retrieves the credit note subtotal.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useCreditNoteTotals = () => {
|
||||
export const useCreditNoteSubtotal = () => {
|
||||
const {
|
||||
values: { entries, currency_code: currencyCode },
|
||||
values: { entries },
|
||||
} = useFormikContext();
|
||||
|
||||
// Retrieves the invoice entries total.
|
||||
const total = React.useMemo(() => getEntriesTotal(entries), [entries]);
|
||||
|
||||
// Retrieves the formatted total money.
|
||||
const formattedTotal = React.useMemo(
|
||||
() => formattedAmount(total, currencyCode),
|
||||
[total, currencyCode],
|
||||
);
|
||||
// Retrieves the formatted subtotal.
|
||||
const formattedSubtotal = React.useMemo(
|
||||
() => formattedAmount(total, currencyCode, { money: false }),
|
||||
[total, currencyCode],
|
||||
);
|
||||
return total;
|
||||
};
|
||||
|
||||
return {
|
||||
total,
|
||||
formattedTotal,
|
||||
formattedSubtotal,
|
||||
};
|
||||
/**
|
||||
* Retrieves the credit note subtotal formatted.
|
||||
* @returns {string}
|
||||
*/
|
||||
export const useCreditNoteSubtotalFormatted = () => {
|
||||
const subtotal = useCreditNoteSubtotal();
|
||||
const { currency_code: currencyCode } = useFormikContext();
|
||||
|
||||
return formattedAmount(subtotal, currencyCode, { money: false });
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the credit note discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useCreditNoteDiscountAmount = () => {
|
||||
const { values } = useFormikContext();
|
||||
const subtotal = useCreditNoteSubtotal();
|
||||
const discount = toSafeNumber(values.discount);
|
||||
|
||||
return values?.discount_type === 'percentage'
|
||||
? (discount * subtotal) / 100
|
||||
: discount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the credit note adjustment amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useCreditNoteAdjustmentAmount = () => {
|
||||
const { values } = useFormikContext();
|
||||
|
||||
return toSafeNumber(values.adjustment);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the credit note total.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useCreditNoteTotal = () => {
|
||||
const subtotal = useCreditNoteSubtotal();
|
||||
const discountAmount = useCreditNoteDiscountAmount();
|
||||
const adjustmentAmount = useCreditNoteAdjustmentAmount();
|
||||
|
||||
return subtotal - discountAmount - adjustmentAmount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the credit note total formatted.
|
||||
* @returns {string}
|
||||
*/
|
||||
export const useCreditNoteTotalFormatted = () => {
|
||||
const total = useCreditNoteTotal();
|
||||
const { currency_code: currencyCode } = useFormikContext();
|
||||
|
||||
return formattedAmount(total, currencyCode);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -217,7 +263,6 @@ export const useCreditNoteIsForeignCustomer = () => {
|
||||
return isForeignCustomer;
|
||||
};
|
||||
|
||||
|
||||
export const useCreditNoteFormBrandingTemplatesOptions = () => {
|
||||
const { brandingTemplates } = useCreditNoteFormContext();
|
||||
|
||||
|
||||
@@ -7,8 +7,12 @@ import {
|
||||
TotalLine,
|
||||
TotalLineBorderStyle,
|
||||
TotalLineTextStyle,
|
||||
FInputGroup,
|
||||
FFormGroup,
|
||||
FSelect,
|
||||
} from '@/components';
|
||||
import { useEstimateTotals } from './utils';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
|
||||
export function EstimateFormFooterRight() {
|
||||
const { formattedSubtotal, formattedTotal } = useEstimateTotals();
|
||||
@@ -20,6 +24,32 @@ export function EstimateFormFooterRight() {
|
||||
value={formattedSubtotal}
|
||||
borderStyle={TotalLineBorderStyle.None}
|
||||
/>
|
||||
|
||||
<FFormGroup name={'discount'} label={'Discount'} inline>
|
||||
<FInputGroup
|
||||
name={'discount'}
|
||||
rightElement={
|
||||
<FSelect
|
||||
name={'discount_type'}
|
||||
items={[
|
||||
{ text: 'USD', value: 'amount' },
|
||||
{ text: '%', value: 'percentage' },
|
||||
]}
|
||||
input={({ text }) => (
|
||||
<Button small minimal>
|
||||
{text}
|
||||
</Button>
|
||||
)}
|
||||
filterable={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup name={'adjustment'} label={'Adjustment'} inline>
|
||||
<FInputGroup name={'adjustment'} />
|
||||
</FFormGroup>
|
||||
|
||||
<TotalLine
|
||||
title={<T id={'estimate_form.label.total'} />}
|
||||
value={formattedTotal}
|
||||
|
||||
@@ -63,6 +63,8 @@ export const defaultEstimate = {
|
||||
entries: [...repeatValue(defaultEstimateEntry, MIN_LINES_NUMBER)],
|
||||
attachments: [],
|
||||
pdf_template_id: '',
|
||||
discount: '',
|
||||
discount_type: 'amount'
|
||||
};
|
||||
|
||||
const ERRORS = {
|
||||
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
TotalLine,
|
||||
TotalLineBorderStyle,
|
||||
TotalLineTextStyle,
|
||||
FFormGroup,
|
||||
FInputGroup,
|
||||
FSelect,
|
||||
} from '@/components';
|
||||
import { useInvoiceAggregatedTaxRates } from './utils';
|
||||
import { TaxType } from '@/interfaces/TaxRates';
|
||||
@@ -18,6 +21,7 @@ import {
|
||||
InvoiceSubTotalFormatted,
|
||||
InvoiceTotalFormatted,
|
||||
} from './components';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
|
||||
export function InvoiceFormFooterRight() {
|
||||
const {
|
||||
@@ -38,6 +42,31 @@ export function InvoiceFormFooterRight() {
|
||||
}
|
||||
value={<InvoiceSubTotalFormatted />}
|
||||
/>
|
||||
<FFormGroup name={'discount'} label={'Discount'} inline>
|
||||
<FInputGroup
|
||||
name={'discount'}
|
||||
rightElement={
|
||||
<FSelect
|
||||
name={'discount_type'}
|
||||
items={[
|
||||
{ text: 'USD', value: 'amount' },
|
||||
{ text: '%', value: 'percentage' },
|
||||
]}
|
||||
input={({ text }) => (
|
||||
<Button small minimal>
|
||||
{text}
|
||||
</Button>
|
||||
)}
|
||||
filterable={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup name={'adjustment'} label={'Adjustment'} inline>
|
||||
<FInputGroup name={'adjustment'} />
|
||||
</FFormGroup>
|
||||
|
||||
{taxEntries.map((tax, index) => (
|
||||
<TotalLine
|
||||
key={index}
|
||||
|
||||
@@ -70,6 +70,9 @@ export const defaultInvoice = {
|
||||
entries: [...repeatValue(defaultInvoiceEntry, MIN_LINES_NUMBER)],
|
||||
attachments: [],
|
||||
payment_methods: {},
|
||||
discount: '',
|
||||
discount_type: 'amount',
|
||||
adjustment: '',
|
||||
};
|
||||
|
||||
// Invoice entry request schema.
|
||||
@@ -301,6 +304,20 @@ export const useInvoiceSubtotal = () => {
|
||||
return React.useMemo(() => getEntriesTotal(entries), [entries]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the invoice discount amount.
|
||||
* @returns {number}
|
||||
*/
|
||||
export const useInvoiceDiscountAmount = () => {
|
||||
const { values } = useFormikContext();
|
||||
const subtotal = useInvoiceSubtotal();
|
||||
const discount = parseFloat(values.discount);
|
||||
|
||||
return values?.discount_type === 'percentage'
|
||||
? (subtotal * discount) / 100
|
||||
: discount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether the invoice has foreign customer.
|
||||
* @returns {boolean}
|
||||
@@ -382,10 +399,12 @@ export const useInvoiceTotal = () => {
|
||||
const subtotal = useInvoiceSubtotal();
|
||||
const totalTaxAmount = useInvoiceTotalTaxAmount();
|
||||
const isExclusiveTax = useIsInvoiceTaxExclusive();
|
||||
const discountAmount = useInvoiceDiscountAmount();
|
||||
|
||||
return R.compose(R.when(R.always(isExclusiveTax), R.add(totalTaxAmount)))(
|
||||
subtotal,
|
||||
);
|
||||
return R.compose(
|
||||
R.when(R.always(isExclusiveTax), R.add(totalTaxAmount)),
|
||||
R.subtract(R.__, discountAmount),
|
||||
)(subtotal);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,8 +8,12 @@ import {
|
||||
TotalLine,
|
||||
TotalLineBorderStyle,
|
||||
TotalLineTextStyle,
|
||||
FInputGroup,
|
||||
FFormGroup,
|
||||
FSelect,
|
||||
} from '@/components';
|
||||
import { useReceiptTotals } from './utils';
|
||||
import { Button } from '@blueprintjs/core';
|
||||
|
||||
export function ReceiptFormFooterRight() {
|
||||
const {
|
||||
@@ -26,6 +30,31 @@ export function ReceiptFormFooterRight() {
|
||||
value={formattedSubtotal}
|
||||
borderStyle={TotalLineBorderStyle.None}
|
||||
/>
|
||||
<FFormGroup name={'discount'} label={'Discount'} inline>
|
||||
<FInputGroup
|
||||
name={'discount'}
|
||||
rightElement={
|
||||
<FSelect
|
||||
name={'discount_type'}
|
||||
items={[
|
||||
{ text: 'USD', value: 'amount' },
|
||||
{ text: '%', value: 'percentage' },
|
||||
]}
|
||||
input={({ text }) => (
|
||||
<Button small minimal>
|
||||
{text}
|
||||
</Button>
|
||||
)}
|
||||
filterable={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup name={'adjustment'} label={'Adjustment'} inline>
|
||||
<FInputGroup name={'adjustment'} />
|
||||
</FFormGroup>
|
||||
|
||||
<TotalLine
|
||||
title={<T id={'receipt_form.label.total'} />}
|
||||
value={formattedTotal}
|
||||
|
||||
Reference in New Issue
Block a user