Skip to content

Commit

Permalink
fix: resolved test cases by creating new free items
Browse files Browse the repository at this point in the history
  • Loading branch information
AbleKSaju committed Dec 3, 2024
1 parent ee348f9 commit 8dbbd25
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 64 deletions.
24 changes: 16 additions & 8 deletions models/baseModels/Invoice/Invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
getPricingRulesConflicts,
removeLoyaltyPoint,
roundFreeItemQty,
getItemQtyMap,
} from 'models/helpers';
import { StockTransfer } from 'models/inventory/StockTransfer';
import { validateBatch } from 'models/inventory/helpers';
Expand Down Expand Up @@ -1264,35 +1265,42 @@ export abstract class Invoice extends Transactional {
continue;
}

let freeItemQty = pricingRuleDoc.freeItemQuantity as number;
let freeItemQty: number | undefined;

if (pricingRuleDoc.isRecursive) {
freeItemQty =
(item.quantity as number) / (pricingRuleDoc.recurseEvery as number);
if (pricingRuleDoc?.freeItem) {
const itemQtyMap = await getItemQtyMap(this as SalesInvoice);
freeItemQty = itemQtyMap[pricingRuleDoc.freeItem]?.availableQty;
}

const canApplyPRLOnItem = canApplyPricingRule(
pricingRuleDoc,
this.date as Date,
item.quantity as number,
item.amount as Money,
freeItemQty
freeItemQty as number
);

if (!canApplyPRLOnItem) {
continue;
}

let roundFreeItemQuantity = pricingRuleDoc.freeItemQuantity as number;

if (pricingRuleDoc.isRecursive) {
roundFreeItemQuantity =
(item.quantity as number) / (pricingRuleDoc.recurseEvery as number);
}

if (pricingRuleDoc.roundFreeItemQty) {
freeItemQty = roundFreeItemQty(
freeItemQty,
roundFreeItemQuantity,
pricingRuleDoc.roundingMethod as 'round' | 'floor' | 'ceil'
);
}

await this.append('items', {
item: pricingRuleDoc.freeItem as string,
quantity: freeItemQty,
quantity: roundFreeItemQuantity,
isFreeItem: true,
pricingRule: pricingRuleDoc.title,
rate: pricingRuleDoc.freeItemRate,
Expand Down Expand Up @@ -1439,8 +1447,8 @@ export abstract class Invoice extends Transactional {
}

const filtered = await filterPricingRules(
this as SalesInvoice,
pricingRuleDocsForItem,
this.date as Date,
item.quantity as number,
item.amount as Money
);
Expand Down
2 changes: 1 addition & 1 deletion models/baseModels/InvoiceItem/InvoiceItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ async function getItemRateFromPricingRule(
(prDetail) => prDetail.referenceItem === doc.item
);

if (!pricingRule) {
if (!pricingRule || !pricingRule.length) {
return;
}

Expand Down
54 changes: 50 additions & 4 deletions models/baseModels/tests/testCouponCodes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import test from 'tape';
import { closeTestFyo, getTestFyo, setupTestFyo } from 'tests/helpers';
import { ModelNameEnum } from 'models/types';
import { getItem } from 'models/inventory/tests/helpers';
import { getItem, getStockMovement } from 'models/inventory/tests/helpers';
import { SalesInvoice } from '../SalesInvoice/SalesInvoice';
import { PricingRule } from '../PricingRule/PricingRule';
import { assertThrows } from 'backend/database/tests/helpers';
import { MovementTypeEnum } from 'models/inventory/types';

const fyo = getTestFyo();
setupTestFyo(fyo, __filename);
Expand All @@ -20,6 +21,11 @@ const itemMap = {
rate: 100,
unit: 'Unit',
},
Pen: {
name: 'Pen',
rate: 700,
unit: 'Unit',
},
};

const partyMap = {
Expand Down Expand Up @@ -52,7 +58,7 @@ const pricingRuleMap = [
title: 'CAP PDR Offer',
appliedItems: [{ item: itemMap.Cap.name }],
discountType: 'Product Discount',
freeItem: 'Cap',
freeItem: 'Pen',
freeItemQuantity: 1,
freeItemUnit: 'Unit',
freeItemRate: 0,
Expand Down Expand Up @@ -91,6 +97,10 @@ const couponCodesMap = [
},
];

const locationMap = {
LocationOne: 'LocationOne',
};

test(' Coupon Codes: create dummy item, party, pricing rules, coupon codes', async (t) => {
// Create Items
for (const { name, rate } of Object.values(itemMap)) {
Expand Down Expand Up @@ -123,6 +133,38 @@ test(' Coupon Codes: create dummy item, party, pricing rules, coupon codes', asy

t.ok(fyo.singles.AccountingSettings?.enablePricingRule);

// Create Locations
for (const name of Object.values(locationMap)) {
await fyo.doc.getNewDoc(ModelNameEnum.Location, { name }).sync();
t.ok(await fyo.db.exists(ModelNameEnum.Location, name), `${name} exists`);
}

// create MaterialReceipt
const stockMovement = await getStockMovement(
MovementTypeEnum.MaterialReceipt,
new Date('2022-11-03T09:57:04.528'),
[
{
item: itemMap.Pen.name,
to: locationMap.LocationOne,
quantity: 25,
rate: 500,
},
],
fyo
);
await (await stockMovement.sync()).submit();
t.equal(
await fyo.db.getStockQuantity(
itemMap.Pen.name,
locationMap.LocationOne,
undefined,
undefined
),
25,
'Pen has quantity twenty five'
);

// Create Coupon Codes
for (const couponCodes of Object.values(couponCodesMap)) {
await fyo.doc.getNewDoc(ModelNameEnum.CouponCode, couponCodes).sync();
Expand Down Expand Up @@ -290,9 +332,13 @@ test('Coupon not applied: incorrect items added.', async (t) => {
await sinv.append('coupons', { coupons: couponCodesMap[0].name });

await sinv.runFormulas();
await sinv.sync();
// await assertThrows(
// async () =>
await sinv.sync(),
// 'Minimum Amount should be less than the Maximum Amount'
// );

t.equal(sinv.coupons?.length, 0, 'coupon code is not applied');
t.equal(sinv.coupons?.length, 0, 'coupon code is not applied');
});

closeTestFyo(fyo, __filename);
52 changes: 47 additions & 5 deletions models/baseModels/tests/testPricingRule.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import test from 'tape';
import { closeTestFyo, getTestFyo, setupTestFyo } from 'tests/helpers';
import { ModelNameEnum } from 'models/types';
import { SalesInvoice } from '../SalesInvoice/SalesInvoice';
import { getItem } from 'models/inventory/tests/helpers';
import { getItem, getStockMovement } from 'models/inventory/tests/helpers';
import { PricingRule } from '../PricingRule/PricingRule';
import { MovementTypeEnum } from 'models/inventory/types';

const fyo = getTestFyo();
setupTestFyo(fyo, __filename);
Expand All @@ -19,6 +20,11 @@ const itemMap = {
rate: 100,
unit: 'Unit',
},
Pen: {
name: 'Pen',
rate: 700,
unit: 'Unit',
},
};

const partyMap = {
Expand Down Expand Up @@ -48,7 +54,7 @@ const pricingRuleMap = [
title: 'CAP PDR Offer',
appliedItems: [{ item: itemMap.Cap.name }],
discountType: 'Product Discount',
freeItem: 'Cap',
freeItem: 'Pen',
freeItemQuantity: 1,
freeItemUnit: 'Unit',
freeItemRate: 0,
Expand All @@ -62,7 +68,11 @@ const pricingRuleMap = [
},
];

test('Pricing Rule: create dummy item, party, pricing rules', async (t) => {
const locationMap = {
LocationOne: 'LocationOne',
};

test('Pricing Rule: create dummy item, party, pricing rules, free items, locations', async (t) => {
// Create Items
for (const { name, rate } of Object.values(itemMap)) {
const item = getItem(name, rate, false);
Expand All @@ -87,6 +97,38 @@ test('Pricing Rule: create dummy item, party, pricing rules', async (t) => {
);
}

// Create Locations
for (const name of Object.values(locationMap)) {
await fyo.doc.getNewDoc(ModelNameEnum.Location, { name }).sync();
t.ok(await fyo.db.exists(ModelNameEnum.Location, name), `${name} exists`);
}

// create MaterialReceipt
const stockMovement = await getStockMovement(
MovementTypeEnum.MaterialReceipt,
new Date('2022-11-03T09:57:04.528'),
[
{
item: itemMap.Pen.name,
to: locationMap.LocationOne,
quantity: 25,
rate: 500,
},
],
fyo
);
await (await stockMovement.sync()).submit();
t.equal(
await fyo.db.getStockQuantity(
itemMap.Pen.name,
locationMap.LocationOne,
undefined,
undefined
),
25,
'Pen has quantity twenty five'
);

await fyo.singles.AccountingSettings?.set('enablePricingRule', true);
t.ok(fyo.singles.AccountingSettings?.enablePricingRule);
});
Expand Down Expand Up @@ -487,7 +529,7 @@ test('create a product discount giving 1 free item', async (t) => {
await sinv.runFormulas();
await sinv.sync();

t.equal(!!sinv.items![1].isFreeItem, true);
t.equal(sinv.items![1].isFreeItem, true);
t.equal(sinv.items![1].rate!.float, pricingRuleMap[1].freeItemRate);
t.equal(sinv.items![1].quantity, pricingRuleMap[1].freeItemQuantity);
});
Expand Down Expand Up @@ -516,7 +558,7 @@ test('create a product discount, recurse 2', async (t) => {
await sinv.runFormulas();
await sinv.sync();

t.equal(!!sinv.items![1].isFreeItem, true);
t.equal(sinv.items![1].isFreeItem, true);
t.equal(sinv.items![1].rate!.float, pricingRuleMap[1].freeItemRate);
t.equal(sinv.items![1].quantity, pricingRuleMap[1].freeItemQuantity);
});
Expand Down
57 changes: 47 additions & 10 deletions models/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ import { safeParseFloat } from 'utils/index';
import { PriceList } from './baseModels/PriceList/PriceList';
import { InvoiceItem } from './baseModels/InvoiceItem/InvoiceItem';
import { SalesInvoiceItem } from './baseModels/SalesInvoiceItem/SalesInvoiceItem';
import { getItemQtyMap } from 'src/utils/pos';
import { ItemQtyMap } from 'src/components/POS/types';
import { ValuationMethod } from './inventory/types';
import {
getRawStockLedgerEntries,
getStockBalanceEntries,
getStockLedgerEntries,
} from 'reports/inventory/helpers';

export function getQuoteActions(
fyo: Fyo,
Expand All @@ -64,6 +70,34 @@ export function getInvoiceActions(
];
}

export async function getItemQtyMap(doc: SalesInvoice): Promise<ItemQtyMap> {
const itemQtyMap: ItemQtyMap = {};
const valuationMethod =
(doc.fyo.singles.InventorySettings?.valuationMethod as ValuationMethod) ??
ValuationMethod.FIFO;

const rawSLEs = await getRawStockLedgerEntries(doc.fyo);
const rawData = getStockLedgerEntries(rawSLEs, valuationMethod);
const posInventory = doc.fyo.singles.POSSettings?.inventory;

const stockBalance = getStockBalanceEntries(rawData, {
location: posInventory,
});

for (const row of stockBalance) {
if (!itemQtyMap[row.item]) {
itemQtyMap[row.item] = { availableQty: 0 };
}

if (row.batch) {
itemQtyMap[row.item][row.batch] = row.balanceQuantity;
}

itemQtyMap[row.item]!.availableQty += row.balanceQuantity;
}
return itemQtyMap;
}

export function getStockTransferActions(
fyo: Fyo,
schemaName: ModelNameEnum.Shipment | ModelNameEnum.PurchaseReceipt
Expand Down Expand Up @@ -916,8 +950,8 @@ export async function getPricingRule(
}

const filtered = await filterPricingRules(
doc as SalesInvoice,
pricingRuleDocsForItem,
doc.date as Date,
item.quantity as number,
item.amount as Money
);
Expand Down Expand Up @@ -979,8 +1013,8 @@ export async function getItemRateFromPriceList(
}

export async function filterPricingRules(
doc: SalesInvoice,
pricingRuleDocsForItem: PricingRule[],
sinvDate: Date,
quantity: number,
amount: Money
): Promise<PricingRule[] | []> {
Expand All @@ -990,14 +1024,14 @@ export async function filterPricingRules(
let freeItemQty: number | undefined;

if (pricingRuleDoc?.freeItem) {
const itemQtyMap = await getItemQtyMap();
freeItemQty = itemQtyMap[pricingRuleDoc.freeItem].availableQty;
const itemQtyMap = await getItemQtyMap(doc);
freeItemQty = itemQtyMap[pricingRuleDoc.freeItem]?.availableQty;
}

if (
canApplyPricingRule(
pricingRuleDoc,
sinvDate,
doc.date as Date,
quantity,
amount,
freeItemQty ?? 0
Expand All @@ -1016,11 +1050,14 @@ export function canApplyPricingRule(
amount: Money,
freeItemQty: number
): boolean {
const freeItemQuantity = pricingRuleDoc.freeItemQuantity;

if (pricingRuleDoc.isRecursive) {
freeItemQty = quantity / (pricingRuleDoc.recurseEvery as number);
}

// Filter by Quantity
if (
pricingRuleDoc.freeItem &&
pricingRuleDoc.freeItemQuantity! >= freeItemQty
) {
if (pricingRuleDoc.freeItem && freeItemQuantity! >= freeItemQty) {
throw new ValidationError(
t`Free item '${pricingRuleDoc.freeItem}' does not have a specified quantity`
);
Expand Down
Loading

0 comments on commit 8dbbd25

Please sign in to comment.