From 6d17f9cbebf4e660493e8ee9d84f50390b0fa3ba Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 25 Jul 2024 18:46:24 +0200 Subject: [PATCH] feat: record excessed payments as credit --- .../controllers/Purchases/BillsPayments.ts | 3 +- .../api/controllers/Sales/PaymentReceives.ts | 4 +- .../core/20190423085242_seed_accounts.ts | 3 +- .../src/database/seeds/data/accounts.js | 24 +++++ packages/server/src/interfaces/Ledger.ts | 2 +- packages/server/src/models/Bill.ts | 4 +- .../src/repositories/AccountRepository.ts | 70 +++++++++++++- .../src/repositories/TenantRepository.ts | 11 ++- .../Customers/CustomersApplication.ts | 24 ++--- .../CommandBillPaymentDTOTransformer.ts | 6 +- .../PaymentReceiveDTOTransformer.ts | 6 +- .../src/services/Tenancy/TenancyService.ts | 7 +- .../webapp/src/constants/accountTypes.tsx | 4 +- .../PaymentForm/PaymentMadeDialogs.tsx | 9 ++ .../PaymentForm/PaymentMadeForm.tsx | 34 +++++-- .../PaymentMadeFormFooterRight.tsx | 13 ++- .../PaymentForm/PaymentMadeFormHeader.tsx | 12 +-- .../PaymentMadeFormHeaderFields.tsx | 32 ++++--- .../PaymentForm/PaymentMadeFormProvider.tsx | 7 +- .../PaymentMadeExcessDialog.tsx | 37 ++++++++ .../PaymentMadeExcessDialogContent.tsx | 93 +++++++++++++++++++ .../dialogs/PaymentMadeExcessDialog/index.ts | 1 + .../PaymentMades/PaymentForm/utils.tsx | 35 ++++++- .../PaymentReceiveForm/PaymentReceiveForm.tsx | 28 ++++-- .../PaymentReceiveFormDialogs.tsx | 12 ++- .../PaymentReceiveFormFootetRight.tsx | 12 ++- .../PaymentReceiveFormHeader.tsx | 10 +- .../PaymentReceiveFormProvider.tsx | 7 +- .../PaymentReceiveHeaderFields.tsx | 28 +++--- .../ExcessPaymentDialog.tsx | 37 ++++++++ .../ExcessPaymentDialogContent.tsx | 86 +++++++++++++++++ .../dialogs/ExcessPaymentDialog/index.ts | 1 + .../PaymentReceiveForm/utils.tsx | 35 ++++++- 33 files changed, 597 insertions(+), 100 deletions(-) create mode 100644 packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeDialogs.tsx create mode 100644 packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/dialogs/PaymentMadeExcessDialog/PaymentMadeExcessDialog.tsx create mode 100644 packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/dialogs/PaymentMadeExcessDialog/PaymentMadeExcessDialogContent.tsx create mode 100644 packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/dialogs/PaymentMadeExcessDialog/index.ts create mode 100644 packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/dialogs/ExcessPaymentDialog/ExcessPaymentDialog.tsx create mode 100644 packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/dialogs/ExcessPaymentDialog/ExcessPaymentDialogContent.tsx create mode 100644 packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/dialogs/ExcessPaymentDialog/index.ts diff --git a/packages/server/src/api/controllers/Purchases/BillsPayments.ts b/packages/server/src/api/controllers/Purchases/BillsPayments.ts index dfe46cf4d..56804b513 100644 --- a/packages/server/src/api/controllers/Purchases/BillsPayments.ts +++ b/packages/server/src/api/controllers/Purchases/BillsPayments.ts @@ -111,6 +111,7 @@ export default class BillsPayments extends BaseController { check('vendor_id').exists().isNumeric().toInt(), check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(), + check('amount').exists().isNumeric().toFloat(), check('payment_account_id').exists().isNumeric().toInt(), check('payment_number').optional({ nullable: true }).trim().escape(), check('payment_date').exists(), @@ -118,7 +119,7 @@ export default class BillsPayments extends BaseController { check('reference').optional().trim().escape(), check('branch_id').optional({ nullable: true }).isNumeric().toInt(), - check('entries').exists().isArray({ min: 1 }), + check('entries').exists().isArray(), check('entries.*.index').optional().isNumeric().toInt(), check('entries.*.bill_id').exists().isNumeric().toInt(), check('entries.*.payment_amount').exists().isNumeric().toFloat(), diff --git a/packages/server/src/api/controllers/Sales/PaymentReceives.ts b/packages/server/src/api/controllers/Sales/PaymentReceives.ts index 5acd359e4..bdb71ce14 100644 --- a/packages/server/src/api/controllers/Sales/PaymentReceives.ts +++ b/packages/server/src/api/controllers/Sales/PaymentReceives.ts @@ -150,6 +150,7 @@ export default class PaymentReceivesController extends BaseController { check('customer_id').exists().isNumeric().toInt(), check('exchange_rate').optional().isFloat({ gt: 0 }).toFloat(), + check('amount').exists().isNumeric().toFloat(), check('payment_date').exists(), check('reference_no').optional(), check('deposit_account_id').exists().isNumeric().toInt(), @@ -158,8 +159,7 @@ export default class PaymentReceivesController extends BaseController { check('branch_id').optional({ nullable: true }).isNumeric().toInt(), - check('entries').isArray({ min: 1 }), - + check('entries').isArray({}), check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(), check('entries.*.index').optional().isNumeric().toInt(), check('entries.*.invoice_id').exists().isNumeric().toInt(), diff --git a/packages/server/src/database/seeds/core/20190423085242_seed_accounts.ts b/packages/server/src/database/seeds/core/20190423085242_seed_accounts.ts index e5e39dba9..7fcbd0e5f 100644 --- a/packages/server/src/database/seeds/core/20190423085242_seed_accounts.ts +++ b/packages/server/src/database/seeds/core/20190423085242_seed_accounts.ts @@ -12,8 +12,7 @@ export default class SeedAccounts extends TenantSeeder { description: this.i18n.__(account.description), currencyCode: this.tenant.metadata.baseCurrency, seededAt: new Date(), - }) -); + })); return knex('accounts').then(async () => { // Inserts seed entries. return knex('accounts').insert(data); diff --git a/packages/server/src/database/seeds/data/accounts.js b/packages/server/src/database/seeds/data/accounts.js index a5f7182ba..8e55665bd 100644 --- a/packages/server/src/database/seeds/data/accounts.js +++ b/packages/server/src/database/seeds/data/accounts.js @@ -9,6 +9,28 @@ export const TaxPayableAccount = { predefined: 1, }; +export const UnearnedRevenueAccount = { + name: 'Unearned Revenue', + slug: 'unearned-revenue', + account_type: 'other-current-liability', + parent_account_id: null, + code: '50005', + active: true, + index: 1, + predefined: true, +}; + +export const PrepardExpenses = { + name: 'Prepaid Expenses', + slug: 'prepaid-expenses', + account_type: 'other-current-asset', + parent_account_id: null, + code: '100010', + active: true, + index: 1, + predefined: true, +}; + export default [ { name: 'Bank Account', @@ -323,4 +345,6 @@ export default [ index: 1, predefined: 0, }, + UnearnedRevenueAccount, + PrepardExpenses, ]; diff --git a/packages/server/src/interfaces/Ledger.ts b/packages/server/src/interfaces/Ledger.ts index d7045eb41..0c39eef4f 100644 --- a/packages/server/src/interfaces/Ledger.ts +++ b/packages/server/src/interfaces/Ledger.ts @@ -40,7 +40,7 @@ export interface ILedgerEntry { date: Date | string; transactionType: string; - transactionSubType: string; + transactionSubType?: string; transactionId: number; diff --git a/packages/server/src/models/Bill.ts b/packages/server/src/models/Bill.ts index 422865cb0..e91f0ea33 100644 --- a/packages/server/src/models/Bill.ts +++ b/packages/server/src/models/Bill.ts @@ -525,9 +525,9 @@ export default class Bill extends mixin(TenantModel, [ return notFoundBillsIds; } - static changePaymentAmount(billId, amount) { + static changePaymentAmount(billId, amount, trx) { const changeMethod = amount > 0 ? 'increment' : 'decrement'; - return this.query() + return this.query(trx) .where('id', billId) [changeMethod]('payment_amount', Math.abs(amount)); } diff --git a/packages/server/src/repositories/AccountRepository.ts b/packages/server/src/repositories/AccountRepository.ts index 8bc6bf7d1..19aaaa70f 100644 --- a/packages/server/src/repositories/AccountRepository.ts +++ b/packages/server/src/repositories/AccountRepository.ts @@ -2,7 +2,12 @@ import { Account } from 'models'; import TenantRepository from '@/repositories/TenantRepository'; import { IAccount } from '@/interfaces'; import { Knex } from 'knex'; -import { TaxPayableAccount } from '@/database/seeds/data/accounts'; +import { + PrepardExpenses, + TaxPayableAccount, + UnearnedRevenueAccount, +} from '@/database/seeds/data/accounts'; +import { TenantMetadata } from '@/system/models'; export default class AccountRepository extends TenantRepository { /** @@ -179,4 +184,67 @@ export default class AccountRepository extends TenantRepository { } return result; }; + + /** + * Finds or creates the unearned revenue. + * @param {Record} extraAttrs + * @param {Knex.Transaction} trx + * @returns + */ + public async findOrCreateUnearnedRevenue( + extraAttrs: Record = {}, + trx?: Knex.Transaction + ) { + // Retrieves the given tenant metadata. + const tenantMeta = await TenantMetadata.query().findOne({ + tenantId: this.tenantId, + }); + const _extraAttrs = { + currencyCode: tenantMeta.baseCurrency, + ...extraAttrs, + }; + let result = await this.model + .query(trx) + .findOne({ slug: UnearnedRevenueAccount.slug, ..._extraAttrs }); + + if (!result) { + result = await this.model.query(trx).insertAndFetch({ + ...UnearnedRevenueAccount, + ..._extraAttrs, + }); + } + return result; + } + + /** + * Finds or creates the prepard expenses account. + * @param {Record} extraAttrs + * @param {Knex.Transaction} trx + * @returns + */ + public async findOrCreatePrepardExpenses( + extraAttrs: Record = {}, + trx?: Knex.Transaction + ) { + // Retrieves the given tenant metadata. + const tenantMeta = await TenantMetadata.query().findOne({ + tenantId: this.tenantId, + }); + const _extraAttrs = { + currencyCode: tenantMeta.baseCurrency, + ...extraAttrs, + }; + + let result = await this.model + .query(trx) + .findOne({ slug: PrepardExpenses.slug, ..._extraAttrs }); + + if (!result) { + result = await this.model.query(trx).insertAndFetch({ + ...PrepardExpenses, + ..._extraAttrs, + }); + } + return result; + } } diff --git a/packages/server/src/repositories/TenantRepository.ts b/packages/server/src/repositories/TenantRepository.ts index b24b9d079..ac85a82a7 100644 --- a/packages/server/src/repositories/TenantRepository.ts +++ b/packages/server/src/repositories/TenantRepository.ts @@ -4,12 +4,17 @@ import CachableRepository from './CachableRepository'; export default class TenantRepository extends CachableRepository { repositoryName: string; - + tenantId: number; + /** * Constructor method. - * @param {number} tenantId + * @param {number} tenantId */ constructor(knex, cache, i18n) { super(knex, cache, i18n); } -} \ No newline at end of file + + setTenantId(tenantId: number) { + this.tenantId = tenantId; + } +} diff --git a/packages/server/src/services/Contacts/Customers/CustomersApplication.ts b/packages/server/src/services/Contacts/Customers/CustomersApplication.ts index 3cf222c02..ac3b1dd3b 100644 --- a/packages/server/src/services/Contacts/Customers/CustomersApplication.ts +++ b/packages/server/src/services/Contacts/Customers/CustomersApplication.ts @@ -45,9 +45,9 @@ export class CustomersApplication { /** * Creates a new customer. - * @param {number} tenantId - * @param {ICustomerNewDTO} customerDTO - * @param {ISystemUser} authorizedUser + * @param {number} tenantId + * @param {ICustomerNewDTO} customerDTO + * @param {ISystemUser} authorizedUser * @returns {Promise} */ public createCustomer = (tenantId: number, customerDTO: ICustomerNewDTO) => { @@ -56,9 +56,9 @@ export class CustomersApplication { /** * Edits details of the given customer. - * @param {number} tenantId - * @param {number} customerId - * @param {ICustomerEditDTO} customerDTO + * @param {number} tenantId + * @param {number} customerId + * @param {ICustomerEditDTO} customerDTO * @return {Promise} */ public editCustomer = ( @@ -75,9 +75,9 @@ export class CustomersApplication { /** * Deletes the given customer and associated transactions. - * @param {number} tenantId - * @param {number} customerId - * @param {ISystemUser} authorizedUser + * @param {number} tenantId + * @param {number} customerId + * @param {ISystemUser} authorizedUser * @returns {Promise} */ public deleteCustomer = ( @@ -94,9 +94,9 @@ export class CustomersApplication { /** * Changes the opening balance of the given customer. - * @param {number} tenantId - * @param {number} customerId - * @param {Date|string} openingBalanceEditDTO + * @param {number} tenantId + * @param {number} customerId + * @param {Date|string} openingBalanceEditDTO * @returns {Promise} */ public editOpeningBalance = ( diff --git a/packages/server/src/services/Purchases/BillPayments/CommandBillPaymentDTOTransformer.ts b/packages/server/src/services/Purchases/BillPayments/CommandBillPaymentDTOTransformer.ts index 5b6f1451d..215d822da 100644 --- a/packages/server/src/services/Purchases/BillPayments/CommandBillPaymentDTOTransformer.ts +++ b/packages/server/src/services/Purchases/BillPayments/CommandBillPaymentDTOTransformer.ts @@ -4,6 +4,7 @@ import { omit, sumBy } from 'lodash'; import { IBillPayment, IBillPaymentDTO, IVendor } from '@/interfaces'; import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform'; import { formatDateFields } from '@/utils'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; @Service() export class CommandBillPaymentDTOTransformer { @@ -23,11 +24,14 @@ export class CommandBillPaymentDTOTransformer { vendor: IVendor, oldBillPayment?: IBillPayment ): Promise { + const amount = + billPaymentDTO.amount ?? sumBy(billPaymentDTO.entries, 'paymentAmount'); + const initialDTO = { ...formatDateFields(omit(billPaymentDTO, ['attachments']), [ 'paymentDate', ]), - amount: sumBy(billPaymentDTO.entries, 'paymentAmount'), + amount, currencyCode: vendor.currencyCode, exchangeRate: billPaymentDTO.exchangeRate || 1, entries: billPaymentDTO.entries, diff --git a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveDTOTransformer.ts b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveDTOTransformer.ts index 8d0357e04..a84569ac9 100644 --- a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveDTOTransformer.ts +++ b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveDTOTransformer.ts @@ -36,7 +36,9 @@ export class PaymentReceiveDTOTransformer { paymentReceiveDTO: IPaymentReceiveCreateDTO | IPaymentReceiveEditDTO, oldPaymentReceive?: IPaymentReceive ): Promise { - const paymentAmount = sumBy(paymentReceiveDTO.entries, 'paymentAmount'); + const amount = + paymentReceiveDTO.amount ?? + sumBy(paymentReceiveDTO.entries, 'paymentAmount'); // Retreive the next invoice number. const autoNextNumber = @@ -54,7 +56,7 @@ export class PaymentReceiveDTOTransformer { ...formatDateFields(omit(paymentReceiveDTO, ['entries', 'attachments']), [ 'paymentDate', ]), - amount: paymentAmount, + amount, currencyCode: customer.currencyCode, ...(paymentReceiveNo ? { paymentReceiveNo } : {}), exchangeRate: paymentReceiveDTO.exchangeRate || 1, diff --git a/packages/server/src/services/Tenancy/TenancyService.ts b/packages/server/src/services/Tenancy/TenancyService.ts index 68414c827..213b2d9c9 100644 --- a/packages/server/src/services/Tenancy/TenancyService.ts +++ b/packages/server/src/services/Tenancy/TenancyService.ts @@ -77,7 +77,12 @@ export default class HasTenancyService { const knex = this.knex(tenantId); const i18n = this.i18n(tenantId); - return tenantRepositoriesLoader(knex, cache, i18n); + const repositories = tenantRepositoriesLoader(knex, cache, i18n); + + Object.values(repositories).forEach((repository) => { + repository.setTenantId(tenantId); + }); + return repositories; }); } diff --git a/packages/webapp/src/constants/accountTypes.tsx b/packages/webapp/src/constants/accountTypes.tsx index e3fa6b287..5cb08e13f 100644 --- a/packages/webapp/src/constants/accountTypes.tsx +++ b/packages/webapp/src/constants/accountTypes.tsx @@ -4,9 +4,9 @@ export const ACCOUNT_TYPE = { BANK: 'bank', ACCOUNTS_RECEIVABLE: 'accounts-receivable', INVENTORY: 'inventory', - OTHER_CURRENT_ASSET: 'other-ACCOUNT_PARENT_TYPE.CURRENT_ASSET', + OTHER_CURRENT_ASSET: 'other-current-asset', FIXED_ASSET: 'fixed-asset', - NON_CURRENT_ASSET: 'non-ACCOUNT_PARENT_TYPE.CURRENT_ASSET', + NON_CURRENT_ASSET: 'non-current-asset', ACCOUNTS_PAYABLE: 'accounts-payable', CREDIT_CARD: 'credit-card', diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeDialogs.tsx b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeDialogs.tsx new file mode 100644 index 000000000..e03c01f52 --- /dev/null +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeDialogs.tsx @@ -0,0 +1,9 @@ +import { ExcessPaymentDialog } from './dialogs/PaymentMadeExcessDialog'; + +export function PaymentMadeDialogs() { + return ( + <> + + + ); +} diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.tsx b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.tsx index 792dd095e..72b2e033c 100644 --- a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.tsx +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.tsx @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import intl from 'react-intl-universal'; import classNames from 'classnames'; -import { Formik, Form } from 'formik'; +import { Formik, Form, FormikHelpers } from 'formik'; import { Intent } from '@blueprintjs/core'; import { sumBy, defaultTo } from 'lodash'; import { useHistory } from 'react-router-dom'; @@ -14,6 +14,7 @@ import PaymentMadeFloatingActions from './PaymentMadeFloatingActions'; import PaymentMadeFooter from './PaymentMadeFooter'; import PaymentMadeFormBody from './PaymentMadeFormBody'; import PaymentMadeFormTopBar from './PaymentMadeFormTopBar'; +import { PaymentMadeDialogs } from './PaymentMadeDialogs'; import { PaymentMadeInnerProvider } from './PaymentMadeInnerProvider'; import { usePaymentMadeFormContext } from './PaymentMadeFormProvider'; @@ -21,6 +22,7 @@ import { compose, orderingLinesIndexes } from '@/utils'; import withSettings from '@/containers/Settings/withSettings'; import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; import { EditPaymentMadeFormSchema, @@ -31,6 +33,7 @@ import { transformToEditForm, transformErrors, transformFormToRequest, + getPaymentExcessAmountFromValues, } from './utils'; /** @@ -42,6 +45,9 @@ function PaymentMadeForm({ // #withCurrentOrganization organization: { base_currency }, + + // #withDialogActions + openDialog, }) { const history = useHistory(); @@ -54,6 +60,7 @@ function PaymentMadeForm({ submitPayload, createPaymentMadeMutate, editPaymentMadeMutate, + isExcessConfirmed, } = usePaymentMadeFormContext(); // Form initial values. @@ -76,13 +83,11 @@ function PaymentMadeForm({ // Handle the form submit. const handleSubmitForm = ( values, - { setSubmitting, resetForm, setFieldError }, + { setSubmitting, resetForm, setFieldError }: FormikHelpers, ) => { setSubmitting(true); - // Total payment amount of entries. - const totalPaymentAmount = sumBy(values.entries, 'payment_amount'); - if (totalPaymentAmount <= 0) { + if (values.amount <= 0) { AppToaster.show({ message: intl.get('you_cannot_make_payment_with_zero_total_amount'), intent: Intent.DANGER, @@ -90,6 +95,16 @@ function PaymentMadeForm({ setSubmitting(false); return; } + const excessAmount = getPaymentExcessAmountFromValues(values); + + // Show the confirmation popup if the excess amount bigger than zero and + // has not been confirmed yet. + if (excessAmount > 0 && !isExcessConfirmed) { + openDialog('payment-made-excessed-payment'); + setSubmitting(false); + + return; + } // Transformes the form values to request body. const form = transformFormToRequest(values); @@ -119,11 +134,12 @@ function PaymentMadeForm({ } setSubmitting(false); }; - if (!isNewMode) { - editPaymentMadeMutate([paymentMadeId, form]).then(onSaved).catch(onError); + return editPaymentMadeMutate([paymentMadeId, form]) + .then(onSaved) + .catch(onError); } else { - createPaymentMadeMutate(form).then(onSaved).catch(onError); + return createPaymentMadeMutate(form).then(onSaved).catch(onError); } }; @@ -149,6 +165,7 @@ function PaymentMadeForm({ + @@ -163,4 +180,5 @@ export default compose( preferredPaymentAccount: parseInt(billPaymentSettings?.withdrawalAccount), })), withCurrentOrganization(), + withDialogActions, )(PaymentMadeForm); diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormFooterRight.tsx b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormFooterRight.tsx index 640901bd0..5f1de27df 100644 --- a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormFooterRight.tsx +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormFooterRight.tsx @@ -1,17 +1,23 @@ // @ts-nocheck import React from 'react'; import styled from 'styled-components'; +import { useFormikContext } from 'formik'; import { T, TotalLines, TotalLine, TotalLineBorderStyle, TotalLineTextStyle, + FormatNumber, } from '@/components'; -import { usePaymentMadeTotals } from './utils'; +import { usePaymentMadeExcessAmount, usePaymentMadeTotals } from './utils'; export function PaymentMadeFormFooterRight() { const { formattedSubtotal, formattedTotal } = usePaymentMadeTotals(); + const excessAmount = usePaymentMadeExcessAmount(); + const { + values: { currency_code: currencyCode }, + } = useFormikContext(); return ( @@ -25,6 +31,11 @@ export function PaymentMadeFormFooterRight() { value={formattedTotal} textStyle={TotalLineTextStyle.Bold} /> + } + textStyle={TotalLineTextStyle.Regular} + /> ); } diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeader.tsx b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeader.tsx index 7c9fd5c47..a82091896 100644 --- a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeader.tsx +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeader.tsx @@ -1,12 +1,12 @@ // @ts-nocheck -import React, { useMemo } from 'react'; +import React from 'react'; import classNames from 'classnames'; import { useFormikContext } from 'formik'; -import { sumBy } from 'lodash'; import { CLASSES } from '@/constants/classes'; import { Money, FormattedMessage as T } from '@/components'; import PaymentMadeFormHeaderFields from './PaymentMadeFormHeaderFields'; +import { usePaymentmadeTotalAmount } from './utils'; /** * Payment made header form. @@ -14,11 +14,10 @@ import PaymentMadeFormHeaderFields from './PaymentMadeFormHeaderFields'; function PaymentMadeFormHeader() { // Formik form context. const { - values: { entries, currency_code }, + values: { currency_code }, } = useFormikContext(); - // Calculate the payment amount of the entries. - const amountPaid = useMemo(() => sumBy(entries, 'payment_amount'), [entries]); + const totalAmount = usePaymentmadeTotalAmount(); return (
@@ -30,8 +29,9 @@ function PaymentMadeFormHeader() { +

- +

diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeaderFields.tsx b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeaderFields.tsx index 6a72e9968..c6b01ae9b 100644 --- a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeaderFields.tsx +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeaderFields.tsx @@ -2,6 +2,7 @@ import React, { useMemo } from 'react'; import styled from 'styled-components'; import classNames from 'classnames'; +import { isEmpty, toSafeInteger } from 'lodash'; import { FormGroup, InputGroup, @@ -13,7 +14,6 @@ import { import { DateInput } from '@blueprintjs/datetime'; import { FastField, Field, useFormikContext, ErrorMessage } from 'formik'; import { FormattedMessage as T, VendorsSelect } from '@/components'; -import { toSafeInteger } from 'lodash'; import { CLASSES } from '@/constants/classes'; import { @@ -68,7 +68,7 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) { const fullAmount = safeSumBy(newEntries, 'payment_amount'); setFieldValue('entries', newEntries); - setFieldValue('full_amount', fullAmount); + setFieldValue('amount', fullAmount); }; // Handles the full-amount field blur. @@ -115,10 +115,10 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) { {/* ------------ Full amount ------------ */} - + {({ form: { - values: { currency_code }, + values: { currency_code, entries }, }, field: { value }, meta: { error, touched }, @@ -129,28 +129,30 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) { className={('form-group--full-amount', Classes.FILL)} intent={inputIntent({ error, touched })} labelInfo={} - helperText={} + helperText={} > { - setFieldValue('full_amount', value); + setFieldValue('amount', value); }} onBlurValue={onFullAmountBlur} /> - + {!isEmpty(entries) && ( + + )} )} diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormProvider.tsx b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormProvider.tsx index f5d0c50d3..6a33713a7 100644 --- a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormProvider.tsx +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormProvider.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -import React, { createContext, useContext } from 'react'; +import React, { createContext, useContext, useState } from 'react'; import { Features } from '@/constants'; import { useFeatureCan } from '@/hooks/state'; import { @@ -71,6 +71,8 @@ function PaymentMadeFormProvider({ query, paymentMadeId, ...props }) { const isFeatureLoading = isBranchesLoading; + const [isExcessConfirmed, setIsExcessConfirmed] = useState(false); + // Provider payload. const provider = { paymentMadeId, @@ -98,6 +100,9 @@ function PaymentMadeFormProvider({ query, paymentMadeId, ...props }) { setSubmitPayload, setPaymentVendorId, + + isExcessConfirmed, + setIsExcessConfirmed, }; return ( diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/dialogs/PaymentMadeExcessDialog/PaymentMadeExcessDialog.tsx b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/dialogs/PaymentMadeExcessDialog/PaymentMadeExcessDialog.tsx new file mode 100644 index 000000000..c93c85a17 --- /dev/null +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/dialogs/PaymentMadeExcessDialog/PaymentMadeExcessDialog.tsx @@ -0,0 +1,37 @@ +// @ts-nocheck +import React from 'react'; +import { Dialog, DialogSuspense } from '@/components'; +import withDialogRedux from '@/components/DialogReduxConnect'; +import { compose } from '@/utils'; + +const ExcessPaymentDialogContent = React.lazy(() => + import('./PaymentMadeExcessDialogContent').then((module) => ({ + default: module.ExcessPaymentDialogContent, + })), +); + +/** + * Exess payment dialog of the payment made form. + */ +function ExcessPaymentDialogRoot({ dialogName, isOpen }) { + return ( + + + + + + ); +} + +export const ExcessPaymentDialog = compose(withDialogRedux())( + ExcessPaymentDialogRoot, +); + +ExcessPaymentDialog.displayName = 'ExcessPaymentDialog'; diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/dialogs/PaymentMadeExcessDialog/PaymentMadeExcessDialogContent.tsx b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/dialogs/PaymentMadeExcessDialog/PaymentMadeExcessDialogContent.tsx new file mode 100644 index 000000000..c57f67131 --- /dev/null +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/dialogs/PaymentMadeExcessDialog/PaymentMadeExcessDialogContent.tsx @@ -0,0 +1,93 @@ +// @ts-nocheck +import * as R from 'ramda'; +import React from 'react'; +import { Button, Classes, Intent } from '@blueprintjs/core'; +import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; +import { FormatNumber } from '@/components'; +import { usePaymentMadeFormContext } from '../../PaymentMadeFormProvider'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { usePaymentMadeExcessAmount } from '../../utils'; + +interface ExcessPaymentValues {} +function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) { + const { + submitForm, + values: { currency_code: currencyCode }, + } = useFormikContext(); + const { setIsExcessConfirmed } = usePaymentMadeFormContext(); + + // Handles the form submitting. + const handleSubmit = ( + values: ExcessPaymentValues, + { setSubmitting }: FormikHelpers, + ) => { + setSubmitting(true); + setIsExcessConfirmed(true); + + return submitForm().then(() => { + setSubmitting(false); + closeDialog(dialogName); + }); + }; + // Handle close button click. + const handleCloseBtn = () => { + closeDialog(dialogName); + }; + const excessAmount = usePaymentMadeExcessAmount(); + + return ( + +
+ + } + onClose={handleCloseBtn} + /> + +
+ ); +} + +export const ExcessPaymentDialogContent = R.compose(withDialogActions)( + ExcessPaymentDialogContentRoot, +); + +interface ExcessPaymentDialogContentFormProps { + excessAmount: string | number | React.ReactNode; + onClose?: () => void; +} + +function ExcessPaymentDialogContentForm({ + excessAmount, + onClose, +}: ExcessPaymentDialogContentFormProps) { + const { submitForm, isSubmitting } = useFormikContext(); + + const handleCloseBtn = () => { + onClose && onClose(); + }; + return ( + <> +
+

+ Would you like to record the excess amount of{' '} + {excessAmount} as credit payment from the vendor. +

+
+ +
+
+ + +
+
+ + ); +} diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/dialogs/PaymentMadeExcessDialog/index.ts b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/dialogs/PaymentMadeExcessDialog/index.ts new file mode 100644 index 000000000..dae9903b5 --- /dev/null +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/dialogs/PaymentMadeExcessDialog/index.ts @@ -0,0 +1 @@ +export * from './PaymentMadeExcessDialog'; \ No newline at end of file diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/utils.tsx b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/utils.tsx index fd469ce88..8b8323939 100644 --- a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/utils.tsx +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentForm/utils.tsx @@ -37,7 +37,7 @@ export const defaultPaymentMadeEntry = { // Default initial values of payment made. export const defaultPaymentMade = { - full_amount: '', + amount: '', vendor_id: '', payment_account_id: '', payment_date: moment(new Date()).format('YYYY-MM-DD'), @@ -53,10 +53,10 @@ export const defaultPaymentMade = { export const transformToEditForm = (paymentMade, paymentMadeEntries) => { const attachments = transformAttachmentsToForm(paymentMade); + const appliedAmount = safeSumBy(paymentMadeEntries, 'payment_amount'); return { ...transformToForm(paymentMade, defaultPaymentMade), - full_amount: safeSumBy(paymentMadeEntries, 'payment_amount'), entries: [ ...paymentMadeEntries.map((paymentMadeEntry) => ({ ...transformToForm(paymentMadeEntry, defaultPaymentMadeEntry), @@ -177,6 +177,30 @@ export const usePaymentMadeTotals = () => { }; }; +export const usePaymentmadeTotalAmount = () => { + const { + values: { amount }, + } = useFormikContext(); + + return amount; +}; + +export const usePaymentMadeAppliedAmount = () => { + const { + values: { entries }, + } = useFormikContext(); + + // Retrieves the invoice entries total. + return React.useMemo(() => sumBy(entries, 'payment_amount'), [entries]); +}; + +export const usePaymentMadeExcessAmount = () => { + const appliedAmount = usePaymentMadeAppliedAmount(); + const totalAmount = usePaymentmadeTotalAmount(); + + return Math.abs(totalAmount - appliedAmount); +}; + /** * Detarmines whether the bill has foreign customer. * @returns {boolean} @@ -191,3 +215,10 @@ export const usePaymentMadeIsForeignCustomer = () => { ); return isForeignCustomer; }; + +export const getPaymentExcessAmountFromValues = (values) => { + const appliedAmount = sumBy(values.entries, 'payment_amount'); + const totalAmount = values.amount; + + return Math.abs(totalAmount - appliedAmount); +}; diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.tsx index a0c84bacb..be69cee4d 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -import React, { useMemo } from 'react'; +import React, { useMemo, useRef } from 'react'; import { sumBy, isEmpty, defaultTo } from 'lodash'; import intl from 'react-intl-universal'; import classNames from 'classnames'; @@ -21,6 +21,7 @@ import { PaymentReceiveInnerProvider } from './PaymentReceiveInnerProvider'; import withSettings from '@/containers/Settings/withSettings'; import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; import { EditPaymentReceiveFormSchema, @@ -36,6 +37,7 @@ import { transformFormToRequest, transformErrors, resetFormState, + getExceededAmountFromValues, } from './utils'; import { PaymentReceiveSyncIncrementSettingsToForm } from './components'; @@ -51,6 +53,9 @@ function PaymentReceiveForm({ // #withCurrentOrganization organization: { base_currency }, + + // #withDialogActions + openDialog, }) { const history = useHistory(); @@ -63,6 +68,7 @@ function PaymentReceiveForm({ submitPayload, editPaymentReceiveMutate, createPaymentReceiveMutate, + isExcessConfirmed, } = usePaymentReceiveFormContext(); // Payment receive number. @@ -94,18 +100,16 @@ function PaymentReceiveForm({ preferredDepositAccount, ], ); - // Handle form submit. const handleSubmitForm = ( values, { setSubmitting, resetForm, setFieldError }, ) => { setSubmitting(true); + const exceededAmount = getExceededAmountFromValues(values); - // Calculates the total payment amount of entries. - const totalPaymentAmount = sumBy(values.entries, 'payment_amount'); - - if (totalPaymentAmount <= 0) { + // Validates the amount should be bigger than zero. + if (values.amount <= 0) { AppToaster.show({ message: intl.get('you_cannot_make_payment_with_zero_total_amount'), intent: Intent.DANGER, @@ -113,6 +117,13 @@ function PaymentReceiveForm({ setSubmitting(false); return; } + // Show the confirm popup if the excessed amount bigger than zero and + // excess confirmation has not been confirmed yet. + if (exceededAmount > 0 && !isExcessConfirmed) { + setSubmitting(false); + openDialog('payment-received-excessed-payment'); + return; + } // Transformes the form values to request body. const form = transformFormToRequest(values); @@ -148,11 +159,11 @@ function PaymentReceiveForm({ }; if (paymentReceiveId) { - editPaymentReceiveMutate([paymentReceiveId, form]) + return editPaymentReceiveMutate([paymentReceiveId, form]) .then(onSaved) .catch(onError); } else { - createPaymentReceiveMutate(form).then(onSaved).catch(onError); + return createPaymentReceiveMutate(form).then(onSaved).catch(onError); } }; return ( @@ -202,4 +213,5 @@ export default compose( preferredDepositAccount: paymentReceiveSettings?.preferredDepositAccount, })), withCurrentOrganization(), + withDialogActions, )(PaymentReceiveForm); diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormDialogs.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormDialogs.tsx index e60b27142..76ccb72a4 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormDialogs.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormDialogs.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useFormikContext } from 'formik'; import PaymentReceiveNumberDialog from '@/containers/Dialogs/PaymentReceiveNumberDialog'; +import { ExcessPaymentDialog } from './dialogs/ExcessPaymentDialog'; /** * Payment receive form dialogs. @@ -21,9 +22,12 @@ export default function PaymentReceiveFormDialogs() { }; return ( - + <> + + + ); } diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormFootetRight.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormFootetRight.tsx index d35d7c6a7..2cee6c9f7 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormFootetRight.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormFootetRight.tsx @@ -7,11 +7,16 @@ import { TotalLine, TotalLineBorderStyle, TotalLineTextStyle, + FormatNumber, } from '@/components'; -import { usePaymentReceiveTotals } from './utils'; +import { + usePaymentReceiveTotals, + usePaymentReceivedTotalExceededAmount, +} from './utils'; export function PaymentReceiveFormFootetRight() { const { formattedSubtotal, formattedTotal } = usePaymentReceiveTotals(); + const exceededAmount = usePaymentReceivedTotalExceededAmount(); return ( @@ -25,6 +30,11 @@ export function PaymentReceiveFormFootetRight() { value={formattedTotal} textStyle={TotalLineTextStyle.Bold} /> + } + textStyle={TotalLineTextStyle.Regular} + /> ); } diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormHeader.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormHeader.tsx index d7e44fadf..c6d08f074 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormHeader.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormHeader.tsx @@ -30,15 +30,9 @@ function PaymentReceiveFormHeader() { function PaymentReceiveFormBigTotal() { // Formik form context. const { - values: { currency_code, entries }, + values: { currency_code, amount }, } = useFormikContext(); - // Calculates the total payment amount from due amount. - const paymentFullAmount = useMemo( - () => sumBy(entries, 'payment_amount'), - [entries], - ); - return (
@@ -46,7 +40,7 @@ function PaymentReceiveFormBigTotal() {

- +

diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormProvider.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormProvider.tsx index 8c08abd3e..c6f5a4729 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormProvider.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormProvider.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -import React, { createContext, useContext } from 'react'; +import React, { createContext, useContext, useState } from 'react'; import { Features } from '@/constants'; import { useFeatureCan } from '@/hooks/state'; import { DashboardInsider } from '@/components'; @@ -74,6 +74,8 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) { const { mutateAsync: editPaymentReceiveMutate } = useEditPaymentReceive(); const { mutateAsync: createPaymentReceiveMutate } = useCreatePaymentReceive(); + const [isExcessConfirmed, setIsExcessConfirmed] = useState(false); + // Provider payload. const provider = { paymentReceiveId, @@ -97,6 +99,9 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) { editPaymentReceiveMutate, createPaymentReceiveMutate, + + isExcessConfirmed, + setIsExcessConfirmed, }; return ( diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveHeaderFields.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveHeaderFields.tsx index 9ed1e5503..98b9fe4e2 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveHeaderFields.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveHeaderFields.tsx @@ -11,7 +11,7 @@ import { Button, } from '@blueprintjs/core'; import { DateInput } from '@blueprintjs/datetime'; -import { toSafeInteger } from 'lodash'; +import { isEmpty, toSafeInteger } from 'lodash'; import { FastField, Field, useFormikContext, ErrorMessage } from 'formik'; import { @@ -124,11 +124,11 @@ export default function PaymentReceiveHeaderFields() { {/* ------------ Full amount ------------ */} - + {({ form: { setFieldValue, - values: { currency_code }, + values: { currency_code, entries }, }, field: { value, onChange }, meta: { error, touched }, @@ -146,21 +146,23 @@ export default function PaymentReceiveHeaderFields() { { - setFieldValue('full_amount', value); + setFieldValue('amount', value); }} onBlurValue={onFullAmountBlur} /> - + {!isEmpty(entries) && ( + + )} )} diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/dialogs/ExcessPaymentDialog/ExcessPaymentDialog.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/dialogs/ExcessPaymentDialog/ExcessPaymentDialog.tsx new file mode 100644 index 000000000..44e660e76 --- /dev/null +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/dialogs/ExcessPaymentDialog/ExcessPaymentDialog.tsx @@ -0,0 +1,37 @@ +// @ts-nocheck +import React from 'react'; +import { Dialog, DialogSuspense } from '@/components'; +import withDialogRedux from '@/components/DialogReduxConnect'; +import { compose } from '@/utils'; + +const ExcessPaymentDialogContent = React.lazy(() => + import('./ExcessPaymentDialogContent').then((module) => ({ + default: module.ExcessPaymentDialogContent, + })), +); + +/** + * Excess payment dialog of the payment received form. + */ +function ExcessPaymentDialogRoot({ dialogName, isOpen }) { + return ( + + + + + + ); +} + +export const ExcessPaymentDialog = compose(withDialogRedux())( + ExcessPaymentDialogRoot, +); + +ExcessPaymentDialog.displayName = 'ExcessPaymentDialog'; diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/dialogs/ExcessPaymentDialog/ExcessPaymentDialogContent.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/dialogs/ExcessPaymentDialog/ExcessPaymentDialogContent.tsx new file mode 100644 index 000000000..868a75bdf --- /dev/null +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/dialogs/ExcessPaymentDialog/ExcessPaymentDialogContent.tsx @@ -0,0 +1,86 @@ +// @ts-nocheck +import * as Yup from 'yup'; +import * as R from 'ramda'; +import { Button, Classes, Intent } from '@blueprintjs/core'; +import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; +import { FormatNumber } from '@/components'; +import { usePaymentReceiveFormContext } from '../../PaymentReceiveFormProvider'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { usePaymentReceivedTotalExceededAmount } from '../../utils'; + +interface ExcessPaymentValues {} + +export function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) { + const { + submitForm, + values: { currency_code: currencyCode }, + } = useFormikContext(); + const { setIsExcessConfirmed } = usePaymentReceiveFormContext(); + const exceededAmount = usePaymentReceivedTotalExceededAmount(); + + const handleSubmit = ( + values: ExcessPaymentValues, + { setSubmitting }: FormikHelpers, + ) => { + setSubmitting(true); + setIsExcessConfirmed(true); + + submitForm().then(() => { + closeDialog(dialogName); + setSubmitting(false); + }); + }; + const handleClose = () => { + closeDialog(dialogName); + }; + + return ( + +
+ + } + onClose={handleClose} + /> + +
+ ); +} + +export const ExcessPaymentDialogContent = R.compose(withDialogActions)( + ExcessPaymentDialogContentRoot, +); + +function ExcessPaymentDialogContentForm({ onClose, exceededAmount }) { + const { submitForm, isSubmitting } = useFormikContext(); + + const handleCloseBtn = () => { + onClose && onClose(); + }; + + return ( + <> +
+

+ Would you like to record the excess amount of{' '} + {exceededAmount} as credit payment from the customer. +

+
+ +
+
+ + +
+
+ + ); +} diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/dialogs/ExcessPaymentDialog/index.ts b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/dialogs/ExcessPaymentDialog/index.ts new file mode 100644 index 000000000..9be100852 --- /dev/null +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/dialogs/ExcessPaymentDialog/index.ts @@ -0,0 +1 @@ +export * from './ExcessPaymentDialog'; \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/utils.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/utils.tsx index 71f75280a..3be7c0ea4 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/utils.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentReceiveForm/utils.tsx @@ -42,12 +42,12 @@ export const defaultPaymentReceive = { // Holds the payment number that entered manually only. payment_receive_no_manually: '', statement: '', - full_amount: '', + amount: '', currency_code: '', branch_id: '', exchange_rate: 1, entries: [], - attachments: [] + attachments: [], }; export const defaultRequestPaymentEntry = { @@ -249,6 +249,30 @@ export const usePaymentReceiveTotals = () => { }; }; +export const usePaymentReceivedTotalAppliedAmount = () => { + const { + values: { entries }, + } = useFormikContext(); + + // Retrieves the invoice entries total. + return React.useMemo(() => sumBy(entries, 'payment_amount'), [entries]); +}; + +export const usePaymentReceivedTotalAmount = () => { + const { + values: { amount }, + } = useFormikContext(); + + return amount; +}; + +export const usePaymentReceivedTotalExceededAmount = () => { + const totalAmount = usePaymentReceivedTotalAmount(); + const totalApplied = usePaymentReceivedTotalAppliedAmount(); + + return Math.abs(totalAmount - totalApplied); +}; + /** * Detarmines whether the payment has foreign customer. * @returns {boolean} @@ -273,3 +297,10 @@ export const resetFormState = ({ initialValues, values, resetForm }) => { }, }); }; + +export const getExceededAmountFromValues = (values) => { + const totalApplied = sumBy(values.entries, 'payment_amount'); + const totalAmount = values.amount; + + return totalAmount - totalApplied; +};