fix(SPG-620) Sales Price & Profit Share Formula - Tambahkan warning/validasi jika formula menyebabkan error

pull/33/head
Aswin Ashar Abdullah 2024-07-19 14:43:47 +07:00
parent 0b236cb879
commit fba7b9aae5
8 changed files with 182 additions and 52 deletions

View File

@ -8,10 +8,20 @@ import {
import { SalesPriceFormulaModel } from 'src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model'; 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 { 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 { 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() @Injectable()
export class UpdateProfitShareFormulaManager extends BaseUpdateManager<SalesPriceFormulaEntity> { export class UpdateProfitShareFormulaManager extends BaseUpdateManager<SalesPriceFormulaEntity> {
async validateProcess(): Promise<void> { async validateProcess(): Promise<void> {
const taxes = await this.dataServiceFirstOpt.getManyByOptions({
where: {
status: In([STATUS.ACTIVE]),
},
});
calculateProfitFormula(this.data.formula_string, taxes, 10000, 50, true);
return; return;
} }

View File

@ -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 { 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 { 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 { 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() @Injectable()
export class ProfitShareFormulaDataOrchestrator { export class ProfitShareFormulaDataOrchestrator {
constructor( constructor(
private updateManager: UpdateProfitShareFormulaManager, private updateManager: UpdateProfitShareFormulaManager,
private serviceData: SalesPriceFormulaDataService, private serviceData: SalesPriceFormulaDataService,
private taxService: TaxDataService,
) {} ) {}
async update(data): Promise<SalesPriceFormulaEntity> { async update(data): Promise<SalesPriceFormulaEntity> {
@ -20,7 +22,11 @@ export class ProfitShareFormulaDataOrchestrator {
}); });
this.updateManager.setData(formula.id, data); 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(); await this.updateManager.execute();
return this.updateManager.getResult(); return this.updateManager.getResult();
} }

View File

@ -10,11 +10,16 @@ import { CqrsModule } from '@nestjs/cqrs';
import { UpdateProfitShareFormulaManager } from './domain/usecases/managers/update-profit-share-formula.manager'; import { UpdateProfitShareFormulaManager } from './domain/usecases/managers/update-profit-share-formula.manager';
import { DetailProfitShareFormulaManager } from './domain/usecases/managers/detail-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 { 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({ @Module({
imports: [ imports: [
ConfigModule.forRoot(), ConfigModule.forRoot(),
TypeOrmModule.forFeature([SalesPriceFormulaModel], CONNECTION_NAME.DEFAULT), TypeOrmModule.forFeature(
[SalesPriceFormulaModel, TaxModel],
CONNECTION_NAME.DEFAULT,
),
CqrsModule, CqrsModule,
], ],
controllers: [ controllers: [
@ -27,6 +32,8 @@ import { SalesPriceFormulaModel } from '../sales-price-formula/data/models/sales
ProfitShareFormulaDataOrchestrator, ProfitShareFormulaDataOrchestrator,
ProfitShareFormulaReadOrchestrator, ProfitShareFormulaReadOrchestrator,
TaxDataService,
], ],
}) })
export class ProfitShareFormulaModule {} export class ProfitShareFormulaModule {}

View File

@ -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,
};
}
}

View File

@ -8,10 +8,20 @@ import {
columnUniques, columnUniques,
validateRelations, validateRelations,
} from 'src/core/strings/constants/interface.constants'; } 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() @Injectable()
export class UpdateSalesPriceFormulaManager extends BaseUpdateManager<SalesPriceFormulaEntity> { export class UpdateSalesPriceFormulaManager extends BaseUpdateManager<SalesPriceFormulaEntity> {
async validateProcess(): Promise<void> { async validateProcess(): Promise<void> {
const taxes = await this.dataServiceFirstOpt.getManyByOptions({
where: {
status: In([STATUS.ACTIVE]),
},
});
calculateSalesFormula(this.data.formula_string, taxes, 10000, true);
return; return;
} }

View File

@ -4,12 +4,14 @@ import { SalesPriceFormulaEntity } from '../entities/sales-price-formula.entity'
import { UpdateSalesPriceFormulaManager } from './managers/update-sales-price-formula.manager'; import { UpdateSalesPriceFormulaManager } from './managers/update-sales-price-formula.manager';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { FormulaType } from '../../constants'; import { FormulaType } from '../../constants';
import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service';
@Injectable() @Injectable()
export class SalesPriceFormulaDataOrchestrator { export class SalesPriceFormulaDataOrchestrator {
constructor( constructor(
private updateManager: UpdateSalesPriceFormulaManager, private updateManager: UpdateSalesPriceFormulaManager,
private serviceData: SalesPriceFormulaDataService, private serviceData: SalesPriceFormulaDataService,
private taxService: TaxDataService,
) {} ) {}
async update(data): Promise<SalesPriceFormulaEntity> { async update(data): Promise<SalesPriceFormulaEntity> {
@ -20,7 +22,11 @@ export class SalesPriceFormulaDataOrchestrator {
}); });
this.updateManager.setData(formula.id, data); 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(); await this.updateManager.execute();
return this.updateManager.getResult(); return this.updateManager.getResult();
} }

View File

@ -12,12 +12,17 @@ import { CqrsModule } from '@nestjs/cqrs';
import { UpdateSalesPriceFormulaManager } from './domain/usecases/managers/update-sales-price-formula.manager'; import { UpdateSalesPriceFormulaManager } from './domain/usecases/managers/update-sales-price-formula.manager';
import { DetailSalesPriceFormulaManager } from './domain/usecases/managers/detail-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 { 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() @Global()
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot(), ConfigModule.forRoot(),
TypeOrmModule.forFeature([SalesPriceFormulaModel], CONNECTION_NAME.DEFAULT), TypeOrmModule.forFeature(
[SalesPriceFormulaModel, TaxModel],
CONNECTION_NAME.DEFAULT,
),
CqrsModule, CqrsModule,
], ],
controllers: [ controllers: [
@ -28,6 +33,7 @@ import { SalesPriceFormulaModel } from './data/models/sales-price-formula.model'
DetailSalesPriceFormulaManager, DetailSalesPriceFormulaManager,
UpdateSalesPriceFormulaManager, UpdateSalesPriceFormulaManager,
TaxDataService,
SalesPriceFormulaDataService, SalesPriceFormulaDataService,
SalesPriceFormulaReadService, SalesPriceFormulaReadService,

View File

@ -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 { 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 { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service';
import { FormulaType } from 'src/modules/transaction/sales-price-formula/constants'; 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 { STATUS } from 'src/core/strings/constants/base.constants';
import { TransactionDataService } from '../../../data/services/transaction-data.service'; import { TransactionDataService } from '../../../data/services/transaction-data.service';
import { TransactionModel } from '../../../data/models/transaction.model'; 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) @EventsHandler(TransactionChangeStatusEvent)
export class SettledTransactionHandler export class SettledTransactionHandler
@ -59,7 +58,7 @@ export class SettledTransactionHandler
.manager.connection.createQueryRunner(); .manager.connection.createQueryRunner();
// const profit_share_value = this.calculateFormula(profit_formula.formula_string, taxes, data.payment_total_net_profit ?? 0); // 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, sales_price.formula_string,
taxes, taxes,
data.payment_total_net_profit ?? 0, data.payment_total_net_profit ?? 0,
@ -74,49 +73,4 @@ export class SettledTransactionHandler
await this.dataService.create(queryRunner, TransactionModel, data); 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,
};
}
} }