Merge pull request #1232 from suhailanzar/partial-Payment

fix: implement complete partial payment handling
This commit is contained in:
Able k Saju
2025-06-16 12:11:55 +05:30
committed by GitHub
8 changed files with 64 additions and 22 deletions
@@ -25,6 +25,7 @@ export class AccountingSettings extends Doc {
enablePricingRule?: boolean;
enableERPNextSync?: boolean;
enablePointOfSaleWithOutInventory?: boolean;
enablePartialPayment?: boolean;
static filters: FiltersMap = {
writeOffAccount: () => ({
+3 -1
View File
@@ -897,7 +897,9 @@ export abstract class Invoice extends Transactional {
if (sinvreturnedDoc.outstandingAmount?.isZero()) {
return this.grandTotal;
} else {
return this.fyo.pesa(0);
return this.grandTotal
?.abs()
.sub(sinvreturnedDoc.outstandingAmount as Money);
}
}
+29 -7
View File
@@ -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;
@@ -458,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 });
@@ -703,17 +720,22 @@ 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;
}
if ((value as Money).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()) {
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(
value as Money,
'Currency'
)}.`
);
}
},
@@ -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`
);
}
}
},
};
+7
View File
@@ -149,6 +149,13 @@
"default": false,
"section": "Features"
},
{
"fieldname": "enablePartialPayment",
"label": "Enable Partial Payment",
"fieldtype": "Check",
"default": false,
"section": "Features"
},
{
"fieldname": "fiscalYearStart",
"label": "Fiscal Year Start Date",
@@ -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)"
/>
</div>
+2 -3
View File
@@ -530,7 +530,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;
@@ -825,6 +825,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'
@@ -832,7 +833,6 @@ export default defineComponent({
if (paymentMethodDoc?.type !== 'Cash') {
await this.paymentDoc.setMultiple({
amount: this.paidAmount.float,
referenceId: this.transferRefNo,
clearanceDate: this.transferClearanceDate,
});
@@ -841,7 +841,6 @@ export default defineComponent({
if (paymentMethodDoc?.type === 'Cash') {
await this.paymentDoc.setMultiple({
paymentAccount: this.defaultPOSCashAccount,
amount: this.paidAmount.float,
});
}
+15 -6
View File
@@ -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)"
/>
<div class="grid grid-cols-2 gap-6">
<Button
@@ -128,6 +128,15 @@
:text-right="true"
:value="sinvDoc?.grandTotal"
/>
<Currency
:df="sinvDoc.fieldMap.outstandingAmount"
:read-only="true"
:show-label="true"
:border="true"
:text-right="true"
:value="sinvDoc?.outstandingAmount"
/>
</div>
<div class="grid grid-cols-2 gap-4 bottom-8">
@@ -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() {