From ffb06f51944087346ec7e1da66c5b2bd03623c98 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 30 Nov 2024 18:02:50 +0200 Subject: [PATCH] feat: add discount fields in sale and purchase forms --- .../Bills/BillForm/BillFormFooterRight.tsx | 32 +++++++ .../Purchases/Bills/BillForm/utils.tsx | 35 ++++++-- .../CreditNotes/CreditNoteForm/utils.tsx | 72 +++++++++++++++ .../CreditNoteFormFooterRight.tsx | 41 ++++++++- .../CreditNotes/CreditNoteForm/components.tsx | 6 +- .../CreditNotes/CreditNoteForm/utils.tsx | 87 ++++++++++++++----- .../EstimateForm/EstimateFormFooterRight.tsx | 30 +++++++ .../Sales/Estimates/EstimateForm/utils.tsx | 2 + .../InvoiceForm/InvoiceFormFooterRight.tsx | 29 +++++++ .../Sales/Invoices/InvoiceForm/utils.tsx | 25 +++++- .../ReceiptForm/ReceiptFormFooterRight.tsx | 29 +++++++ 11 files changed, 352 insertions(+), 36 deletions(-) diff --git a/packages/webapp/src/containers/Purchases/Bills/BillForm/BillFormFooterRight.tsx b/packages/webapp/src/containers/Purchases/Bills/BillForm/BillFormFooterRight.tsx index 0657299d6..5300bb9ed 100644 --- a/packages/webapp/src/containers/Purchases/Bills/BillForm/BillFormFooterRight.tsx +++ b/packages/webapp/src/containers/Purchases/Bills/BillForm/BillFormFooterRight.tsx @@ -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 ----------- */} + + ( + + )} + filterable={false} + /> + } + /> + + + {/* ----------- Adjustment ----------- */} + + + + {taxEntries.map((tax, index) => ( { }; /** - * 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); }; diff --git a/packages/webapp/src/containers/Purchases/CreditNotes/CreditNoteForm/utils.tsx b/packages/webapp/src/containers/Purchases/CreditNotes/CreditNoteForm/utils.tsx index a4ce4c66a..cd5bf031f 100644 --- a/packages/webapp/src/containers/Purchases/CreditNotes/CreditNoteForm/utils.tsx +++ b/packages/webapp/src/containers/Purchases/CreditNotes/CreditNoteForm/utils.tsx @@ -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} diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormFooterRight.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormFooterRight.tsx index 7aa4a9fea..4e245e030 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormFooterRight.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormFooterRight.tsx @@ -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 ( } - value={formattedSubtotal} + value={subtotalFormatted} borderStyle={TotalLineBorderStyle.None} /> + + ( + + )} + filterable={false} + /> + } + /> + + + + + + } - value={formattedTotal} + value={totalFormatted} textStyle={TotalLineTextStyle.Bold} /> diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/components.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/components.tsx index afe139f48..ed1aafe3d 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/components.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/components.tsx @@ -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); diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/utils.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/utils.tsx index fad59288f..4ebf087cc 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/utils.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/utils.tsx @@ -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(); diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormFooterRight.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormFooterRight.tsx index 93135fdd2..3b157d3fd 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormFooterRight.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormFooterRight.tsx @@ -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} /> + + + ( + + )} + filterable={false} + /> + } + /> + + + + + + } value={formattedTotal} diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/utils.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/utils.tsx index 355c7619e..95c926446 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/utils.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/utils.tsx @@ -63,6 +63,8 @@ export const defaultEstimate = { entries: [...repeatValue(defaultEstimateEntry, MIN_LINES_NUMBER)], attachments: [], pdf_template_id: '', + discount: '', + discount_type: 'amount' }; const ERRORS = { diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormFooterRight.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormFooterRight.tsx index 68bc36872..950575c15 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormFooterRight.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormFooterRight.tsx @@ -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={} /> + + ( + + )} + filterable={false} + /> + } + /> + + + + + + {taxEntries.map((tax, index) => ( { 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); }; /** diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFooterRight.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFooterRight.tsx index 87f4bb896..76c2698d3 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFooterRight.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFooterRight.tsx @@ -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} /> + + ( + + )} + filterable={false} + /> + } + /> + + + + + + } value={formattedTotal}