mirror of
https://github.com/frappe/books.git
synced 2024-12-22 19:09:01 +00:00
feat: validations
This commit is contained in:
parent
9a95d6fc96
commit
6e2fdfc5be
@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user