mirror of
https://github.com/frappe/books.git
synced 2026-08-24 02:24:17 -05:00
Merge pull request #1314 from Gadha2311/batchwise-returninvoice
fix: resolve batch-wise return issue for loyalty points
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
||||
roundFreeItemQty,
|
||||
getReturnQtyTotal,
|
||||
getReturnLoyaltyPoints,
|
||||
getItemQtyMap,
|
||||
} from 'models/helpers';
|
||||
import { StockTransfer } from 'models/inventory/StockTransfer';
|
||||
import { validateBatch } from 'models/inventory/helpers';
|
||||
@@ -221,9 +222,7 @@ export abstract class Invoice extends Transactional {
|
||||
await this._removeLoyaltyPointEntry();
|
||||
await this._updateIsItemsReturned();
|
||||
this.reduceUsedCountOfCoupons();
|
||||
if (this.schemaName === ModelNameEnum.SalesInvoice)
|
||||
[await this.updateIsItemsFullyReturned(this)];
|
||||
return;
|
||||
await this.updateIsItemsFullyReturned(this);
|
||||
}
|
||||
|
||||
if (this.isQuote) {
|
||||
@@ -264,7 +263,9 @@ export abstract class Invoice extends Transactional {
|
||||
}
|
||||
|
||||
await this._updateIsItemsReturned();
|
||||
await this._createLoyaltyPointEntry();
|
||||
if (!this.isReturn) {
|
||||
await this._createLoyaltyPointEntry();
|
||||
}
|
||||
|
||||
if (this.schemaName === ModelNameEnum.SalesInvoice) {
|
||||
this.updateUsedCountOfCoupons();
|
||||
@@ -692,6 +693,17 @@ export abstract class Invoice extends Transactional {
|
||||
|
||||
for (const item of docItems) {
|
||||
if (totalQtyOfReturnedItems) {
|
||||
if (item.isFreeItem) {
|
||||
returnDocItems.push({
|
||||
...item,
|
||||
name: undefined,
|
||||
quantity: -(item.quantity as number),
|
||||
transferQuantity: -(
|
||||
(item.quantity as number) / (item.unitConversionFactor as number)
|
||||
),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (item.batch) {
|
||||
const returnData = totalQtyOfReturnedItems[item.item as string];
|
||||
if (typeof returnData === 'object' && returnData?.batches) {
|
||||
@@ -822,19 +834,21 @@ export abstract class Invoice extends Transactional {
|
||||
}
|
||||
|
||||
async updateIsItemsFullyReturned(doc?: Invoice) {
|
||||
let sinvDoc;
|
||||
if (doc?.returnAgainst) {
|
||||
sinvDoc = await this.fyo.doc.getDoc(
|
||||
ModelNameEnum.SalesInvoice,
|
||||
doc.returnAgainst
|
||||
);
|
||||
if (!doc?.returnAgainst || doc.schemaName !== ModelNameEnum.SalesInvoice) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sinvDoc = await this.fyo.doc.getDoc(
|
||||
ModelNameEnum.SalesInvoice,
|
||||
doc.returnAgainst
|
||||
);
|
||||
|
||||
const totalQtyOfReturnedItems = await getReturnQtyTotal(
|
||||
(sinvDoc as Invoice) ?? this
|
||||
);
|
||||
const isFullyReturned = Object.values(totalQtyOfReturnedItems).every(
|
||||
(quantity) => quantity === 0
|
||||
(value) =>
|
||||
typeof value === 'number' ? value === 0 : value?.quantity === 0
|
||||
);
|
||||
if (!isFullyReturned) {
|
||||
return;
|
||||
@@ -1360,6 +1374,21 @@ export abstract class Invoice extends Transactional {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isFreeItem = row.isFreeItem ?? false;
|
||||
if (isFreeItem) {
|
||||
await transfer.append('items', {
|
||||
item: row.item,
|
||||
quantity: row.quantity,
|
||||
location,
|
||||
rate: this.fyo.pesa(0),
|
||||
batch: row.batch || null,
|
||||
description: row.description,
|
||||
hsnCode: row.hsnCode,
|
||||
isFreeItem,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
let quantity;
|
||||
if (itemDoc.trackItem) {
|
||||
quantity = row.stockNotTransferred;
|
||||
@@ -1567,7 +1596,7 @@ export abstract class Invoice extends Transactional {
|
||||
}
|
||||
|
||||
clearFreeItems() {
|
||||
if (this.pricingRuleDetail?.length || !this.items) {
|
||||
if (this.pricingRuleDetail?.length || !this.items || this.isReturn) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1585,7 +1614,9 @@ export abstract class Invoice extends Transactional {
|
||||
return;
|
||||
}
|
||||
|
||||
this.items = this.items.filter((item) => !item.isFreeItem);
|
||||
if (!this.isReturn) {
|
||||
this.items = this.items.filter((item) => !item.isFreeItem);
|
||||
}
|
||||
|
||||
for (const item of this.items) {
|
||||
const pricingRuleDetailForItem = this.pricingRuleDetail?.filter(
|
||||
@@ -1629,18 +1660,34 @@ export abstract class Invoice extends Transactional {
|
||||
}
|
||||
|
||||
if (pricingRuleDoc.roundFreeItemQty) {
|
||||
roundFreeItemQty(
|
||||
roundFreeItemQuantity = roundFreeItemQty(
|
||||
roundFreeItemQuantity,
|
||||
pricingRuleDoc.roundingMethod as 'round' | 'floor' | 'ceil'
|
||||
'floor'
|
||||
);
|
||||
}
|
||||
|
||||
if (roundFreeItemQuantity <= 0) {
|
||||
throw new ValidationError(
|
||||
t`Free item "${
|
||||
pricingRuleDoc.freeItem as string
|
||||
}" was not added due to zero
|
||||
quantity`
|
||||
);
|
||||
}
|
||||
|
||||
const freeItem = pricingRuleDoc.freeItem as string;
|
||||
const itemQtyMap = await getItemQtyMap(this as SalesInvoice);
|
||||
const availableQty = itemQtyMap[freeItem]?.availableQty ?? 0;
|
||||
|
||||
if (availableQty < roundFreeItemQuantity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.append('items', {
|
||||
item: pricingRuleDoc.freeItem as string,
|
||||
item: freeItem,
|
||||
quantity: roundFreeItemQuantity,
|
||||
isFreeItem: true,
|
||||
pricingRule: pricingRuleDoc.title,
|
||||
rate: pricingRuleDoc.freeItemRate,
|
||||
unit: pricingRuleDoc.freeItemUnit,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ import { ListViewSettings } from 'fyo/model/types';
|
||||
|
||||
export class LoyaltyPointEntry extends Doc {
|
||||
loyaltyProgram?: string;
|
||||
loyaltyProgramTier?: string;
|
||||
customer?: string;
|
||||
invoice?: string;
|
||||
purchaseAmount?: number;
|
||||
postingDate?: Date;
|
||||
expiryDate?: Date;
|
||||
|
||||
static override getListViewSettings(): ListViewSettings {
|
||||
|
||||
@@ -30,7 +30,6 @@ export class PricingRule extends Doc {
|
||||
freeItem?: string;
|
||||
freeItemQuantity?: number;
|
||||
freeItemUnit?: string;
|
||||
freeItemRate?: Money;
|
||||
roundFreeItemQty?: number;
|
||||
roundingMethod?: string;
|
||||
|
||||
@@ -158,7 +157,6 @@ export class PricingRule extends Doc {
|
||||
freeItem: () => this.isDiscountTypeIsPriceDiscount,
|
||||
freeItemQuantity: () => this.isDiscountTypeIsPriceDiscount,
|
||||
freeItemUnit: () => this.isDiscountTypeIsPriceDiscount,
|
||||
freeItemRate: () => this.isDiscountTypeIsPriceDiscount,
|
||||
roundFreeItemQty: () => this.isDiscountTypeIsPriceDiscount,
|
||||
roundingMethod: () =>
|
||||
this.isDiscountTypeIsPriceDiscount || !this.roundFreeItemQty,
|
||||
|
||||
+29
-5
@@ -47,6 +47,7 @@ import {
|
||||
getStockBalanceEntries,
|
||||
getStockLedgerEntries,
|
||||
} from 'reports/inventory/helpers';
|
||||
import { LoyaltyPointEntry } from './baseModels/LoyaltyPointEntry/LoyaltyPointEntry';
|
||||
|
||||
export function getQuoteActions(
|
||||
fyo: Fyo,
|
||||
@@ -748,14 +749,16 @@ export async function addItem<M extends ModelsWithItems>(name: string, doc: M) {
|
||||
|
||||
export async function getReturnLoyaltyPoints(doc: Invoice) {
|
||||
const returnDocs = await doc.fyo.db.getAll(doc.schemaName, {
|
||||
fields: ['*'],
|
||||
fields: ['name', 'loyaltyPoints'],
|
||||
filters: {
|
||||
returnAgainst: doc.returnAgainst as string,
|
||||
submitted: true,
|
||||
},
|
||||
});
|
||||
|
||||
const totalLoyaltyPoints = returnDocs.reduce(
|
||||
const sunvDocs = returnDocs.filter((sinvDoc) => sinvDoc.name !== doc.name);
|
||||
|
||||
const totalLoyaltyPoints = sunvDocs.reduce(
|
||||
(sum, doc) => sum + Math.abs(doc.loyaltyPoints as number),
|
||||
0
|
||||
);
|
||||
@@ -960,17 +963,38 @@ export async function removeLoyaltyPoint(doc: Doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loyalityPointEntryDoc = await doc.fyo.doc.getDoc(
|
||||
const lPEntryDoc = (await doc.fyo.doc.getDoc(
|
||||
ModelNameEnum.LoyaltyPointEntry,
|
||||
data[0].name
|
||||
);
|
||||
)) as LoyaltyPointEntry;
|
||||
|
||||
const newLoyaltyPoint =
|
||||
(lPEntryDoc?.loyaltyPoints as number) +
|
||||
Math.abs(doc.loyaltyPoints as number);
|
||||
|
||||
if (newLoyaltyPoint !== 0) {
|
||||
const newLoyaltyPointEntry = doc.fyo.doc.getNewDoc(
|
||||
ModelNameEnum.LoyaltyPointEntry,
|
||||
{
|
||||
loyaltyProgram: lPEntryDoc.loyaltyProgram,
|
||||
customer: lPEntryDoc.customer,
|
||||
invoice: lPEntryDoc.invoice,
|
||||
postingDate: lPEntryDoc.date as Date,
|
||||
purchaseAmount: lPEntryDoc.purchaseAmount,
|
||||
expiryDate: lPEntryDoc.expiryDate,
|
||||
loyaltyProgramTier: lPEntryDoc.loyaltyProgramTier,
|
||||
loyaltyPoints: newLoyaltyPoint,
|
||||
}
|
||||
);
|
||||
await newLoyaltyPointEntry.sync();
|
||||
}
|
||||
|
||||
const party = (await doc.fyo.doc.getDoc(
|
||||
ModelNameEnum.Party,
|
||||
doc.party as string
|
||||
)) as Party;
|
||||
|
||||
await loyalityPointEntryDoc.delete();
|
||||
await lPEntryDoc.delete();
|
||||
await party.updateLoyaltyPoints();
|
||||
}
|
||||
|
||||
|
||||
@@ -147,12 +147,6 @@
|
||||
"target": "UOM",
|
||||
"section": "Product Discount Scheme"
|
||||
},
|
||||
{
|
||||
"fieldname": "freeItemRate",
|
||||
"label": "Rate",
|
||||
"fieldtype": "Currency",
|
||||
"section": "Product Discount Scheme"
|
||||
},
|
||||
{
|
||||
"fieldname": "roundFreeItemQty",
|
||||
"label": "Round Free Item Quantity",
|
||||
|
||||
@@ -837,6 +837,7 @@ export default defineComponent({
|
||||
|
||||
await this.paymentDoc.set('paymentMethod', paymentMethod);
|
||||
await this.paymentDoc.set('amount', this.fyo.pesa(this.paidAmount.float));
|
||||
await this.paymentDoc.set('referenceType', ModelNameEnum.SalesInvoice);
|
||||
|
||||
const paymentMethodDoc = await this.paymentDoc.loadAndGetLink(
|
||||
'paymentMethod'
|
||||
|
||||
Reference in New Issue
Block a user