2
0
mirror of https://github.com/frappe/books.git synced 2024-11-14 09:24:04 +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 { import {
addItem, addItem,
canApplyPricingRule, canApplyPricingRule,
createLoyaltyPointEntry,
filterPricingRules, filterPricingRules,
getAddedLPWithGrandTotal,
getExchangeRate, getExchangeRate,
getNumberSeries, getNumberSeries,
getPricingRulesConflicts, getPricingRulesConflicts,
@ -38,6 +40,7 @@ import { AccountFieldEnum, PaymentTypeEnum } from '../Payment/types';
import { PricingRule } from '../PricingRule/PricingRule'; import { PricingRule } from '../PricingRule/PricingRule';
import { ApplicablePricingRules } from './types'; import { ApplicablePricingRules } from './types';
import { PricingRuleDetail } from '../PricingRuleDetail/PricingRuleDetail'; import { PricingRuleDetail } from '../PricingRuleDetail/PricingRuleDetail';
import { LoyaltyProgram } from '../LoyaltyProgram/LoyaltyProgram';
export type TaxDetail = { export type TaxDetail = {
account: string; account: string;
@ -69,8 +72,10 @@ export abstract class Invoice extends Transactional {
setDiscountAmount?: boolean; setDiscountAmount?: boolean;
discountAmount?: Money; discountAmount?: Money;
discountPercent?: number; discountPercent?: number;
loyaltyPoints?: number;
discountAfterTax?: boolean; discountAfterTax?: boolean;
stockNotTransferred?: number; stockNotTransferred?: number;
loyaltyProgram?: string;
backReference?: string; backReference?: string;
submitted?: boolean; submitted?: boolean;
@ -207,6 +212,7 @@ export abstract class Invoice extends Transactional {
} }
await this._updateIsItemsReturned(); await this._updateIsItemsReturned();
await this._createLoyaltyPointEntry();
} }
async afterCancel() { async afterCancel() {
@ -538,6 +544,28 @@ export abstract class Invoice extends Transactional {
await invoiceDoc.submit(); 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() { async _validateHasLinkedReturnInvoices() {
if (!this.name || this.isReturn || this.isQuote) { if (!this.name || this.isReturn || this.isQuote) {
return; 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 = { formulas: FormulaMap = {
account: { account: {
formula: async () => { formula: async () => {
@ -571,6 +609,16 @@ export abstract class Invoice extends Transactional {
}, },
dependsOn: ['party'], 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: { currency: {
formula: async () => { formula: async () => {
const currency = (await this.fyo.getValue( const currency = (await this.fyo.getValue(

View File

@ -54,6 +54,40 @@ export class Party extends Doc {
await this.setAndSync({ outstandingAmount }); 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( async _getTotalOutstandingAmount(
schemaName: 'SalesInvoice' | 'PurchaseInvoice' schemaName: 'SalesInvoice' | 'PurchaseInvoice'
) { ) {

View File

@ -25,6 +25,9 @@ import { Lead } from './baseModels/Lead/Lead';
import { PricingRule } from './baseModels/PricingRule/PricingRule'; import { PricingRule } from './baseModels/PricingRule/PricingRule';
import { ValidationError } from 'fyo/utils/errors'; import { ValidationError } from 'fyo/utils/errors';
import { ApplicablePricingRules } from './baseModels/Invoice/types'; 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( export function getQuoteActions(
fyo: Fyo, fyo: Fyo,
@ -663,6 +666,95 @@ export async function addItem<M extends ModelsWithItems>(name: string, doc: M) {
await item.set('item', name); 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( export async function getPricingRule(
doc: Invoice doc: Invoice
): Promise<ApplicablePricingRules[] | null> { ): Promise<ApplicablePricingRules[] | null> {