Merge pull request #1305 from frappe/fix-loyaltypoints-in-batch

fix: correct loyalty points calculation for returned batch items
This commit is contained in:
Able k Saju
2025-07-29 11:26:37 +05:30
committed by GitHub
3 changed files with 72 additions and 42 deletions
+37 -37
View File
@@ -25,6 +25,7 @@ import {
removeLoyaltyPoint,
roundFreeItemQty,
getReturnQtyTotal,
getReturnLoyaltyPoints,
} from 'models/helpers';
import { StockTransfer } from 'models/inventory/StockTransfer';
import { validateBatch } from 'models/inventory/helpers';
@@ -58,6 +59,13 @@ export type TaxDetail = {
rate: number;
};
export type ReturnedItemData =
| number
| {
quantity?: number;
batches?: Record<string, number>;
};
export type InvoiceTaxItem = {
details: TaxDetail;
exchangeRate?: number;
@@ -683,34 +691,30 @@ export abstract class Invoice extends Transactional {
);
for (const item of docItems) {
const itemName = item.item as string;
let balQuantity: number;
if (!returnBalanceItemsQty) {
if (totalQtyOfReturnedItems) {
if (item.batch) {
const returnData = totalQtyOfReturnedItems[item.item as string];
if (typeof returnData === 'object' && returnData?.batches) {
balQuantity = -returnData.batches[item.batch as string] || 0;
returnDocItems = docItems.map((docItem) => ({
...docItem,
name: undefined,
quantity: -returnData?.batches![docItem.batch as string] || 0,
}));
}
} else {
balQuantity = -(totalQtyOfReturnedItems[itemName] || 0);
returnDocItems = docItems.map((docItem) => ({
...docItem,
name: undefined,
quantity: -(totalQtyOfReturnedItems[docItem.item as string] || 0),
}));
}
const returnDocs = docItems.map((docItem) => ({
...docItem,
name: undefined,
quantity: balQuantity,
}));
returnDocItems.push(returnDocs[0]);
for (const row of returnDocItems) {
row.itemDiscountedTotal = await this.getItemsDiscountedTotal(
row as InvoiceItem
);
}
continue;
break;
}
const isItemExist = !!returnDocItems.filter(
@@ -722,7 +726,7 @@ export abstract class Invoice extends Transactional {
}
const returnedItem: ReturnDocItem | undefined =
returnBalanceItemsQty[item.item as string];
returnBalanceItemsQty![item.item as string];
if (!returnedItem) {
continue;
@@ -745,12 +749,15 @@ export abstract class Invoice extends Transactional {
'\n'
);
}
const returnedItemsData = totalQtyOfReturnedItems[itemName];
const returnedItemsData = totalQtyOfReturnedItems[
item.item as string
] as ReturnedItemData;
if (
typeof returnedItemsData === 'object' &&
returnedItemsData?.batches
returnedItemsData.batches
) {
quantity = -returnedItemsData?.batches?.[item.batch as string];
quantity = -returnedItemsData?.batches[item.batch as string];
transferQuantity = quantity / (item.unitConversionFactor as number);
}
}
@@ -922,33 +929,26 @@ export abstract class Invoice extends Transactional {
this.loyaltyPoints as number
);
const result = baseTotal.sub(totalLoyaltyAmount);
return result;
return baseTotal.sub(totalLoyaltyAmount);
}
if (this.isReturn) {
const originalInvoice = (await this.fyo.doc.getDoc(
this.schemaName,
this.returnAgainst
)) as Invoice;
const loyaltyAmount = await getReturnLoyaltyPoints(this);
const returnRatio = baseTotal
.abs()
.div(originalInvoice.netTotal as Money);
const totalAmount = baseTotal.abs().sub(loyaltyAmount);
const originalLoyaltyAmount = await getAddedLPWithGrandTotal(
this.fyo,
originalInvoice.loyaltyProgram as string,
originalInvoice.loyaltyPoints as number
);
this.loyaltyPoints = loyaltyAmount;
if (totalAmount.isNegative()) {
this.loyaltyPoints = totalAmount.abs().float - Math.abs(loyaltyAmount);
return this.fyo.pesa(0);
}
const proportionalLoyaltyAmount = originalLoyaltyAmount.mul(returnRatio);
return baseTotal.abs().sub(proportionalLoyaltyAmount).neg();
return baseTotal.abs().sub(loyaltyAmount);
}
return baseTotal;
}
formulas: FormulaMap = {
account: {
formula: async () => {
+12 -5
View File
@@ -5,6 +5,7 @@ import { ModelNameEnum } from 'models/types';
import {
getAddedLPWithGrandTotal,
getInvoiceActions,
getReturnLoyaltyPoints,
getTransactionStatusColumn,
} from '../../helpers';
import { Invoice } from '../Invoice/Invoice';
@@ -42,11 +43,17 @@ export class SalesInvoice extends Invoice {
this.loyaltyProgram
)) as LoyaltyProgram;
const totalAmount = await getAddedLPWithGrandTotal(
this.fyo,
this.loyaltyProgram as string,
this.loyaltyPoints as number
);
let totalAmount;
if (this.isReturn) {
totalAmount = this.fyo.pesa(await getReturnLoyaltyPoints(this));
} else {
totalAmount = await getAddedLPWithGrandTotal(
this.fyo,
this.loyaltyProgram as string,
this.loyaltyPoints as number
);
}
await posting.debit(
loyaltyProgramDoc.expenseAccount as string,
+23
View File
@@ -746,6 +746,29 @@ export async function addItem<M extends ModelsWithItems>(name: string, doc: M) {
await item.set('item', name);
}
export async function getReturnLoyaltyPoints(doc: Invoice) {
const returnDocs = await doc.fyo.db.getAll(doc.schemaName, {
fields: ['*'],
filters: {
returnAgainst: doc.returnAgainst as string,
submitted: true,
},
});
const totalLoyaltyPoints = returnDocs.reduce(
(sum, doc) => sum + Math.abs(doc.loyaltyPoints as number),
0
);
const loyaltyPoints = await doc.fyo.getValue(
ModelNameEnum.SalesInvoice,
doc.returnAgainst as string,
'loyaltyPoints'
);
return Math.abs((loyaltyPoints as number) - Math.abs(totalLoyaltyPoints));
}
export async function getReturnQtyTotal(
doc: Invoice
): Promise<