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, {
|
const loyaltyPrograms = await dm.db?.getAll(ModelNameEnum.LoyaltyProgram, {
|
||||||
fields: ['name', 'toDate', 'status', 'isEnabled', 'maximumUse', 'used'],
|
fields: ['name', 'toDate', 'status', 'isEnabled', 'maximumUse', 'used'],
|
||||||
filters: {
|
filters: {
|
||||||
status: ['not in', ['Expired', 'Maxed']],
|
status: ['not in', ['Expired']],
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loyaltyPrograms) {
|
if (loyaltyPrograms) {
|
||||||
for (const program of 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) {
|
if (program.toDate && new Date(String(program.toDate)) <= currentDate) {
|
||||||
await dm.db?.knex!(ModelNameEnum.LoyaltyProgram)
|
await dm.db?.knex!(ModelNameEnum.LoyaltyProgram)
|
||||||
.where({ name: program.name })
|
.where({ name: program.name })
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
getItemVisibility,
|
getItemVisibility,
|
||||||
validateLoyaltyProgram,
|
validateLoyaltyProgram,
|
||||||
getLoyaltyProgramTier,
|
getLoyaltyProgramTier,
|
||||||
isLoyaltyProgramMaxedOut,
|
isLoyaltyProgramExpiredAndMaxed,
|
||||||
} from 'models/helpers';
|
} from 'models/helpers';
|
||||||
import { StockTransfer } from 'models/inventory/StockTransfer';
|
import { StockTransfer } from 'models/inventory/StockTransfer';
|
||||||
import { validateBatch } from 'models/inventory/helpers';
|
import { validateBatch } from 'models/inventory/helpers';
|
||||||
@@ -886,7 +886,7 @@ export abstract class Invoice extends Transactional {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateUsedCountOfLoyaltyProgram() {
|
async updateUsedCountOfLoyaltyProgram() {
|
||||||
if (!this.loyaltyProgram || !this.redeemLoyaltyPoints) {
|
if (!this.loyaltyProgram) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -895,13 +895,27 @@ export abstract class Invoice extends Transactional {
|
|||||||
this.loyaltyProgram
|
this.loyaltyProgram
|
||||||
);
|
);
|
||||||
|
|
||||||
await loyaltyProgramDoc.setAndSync({
|
const maximumUse = loyaltyProgramDoc.maximumUse as number;
|
||||||
used: (loyaltyProgramDoc.used as number) + 1,
|
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() {
|
async reduceUsedCountOfLoyaltyProgram() {
|
||||||
if (!this.loyaltyProgram || !this.redeemLoyaltyPoints) {
|
if (!this.loyaltyProgram) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -910,9 +924,22 @@ export abstract class Invoice extends Transactional {
|
|||||||
this.loyaltyProgram
|
this.loyaltyProgram
|
||||||
);
|
);
|
||||||
|
|
||||||
await loyaltyProgramDoc.setAndSync({
|
const maximumUse = loyaltyProgramDoc.maximumUse as number;
|
||||||
used: (loyaltyProgramDoc.used as number) - 1,
|
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) {
|
async updateIsItemsFullyReturned(doc?: Invoice) {
|
||||||
@@ -975,11 +1002,6 @@ export abstract class Invoice extends Transactional {
|
|||||||
this.loyaltyProgram
|
this.loyaltyProgram
|
||||||
)) as LoyaltyProgram;
|
)) as LoyaltyProgram;
|
||||||
|
|
||||||
// Check if loyalty program is enabled
|
|
||||||
if (!loyaltyProgramDoc.isEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const invoiceDate = this.date as Date;
|
const invoiceDate = this.date as Date;
|
||||||
const fromDate = loyaltyProgramDoc.fromDate as Date;
|
const fromDate = loyaltyProgramDoc.fromDate as Date;
|
||||||
const toDate = loyaltyProgramDoc.toDate as Date;
|
const toDate = loyaltyProgramDoc.toDate as Date;
|
||||||
@@ -1093,12 +1115,7 @@ export abstract class Invoice extends Transactional {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxedOut = await isLoyaltyProgramMaxedOut(
|
return loyaltyProgramName;
|
||||||
this.fyo,
|
|
||||||
loyaltyProgramName
|
|
||||||
);
|
|
||||||
|
|
||||||
return maxedOut ? '' : loyaltyProgramName;
|
|
||||||
},
|
},
|
||||||
dependsOn: ['party', 'name'],
|
dependsOn: ['party', 'name'],
|
||||||
},
|
},
|
||||||
@@ -1108,6 +1125,17 @@ export abstract class Invoice extends Transactional {
|
|||||||
return 0;
|
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(
|
const loyaltyPoints = await this.fyo.getValue(
|
||||||
ModelNameEnum.Party,
|
ModelNameEnum.Party,
|
||||||
this.party,
|
this.party,
|
||||||
@@ -1115,7 +1143,7 @@ export abstract class Invoice extends Transactional {
|
|||||||
);
|
);
|
||||||
return loyaltyPoints || 0;
|
return loyaltyPoints || 0;
|
||||||
},
|
},
|
||||||
dependsOn: ['party'],
|
dependsOn: ['party', 'loyaltyProgram'],
|
||||||
},
|
},
|
||||||
currency: {
|
currency: {
|
||||||
formula: async () => {
|
formula: async () => {
|
||||||
@@ -1308,7 +1336,13 @@ export abstract class Invoice extends Transactional {
|
|||||||
loyaltyProgram: () => !this.loyaltyProgram,
|
loyaltyProgram: () => !this.loyaltyProgram,
|
||||||
availableLoyaltyPoints: () => !this.loyaltyProgram || this.isReturn,
|
availableLoyaltyPoints: () => !this.loyaltyProgram || this.isReturn,
|
||||||
loyaltyPoints: () => !this.redeemLoyaltyPoints || 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,
|
coupons: () => this.isSubmitted && !this.coupons?.length,
|
||||||
priceList: () =>
|
priceList: () =>
|
||||||
!this.fyo.singles.AccountingSettings?.enablePriceList ||
|
!this.fyo.singles.AccountingSettings?.enablePriceList ||
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { DocValue } from 'fyo/core/types';
|
import { DocValue } from 'fyo/core/types';
|
||||||
import { Doc } from 'fyo/model/doc';
|
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 { ValidationError } from 'fyo/utils/errors';
|
||||||
import { CollectionRulesItems } from '../CollectionRulesItems/CollectionRulesItems';
|
import { CollectionRulesItems } from '../CollectionRulesItems/CollectionRulesItems';
|
||||||
import { AccountRootTypeEnum } from '../Account/types';
|
|
||||||
import { getLoyaltyProgramStatusColumn } from '../../helpers';
|
import { getLoyaltyProgramStatusColumn } from '../../helpers';
|
||||||
|
|
||||||
export class LoyaltyProgram extends Doc {
|
export class LoyaltyProgram extends Doc {
|
||||||
@@ -11,7 +10,7 @@ export class LoyaltyProgram extends Doc {
|
|||||||
expiryDuration?: number;
|
expiryDuration?: number;
|
||||||
maximumUse?: number;
|
maximumUse?: number;
|
||||||
used?: number;
|
used?: number;
|
||||||
status?: 'Active' | 'Expired' | 'Maxed' | 'Disabled';
|
status?: 'Active' | 'Expired' | 'Disabled' | 'Maxed';
|
||||||
|
|
||||||
validations: ValidationMap = {
|
validations: ValidationMap = {
|
||||||
used: (value: DocValue) => {
|
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 {
|
static getListViewSettings(): ListViewSettings {
|
||||||
return {
|
return {
|
||||||
columns: ['name', getLoyaltyProgramStatusColumn(), 'fromDate', 'toDate'],
|
columns: ['name', getLoyaltyProgramStatusColumn(), 'fromDate', 'toDate'],
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { Money } from 'pesa';
|
import { Money } from 'pesa';
|
||||||
import { PartyRole } from './types';
|
import { PartyRole } from './types';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { isLoyaltyProgramExpiredAndMaxed } from 'models/helpers';
|
||||||
|
|
||||||
export class Party extends Doc {
|
export class Party extends Doc {
|
||||||
role?: PartyRole;
|
role?: PartyRole;
|
||||||
@@ -66,6 +67,17 @@ export class Party extends Doc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _getTotalLoyaltyPoints() {
|
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, {
|
const data = (await this.fyo.db.getAll(ModelNameEnum.LoyaltyPointEntry, {
|
||||||
fields: ['name', 'loyaltyPoints', 'expiryDate', 'postingDate'],
|
fields: ['name', 'loyaltyPoints', 'expiryDate', 'postingDate'],
|
||||||
filters: {
|
filters: {
|
||||||
|
|||||||
+40
-34
@@ -765,13 +765,6 @@ export function getLoyaltyProgramStatus(doc?: RenderData | Doc): string {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const maximumUse = doc.maximumUse as number;
|
|
||||||
const used = doc.used as number;
|
|
||||||
|
|
||||||
if (maximumUse > 0 && used >= maximumUse) {
|
|
||||||
return 'Maxed';
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
currentDate.setHours(0, 0, 0, 0);
|
currentDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
@@ -781,6 +774,13 @@ export function getLoyaltyProgramStatus(doc?: RenderData | Doc): string {
|
|||||||
return 'Expired';
|
return 'Expired';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maximumUse = doc.maximumUse as number;
|
||||||
|
const used = doc.used as number;
|
||||||
|
|
||||||
|
if (maximumUse > 0 && used >= maximumUse) {
|
||||||
|
return 'Maxed';
|
||||||
|
}
|
||||||
|
|
||||||
return 'Active';
|
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) {
|
export function removeFreeItems(sinvDoc: SalesInvoice) {
|
||||||
if (!sinvDoc || !sinvDoc.items) {
|
if (!sinvDoc || !sinvDoc.items) {
|
||||||
return;
|
return;
|
||||||
@@ -1765,3 +1738,36 @@ export function roundFreeItemQty(
|
|||||||
): number {
|
): number {
|
||||||
return Math[roundingMethod](quantity);
|
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",
|
"fieldtype": "Link",
|
||||||
"target": "LoyaltyProgram",
|
"target": "LoyaltyProgram",
|
||||||
"label": "Loyalty Program",
|
"label": "Loyalty Program",
|
||||||
"section": "References",
|
"section": "Loyalty Points Redemption",
|
||||||
"readOnly": true
|
"readOnly": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Available Loyalty Points",
|
"label": "Available Loyalty Points",
|
||||||
"readOnly": true,
|
"readOnly": true,
|
||||||
"section": "References"
|
"section": "Loyalty Points Redemption"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "redeemLoyaltyPoints",
|
"fieldname": "redeemLoyaltyPoints",
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ export default defineComponent({
|
|||||||
Unpaid: this.t`Unpaid`,
|
Unpaid: this.t`Unpaid`,
|
||||||
PartlyPaid: this.t`Partly Paid`,
|
PartlyPaid: this.t`Partly Paid`,
|
||||||
Expired: this.t`Expired`,
|
Expired: this.t`Expired`,
|
||||||
Maxed: this.t`Maxed`,
|
|
||||||
Active: this.t`Active`,
|
Active: this.t`Active`,
|
||||||
|
Maxed: this.t`Maxed`,
|
||||||
}[this.status];
|
}[this.status];
|
||||||
},
|
},
|
||||||
color(): UIColors {
|
color(): UIColors {
|
||||||
@@ -104,8 +104,8 @@ const statusColorMap: Record<Status, UIColors> = {
|
|||||||
Unpaid: 'red',
|
Unpaid: 'red',
|
||||||
PartlyPaid: 'yellow',
|
PartlyPaid: 'yellow',
|
||||||
Expired: 'red',
|
Expired: 'red',
|
||||||
Maxed: 'orange',
|
|
||||||
Active: 'green',
|
Active: 'green',
|
||||||
|
Maxed: 'orange',
|
||||||
};
|
};
|
||||||
|
|
||||||
function getStatus(doc: Doc) {
|
function getStatus(doc: Doc) {
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ import {
|
|||||||
removeFreeItems,
|
removeFreeItems,
|
||||||
getItemRateFromPriceList,
|
getItemRateFromPriceList,
|
||||||
getItemVisibility,
|
getItemVisibility,
|
||||||
isLoyaltyProgramMaxedOut,
|
isLoyaltyProgramExpiredAndMaxed,
|
||||||
} from 'models/helpers';
|
} from 'models/helpers';
|
||||||
import {
|
import {
|
||||||
POSItem,
|
POSItem,
|
||||||
@@ -499,12 +499,12 @@ export default defineComponent({
|
|||||||
const loyaltyProgramName = party[0]?.loyaltyProgram as string;
|
const loyaltyProgramName = party[0]?.loyaltyProgram as string;
|
||||||
|
|
||||||
if (loyaltyProgramName) {
|
if (loyaltyProgramName) {
|
||||||
const isMaxedOut = await isLoyaltyProgramMaxedOut(
|
const isExpiredAndMaxed = await isLoyaltyProgramExpiredAndMaxed(
|
||||||
this.fyo,
|
this.fyo,
|
||||||
loyaltyProgramName
|
loyaltyProgramName
|
||||||
);
|
);
|
||||||
if (isMaxedOut) {
|
if (isExpiredAndMaxed) {
|
||||||
this.loyaltyProgram = '';
|
this.loyaltyProgram = loyaltyProgramName;
|
||||||
this.loyaltyPoints = 0;
|
this.loyaltyPoints = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user