2
0
mirror of https://github.com/frappe/books.git synced 2024-11-14 01:14:03 +00:00

feat: create LoyaltyPointEntry

This commit is contained in:
AbleKSaju 2024-08-22 16:54:14 +05:30
parent 31f97d0f39
commit 3163f835dd
3 changed files with 174 additions and 0 deletions

View File

@ -14,7 +14,9 @@ import { Transactional } from 'models/Transactional/Transactional';
import {
addItem,
canApplyPricingRule,
createLoyaltyPointEntry,
filterPricingRules,
getAddedLPWithGrandTotal,
getExchangeRate,
getNumberSeries,
getPricingRulesConflicts,
@ -38,6 +40,7 @@ import { AccountFieldEnum, PaymentTypeEnum } from '../Payment/types';
import { PricingRule } from '../PricingRule/PricingRule';
import { ApplicablePricingRules } from './types';
import { PricingRuleDetail } from '../PricingRuleDetail/PricingRuleDetail';
import { LoyaltyProgram } from '../LoyaltyProgram/LoyaltyProgram';
export type TaxDetail = {
account: string;
@ -69,8 +72,10 @@ export abstract class Invoice extends Transactional {
setDiscountAmount?: boolean;
discountAmount?: Money;
discountPercent?: number;
loyaltyPoints?: number;
discountAfterTax?: boolean;
stockNotTransferred?: number;
loyaltyProgram?: string;
backReference?: string;
submitted?: boolean;
@ -207,6 +212,7 @@ export abstract class Invoice extends Transactional {
}
await this._updateIsItemsReturned();
await this._createLoyaltyPointEntry();
}
async afterCancel() {
@ -538,6 +544,28 @@ export abstract class Invoice extends Transactional {
await invoiceDoc.submit();
}
async _createLoyaltyPointEntry() {
if (!this.loyaltyProgram) {
return;
}
const loyaltyProgramDoc = (await this.fyo.doc.getDoc(
ModelNameEnum.LoyaltyProgram,
this.loyaltyProgram
)) as LoyaltyProgram;
const expiryDate = this.date as Date;
const fromDate = loyaltyProgramDoc.fromDate as Date;
const toDate = loyaltyProgramDoc.toDate as Date;
if (fromDate <= expiryDate && toDate >= expiryDate) {
const party = (await this.loadAndGetLink('party')) as Party;
await createLoyaltyPointEntry(this);
await party.updateLoyaltyPoints();
}
}
async _validateHasLinkedReturnInvoices() {
if (!this.name || this.isReturn || this.isQuote) {
return;
@ -560,6 +588,16 @@ export abstract class Invoice extends Transactional {
);
}
async getLPAddedBaseGrandTotal() {
const totalLotaltyAmount = await getAddedLPWithGrandTotal(
this.fyo,
this.loyaltyProgram as string,
this.loyaltyPoints as number
);
return totalLotaltyAmount.sub(this.baseGrandTotal as Money).abs();
}
formulas: FormulaMap = {
account: {
formula: async () => {
@ -571,6 +609,16 @@ export abstract class Invoice extends Transactional {
},
dependsOn: ['party'],
},
loyaltyProgram: {
formula: async () => {
const partyDoc = await this.fyo.doc.getDoc(
ModelNameEnum.Party,
this.party
);
return partyDoc?.loyaltyProgram as string;
},
dependsOn: ['party', 'name'],
},
currency: {
formula: async () => {
const currency = (await this.fyo.getValue(

View File

@ -54,6 +54,40 @@ export class Party extends Doc {
await this.setAndSync({ outstandingAmount });
}
async updateLoyaltyPoints() {
let loyaltyPoints = 0;
if (this.role === 'Customer' || this.role === 'Both') {
loyaltyPoints = await this._getTotalLoyaltyPoints();
}
await this.setAndSync({ loyaltyPoints });
}
async _getTotalLoyaltyPoints() {
const data = (await this.fyo.db.getAll(ModelNameEnum.LoyaltyPointEntry, {
fields: ['name', 'loyaltyPoints', 'expiryDate', 'postingDate'],
filters: {
customer: this.name as string,
},
})) as {
name: string;
loyaltyPoints: number;
expiryDate: Date;
postingDate: Date;
}[];
const totalLoyaltyPoints = data.reduce((total, entry) => {
if (entry.expiryDate > entry.postingDate) {
return total + entry.loyaltyPoints;
}
return total;
}, 0);
return totalLoyaltyPoints;
}
async _getTotalOutstandingAmount(
schemaName: 'SalesInvoice' | 'PurchaseInvoice'
) {

View File

@ -25,6 +25,9 @@ import { Lead } from './baseModels/Lead/Lead';
import { PricingRule } from './baseModels/PricingRule/PricingRule';
import { ValidationError } from 'fyo/utils/errors';
import { ApplicablePricingRules } from './baseModels/Invoice/types';
import { LoyaltyProgram } from './baseModels/LoyaltyProgram/LoyaltyProgram';
import { CollectionRulesItems } from './baseModels/CollectionRulesItems/CollectionRulesItems';
import { isPesa } from 'fyo/utils';
export function getQuoteActions(
fyo: Fyo,
@ -663,6 +666,95 @@ export async function addItem<M extends ModelsWithItems>(name: string, doc: M) {
await item.set('item', name);
}
export async function createLoyaltyPointEntry(doc: Invoice) {
const loyaltyProgramDoc = (await doc.fyo.doc.getDoc(
ModelNameEnum.LoyaltyProgram,
doc?.loyaltyProgram
)) as LoyaltyProgram;
if (!loyaltyProgramDoc.isEnabled) {
return;
}
const expiryDate = new Date(Date.now());
expiryDate.setDate(
expiryDate.getDate() + (loyaltyProgramDoc.expiryDuration || 0)
);
const loyaltyProgramTier = getLoyaltyProgramTier(
loyaltyProgramDoc,
doc?.grandTotal as Money
) as CollectionRulesItems;
if (!loyaltyProgramTier) {
return;
}
const collectionFactor = loyaltyProgramTier.collectionFactor as number;
const loyaltyPoint =
Math.round(doc?.grandTotal?.float || 0) * collectionFactor;
const newLoyaltyPointEntry = doc.fyo.doc.getNewDoc(
ModelNameEnum.LoyaltyPointEntry,
{
loyaltyProgram: doc.loyaltyProgram,
customer: doc.party,
invoice: doc.name,
postingDate: doc.date,
purchaseAmount: doc.grandTotal,
expiryDate: expiryDate,
loyaltyProgramTier: loyaltyProgramTier?.tierName,
loyaltyPoints: loyaltyPoint,
}
);
return await newLoyaltyPointEntry.sync();
}
export async function getAddedLPWithGrandTotal(
fyo: Fyo,
loyaltyProgram: string,
loyaltyPoints: number
) {
const loyaltyProgramDoc = (await fyo.doc.getDoc(
ModelNameEnum.LoyaltyProgram,
loyaltyProgram
)) as LoyaltyProgram;
const conversionFactor = loyaltyProgramDoc.conversionFactor as number;
return fyo.pesa((loyaltyPoints || 0) * conversionFactor);
}
export function getLoyaltyProgramTier(
loyaltyProgramData: LoyaltyProgram,
grandTotal: Money
): CollectionRulesItems | undefined {
if (!loyaltyProgramData.collectionRules) {
return;
}
let loyaltyProgramTier: CollectionRulesItems | undefined;
for (const row of loyaltyProgramData.collectionRules) {
if (isPesa(row.minimumTotalSpent)) {
const minimumSpent = row.minimumTotalSpent;
if (!minimumSpent.lte(grandTotal)) {
continue;
}
if (
!loyaltyProgramTier ||
minimumSpent.gt(loyaltyProgramTier.minimumTotalSpent as Money)
) {
loyaltyProgramTier = row;
}
}
}
return loyaltyProgramTier;
}
export async function getPricingRule(
doc: Invoice
): Promise<ApplicablePricingRules[] | null> {