From 6e2fdfc5be6fbf428337de534437977f5c650f2d Mon Sep 17 00:00:00 2001 From: AbleKSaju <126228406+AbleKSaju@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:24:48 +0530 Subject: [PATCH] feat: validations --- .../AppliedCouponCodes/AppliedCouponCodes.ts | 93 +------- models/helpers.ts | 218 +++++++++++++++++- 2 files changed, 217 insertions(+), 94 deletions(-) diff --git a/models/baseModels/AppliedCouponCodes/AppliedCouponCodes.ts b/models/baseModels/AppliedCouponCodes/AppliedCouponCodes.ts index f7158c1b..1db78ac5 100644 --- a/models/baseModels/AppliedCouponCodes/AppliedCouponCodes.ts +++ b/models/baseModels/AppliedCouponCodes/AppliedCouponCodes.ts @@ -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); }, }; } diff --git a/models/helpers.ts b/models/helpers.ts index 141ecc9c..690b5515 100644 --- a/models/helpers.ts +++ b/models/helpers.ts @@ -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 { + 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 { 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 {