Merge pull request #1260 from frappe/fix-add-loyaltyamount-in-gt

fix: include converted loyalty points in grand total
This commit is contained in:
Able k Saju
2025-06-26 10:17:17 +05:30
committed by GitHub
7 changed files with 72 additions and 67 deletions
+28 -3
View File
@@ -1,4 +1,4 @@
import { Fyo } from 'fyo';
import { Fyo, t } from 'fyo';
import { DocValueMap } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import {
@@ -193,6 +193,21 @@ export abstract class Invoice extends Transactional {
await this._validatePricingRule();
}
async beforeSubmit() {
const partyDoc = (await this.fyo.doc.getDoc(
ModelNameEnum.Party,
this.party
)) as Party;
if ((this.loyaltyPoints as number) > (partyDoc?.loyaltyPoints || 0)) {
throw new ValidationError(
t`${this.party as string} only has ${
partyDoc.loyaltyPoints as number
} points`
);
}
}
async afterSubmit() {
await super.afterSubmit();
if (this.isReturn) {
@@ -446,6 +461,9 @@ export abstract class Invoice extends Transactional {
const totalDiscount = this.getTotalDiscount();
if (!this.taxes!.length) {
if (this.redeemLoyaltyPoints) {
return this.getLPAddedBaseGrandTotal();
}
return (this.netTotal as Money).sub(totalDiscount);
}
@@ -880,6 +898,10 @@ export abstract class Invoice extends Transactional {
this.loyaltyPoints as number
);
if (this.isReturn) {
return this.grandTotal;
}
return this.initialGrandTotal?.sub(totalLotaltyAmount);
}
formulas: FormulaMap = {
@@ -937,7 +959,10 @@ export abstract class Invoice extends Transactional {
},
netTotal: { formula: () => this.getSum('items', 'amount', false) },
taxes: { formula: async () => await this.getTaxSummary() },
grandTotal: { formula: () => this.getGrandTotal() },
grandTotal: {
formula: async () => await this.getGrandTotal(),
dependsOn: ['loyaltyPoints'],
},
baseGrandTotal: {
formula: () => (this.grandTotal as Money).mul(this.exchangeRate! ?? 1),
dependsOn: ['grandTotal', 'exchangeRate'],
@@ -955,7 +980,7 @@ export abstract class Invoice extends Transactional {
if (sinvreturnedDoc.outstandingAmount?.isZero()) {
return this.grandTotal?.abs();
} else {
const totalPaid = this.grandTotal
const totalPaid = sinvreturnedDoc.grandTotal
?.abs()
.sub(sinvreturnedDoc.outstandingAmount as Money);
+21 -2
View File
@@ -13,6 +13,8 @@ import { LoyaltyProgram } from '../LoyaltyProgram/LoyaltyProgram';
import { DocValue } from 'fyo/core/types';
import { Party } from '../Party/Party';
import { ValidationError } from 'fyo/utils/errors';
import { Money } from 'pesa';
import { Doc } from 'fyo/model/doc';
export class SalesInvoice extends Invoice {
items?: SalesInvoiceItem[];
@@ -81,7 +83,7 @@ export class SalesInvoice extends Invoice {
validations: ValidationMap = {
loyaltyPoints: async (value: DocValue) => {
if (!this.redeemLoyaltyPoints || this.isSubmitted) {
if (!this.redeemLoyaltyPoints || this.isSubmitted || this.isReturn) {
return;
}
@@ -116,7 +118,24 @@ export class SalesInvoice extends Invoice {
((loyaltyProgramDoc?.conversionFactor as number) || 0);
if (!this.isReturn) {
if (this.grandTotal?.lt(loyaltyPoint)) {
const totalDiscount = this.getTotalDiscount();
let baseGrandTotal;
if (!this.taxes!.length) {
baseGrandTotal = (this.netTotal as Money).sub(totalDiscount);
} else {
baseGrandTotal = ((this.taxes ?? []) as Doc[])
.map((doc) => doc.amount as Money)
.reduce((a, b) => {
if (this.isReturn) {
return a.abs().add(b.abs()).neg();
}
return a.add(b.abs());
}, (this.netTotal as Money).abs())
.sub(totalDiscount);
}
if (baseGrandTotal?.lt(loyaltyPoint)) {
throw new ValidationError(
t`no need ${value as number} points to purchase this item`
);
+11 -3
View File
@@ -231,10 +231,18 @@ export class StockMovementItem extends TransferItem {
);
}
},
batch: () => {
if (this.fyo.singles.InventorySettings?.enableBatches && !this.batch) {
batch: async () => {
if (!this.item || !this.batch) return;
const batchDoc = await this.fyo.doc.getDoc(
ModelNameEnum.Batch,
this.batch
);
if (!batchDoc) return;
if (batchDoc.item !== this.item) {
throw new ValidationError(
t`Batch is required for Item ${this.item as string}`
t`Batch ${this.batch} does not belong to Item ${this.item}`
);
}
},
@@ -305,6 +305,9 @@ export default defineComponent({
},
async mounted() {
this.$watch('row.quantity', (newVal: number) => {
this.setQuantity(newVal);
});
const posProfileName = this.fyo.singles.POSSettings?.posProfile;
if (posProfileName) {
+8
View File
@@ -40,6 +40,14 @@ export default defineComponent({
return this.t`Partly Paid ${outstandingPayment}`;
}
if (this.status === 'Outstanding') {
const outstandingPayment = this.fyo.format(
this.doc.outstandingAmount as Money,
'Currency'
);
return this.t`Unpaid ${outstandingPayment}`;
}
return {
Draft: this.t`Draft`,
Cancelled: this.t`Cancelled`,
-58
View File
@@ -146,7 +146,6 @@ import {
getItemQtyMap,
getPricingRule,
removeFreeItems,
getAddedLPWithGrandTotal,
getItemRateFromPriceList,
} from 'models/helpers';
import {
@@ -611,19 +610,6 @@ export default defineComponent({
async setLoyaltyPoints(value: number) {
this.appliedLoyaltyPoints = value;
await this.sinvDoc.set('redeemLoyaltyPoints', true);
const totalLotaltyAmount = await getAddedLPWithGrandTotal(
this.fyo,
this.loyaltyProgram,
value
);
const total = totalLotaltyAmount
.sub(this.sinvDoc.baseGrandTotal as Money)
.abs();
this.sinvDoc.grandTotal = total;
this.sinvDoc.outstandingAmount = total;
},
async selectedInvoiceName(doc: SalesInvoice) {
const salesInvoiceDoc = (await this.fyo.doc.getDoc(
@@ -704,50 +690,6 @@ export default defineComponent({
)) as number;
if (item.hasBatch) {
const isTrackItem =
this.fyo.singles.POSSettings?.itemVisibility == 'Inventory Items';
for (const invItem of existingItems) {
const itemQty = invItem.quantity ?? 0;
if (!isTrackItem) {
invItem.quantity = quantity
? (invItem.quantity as number) + quantity
: (invItem.quantity as number) + 1;
} else {
const qtyInBatch =
this.itemQtyMap[invItem.item as string][
invItem.batch as string
] ?? 0;
if (itemQty < qtyInBatch) {
invItem.quantity = quantity
? (invItem.quantity as number) + quantity
: (invItem.quantity as number) + 1;
invItem.rate = item.rate as Money;
}
await this.applyPricingRule();
await this.sinvDoc.runFormulas();
await validateQty(
this.sinvDoc as SalesInvoice,
item as Item,
existingItems as InvoiceItem[]
);
return;
}
await this.applyPricingRule();
await this.sinvDoc.runFormulas();
await validateQty(
this.sinvDoc as SalesInvoice,
item as Item,
existingItems as InvoiceItem[]
);
return;
}
await this.sinvDoc.append('items', {
rate: item.rate as Money,
item: item.name,
+1 -1
View File
@@ -1,4 +1,4 @@
<main class="h-auto w-auto m-2.5 p-0.5 pb-5 bg-white border border-gray-500">
<main class="h-auto w-auto m-1 p-0.5 pb-5 bg-white">
<!-- Invoice Header -->
<header class="pt-4 flex flex-col items-center">
<!-- Company Logo & Name -->