From 2b6e8c035dd5dbe203864706667fdeef093f65bd Mon Sep 17 00:00:00 2001 From: suhailanzar Date: Thu, 19 Jun 2025 17:02:10 +0530 Subject: [PATCH 1/3] fix: updated partial return items --- models/baseModels/Invoice/Invoice.ts | 63 +++++++++++++++++--- models/baseModels/InvoiceItem/InvoiceItem.ts | 19 ++---- models/helpers.ts | 42 ++++++++++++- schemas/app/Invoice.json | 6 ++ src/pages/POS/LoyaltyProgramModal.vue | 4 +- src/pages/POS/POS.vue | 2 +- 6 files changed, 110 insertions(+), 26 deletions(-) diff --git a/models/baseModels/Invoice/Invoice.ts b/models/baseModels/Invoice/Invoice.ts index 954a2a2b..6c32d25d 100644 --- a/models/baseModels/Invoice/Invoice.ts +++ b/models/baseModels/Invoice/Invoice.ts @@ -24,6 +24,7 @@ import { getPricingRulesConflicts, removeLoyaltyPoint, roundFreeItemQty, + getReturnQtyTotal, } from 'models/helpers'; import { StockTransfer } from 'models/inventory/StockTransfer'; import { validateBatch } from 'models/inventory/helpers'; @@ -94,6 +95,7 @@ export abstract class Invoice extends Transactional { isReturned?: boolean; returnAgainst?: string; + isFullyReturned?: boolean; pricingRuleDetail?: PricingRuleDetail[]; @@ -203,7 +205,7 @@ export abstract class Invoice extends Transactional { await this._removeLoyaltyPointEntry(); await this._updateIsItemsReturned(); this.reduceUsedCountOfCoupons(); - + await this.updateIsItemsFullyReturned(); return; } @@ -463,6 +465,9 @@ export abstract class Invoice extends Transactional { }, (this.netTotal as Money).abs()) .sub(totalDiscount); + if (this.redeemLoyaltyPoints) { + return this.getLPAddedBaseGrandTotal(); + } return grandTotal; } @@ -655,6 +660,8 @@ export abstract class Invoice extends Transactional { let returnDocItems: DocValueMap[] = []; + const sumOfReturnDocs = await getReturnQtyTotal(this); + const returnBalanceItemsQty = await this.fyo.db.getReturnBalanceItemsQty( this.schemaName, this.name @@ -662,13 +669,16 @@ export abstract class Invoice extends Transactional { for (const item of docItems) { if (!returnBalanceItemsQty) { - returnDocItems = docItems; + returnDocItems = docItems.map((docItem) => ({ + ...docItem, + name: undefined, + quantity: -(sumOfReturnDocs[docItem.item as string] || 0), + })); + for (const row of returnDocItems) { - row.name = undefined; row.itemDiscountedTotal = await this.getItemsDiscountedTotal( row as InvoiceItem ); - (row.quantity as number) *= -1; } break; } @@ -684,6 +694,10 @@ export abstract class Invoice extends Transactional { const returnedItem: ReturnDocItem | undefined = returnBalanceItemsQty[item.item as string]; + if (!returnedItem) { + continue; + } + let quantity = returnedItem.quantity; let serialNumber: string | undefined = returnedItem.serialNumbers?.join('\n'); @@ -710,6 +724,24 @@ export abstract class Invoice extends Transactional { quantity: quantity, }); } + + returnDocItems.forEach((docItems) => { + const itemName = docItems.item; + if (typeof itemName === 'string' || typeof itemName === 'number') { + if (itemName in sumOfReturnDocs) { + docItems.quantity = sumOfReturnDocs[itemName]; + } + } + }); + + returnDocItems = returnDocItems.filter( + (docItems) => (docItems.quantity as number) > 0 + ); + + returnDocItems.forEach((docItems) => { + docItems.quantity = -(docItems.quantity as number); + }); + const returnDocData = { ...docData, name: undefined, @@ -752,6 +784,25 @@ export abstract class Invoice extends Transactional { }); } + async updateIsItemsFullyReturned() { + if (!this.returnAgainst) { + return; + } + const sumOfReturnDocs = await getReturnQtyTotal(this); + const isFullyReturned = Object.values(sumOfReturnDocs).every( + (quantity) => quantity === 0 + ); + if (!isFullyReturned) { + return; + } + const invoiceDoc = await this.fyo.doc.getDoc( + this.schemaName, + this.returnAgainst + ); + await invoiceDoc.setAndSync({ isFullyReturned }); + await invoiceDoc.submit(); + } + async _updateIsItemsReturned() { if (!this.isReturn || !this.returnAgainst || this.isQuote) { return; @@ -917,10 +968,6 @@ export abstract class Invoice extends Transactional { } } - if (this.redeemLoyaltyPoints) { - return await this.getLPAddedBaseGrandTotal(); - } - return this.baseGrandTotal; }, dependsOn: ['discountAmount', 'discountPercent'], diff --git a/models/baseModels/InvoiceItem/InvoiceItem.ts b/models/baseModels/InvoiceItem/InvoiceItem.ts index 9eea9d76..bbf71787 100644 --- a/models/baseModels/InvoiceItem/InvoiceItem.ts +++ b/models/baseModels/InvoiceItem/InvoiceItem.ts @@ -401,16 +401,7 @@ export abstract class InvoiceItem extends Doc { return getTaxedTotalBeforeDiscounting(totalTaxRate, rate, quantity); }, - dependsOn: [ - 'itemDiscountAmount', - 'itemDiscountPercent', - 'itemDiscountedTotal', - 'setItemDiscountAmount', - 'tax', - 'rate', - 'quantity', - 'item', - ], + dependsOn: ['rate', 'quantity', 'item'], }, stockNotTransferred: { formula: async () => { @@ -735,12 +726,12 @@ function getDiscountedTotalBeforeTaxation( * - if percent: Quantity * Rate (1 - DiscountPercent / 100) */ - const amount = rate.mul(quantity); if (setDiscountAmount) { - return amount.sub(itemDiscountAmount); + return rate.sub(itemDiscountAmount).mul(quantity); + } else if (itemDiscountPercent > 0) { + return rate.mul(quantity).percent(itemDiscountPercent); } - - return amount.mul(1 - itemDiscountPercent / 100); + return rate.mul(quantity); } function getTaxedTotalAfterDiscounting( diff --git a/models/helpers.ts b/models/helpers.ts index c05abda4..6a6b9385 100644 --- a/models/helpers.ts +++ b/models/helpers.ts @@ -286,7 +286,6 @@ export function getMakeReturnDocAction(fyo: Fyo): Action { label: fyo.t`Return`, group: fyo.t`Create`, condition: (doc: Doc) => - !doc.isReturn && (!!fyo.singles.AccountingSettings?.enableInvoiceReturns || !!fyo.singles.InventorySettings?.enableStockReturns) && doc.isSubmitted && @@ -712,6 +711,47 @@ export async function addItem(name: string, doc: M) { await item.set('item', name); } +export async function getReturnQtyTotal( + doc: Invoice +): Promise> { + const returnDocs = await doc.fyo.db.getAll(doc.schemaName, { + fields: ['*'], + filters: { + returnAgainst: doc.name as string, + }, + }); + + const returnDocNames = await Promise.all( + returnDocs.map((docss) => + doc.fyo.doc.getDoc(doc.schemaName, docss.name as string) + ) + ); + + const quantitySum: { [key: string]: number } = {}; + + if ('items' in doc && Array.isArray(doc.items)) { + doc.items.forEach((docItem) => { + const itemName = docItem.item as string; + if (itemName) { + quantitySum[itemName] = (docItem.quantity as number) || 0; + } + }); + } + + returnDocNames.forEach((documents) => { + if ('items' in documents && Array.isArray(documents.items)) { + documents.items.forEach((item: InvoiceItem) => { + const itemName = item.item; + if (itemName && quantitySum.hasOwnProperty(itemName)) { + quantitySum[itemName] = + quantitySum[itemName] - Math.abs(item.quantity as number); + } + }); + } + }); + return quantitySum; +} + export async function createLoyaltyPointEntry(doc: Invoice) { const loyaltyProgramDoc = (await doc.fyo.doc.getDoc( ModelNameEnum.LoyaltyProgram, diff --git a/schemas/app/Invoice.json b/schemas/app/Invoice.json index 74191775..4bff7f5a 100644 --- a/schemas/app/Invoice.json +++ b/schemas/app/Invoice.json @@ -187,6 +187,12 @@ "hidden": true, "default": false }, + { + "fieldname": "isFullyReturned", + "fieldtype": "Check", + "hidden": true, + "default": false + }, { "fieldname": "isSyncedWithErp", "fieldtype": "Check", diff --git a/src/pages/POS/LoyaltyProgramModal.vue b/src/pages/POS/LoyaltyProgramModal.vue index a2934fe1..afcb3568 100644 --- a/src/pages/POS/LoyaltyProgramModal.vue +++ b/src/pages/POS/LoyaltyProgramModal.vue @@ -1,6 +1,6 @@