From 51a2901c9bbf77d8c34384fbeb8495a8ba1362fa Mon Sep 17 00:00:00 2001 From: suhailanzar Date: Wed, 4 Jun 2025 11:56:14 +0530 Subject: [PATCH 1/4] fix: implement complete partial payment handling --- .../AccountingSettings/AccountingSettings.ts | 1 + models/baseModels/Invoice/Invoice.ts | 5 ++- models/baseModels/Payment/Payment.ts | 42 +++++++++++++++---- .../baseModels/SalesInvoice/SalesInvoice.ts | 10 +++-- schemas/app/AccountingSettings.json | 7 ++++ 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/models/baseModels/AccountingSettings/AccountingSettings.ts b/models/baseModels/AccountingSettings/AccountingSettings.ts index 899f5a76..3eef783d 100644 --- a/models/baseModels/AccountingSettings/AccountingSettings.ts +++ b/models/baseModels/AccountingSettings/AccountingSettings.ts @@ -25,6 +25,7 @@ export class AccountingSettings extends Doc { enablePricingRule?: boolean; enableERPNextSync?: boolean; enablePointOfSaleWithOutInventory?: boolean; + enablePartialPayment?: boolean; static filters: FiltersMap = { writeOffAccount: () => ({ diff --git a/models/baseModels/Invoice/Invoice.ts b/models/baseModels/Invoice/Invoice.ts index 2aa675f2..5565cb4a 100644 --- a/models/baseModels/Invoice/Invoice.ts +++ b/models/baseModels/Invoice/Invoice.ts @@ -838,7 +838,10 @@ export abstract class Invoice extends Transactional { if (sinvreturnedDoc.outstandingAmount?.isZero()) { return this.grandTotal; } else { - return this.fyo.pesa(0); + const returnAmount = this.grandTotal + ?.abs() + .sub(sinvreturnedDoc.outstandingAmount as Money); + return returnAmount; } } diff --git a/models/baseModels/Payment/Payment.ts b/models/baseModels/Payment/Payment.ts index d1d16b62..5bbbf0e0 100644 --- a/models/baseModels/Payment/Payment.ts +++ b/models/baseModels/Payment/Payment.ts @@ -42,6 +42,7 @@ export class Payment extends Transactional { referenceType?: ModelNameEnum.SalesInvoice | ModelNameEnum.PurchaseInvoice; for?: PaymentFor[]; _accountsMap?: AccountTypeMap; + initialAmount?: Money; async paymentMethodDoc() { return (await this.loadAndGetLink('paymentMethod')) as PaymentMethod; @@ -112,6 +113,7 @@ export class Payment extends Transactional { } await this.validateFor(); + this.validatePartialAmount(); this.validateAccounts(); this.validateTotalReferenceAmount(); await this.validateReferences(); @@ -445,6 +447,24 @@ export class Payment extends Transactional { throw new ValidationError(message); } + validatePartialAmount() { + if (!this.fyo.singles.AccountingSettings?.enablePartialPayment) { + const amount = + (this.amountPaid as Money)?.isZero?.() === false + ? (this.amountPaid as Money) + : (this.amount as Money); + const initialAmount = this.initialAmount as Money; + if (amount.lt(initialAmount) && !amount.eq(initialAmount)) { + if (this.writeoff?.isZero()) { + this.amount = this.initialAmount; + throw new ValidationError( + this.fyo.t`Enable Partial payment to pay partial amount` + ); + } + } + } + } + async afterSubmit() { await super.afterSubmit(); await this.updateReferenceDocOutstanding(); @@ -703,17 +723,23 @@ export class Payment extends Transactional { return; } - const amount = (this.getSum('for', 'amount', false) as Money).abs(); - - if ((value as Money).gt(amount)) { + if (!this.initialAmount) { + this.initialAmount = this.amount as Money; + } + const moneyValue = value as Money; + if (moneyValue.gt(this.initialAmount)) { throw new ValidationError( - this.fyo.t`Payment amount cannot - exceed ${this.fyo.format(amount, 'Currency')}.` + this.fyo.t`Payment amount cannot exceed ${this.fyo.format( + this.initialAmount, + 'Currency' + )}.` ); - } else if ((value as Money).isZero()) { + } else if (moneyValue.isZero()) { throw new ValidationError( - this.fyo.t`Payment amount cannot - be ${this.fyo.format(value, 'Currency')}.` + this.fyo.t`Payment amount cannot be ${this.fyo.format( + moneyValue, + 'Currency' + )}.` ); } }, diff --git a/models/baseModels/SalesInvoice/SalesInvoice.ts b/models/baseModels/SalesInvoice/SalesInvoice.ts index 820dc3b2..55a62476 100644 --- a/models/baseModels/SalesInvoice/SalesInvoice.ts +++ b/models/baseModels/SalesInvoice/SalesInvoice.ts @@ -115,10 +115,12 @@ export class SalesInvoice extends Invoice { ((value as number) || 0) * ((loyaltyProgramDoc?.conversionFactor as number) || 0); - if (this.grandTotal?.lt(loyaltyPoint)) { - throw new ValidationError( - t`no need ${value as number} points to purchase this item` - ); + if (!this.isReturn) { + if (this.grandTotal?.lt(loyaltyPoint)) { + throw new ValidationError( + t`no need ${value as number} points to purchase this item` + ); + } } }, }; diff --git a/schemas/app/AccountingSettings.json b/schemas/app/AccountingSettings.json index aa443f49..594a0fd7 100644 --- a/schemas/app/AccountingSettings.json +++ b/schemas/app/AccountingSettings.json @@ -142,6 +142,13 @@ "default": false, "section": "Features" }, + { + "fieldname": "enablePartialPayment", + "label": "Enable Partial Payment", + "fieldtype": "Check", + "default": false, + "section": "Features" + }, { "fieldname": "fiscalYearStart", "label": "Fiscal Year Start Date", From b01cb2a7dafa0d8941579b077b120392609f7035 Mon Sep 17 00:00:00 2001 From: suhailanzar Date: Thu, 5 Jun 2025 10:03:40 +0530 Subject: [PATCH 2/4] fix: updated partial payment in pos --- src/pages/POS/POS.vue | 5 ++--- src/pages/POS/PaymentModal.vue | 21 +++++++++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/pages/POS/POS.vue b/src/pages/POS/POS.vue index a3978995..0c3c6941 100644 --- a/src/pages/POS/POS.vue +++ b/src/pages/POS/POS.vue @@ -436,7 +436,7 @@ export default defineComponent({ this.tableView = !this.tableView; }, setPaidAmount(amount: Money) { - this.paidAmount = amount; + this.paidAmount = this.fyo.pesa(amount.toString()); }, setPaymentMethod(method: string) { this.paymentMethod = method; @@ -731,6 +731,7 @@ export default defineComponent({ const paymentMethod = this.paymentMethod; await this.paymentDoc.set('paymentMethod', paymentMethod); + await this.paymentDoc.set('amount', this.fyo.pesa(this.paidAmount.float)); const paymentMethodDoc = await this.paymentDoc.loadAndGetLink( 'paymentMethod' @@ -738,7 +739,6 @@ export default defineComponent({ if (paymentMethodDoc?.type !== 'Cash') { await this.paymentDoc.setMultiple({ - amount: this.paidAmount.float, referenceId: this.transferRefNo, clearanceDate: this.transferClearanceDate, }); @@ -747,7 +747,6 @@ export default defineComponent({ if (paymentMethodDoc?.type === 'Cash') { await this.paymentDoc.setMultiple({ paymentAccount: this.defaultPOSCashAccount, - amount: this.paidAmount.float, }); } diff --git a/src/pages/POS/PaymentModal.vue b/src/pages/POS/PaymentModal.vue index 2091864b..8cbf5e0f 100644 --- a/src/pages/POS/PaymentModal.vue +++ b/src/pages/POS/PaymentModal.vue @@ -7,7 +7,7 @@ :border="true" :text-right="true" :value="paidAmount" - @change="(amount:Money)=> $emit('setPaidAmount', amount)" + @change="(amount:Money)=> $emit('setPaidAmount', (amount as Money).float)" />
@@ -278,10 +287,7 @@ export default defineComponent({ return this.fyo.pesa(this.paidAmount.float).sub(grandTotal); }, showBalanceAmount(): boolean { - if ( - this.fyo.pesa(this.paidAmount.float).eq(fyo.pesa(0)) && - this.transferAmount.eq(fyo.pesa(0)) - ) { + if (this.paidAmount.float === 0) { return false; } @@ -368,7 +374,10 @@ export default defineComponent({ setPaymentMethodAndAmount(paymentMethod?: string) { if (paymentMethod) { this.$emit('setPaymentMethod', paymentMethod); - this.$emit('setPaidAmount', (this.sinvDoc.grandTotal as Money).float); + this.$emit( + 'setPaidAmount', + (this.sinvDoc.outstandingAmount as Money).float + ); } }, async setPaymentMethods() { From aa54dc3a9b4eb9c061046ab295ecb2aa8d889043 Mon Sep 17 00:00:00 2001 From: suhailanzar Date: Thu, 5 Jun 2025 12:26:58 +0530 Subject: [PATCH 3/4] fix: payment issue after validation --- models/baseModels/Invoice/Invoice.ts | 3 +- models/baseModels/Payment/Payment.ts | 42 +++++++++++++--------------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/models/baseModels/Invoice/Invoice.ts b/models/baseModels/Invoice/Invoice.ts index 5565cb4a..865fa124 100644 --- a/models/baseModels/Invoice/Invoice.ts +++ b/models/baseModels/Invoice/Invoice.ts @@ -838,10 +838,9 @@ export abstract class Invoice extends Transactional { if (sinvreturnedDoc.outstandingAmount?.isZero()) { return this.grandTotal; } else { - const returnAmount = this.grandTotal + return this.grandTotal ?.abs() .sub(sinvreturnedDoc.outstandingAmount as Money); - return returnAmount; } } diff --git a/models/baseModels/Payment/Payment.ts b/models/baseModels/Payment/Payment.ts index 5bbbf0e0..058e845c 100644 --- a/models/baseModels/Payment/Payment.ts +++ b/models/baseModels/Payment/Payment.ts @@ -113,7 +113,6 @@ export class Payment extends Transactional { } await this.validateFor(); - this.validatePartialAmount(); this.validateAccounts(); this.validateTotalReferenceAmount(); await this.validateReferences(); @@ -447,24 +446,6 @@ export class Payment extends Transactional { throw new ValidationError(message); } - validatePartialAmount() { - if (!this.fyo.singles.AccountingSettings?.enablePartialPayment) { - const amount = - (this.amountPaid as Money)?.isZero?.() === false - ? (this.amountPaid as Money) - : (this.amount as Money); - const initialAmount = this.initialAmount as Money; - if (amount.lt(initialAmount) && !amount.eq(initialAmount)) { - if (this.writeoff?.isZero()) { - this.amount = this.initialAmount; - throw new ValidationError( - this.fyo.t`Enable Partial payment to pay partial amount` - ); - } - } - } - } - async afterSubmit() { await super.afterSubmit(); await this.updateReferenceDocOutstanding(); @@ -478,6 +459,22 @@ export class Payment extends Transactional { row.referenceName ); + if (!this.fyo.singles.AccountingSettings?.enablePartialPayment) { + const amount = !(this.amountPaid as Money).isZero() + ? (this.amountPaid as Money) + : (this.amount as Money); + const initialAmount = this.initialAmount as Money; + if (amount.lt(initialAmount) && !amount.eq(initialAmount)) { + if (this.writeoff?.isZero()) { + row.amount = this.initialAmount; + row.amountPaid = this.fyo.pesa(0); + throw new ValidationError( + this.fyo.t`Enable Partial payment to pay partial amount` + ); + } + } + } + const previousOutstandingAmount = referenceDoc.outstandingAmount as Money; const outstandingAmount = previousOutstandingAmount.sub(row.amount!); await referenceDoc.setAndSync({ outstandingAmount }); @@ -726,18 +723,17 @@ export class Payment extends Transactional { if (!this.initialAmount) { this.initialAmount = this.amount as Money; } - const moneyValue = value as Money; - if (moneyValue.gt(this.initialAmount)) { + if ((value as Money).gt(this.initialAmount)) { throw new ValidationError( this.fyo.t`Payment amount cannot exceed ${this.fyo.format( this.initialAmount, 'Currency' )}.` ); - } else if (moneyValue.isZero()) { + } else if ((value as Money).isZero()) { throw new ValidationError( this.fyo.t`Payment amount cannot be ${this.fyo.format( - moneyValue, + value as Money, 'Currency' )}.` ); From 7c793437667db6b9098538bfa1dfcc4579f0554e Mon Sep 17 00:00:00 2001 From: suhailanzar Date: Wed, 11 Jun 2025 22:51:58 +0530 Subject: [PATCH 4/4] fix: updated the pos discount field issue --- src/components/POS/Classic/SelectedItemRow.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/POS/Classic/SelectedItemRow.vue b/src/components/POS/Classic/SelectedItemRow.vue index f35546fa..99923b95 100644 --- a/src/components/POS/Classic/SelectedItemRow.vue +++ b/src/components/POS/Classic/SelectedItemRow.vue @@ -182,7 +182,7 @@ :show-label="true" :border="true" :value="row.itemDiscountAmount" - :read-only="isDiscountsReadOnly(row.itemDiscountPercent as number < 0)" + :read-only="isDiscountsReadOnly(row.itemDiscountPercent as number > 0)" @change="(value:number) => setItemDiscount('amount', value)" />