mirror of
https://github.com/frappe/books.git
synced 2026-08-24 02:24:17 -05:00
fix: added validation for maxed loyaltyprogram
This commit is contained in:
@@ -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 })
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user