fix: formula calculation
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
23043fb7f9
commit
e09c76309e
|
@ -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,
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
Loading…
Reference in New Issue