fix: formula calculation
continuous-integration/drone/push Build is passing Details

pull/93/head
shancheas 2024-09-19 19:33:28 +07:00
parent 23043fb7f9
commit e09c76309e
9 changed files with 182 additions and 40 deletions

View File

@ -45,8 +45,10 @@ import { GoogleCalendarModule } from './modules/configuration/google-calendar/go
import { TransactionModule } from './modules/transaction/transaction/transaction.module';
import { TransactionModel } from './modules/transaction/transaction/data/models/transaction.model';
import {
TransactionBreakdownTaxModel,
TransactionItemBreakdownModel,
TransactionItemModel,
TransactionItemTaxModel,
} from './modules/transaction/transaction/data/models/transaction-item.model';
import { TransactionTaxModel } from './modules/transaction/transaction/data/models/transaction-tax.model';
import { ReconciliationModule } from './modules/transaction/reconciliation/reconciliation.module';
@ -121,6 +123,8 @@ import { AuthService } from './core/guards/domain/services/auth.service';
TransactionTaxModel,
TransactionDemographyModel,
TransactionItemBreakdownModel,
TransactionItemTaxModel,
TransactionBreakdownTaxModel,
UserModel,
UserLoginModel,

View File

@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, UnprocessableEntityException } from '@nestjs/common';
import { BaseDataService } from 'src/core/modules/data/service/base-data.service';
import { SalesPriceFormulaEntity } from '../../domain/entities/sales-price-formula.entity';
import { InjectRepository } from '@nestjs/typeorm';
@ -7,6 +7,7 @@ import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { Repository } from 'typeorm';
import { FormulaType } from '../../constants';
import { TaxModel } from 'src/modules/transaction/tax/data/models/tax.model';
import { ItemModel } from 'src/modules/item-related/item/data/models/item.model';
@Injectable()
export class SalesPriceFormulaDataService extends BaseDataService<SalesPriceFormulaEntity> {
@ -15,6 +16,8 @@ export class SalesPriceFormulaDataService extends BaseDataService<SalesPriceForm
private repo: Repository<SalesPriceFormulaModel>,
@InjectRepository(TaxModel, CONNECTION_NAME.DEFAULT)
private tax: Repository<TaxModel>,
@InjectRepository(ItemModel, CONNECTION_NAME.DEFAULT)
private item: Repository<ItemModel>,
) {
super(repo);
}
@ -27,6 +30,20 @@ export class SalesPriceFormulaDataService extends BaseDataService<SalesPriceForm
});
}
async itemTax(id: string) {
const item = await this.item.findOne({
relations: ['tenant'],
where: {
id,
},
});
const profitShare = (item?.share_profit ?? 0) / 100;
const tenantShare = (item?.tenant?.share_margin ?? 0) / 100;
return { profitShare, tenantShare };
}
async salesPriceFormula() {
const salesFormula = await this.repo.findOne({
where: {
@ -43,6 +60,32 @@ export class SalesPriceFormulaDataService extends BaseDataService<SalesPriceForm
);
}
const counter = {};
do {
let isInfinite = false;
for (const tax of taxes) {
salesFormula.formula_string = salesFormula.formula_string.replace(
`${tax.name}_value`,
`(${tax.formula_string})`,
);
if (salesFormula.formula_string.includes(`${tax.name}_value`))
counter[tax.name] = counter[tax.name] ? counter[tax.name] + 1 : 1;
for (const count of Object.keys(counter)) {
if (!isInfinite && counter[count] > 50) isInfinite = true;
}
}
if (isInfinite) {
throw new UnprocessableEntityException({
message: 'Infinity Loop Formula, please fix formula',
error: 'Infinity Loop Formula',
meta: counter,
});
}
} while (salesFormula.formula_string.includes('_value'));
return salesFormula;
}
}

View File

@ -30,7 +30,15 @@ export function calculateFormula(
return value;
} catch (e) {
apm.captureError(e);
throw new BadRequestException('Wrong value');
throw new BadRequestException({
message: 'Wrong value',
meta: {
formula,
variable,
total,
solveFor,
},
});
}
}

View File

@ -14,13 +14,14 @@ import { DetailSalesPriceFormulaManager } from './domain/usecases/managers/detai
import { SalesPriceFormulaModel } from './data/models/sales-price-formula.model';
import { TaxDataService } from '../tax/data/services/tax-data.service';
import { TaxModel } from '../tax/data/models/tax.model';
import { ItemModel } from 'src/modules/item-related/item/data/models/item.model';
@Global()
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forFeature(
[SalesPriceFormulaModel, TaxModel],
[SalesPriceFormulaModel, TaxModel, ItemModel],
CONNECTION_NAME.DEFAULT,
),
CqrsModule,

View File

@ -113,6 +113,13 @@ export class TransactionItemModel
},
)
bundling_items: TransactionItemBreakdownModel[];
@OneToMany(() => TransactionItemTaxModel, (model) => model.transaction, {
cascade: true,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
item_taxes: TransactionItemTaxModel[];
}
@Entity(TABLE_NAME.TRANSACTION_ITEM_BREAKDOWN)
@ -150,6 +157,13 @@ export class TransactionItemBreakdownModel extends BaseCoreModel<TransactionBund
})
@JoinColumn({ name: 'transaction_item_id' })
transaction_item: TransactionItemModel;
@OneToMany(() => TransactionBreakdownTaxModel, (model) => model.transaction, {
cascade: true,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
item_taxes: TransactionBreakdownTaxModel[];
}
@Entity(TABLE_NAME.TRANSACTION_ITEM_TAX)

View File

@ -6,6 +6,7 @@ import {
} from '../../constants';
import { STATUS } from 'src/core/strings/constants/base.constants';
import { TransactionItemEntity } from './transaction-item.entity';
import { TransactionTaxEntity } from './transaction-tax.entity';
export interface TransactionEntity extends BaseStatusEntity {
// general info
@ -89,4 +90,5 @@ export interface TransactionEntity extends BaseStatusEntity {
calendar_link?: string;
items: TransactionItemEntity[];
taxes?: TransactionTaxEntity[];
}

View File

@ -27,18 +27,14 @@ export class PriceCalculator {
async calculate(transaction: TransactionEntity) {
const prices = [];
// for (const item of transaction.items) {
// const price = await this.calculateItem(item);
// const priceValues = this.calculatePrice(price);
// prices.push(priceValues);
// }
const transaction_taxes = [];
for (let i = 0; i < transaction.items.length; i++) {
const item = transaction.items[i];
const price = await this.calculateItem(item);
const priceValues = this.calculatePrice(price);
prices.push(priceValues);
transaction_taxes.push(price.taxesValue);
if (item.bundling_items) {
for (let b = 0; b < item.bundling_items.length; b++) {
@ -48,40 +44,66 @@ export class PriceCalculator {
const bundlingPrice = await this.calculateItem(bundling);
const bundlingValues = this.calculatePrice(bundlingPrice);
Object.assign(bundling, bundlingValues);
const item_taxes = { item_taxes: bundlingPrice.taxesValue };
Object.assign(bundling, bundlingValues, item_taxes);
item.bundling_items[b] = bundling;
}
}
Object.assign(item, priceValues);
const item_taxes = { item_taxes: price.taxesValue };
Object.assign(item, priceValues, item_taxes);
transaction.items[i] = item;
}
const tatal_taxes = this.mergeAndSumArrays(transaction_taxes);
transaction.taxes = tatal_taxes;
const { payment_total_dpp, ...otherValue } =
this.sumArrayBasedOnObjectKey(prices);
return { dpp_value: payment_total_dpp, other: otherValue, tax_datas: [] };
return {
dpp_value: payment_total_dpp,
other: otherValue,
tax_datas: tatal_taxes,
};
}
async calculateItem(
transaction: TransactionItemEntity | TransactionBundlingItemEntity,
) {
const itemShare = 20 / 100;
const profitShare = 10 / 100;
// const itemShare = 20 / 100;
// const profitShare = 10 / 100;
const { profitShare, tenantShare } = await this.formulaService.itemTax(
transaction.item_id,
);
const tax = await this.taxService.taxKeyValue();
const taxes = { ...tax, item_share: itemShare, profit_share: profitShare };
const formulas = await this.formulaService.salesPriceFormula();
/* Update constant from
- profit_share -> tenant_share
- item_share -> profit_share
*/
const taxes = {
...tax,
tenant_share: tenantShare,
profit_share: profitShare,
};
const dpp = await this.formulaService.salesPriceFormula();
const taxFormula = await this.taxService.getManyByOptions({});
const shareFormulas = await this.formulaService.profitShareFormula();
const taxShareFormulas = shareFormulas.map((formula) => {
return {
...formula,
name: formula.value_for,
};
});
const formulas = [...taxFormula, ...taxShareFormulas];
const values = {
total: transaction.total_price,
...this.initialValue(taxFormula),
...this.initialValue(formulas),
...taxes,
};
// const dpp = formulas.find((formula) => formula.value_for == 'dpp');
const dpp = formulas;
const dppValue = calculateFormula(
dpp.formula_string,
@ -93,11 +115,14 @@ export class PriceCalculator {
values['dpp_value'] = dppValue;
let calledVariable = [];
const taxesValue = [];
do {
const valueFor = this.withNullValue(values, calledVariable);
const formula = taxFormula.find(
(formula) => `${formula.name}_value` == valueFor,
);
const formula = formulas.find((formula) => {
const name = formula['name'] ?? formula['value_for'];
return `${name}_value` == valueFor;
});
let result = null;
try {
@ -109,26 +134,38 @@ export class PriceCalculator {
}
values[valueFor] = result;
if (result != null) {
const tax = taxFormula.find(({ id }) => id == formula.id);
if (tax != null) {
taxesValue.push({
tax_id: tax?.id,
tax_name: tax?.name,
taxt_value: result,
tax_total_value: result,
transaction_id: transaction.id,
});
}
}
// values[`${valueFor}_value`] = result;
} while (this.containsNullValue(values));
const itemShareFormula = shareFormulas.find(
(f) => f.value_for == 'item_share',
);
values['item_share_value'] = math.evaluate(
itemShareFormula.formula_string,
values,
);
// const itemShareFormula = shareFormulas.find(
// (f) => f.value_for == 'tenant_share',
// );
// values['tenant_share_value'] = math.evaluate(
// itemShareFormula.formula_string,
// values,
// );
const profitShareFormula = shareFormulas.find(
(f) => f.value_for == 'profit_share',
);
values['profit_share_value'] = math.evaluate(
profitShareFormula.formula_string,
values,
);
// const profitShareFormula = shareFormulas.find(
// (f) => f.value_for == 'profit_share',
// );
// values['profit_share_value'] = math.evaluate(
// profitShareFormula.formula_string,
// values,
// );
return { dpp_value: dppValue, tax_datas: values };
return { dpp_value: dppValue, tax_datas: values, taxesValue };
}
calculatePrice(prices: any) {
@ -137,14 +174,15 @@ export class PriceCalculator {
.filter((key) => key.endsWith('_value'))
.reduce((acc, key) => ({ ...acc, [key]: data[key] }), {});
const { dpp_value, item_share_value, profit_share_value, ...tax } =
const { dpp_value, tenant_share_value, profit_share_value, ...tax } =
filteredObject;
const taxes = this.sumPriceObject(tax);
return {
total_profit_share: item_share_value,
total_share_tenant: profit_share_value,
total_profit_share: profit_share_value,
total_share_tenant: tenant_share_value,
payment_total_tax: taxes,
payment_total_dpp: dpp_value,
tax,
};
}
@ -185,6 +223,28 @@ export class PriceCalculator {
return false; // If no null values are found, return false
}
mergeAndSumArrays(arrays): any {
const mergedData = {};
arrays.forEach((arr) => {
arr.forEach((item) => {
if (!mergedData[item.tax_id]) {
mergedData[item.tax_id] = {
tax_name: item.tax_name,
taxt_value: 0,
tax_total_value: 0,
transaction_id: item.transaction_id,
};
}
mergedData[item.tax_id].taxt_value += item.taxt_value;
mergedData[item.tax_id].tax_total_value += item.tax_total_value;
});
});
return Object.values(mergedData);
}
withNullValue(mainObj, called = []) {
const obj = { ...mainObj };
for (const variable of called) {

View File

@ -64,7 +64,9 @@ export class SettledTransactionHandler
});
// const profit_share_value = this.calculateFormula(profit_formula.formula_string, taxes, data.payment_total_net_profit ?? 0);
const { dpp_value, tax_datas } = await this.calculator.calculate(data);
const { dpp_value, tax_datas, other } = await this.calculator.calculate(
data,
);
// calculateSalesFormula(
// sales_price.formula_string,
@ -82,6 +84,8 @@ export class SettledTransactionHandler
taxes: tax_datas,
calendar_id: google_calendar?.id,
calendar_link: google_calendar?.htmlLink,
payment_total_share: other.total_profit_share,
payment_total_tax: other.payment_total_tax,
});
} else if (oldSettled) {
// console.log(data, 'data oldSettled');

View File

@ -19,8 +19,10 @@ import { BatchDeleteTransactionManager } from './domain/usecases/managers/batch-
import { BatchConfirmTransactionManager } from './domain/usecases/managers/batch-confirm-transaction.manager';
import { TransactionModel } from './data/models/transaction.model';
import {
TransactionBreakdownTaxModel,
TransactionItemBreakdownModel,
TransactionItemModel,
TransactionItemTaxModel,
} from './data/models/transaction-item.model';
import { TransactionTaxModel } from './data/models/transaction-tax.model';
import { CancelTransactionManager } from './domain/usecases/managers/cancel-transaction.manager';
@ -40,6 +42,7 @@ import { PaymentMethodDataService } from '../payment-method/data/services/paymen
import { PaymentMethodModel } from '../payment-method/data/models/payment-method.model';
import { TransactionDemographyModel } from './data/models/transaction-demography.model';
import { PriceCalculator } from './domain/usecases/calculator/price.calculator';
import { ItemModel } from 'src/modules/item-related/item/data/models/item.model';
@Module({
exports: [TransactionReadService],
@ -52,9 +55,12 @@ import { PriceCalculator } from './domain/usecases/calculator/price.calculator';
TransactionDemographyModel,
TransactionItemBreakdownModel,
TransactionTaxModel,
TransactionItemTaxModel,
TransactionBreakdownTaxModel,
TaxModel,
SalesPriceFormulaModel,
PaymentMethodModel,
ItemModel,
],
CONNECTION_NAME.DEFAULT,
),