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

feat: validations

This commit is contained in:
AbleKSaju 2024-10-08 12:24:48 +05:30
parent 9a95d6fc96
commit 6e2fdfc5be
2 changed files with 217 additions and 94 deletions

View File

@ -1,11 +1,7 @@
import { DocValue } from 'fyo/core/types';
import { ValidationMap } from 'fyo/model/types';
import { ValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types';
import { Money } from 'pesa';
import { InvoiceItem } from '../InvoiceItem/InvoiceItem';
import { getApplicableCouponCodesName } from 'models/helpers';
import { SalesInvoice } from '../SalesInvoice/SalesInvoice';
import { validateCouponCode } from 'models/helpers';
export class AppliedCouponCodes extends InvoiceItem {
coupons?: string;
@ -16,92 +12,7 @@ export class AppliedCouponCodes extends InvoiceItem {
return;
}
const coupon = await this.fyo.db.getAll(ModelNameEnum.CouponCode, {
fields: [
'minAmount',
'maxAmount',
'pricingRule',
'validFrom',
'validTo',
'maximumUse',
'used',
'isEnabled',
],
filters: { name: value as string },
});
if (!coupon[0].isEnabled) {
throw new ValidationError(
'Coupon code cannot be applied as it is not enabled'
);
}
if ((coupon[0]?.maximumUse as number) <= (coupon[0]?.used as number)) {
throw new ValidationError(
'Coupon code has been used maximum number of times'
);
}
const applicableCouponCodesNames = await getApplicableCouponCodesName(
value as string,
this.parentdoc as SalesInvoice
);
if (!applicableCouponCodesNames?.length) {
throw new ValidationError(
this.fyo.t`Coupon ${
value as string
} is not applicable for applied items.`
);
}
const couponExist = this.parentdoc?.coupons?.some(
(coupon) => coupon?.coupons === value
);
if (couponExist) {
throw new ValidationError(
this.fyo.t`${value as string} already applied.`
);
}
if (
(coupon[0].minAmount as Money).gte(
this.parentdoc?.grandTotal as Money
) &&
!(coupon[0].minAmount as Money).isZero()
) {
throw new ValidationError(
this.fyo.t`The Grand Total must exceed ${
(coupon[0].minAmount as Money).float
} to apply the coupon ${value as string}.`
);
}
if (
(coupon[0].maxAmount as Money).lte(
this.parentdoc?.grandTotal as Money
) &&
!(coupon[0].maxAmount as Money).isZero()
) {
throw new ValidationError(
this.fyo.t`The Grand Total must be less than ${
(coupon[0].maxAmount as Money).float
} to apply this coupon.`
);
}
if ((coupon[0].validFrom as Date) > (this.parentdoc?.date as Date)) {
throw new ValidationError(
this.fyo.t`Valid From Date should be less than Valid To Date.`
);
}
if ((coupon[0].validTo as Date) < (this.parentdoc?.date as Date)) {
throw new ValidationError(
this.fyo.t`Valid To Date should be greater than Valid From Date.`
);
}
await validateCouponCode(this as AppliedCouponCodes, value as string);
},
};
}

View File

@ -30,6 +30,8 @@ import { isPesa } from 'fyo/utils';
import { Party } from './baseModels/Party/Party';
import { CouponCode } from './baseModels/CouponCode/CouponCode';
import { SalesInvoice } from './baseModels/SalesInvoice/SalesInvoice';
import { AppliedCouponCodes } from './baseModels/AppliedCouponCodes/AppliedCouponCodes';
import { ValidationError } from 'fyo/utils/errors';
export function getQuoteActions(
fyo: Fyo,
@ -794,8 +796,64 @@ export async function removeLoyaltyPoint(doc: Doc) {
await party.updateLoyaltyPoints();
}
export async function getPricingRulesOfCoupons(
doc: SalesInvoice,
couponName?: string,
pricingRuleDocNames?: string[]
): Promise<PricingRule[] | undefined> {
if (!doc?.coupons?.length && !couponName) {
return;
}
let appliedCoupons: CouponCode[] = [];
const couponsToFetch = couponName
? [couponName]
: doc?.coupons?.map((coupon) => coupon.coupons);
if (couponsToFetch?.length) {
const result = (
await Promise.all(
couponsToFetch.map(async (name) => {
return (await doc.fyo.db.getAll(ModelNameEnum.CouponCode, {
fields: ['*'],
filters: { name: name as string },
})) as CouponCode[];
})
)
).flat();
appliedCoupons = [...appliedCoupons, ...result];
}
const filteredPricingRuleNames = appliedCoupons.filter(
(val) => val.pricingRule === pricingRuleDocNames![0]
);
if (!filteredPricingRuleNames.length) {
return;
}
const pricingRuleDocsForItem = (await doc.fyo.db.getAll(
ModelNameEnum.PricingRule,
{
fields: ['*'],
filters: {
name: ['in', pricingRuleDocNames as string[]],
isEnabled: true,
isCouponCodeBased: true,
},
orderBy: 'priority',
order: 'desc',
}
)) as PricingRule[];
return pricingRuleDocsForItem;
}
export async function getPricingRule(
doc: Invoice
doc: Invoice,
couponName?: string
): Promise<ApplicablePricingRules[] | undefined> {
if (
!doc.fyo.singles.AccountingSettings?.enablePricingRule ||
@ -822,19 +880,41 @@ export async function getPricingRule(
})
).map((doc) => doc.parent) as string[];
const pricingRuleDocsForItem = (await doc.fyo.db.getAll(
let pricingRuleDocsForItem;
const pricingRuleDocs = (await doc.fyo.db.getAll(
ModelNameEnum.PricingRule,
{
fields: ['*'],
filters: {
name: ['in', pricingRuleDocNames],
isEnabled: true,
isCouponCodeBased: false,
},
orderBy: 'priority',
order: 'desc',
}
)) as PricingRule[];
if (pricingRuleDocs.length) {
pricingRuleDocsForItem = pricingRuleDocs;
}
if (!pricingRuleDocs.length || couponName) {
const couponPricingRules: PricingRule[] | undefined =
await getPricingRulesOfCoupons(
doc as SalesInvoice,
couponName,
pricingRuleDocNames
);
pricingRuleDocsForItem = couponPricingRules as PricingRule[];
}
if (!pricingRuleDocsForItem) {
continue;
}
const filtered = filterPricingRules(
pricingRuleDocsForItem,
doc.date as Date,
@ -857,6 +937,7 @@ export async function getPricingRule(
pricingRule: filtered[0],
});
}
return pricingRules;
}
@ -990,7 +1071,7 @@ export async function getApplicableCouponCodesName(
return [];
}
const applicablePricingRules = await getPricingRule(sinvDoc);
const applicablePricingRules = await getPricingRule(sinvDoc, couponName);
if (!applicablePricingRules?.length) {
return [];
@ -1006,6 +1087,137 @@ export async function getApplicableCouponCodesName(
}));
}
export async function validateCouponCode(
doc: AppliedCouponCodes,
value: string,
sinvDoc?: SalesInvoice
) {
const coupon = await doc.fyo.db.getAll(ModelNameEnum.CouponCode, {
fields: [
'minAmount',
'maxAmount',
'pricingRule',
'validFrom',
'validTo',
'maximumUse',
'used',
'isEnabled',
],
filters: { name: value },
});
if (!coupon[0]?.isEnabled) {
throw new ValidationError(
'Coupon code cannot be applied as it is not enabled'
);
}
if ((coupon[0]?.maximumUse as number) <= (coupon[0]?.used as number)) {
throw new ValidationError(
'Coupon code has been used maximum number of times'
);
}
if (!doc.parentdoc) {
doc.parentdoc = sinvDoc;
}
const applicableCouponCodesNames = await getApplicableCouponCodesName(
value,
doc.parentdoc as SalesInvoice
);
if (!applicableCouponCodesNames?.length) {
throw new ValidationError(
t`Coupon ${value} is not applicable for applied items.`
);
}
const couponExist = doc.parentdoc?.coupons?.some(
(coupon) => coupon?.coupons === value
);
if (couponExist) {
throw new ValidationError(t`${value} already applied.`);
}
if (
(coupon[0].minAmount as Money).gte(doc.parentdoc?.grandTotal as Money) &&
!(coupon[0].minAmount as Money).isZero()
) {
throw new ValidationError(
t`The Grand Total must exceed ${
(coupon[0].minAmount as Money).float
} to apply the coupon ${value}.`
);
}
if (
(coupon[0].maxAmount as Money).lte(doc.parentdoc?.grandTotal as Money) &&
!(coupon[0].maxAmount as Money).isZero()
) {
throw new ValidationError(
t`The Grand Total must be less than ${
(coupon[0].maxAmount as Money).float
} to apply this coupon.`
);
}
if ((coupon[0].validFrom as Date) > (doc.parentdoc?.date as Date)) {
throw new ValidationError(
t`Valid From Date should be less than Valid To Date.`
);
}
if ((coupon[0].validTo as Date) < (doc.parentdoc?.date as Date)) {
throw new ValidationError(
t`Valid To Date should be greater than Valid From Date.`
);
}
}
export function removeFreeItems(sinvDoc: SalesInvoice) {
if (!sinvDoc || !sinvDoc.items) {
return;
}
if (!!sinvDoc.isPricingRuleApplied) {
return;
}
for (const item of sinvDoc.items) {
if (item.isFreeItem) {
sinvDoc.items = sinvDoc.items?.filter(
(invoiceItem) => invoiceItem.name !== item.name
);
}
}
}
export async function updatePricingRule(sinvDoc: SalesInvoice) {
const applicablePricingRuleNames = await getPricingRule(sinvDoc);
if (!applicablePricingRuleNames || !applicablePricingRuleNames.length) {
sinvDoc.pricingRuleDetail = undefined;
sinvDoc.isPricingRuleApplied = false;
removeFreeItems(sinvDoc);
return;
}
setTimeout(() => {
(async () => {
const appliedPricingRuleCount = sinvDoc?.items?.filter(
(val) => val.isFreeItem
).length;
if (appliedPricingRuleCount !== applicablePricingRuleNames?.length) {
await sinvDoc.appendPricingRuleDetail(applicablePricingRuleNames);
await sinvDoc.applyProductDiscount();
}
})();
}, 1);
}
export function getPricingRulesConflicts(
pricingRules: PricingRule[]
): undefined | boolean {