fix: added validation for maxed loyaltyprogram

This commit is contained in:
Gadha2311
2026-02-04 15:07:22 +05:30
parent 8e10bf313d
commit cb092ede86
8 changed files with 119 additions and 97 deletions
+1 -14
View File
@@ -10,26 +10,13 @@ export async function checkLoyaltyProgramExpiry() {
const loyaltyPrograms = await dm.db?.getAll(ModelNameEnum.LoyaltyProgram, {
fields: ['name', 'toDate', 'status', 'isEnabled', 'maximumUse', 'used'],
filters: {
status: ['not in', ['Expired', 'Maxed']],
status: ['not in', ['Expired']],
isEnabled: true,
},
});
if (loyaltyPrograms) {
for (const program of loyaltyPrograms) {
const maximumUse = Number(program.maximumUse) || 0;
const used = Number(program.used) || 0;
if (maximumUse > 0 && used >= maximumUse) {
await dm.db?.knex!(ModelNameEnum.LoyaltyProgram)
.where({ name: program.name })
.update({
status: 'Maxed',
isEnabled: false,
});
continue;
}
if (program.toDate && new Date(String(program.toDate)) <= currentDate) {
await dm.db?.knex!(ModelNameEnum.LoyaltyProgram)
.where({ name: program.name })
+56 -22
View File
@@ -30,7 +30,7 @@ import {
getItemVisibility,
validateLoyaltyProgram,
getLoyaltyProgramTier,
isLoyaltyProgramMaxedOut,
isLoyaltyProgramExpiredAndMaxed,
} from 'models/helpers';
import { StockTransfer } from 'models/inventory/StockTransfer';
import { validateBatch } from 'models/inventory/helpers';
@@ -886,7 +886,7 @@ export abstract class Invoice extends Transactional {
}
async updateUsedCountOfLoyaltyProgram() {
if (!this.loyaltyProgram || !this.redeemLoyaltyPoints) {
if (!this.loyaltyProgram) {
return;
}
@@ -895,13 +895,27 @@ export abstract class Invoice extends Transactional {
this.loyaltyProgram
);
await loyaltyProgramDoc.setAndSync({
used: (loyaltyProgramDoc.used as number) + 1,
});
const maximumUse = loyaltyProgramDoc.maximumUse as number;
const used = (loyaltyProgramDoc.used as number) || 0;
if (this.redeemLoyaltyPoints) {
const newUsedCount = used + 1;
if (maximumUse > 0 && newUsedCount >= maximumUse) {
await loyaltyProgramDoc.setAndSync({
used: newUsedCount,
isEnabled: false,
});
} else {
await loyaltyProgramDoc.setAndSync({
used: newUsedCount,
});
}
}
}
async reduceUsedCountOfLoyaltyProgram() {
if (!this.loyaltyProgram || !this.redeemLoyaltyPoints) {
if (!this.loyaltyProgram) {
return;
}
@@ -910,9 +924,22 @@ export abstract class Invoice extends Transactional {
this.loyaltyProgram
);
await loyaltyProgramDoc.setAndSync({
used: (loyaltyProgramDoc.used as number) - 1,
});
const maximumUse = loyaltyProgramDoc.maximumUse as number;
const used = (loyaltyProgramDoc.used as number) || 0;
const newUsedCount = used - 1;
if (this.redeemLoyaltyPoints) {
if (newUsedCount < maximumUse) {
await loyaltyProgramDoc.setAndSync({
used: newUsedCount,
isEnabled: true,
});
} else {
await loyaltyProgramDoc.setAndSync({
used: newUsedCount,
});
}
}
}
async updateIsItemsFullyReturned(doc?: Invoice) {
@@ -975,11 +1002,6 @@ export abstract class Invoice extends Transactional {
this.loyaltyProgram
)) as LoyaltyProgram;
// Check if loyalty program is enabled
if (!loyaltyProgramDoc.isEnabled) {
return;
}
const invoiceDate = this.date as Date;
const fromDate = loyaltyProgramDoc.fromDate as Date;
const toDate = loyaltyProgramDoc.toDate as Date;
@@ -1093,12 +1115,7 @@ export abstract class Invoice extends Transactional {
return '';
}
const maxedOut = await isLoyaltyProgramMaxedOut(
this.fyo,
loyaltyProgramName
);
return maxedOut ? '' : loyaltyProgramName;
return loyaltyProgramName;
},
dependsOn: ['party', 'name'],
},
@@ -1108,6 +1125,17 @@ export abstract class Invoice extends Transactional {
return 0;
}
const loyaltyProgramName = this.loyaltyProgram as string;
if (loyaltyProgramName) {
const isExpiredAndMaxed = await isLoyaltyProgramExpiredAndMaxed(
this.fyo,
loyaltyProgramName
);
if (isExpiredAndMaxed) {
return 0;
}
}
const loyaltyPoints = await this.fyo.getValue(
ModelNameEnum.Party,
this.party,
@@ -1115,7 +1143,7 @@ export abstract class Invoice extends Transactional {
);
return loyaltyPoints || 0;
},
dependsOn: ['party'],
dependsOn: ['party', 'loyaltyProgram'],
},
currency: {
formula: async () => {
@@ -1308,7 +1336,13 @@ export abstract class Invoice extends Transactional {
loyaltyProgram: () => !this.loyaltyProgram,
availableLoyaltyPoints: () => !this.loyaltyProgram || this.isReturn,
loyaltyPoints: () => !this.redeemLoyaltyPoints || this.isReturn,
redeemLoyaltyPoints: () => !this.loyaltyProgram || this.isReturn,
redeemLoyaltyPoints: () => {
if (!this.loyaltyProgram || this.isReturn) {
return true;
}
return (this.availableLoyaltyPoints ?? 0) <= 0;
},
coupons: () => this.isSubmitted && !this.coupons?.length,
priceList: () =>
!this.fyo.singles.AccountingSettings?.enablePriceList ||
@@ -1,9 +1,8 @@
import { DocValue } from 'fyo/core/types';
import { Doc } from 'fyo/model/doc';
import { FiltersMap, ListViewSettings, ValidationMap } from 'fyo/model/types';
import { ListViewSettings, ValidationMap } from 'fyo/model/types';
import { ValidationError } from 'fyo/utils/errors';
import { CollectionRulesItems } from '../CollectionRulesItems/CollectionRulesItems';
import { AccountRootTypeEnum } from '../Account/types';
import { getLoyaltyProgramStatusColumn } from '../../helpers';
export class LoyaltyProgram extends Doc {
@@ -11,7 +10,7 @@ export class LoyaltyProgram extends Doc {
expiryDuration?: number;
maximumUse?: number;
used?: number;
status?: 'Active' | 'Expired' | 'Maxed' | 'Disabled';
status?: 'Active' | 'Expired' | 'Disabled' | 'Maxed';
validations: ValidationMap = {
used: (value: DocValue) => {
@@ -35,22 +34,6 @@ export class LoyaltyProgram extends Doc {
},
};
async afterSubmit() {
const maximumUse = (this.maximumUse as number) || 0;
const used = (this.used as number) || 0;
if (maximumUse > 0 && used >= maximumUse) {
await this.setAndSync({ status: 'Maxed', isEnabled: false });
}
}
static filters: FiltersMap = {
expenseAccount: () => ({
rootType: AccountRootTypeEnum.Expense,
isGroup: false,
}),
};
static getListViewSettings(): ListViewSettings {
return {
columns: ['name', getLoyaltyProgramStatusColumn(), 'fromDate', 'toDate'],
+12
View File
@@ -14,6 +14,7 @@ import {
import { Money } from 'pesa';
import { PartyRole } from './types';
import { ModelNameEnum } from 'models/types';
import { isLoyaltyProgramExpiredAndMaxed } from 'models/helpers';
export class Party extends Doc {
role?: PartyRole;
@@ -66,6 +67,17 @@ export class Party extends Doc {
}
async _getTotalLoyaltyPoints() {
const loyaltyProgramName = this.loyaltyProgram as string;
if (loyaltyProgramName) {
const isExpiredAndMaxed = await isLoyaltyProgramExpiredAndMaxed(
this.fyo,
loyaltyProgramName
);
if (isExpiredAndMaxed) {
return 0;
}
}
const data = (await this.fyo.db.getAll(ModelNameEnum.LoyaltyPointEntry, {
fields: ['name', 'loyaltyPoints', 'expiryDate', 'postingDate'],
filters: {
+40 -34
View File
@@ -765,13 +765,6 @@ export function getLoyaltyProgramStatus(doc?: RenderData | Doc): string {
return '';
}
const maximumUse = doc.maximumUse as number;
const used = doc.used as number;
if (maximumUse > 0 && used >= maximumUse) {
return 'Maxed';
}
const currentDate = new Date();
currentDate.setHours(0, 0, 0, 0);
@@ -781,6 +774,13 @@ export function getLoyaltyProgramStatus(doc?: RenderData | Doc): string {
return 'Expired';
}
const maximumUse = doc.maximumUse as number;
const used = doc.used as number;
if (maximumUse > 0 && used >= maximumUse) {
return 'Maxed';
}
return 'Active';
}
@@ -1664,33 +1664,6 @@ export async function validateLoyaltyProgram(
}
}
export async function isLoyaltyProgramMaxedOut(
fyo: Fyo,
loyaltyProgramName: string
): Promise<boolean> {
const loyaltyProgram = await fyo.db.getAll(ModelNameEnum.LoyaltyProgram, {
fields: ['maximumUse', 'used', 'isEnabled'],
filters: { name: loyaltyProgramName },
});
if (!loyaltyProgram[0]) {
return false;
}
if (!loyaltyProgram[0]?.isEnabled) {
return true;
}
const maximumUse = loyaltyProgram[0]?.maximumUse as number;
const used = loyaltyProgram[0]?.used as number;
if (!maximumUse) {
return false;
}
return used >= maximumUse;
}
export function removeFreeItems(sinvDoc: SalesInvoice) {
if (!sinvDoc || !sinvDoc.items) {
return;
@@ -1765,3 +1738,36 @@ export function roundFreeItemQty(
): number {
return Math[roundingMethod](quantity);
}
export async function isLoyaltyProgramExpiredAndMaxed(
fyo: Fyo,
loyaltyProgramName: string
): Promise<boolean> {
if (!loyaltyProgramName) {
return false;
}
const loyaltyProgram = await fyo.db.getAll(ModelNameEnum.LoyaltyProgram, {
fields: ['toDate', 'maximumUse', 'used', 'isEnabled'],
filters: { name: loyaltyProgramName },
});
if (!loyaltyProgram.length) {
return false;
}
const program = loyaltyProgram[0];
const currentDate = new Date();
currentDate.setHours(0, 0, 0, 0);
const toDate = program.toDate as Date;
const isExpired =
toDate && new Date(toDate).getTime() < currentDate.getTime();
const maximumUse = (program.maximumUse as number) || 0;
const used = (program.used as number) || 0;
const isMaxed = maximumUse > 0 && used >= maximumUse;
const result = isExpired || isMaxed;
return result;
}
+2 -2
View File
@@ -82,7 +82,7 @@
"fieldtype": "Link",
"target": "LoyaltyProgram",
"label": "Loyalty Program",
"section": "References",
"section": "Loyalty Points Redemption",
"readOnly": true
},
{
@@ -90,7 +90,7 @@
"fieldtype": "Int",
"label": "Available Loyalty Points",
"readOnly": true,
"section": "References"
"section": "Loyalty Points Redemption"
},
{
"fieldname": "redeemLoyaltyPoints",
+2 -2
View File
@@ -79,8 +79,8 @@ export default defineComponent({
Unpaid: this.t`Unpaid`,
PartlyPaid: this.t`Partly Paid`,
Expired: this.t`Expired`,
Maxed: this.t`Maxed`,
Active: this.t`Active`,
Maxed: this.t`Maxed`,
}[this.status];
},
color(): UIColors {
@@ -104,8 +104,8 @@ const statusColorMap: Record<Status, UIColors> = {
Unpaid: 'red',
PartlyPaid: 'yellow',
Expired: 'red',
Maxed: 'orange',
Active: 'green',
Maxed: 'orange',
};
function getStatus(doc: Doc) {
+4 -4
View File
@@ -166,7 +166,7 @@ import {
removeFreeItems,
getItemRateFromPriceList,
getItemVisibility,
isLoyaltyProgramMaxedOut,
isLoyaltyProgramExpiredAndMaxed,
} from 'models/helpers';
import {
POSItem,
@@ -499,12 +499,12 @@ export default defineComponent({
const loyaltyProgramName = party[0]?.loyaltyProgram as string;
if (loyaltyProgramName) {
const isMaxedOut = await isLoyaltyProgramMaxedOut(
const isExpiredAndMaxed = await isLoyaltyProgramExpiredAndMaxed(
this.fyo,
loyaltyProgramName
);
if (isMaxedOut) {
this.loyaltyProgram = '';
if (isExpiredAndMaxed) {
this.loyaltyProgram = loyaltyProgramName;
this.loyaltyPoints = 0;
return;
}