2
0
mirror of https://github.com/frappe/books.git synced 2024-12-22 19:09:01 +00:00

fix: prevent multiple pricing rules on same item across batches

This commit is contained in:
AbleKSaju 2024-09-11 10:22:46 +05:30
parent 9347acc0aa
commit 3f88b38d4a
3 changed files with 62 additions and 93 deletions

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 {
@ -1186,6 +1186,7 @@ export abstract class Invoice extends Transactional {
if (!this.isSales || !this.items) {
return;
}
const pricingRules: ApplicablePricingRules[] = [];
for (const item of this.items) {
@ -1193,6 +1194,23 @@ export abstract class Invoice extends Transactional {
continue;
}
const duplicatePricingRule = this.pricingRuleDetail?.filter(
(pricingrule: PricingRuleDetail) =>
pricingrule.referenceItem == item.item
);
if (duplicatePricingRule && duplicatePricingRule?.length >= 2) {
const { showToast } = await import('src/utils/interactive');
const message = t`Pricing Rule '${
duplicatePricingRule[0]?.referenceName as string
}' is already applied to item '${
item.item as string
}' in another batch.`;
showToast({ type: 'error', message });
continue;
}
const pricingRuleDocNames = (
await this.fyo.db.getAll(ModelNameEnum.PricingRuleItem, {
fields: ['parent'],
@ -1227,21 +1245,16 @@ export abstract class Invoice extends Transactional {
continue;
}
for (const filteredDoc of filtered) {
const isPricingRuleHasConflicts = await getPricingRulesConflicts(
filteredDoc,
filteredDoc.priority as number
);
const isPricingRuleHasConflicts = getPricingRulesConflicts(filtered);
if (isPricingRuleHasConflicts) {
continue;
}
pricingRules.push({
applyOnItem: item.item as string,
pricingRule: filtered[0],
});
if (isPricingRuleHasConflicts) {
continue;
}
pricingRules.push({
applyOnItem: item.item as string,
pricingRule: filtered[0],
});
}
return pricingRules;

View File

@ -24,7 +24,6 @@ import { InvoiceStatus, ModelNameEnum } from './types';
import { Lead } from './baseModels/Lead/Lead';
import { PricingRule } from './baseModels/PricingRule/PricingRule';
import { ApplicablePricingRules } from './baseModels/Invoice/types';
import { PricingRuleItem } from './baseModels/PricingRuleItem/PricingRuleItem';
export function getQuoteActions(
fyo: Fyo,
@ -713,23 +712,18 @@ export async function getPricingRule(
continue;
}
for (const filteredPricingRule of filtered) {
const isPricingRuleHasConflicts = await getPricingRulesConflicts(
filteredPricingRule,
filteredPricingRule.priority as number
);
const isPricingRuleHasConflicts = getPricingRulesConflicts(filtered);
if (isPricingRuleHasConflicts) {
continue;
}
pricingRules.push({
applyOnItem: item.item as string,
pricingRule: filtered[0],
});
if (isPricingRuleHasConflicts) {
continue;
}
return pricingRules;
pricingRules.push({
applyOnItem: item.item as string,
pricingRule: filtered[0],
});
}
return pricingRules;
}
export function filterPricingRules(
@ -801,72 +795,30 @@ export function canApplyPricingRule(
}
return true;
}
export function getPricingRulesConflicts(
pricingRules: PricingRule[]
): undefined | boolean {
const pricingRuleDocs = Array.from(pricingRules);
export async function getPricingRulesConflicts(
pricingRule: PricingRule,
priority: number
): Promise<{ item: string; pricingRule: string } | undefined> {
const items = pricingRule.appliedItems?.map((item) => item.item) as string[];
for (const item of items) {
const duplicatePricingRuleItems = (
await pricingRule.fyo.db.getAll(ModelNameEnum.PricingRuleItem, {
fields: ['parent'],
filters: {
item: item,
},
})
).filter(
(doc) => doc.parent !== (pricingRule.name as string)
) as PricingRuleItem[];
const pricingRuleNames = duplicatePricingRuleItems.map(
(item) => item.parent
) as string[];
const pricingRuleDocs = (await pricingRule.fyo.db.getAll(
ModelNameEnum.PricingRule,
{
fields: ['*'],
filters: {
name: ['in', pricingRuleNames],
},
}
)) as PricingRule[];
for (const pricingRuleDoc of pricingRuleDocs) {
const minQtys = [
pricingRuleDoc.minQuantity,
pricingRule.minQuantity,
].sort() as number[];
const maxQtys = [
pricingRuleDoc.maxQuantity,
pricingRule.maxQuantity,
].sort() as number[];
const qtyHasConflicts = minQtys[1] <= maxQtys[0];
const minAmounts = [
pricingRuleDoc.minAmount?.float,
pricingRule.minAmount?.float,
].sort() as number[];
const maxAmounts = [
pricingRuleDoc.maxAmount?.float,
pricingRule.maxAmount?.float,
].sort() as number[];
const amountHasConflicts = minAmounts[1] <= maxAmounts[0];
if (
(amountHasConflicts || qtyHasConflicts) &&
pricingRuleDoc.priority === priority
) {
return { pricingRule: pricingRuleDoc.name as string, item: item };
}
}
const firstPricingRule = pricingRuleDocs.shift();
if (!firstPricingRule) {
return;
}
const conflictingPricingRuleNames: string[] = [];
for (const pricingRuleDoc of pricingRuleDocs.slice(0)) {
if (pricingRuleDoc.priority !== firstPricingRule?.priority) {
continue;
}
conflictingPricingRuleNames.push(pricingRuleDoc.name as string);
}
if (!conflictingPricingRuleNames.length) {
return;
}
return true;
}
export function roundFreeItemQty(

View File

@ -417,6 +417,9 @@ export default defineComponent({
invItem.quantity = (invItem.quantity as number) + 1;
invItem.rate = item.rate as Money;
await this.applyPricingRule();
await this.sinvDoc.runFormulas();
return;
}
}
@ -432,6 +435,7 @@ export default defineComponent({
message: t`${error as string}`,
});
}
return;
}