From fba7b9aae5d2c1bfd8a184d3944c949109e9265c Mon Sep 17 00:00:00 2001 From: Aswin Ashar Abdullah Date: Fri, 19 Jul 2024 14:43:47 +0700 Subject: [PATCH] fix(SPG-620) Sales Price & Profit Share Formula - Tambahkan warning/validasi jika formula menyebabkan error --- .../update-profit-share-formula.manager.ts | 10 ++ .../profit-share-formula-data.orchestrator.ts | 8 +- .../profit-share-formula.module.ts | 9 +- .../helpers/calculation-formula.helper.ts | 131 ++++++++++++++++++ .../update-sales-price-formula.manager.ts | 10 ++ .../sales-price-formula-data.orchestrator.ts | 8 +- .../sales-price-formula.module.ts | 8 +- .../handlers/settled-transaction.handler.ts | 50 +------ 8 files changed, 182 insertions(+), 52 deletions(-) create mode 100644 src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper.ts diff --git a/src/modules/transaction/profit-share-formula/domain/usecases/managers/update-profit-share-formula.manager.ts b/src/modules/transaction/profit-share-formula/domain/usecases/managers/update-profit-share-formula.manager.ts index 59139db..f9151e1 100644 --- a/src/modules/transaction/profit-share-formula/domain/usecases/managers/update-profit-share-formula.manager.ts +++ b/src/modules/transaction/profit-share-formula/domain/usecases/managers/update-profit-share-formula.manager.ts @@ -8,10 +8,20 @@ import { import { SalesPriceFormulaModel } from 'src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model'; import { ProfitShareFormulaUpdatedEvent } from '../../entities/event/profit-share-formula-updated.event'; import { SalesPriceFormulaEntity } from 'src/modules/transaction/sales-price-formula/domain/entities/sales-price-formula.entity'; +import { In } from 'typeorm'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { calculateProfitFormula } from 'src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper'; @Injectable() export class UpdateProfitShareFormulaManager extends BaseUpdateManager { async validateProcess(): Promise { + const taxes = await this.dataServiceFirstOpt.getManyByOptions({ + where: { + status: In([STATUS.ACTIVE]), + }, + }); + + calculateProfitFormula(this.data.formula_string, taxes, 10000, 50, true); return; } diff --git a/src/modules/transaction/profit-share-formula/domain/usecases/profit-share-formula-data.orchestrator.ts b/src/modules/transaction/profit-share-formula/domain/usecases/profit-share-formula-data.orchestrator.ts index 2b3806f..a1d68df 100644 --- a/src/modules/transaction/profit-share-formula/domain/usecases/profit-share-formula-data.orchestrator.ts +++ b/src/modules/transaction/profit-share-formula/domain/usecases/profit-share-formula-data.orchestrator.ts @@ -4,12 +4,14 @@ import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { SalesPriceFormulaDataService } from 'src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service'; import { FormulaType } from 'src/modules/transaction/sales-price-formula/constants'; import { SalesPriceFormulaEntity } from 'src/modules/transaction/sales-price-formula/domain/entities/sales-price-formula.entity'; +import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service'; @Injectable() export class ProfitShareFormulaDataOrchestrator { constructor( private updateManager: UpdateProfitShareFormulaManager, private serviceData: SalesPriceFormulaDataService, + private taxService: TaxDataService, ) {} async update(data): Promise { @@ -20,7 +22,11 @@ export class ProfitShareFormulaDataOrchestrator { }); this.updateManager.setData(formula.id, data); - this.updateManager.setService(this.serviceData, TABLE_NAME.PRICE_FORMULA); + this.updateManager.setService( + this.serviceData, + TABLE_NAME.PRICE_FORMULA, + this.taxService, + ); await this.updateManager.execute(); return this.updateManager.getResult(); } diff --git a/src/modules/transaction/profit-share-formula/profit-share-formula.module.ts b/src/modules/transaction/profit-share-formula/profit-share-formula.module.ts index ea4105b..31ff6f2 100644 --- a/src/modules/transaction/profit-share-formula/profit-share-formula.module.ts +++ b/src/modules/transaction/profit-share-formula/profit-share-formula.module.ts @@ -10,11 +10,16 @@ import { CqrsModule } from '@nestjs/cqrs'; import { UpdateProfitShareFormulaManager } from './domain/usecases/managers/update-profit-share-formula.manager'; import { DetailProfitShareFormulaManager } from './domain/usecases/managers/detail-profit-share-formula.manager'; import { SalesPriceFormulaModel } from '../sales-price-formula/data/models/sales-price-formula.model'; +import { TaxDataService } from '../tax/data/services/tax-data.service'; +import { TaxModel } from '../tax/data/models/tax.model'; @Module({ imports: [ ConfigModule.forRoot(), - TypeOrmModule.forFeature([SalesPriceFormulaModel], CONNECTION_NAME.DEFAULT), + TypeOrmModule.forFeature( + [SalesPriceFormulaModel, TaxModel], + CONNECTION_NAME.DEFAULT, + ), CqrsModule, ], controllers: [ @@ -27,6 +32,8 @@ import { SalesPriceFormulaModel } from '../sales-price-formula/data/models/sales ProfitShareFormulaDataOrchestrator, ProfitShareFormulaReadOrchestrator, + + TaxDataService, ], }) export class ProfitShareFormulaModule {} diff --git a/src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper.ts b/src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper.ts new file mode 100644 index 0000000..b699e1c --- /dev/null +++ b/src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper.ts @@ -0,0 +1,131 @@ +import * as math from 'mathjs'; +import { Equation, parse } from 'algebra.js'; +import { HttpStatus, UnprocessableEntityException } from '@nestjs/common'; + +export function calculateSalesFormula( + formula: string, + taxes: object[], + total: number, + throwError = false, +) { + try { + let { value, variable, tax_datas } = mappingTaxes(taxes, formula, total); + + const x1 = math.simplify(formula, variable).toString(); + console.log('Formula ', x1); + const dppFormula = parse(x1); + const totalFormula = parse(total.toString()); + const equation = new Equation(totalFormula, dppFormula); + + console.log(equation.toString()); + const result = equation.solveFor('dpp').toString(); + console.log(result, 'formula'); + + value = math.evaluate(result); + console.log(value, 'value'); + + return { + dpp_value: value, + tax_datas: tax_datas, + }; + } catch (e) { + returnError(throwError, e, taxes); + } +} + +export function calculateProfitFormula( + formula: string, + taxes: object[], + total: number, + profit_share = 0, + throwError = false, +) { + try { + let { value, variable, tax_datas } = mappingTaxes( + taxes, + formula, + total, + profit_share, + ); + + const result = math.simplify(formula, variable).toString(); + console.log(result, 'formula'); + + value = math.evaluate(result); + console.log(value, 'value'); + + return { + dpp_value: value, + tax_datas: tax_datas, + }; + } catch (e) { + returnError(throwError, e, taxes); + } +} + +function mappingTaxes(taxes, formula, total, profit_share = 0) { + let value = 0; + const variable = {}; + let tax_datas = []; + const const_variable = ['profit_share', 'item_share', 'dpp']; + + const regex = /([a-zA-Z0-9_]+)/g; + + const matches: string[] = formula.match(regex); + const uniqueMatches = new Set(matches); + const keys = Array.from(uniqueMatches); + + for (const key of keys) { + if (!const_variable.includes(key)) { + const keyData = taxes.find((tax) => tax.name == key); + variable[key] = keyData.value / 100; + + tax_datas.push({ + tax_id: keyData.id, + tax_name: keyData.name, + tax_value: keyData.value, + tax_total_value: (keyData.value / 100) * Number(total), + }); + } else { + switch (key) { + case 'profit_share': + variable[key] = profit_share / 100; + break; + + case 'item_share': + variable[key] = profit_share / 100; + break; + + case 'dpp': + if (profit_share > 0) variable[key] = total; + break; + default: + variable[key] = profit_share; + break; + } + } + } + + return { + value: value, + variable: variable, + tax_datas: tax_datas, + }; +} + +function returnError(throwError, e, taxes) { + if (throwError) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! Formula error`, + error: 'Unprocessable Entity', + }); + } else { + console.log(e); + + return { + dpp_value: 0, + tax_datas: taxes, + }; + } +} diff --git a/src/modules/transaction/sales-price-formula/domain/usecases/managers/update-sales-price-formula.manager.ts b/src/modules/transaction/sales-price-formula/domain/usecases/managers/update-sales-price-formula.manager.ts index 89d24ee..1849e7a 100644 --- a/src/modules/transaction/sales-price-formula/domain/usecases/managers/update-sales-price-formula.manager.ts +++ b/src/modules/transaction/sales-price-formula/domain/usecases/managers/update-sales-price-formula.manager.ts @@ -8,10 +8,20 @@ import { columnUniques, validateRelations, } from 'src/core/strings/constants/interface.constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { In } from 'typeorm'; +import { calculateSalesFormula } from './helpers/calculation-formula.helper'; @Injectable() export class UpdateSalesPriceFormulaManager extends BaseUpdateManager { async validateProcess(): Promise { + const taxes = await this.dataServiceFirstOpt.getManyByOptions({ + where: { + status: In([STATUS.ACTIVE]), + }, + }); + + calculateSalesFormula(this.data.formula_string, taxes, 10000, true); return; } diff --git a/src/modules/transaction/sales-price-formula/domain/usecases/sales-price-formula-data.orchestrator.ts b/src/modules/transaction/sales-price-formula/domain/usecases/sales-price-formula-data.orchestrator.ts index 3b17c87..70408d4 100644 --- a/src/modules/transaction/sales-price-formula/domain/usecases/sales-price-formula-data.orchestrator.ts +++ b/src/modules/transaction/sales-price-formula/domain/usecases/sales-price-formula-data.orchestrator.ts @@ -4,12 +4,14 @@ import { SalesPriceFormulaEntity } from '../entities/sales-price-formula.entity' import { UpdateSalesPriceFormulaManager } from './managers/update-sales-price-formula.manager'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { FormulaType } from '../../constants'; +import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service'; @Injectable() export class SalesPriceFormulaDataOrchestrator { constructor( private updateManager: UpdateSalesPriceFormulaManager, private serviceData: SalesPriceFormulaDataService, + private taxService: TaxDataService, ) {} async update(data): Promise { @@ -20,7 +22,11 @@ export class SalesPriceFormulaDataOrchestrator { }); this.updateManager.setData(formula.id, data); - this.updateManager.setService(this.serviceData, TABLE_NAME.PRICE_FORMULA); + this.updateManager.setService( + this.serviceData, + TABLE_NAME.PRICE_FORMULA, + this.taxService, + ); await this.updateManager.execute(); return this.updateManager.getResult(); } diff --git a/src/modules/transaction/sales-price-formula/sales-price-formula.module.ts b/src/modules/transaction/sales-price-formula/sales-price-formula.module.ts index a6e803f..accd83c 100644 --- a/src/modules/transaction/sales-price-formula/sales-price-formula.module.ts +++ b/src/modules/transaction/sales-price-formula/sales-price-formula.module.ts @@ -12,12 +12,17 @@ import { CqrsModule } from '@nestjs/cqrs'; import { UpdateSalesPriceFormulaManager } from './domain/usecases/managers/update-sales-price-formula.manager'; import { DetailSalesPriceFormulaManager } from './domain/usecases/managers/detail-sales-price-formula.manager'; 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'; @Global() @Module({ imports: [ ConfigModule.forRoot(), - TypeOrmModule.forFeature([SalesPriceFormulaModel], CONNECTION_NAME.DEFAULT), + TypeOrmModule.forFeature( + [SalesPriceFormulaModel, TaxModel], + CONNECTION_NAME.DEFAULT, + ), CqrsModule, ], controllers: [ @@ -28,6 +33,7 @@ import { SalesPriceFormulaModel } from './data/models/sales-price-formula.model' DetailSalesPriceFormulaManager, UpdateSalesPriceFormulaManager, + TaxDataService, SalesPriceFormulaDataService, SalesPriceFormulaReadService, diff --git a/src/modules/transaction/transaction/domain/usecases/handlers/settled-transaction.handler.ts b/src/modules/transaction/transaction/domain/usecases/handlers/settled-transaction.handler.ts index 0f3a259..f26b6b5 100644 --- a/src/modules/transaction/transaction/domain/usecases/handlers/settled-transaction.handler.ts +++ b/src/modules/transaction/transaction/domain/usecases/handlers/settled-transaction.handler.ts @@ -3,11 +3,10 @@ import { TransactionChangeStatusEvent } from '../../entities/event/transaction-c import { SalesPriceFormulaDataService } from 'src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service'; import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service'; import { FormulaType } from 'src/modules/transaction/sales-price-formula/constants'; -import * as math from 'mathjs'; -import { Equation, parse } from 'algebra.js'; import { STATUS } from 'src/core/strings/constants/base.constants'; import { TransactionDataService } from '../../../data/services/transaction-data.service'; import { TransactionModel } from '../../../data/models/transaction.model'; +import { calculateSalesFormula } from 'src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper'; @EventsHandler(TransactionChangeStatusEvent) export class SettledTransactionHandler @@ -59,7 +58,7 @@ export class SettledTransactionHandler .manager.connection.createQueryRunner(); // const profit_share_value = this.calculateFormula(profit_formula.formula_string, taxes, data.payment_total_net_profit ?? 0); - const { dpp_value, tax_datas } = this.calculateSalesFormula( + const { dpp_value, tax_datas } = calculateSalesFormula( sales_price.formula_string, taxes, data.payment_total_net_profit ?? 0, @@ -74,49 +73,4 @@ export class SettledTransactionHandler await this.dataService.create(queryRunner, TransactionModel, data); } - - calculateSalesFormula(formula, taxes, total) { - let value = 0; - let tax_datas = []; - const regex = /([a-zA-Z0-9_]+)/g; - const variable = {}; - - const matches: string[] = formula.match(regex); - const uniqueMatches = new Set(matches); - const keys = Array.from(uniqueMatches).filter((key) => key != 'dpp'); - - for (const key of keys) { - const keyData = taxes.find((tax) => tax.name == key); - variable[key] = keyData.value / 100; - - tax_datas.push({ - tax_id: keyData.id, - tax_name: keyData.name, - tax_value: keyData.value, - tax_total_value: (keyData.value / 100) * Number(total), - }); - } - - try { - const x1 = math.simplify(formula, variable).toString(); - console.log('Formula ', x1); - const dppFormula = parse(x1); - const totalFormula = parse(total.toString()); - const equation = new Equation(totalFormula, dppFormula); - - console.log(equation.toString()); - const result = equation.solveFor('dpp').toString(); - console.log(result); - - value = math.evaluate(result); - console.log(value); - } catch (e) { - console.log(e); - } - - return { - dpp_value: value, - tax_datas: tax_datas, - }; - } }