Compare commits

...

4 Commits

Author SHA1 Message Date
shancheas 0d75cbb5a9 feat: add profit share and tenant share
continuous-integration/drone/push Build is passing Details
2024-09-13 17:27:01 +07:00
shancheas d9b0b4ec85 Merge branch 'development' of ssh://git.eigen.co.id:2222/eigen/pos-be into fix/report 2024-09-13 16:56:34 +07:00
shancheas 19494b3328 Merge branch 'development' of ssh://git.eigen.co.id:2222/eigen/pos-be into development 2024-09-11 16:12:36 +07:00
shancheas e1f2cdfa4d wip: calculate share item 2024-09-11 16:12:22 +07:00
11 changed files with 341 additions and 23 deletions

View File

@ -0,0 +1,49 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddTaxItemTransaction1726045820711 implements MigrationInterface {
name = 'AddTaxItemTransaction1726045820711';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "transaction_item_taxes" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "tax_id" character varying, "tax_name" character varying, "taxt_value" numeric, "tax_total_value" numeric, "transaction_id" uuid, CONSTRAINT "PK_fc5f6da61b24eb5bfdd503b0a0d" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "t_breakdown_item_taxes" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "tax_id" character varying, "tax_name" character varying, "taxt_value" numeric, "tax_total_value" numeric, "transaction_id" uuid, CONSTRAINT "PK_a1ef08d2c68169a50102aa70eca" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" ADD "total_profit_share" numeric`,
);
await queryRunner.query(
`ALTER TABLE "transaction_item_breakdowns" ADD "total_profit_share" numeric`,
);
await queryRunner.query(
`ALTER TABLE "price_formulas" ADD "value_for" character varying NOT NULL DEFAULT 'dpp'`,
);
await queryRunner.query(
`ALTER TABLE "transaction_item_taxes" ADD CONSTRAINT "FK_f5c4966a381d903899cafb4b5ba" FOREIGN KEY ("transaction_id") REFERENCES "transaction_items"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE "t_breakdown_item_taxes" ADD CONSTRAINT "FK_74bedce7e94f6707ddf26ef0c0f" FOREIGN KEY ("transaction_id") REFERENCES "transaction_item_breakdowns"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "t_breakdown_item_taxes" DROP CONSTRAINT "FK_74bedce7e94f6707ddf26ef0c0f"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_item_taxes" DROP CONSTRAINT "FK_f5c4966a381d903899cafb4b5ba"`,
);
await queryRunner.query(
`ALTER TABLE "price_formulas" DROP COLUMN "value_for"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_item_breakdowns" DROP COLUMN "total_profit_share"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" DROP COLUMN "total_profit_share"`,
);
await queryRunner.query(`DROP TABLE "t_breakdown_item_taxes"`);
await queryRunner.query(`DROP TABLE "transaction_item_taxes"`);
}
}

View File

@ -22,6 +22,8 @@ export enum TABLE_NAME {
TRANSACTION_ITEM = 'transaction_items',
TRANSACTION_ITEM_BREAKDOWN = 'transaction_item_breakdowns',
TRANSACTION_TAX = 'transaction_taxes',
TRANSACTION_ITEM_TAX = 'transaction_item_taxes',
TRANSACTION_ITEM_BREAKDOWN_TAX = 't_breakdown_item_taxes',
TRANSACTION_DEMOGRAPHY = 'transaction_demographies',
USER = 'users',
USER_LOGIN = 'users_login',

View File

@ -127,6 +127,13 @@ export default <ReportConfigEntity>{
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},
{
column: 'tr_item__total_price',
query: 'tr_item.total_price',
label: 'Total Penjualan',
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},
{
column: 'tr_item_bundling__payment_total_dpp',
query: 'tr_item_bundling.payment_total_dpp',
@ -142,9 +149,16 @@ export default <ReportConfigEntity>{
format: DATA_FORMAT.CURRENCY,
},
{
column: 'tr_item__total_price',
query: 'tr_item.total_price',
label: 'Total Penjualan',
column: 'tr_item_bundling__total_profit_share',
query: 'tr_item_bundling.total_profit_share',
label: 'Profit Share',
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},
{
column: 'tr_item_bundling__total_share_tenant',
query: 'tr_item_bundling.total_share_tenant',
label: 'Tenant Share',
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},

View File

@ -112,6 +112,13 @@ export default <ReportConfigEntity>{
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},
{
column: 'tr_item__total_price',
query: 'tr_item.total_price',
label: 'Total Penjualan',
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},
{
column: 'tr_item__payment_total_dpp',
query: 'tr_item.payment_total_dpp',
@ -127,9 +134,16 @@ export default <ReportConfigEntity>{
format: DATA_FORMAT.CURRENCY,
},
{
column: 'tr_item__total_price',
query: 'tr_item.total_price',
label: 'Total Penjualan',
column: 'tr_item__total_profit_share',
query: 'tr_item.total_profit_share',
label: 'Profit Share',
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},
{
column: 'tr_item__total_share_tenant',
query: 'tr_item.total_share_tenant',
label: 'Tenant Share',
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},

View File

@ -102,16 +102,9 @@ export default <ReportConfigEntity>{
format: DATA_FORMAT.CURRENCY,
},
{
column: 'main__payment_total_dpp',
query: 'main.payment_total_dpp',
label: 'DPP',
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},
{
column: 'main__payment_total_tax',
query: 'main.payment_total_tax',
label: 'Total Pajak',
column: 'main__payment_sub_total',
query: 'main.payment_sub_total',
label: 'Subtotal',
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},
@ -136,6 +129,27 @@ export default <ReportConfigEntity>{
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},
{
column: 'main__payment_total_dpp',
query: 'main.payment_total_dpp',
label: 'DPP',
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},
{
column: 'main__payment_total_tax',
query: 'main.payment_total_tax',
label: 'Total Pajak',
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},
{
column: 'main__payment_total_share',
query: 'main.payment_total_share',
label: 'Profit Share',
type: DATA_TYPE.MEASURE,
format: DATA_FORMAT.CURRENCY,
},
{
column: 'refund__refund_date',
query: 'refund.refund_date',

View File

@ -5,6 +5,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { SalesPriceFormulaModel } from '../models/sales-price-formula.model';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { Repository } from 'typeorm';
import { FormulaType } from '../../constants';
@Injectable()
export class SalesPriceFormulaDataService extends BaseDataService<SalesPriceFormulaEntity> {
@ -14,4 +15,12 @@ export class SalesPriceFormulaDataService extends BaseDataService<SalesPriceForm
) {
super(repo);
}
profitShareFormula() {
return this.repo.find({
where: {
type: FormulaType.PROFIT_SHARE,
},
});
}
}

View File

@ -1,6 +1,37 @@
import * as math from 'mathjs';
import { Equation, parse } from 'algebra.js';
import { HttpStatus, UnprocessableEntityException } from '@nestjs/common';
import {
BadRequestException,
HttpStatus,
UnprocessableEntityException,
} from '@nestjs/common';
import { apm } from 'src/core/apm';
export function calculateFormula(
formula: string,
variable: object[],
total: number,
) {
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, 'formula');
const value = math.evaluate(result);
console.log(value, 'value');
return value;
} catch (e) {
apm.captureError(e);
throw new BadRequestException('Wrong value');
}
}
export function calculateSalesFormula(
formula: string,

View File

@ -3,7 +3,10 @@ import { BaseDataService } from 'src/core/modules/data/service/base-data.service
import { TaxEntity } from '../../domain/entities/tax.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { TaxModel } from '../models/tax.model';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import {
CONNECTION_NAME,
STATUS,
} from 'src/core/strings/constants/base.constants';
import { Repository } from 'typeorm';
@Injectable()
@ -14,4 +17,21 @@ export class TaxDataService extends BaseDataService<TaxEntity> {
) {
super(repo);
}
async taxKeyValue(): Promise<any> {
const taxes = await this.getManyByOptions({
where: {
status: STATUS.ACTIVE,
},
});
const keyVal = {};
for (const tax of taxes) {
const { name, value } = tax;
keyVal[name] = value;
}
return keyVal;
}
}

View File

@ -7,6 +7,7 @@ import {
} from '../../domain/entities/transaction-item.entity';
import { TransactionModel } from './transaction.model';
import { RefundItemModel } from 'src/modules/transaction/refund/data/models/refund-item.model';
import { TransactionTaxEntity } from '../../domain/entities/transaction-tax.entity';
@Entity(TABLE_NAME.TRANSACTION_ITEM)
export class TransactionItemModel
@ -64,6 +65,9 @@ export class TransactionItemModel
@Column('decimal', { name: 'total_share_tenant', nullable: true })
total_share_tenant: number;
@Column('decimal', { name: 'total_profit_share', nullable: true })
total_profit_share: number;
@Column('int', { name: 'qty', nullable: true })
qty: number;
@ -122,6 +126,9 @@ export class TransactionItemBreakdownModel extends BaseCoreModel<TransactionBund
@Column('bigint', { nullable: true })
item_rates: number;
@Column('decimal', { nullable: true })
total_profit_share: number;
@ManyToOne(() => TransactionItemModel, (model) => model.bundling_items, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
@ -129,3 +136,57 @@ export class TransactionItemBreakdownModel extends BaseCoreModel<TransactionBund
@JoinColumn({ name: 'transaction_item_id' })
transaction_item: TransactionItemModel;
}
@Entity(TABLE_NAME.TRANSACTION_ITEM_TAX)
export class TransactionItemTaxModel
extends BaseCoreModel<TransactionTaxEntity>
implements TransactionTaxEntity
{
@Column('varchar', { name: 'tax_id', nullable: true })
tax_id: string;
@Column('varchar', { name: 'tax_name', nullable: true })
tax_name: string;
@Column('decimal', { name: 'taxt_value', nullable: true })
taxt_value: number;
@Column('decimal', { name: 'tax_total_value', nullable: true })
tax_total_value: number;
@Column('varchar', { name: 'transaction_id', nullable: true })
transaction_id: string;
@ManyToOne(() => TransactionItemModel, (model) => model.taxes, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
@JoinColumn({ name: 'transaction_id' })
transaction: TransactionItemModel;
}
@Entity(TABLE_NAME.TRANSACTION_ITEM_BREAKDOWN_TAX)
export class TransactionBreakdownTaxModel
extends BaseCoreModel<TransactionTaxEntity>
implements TransactionTaxEntity
{
@Column('varchar', { name: 'tax_id', nullable: true })
tax_id: string;
@Column('varchar', { name: 'tax_name', nullable: true })
tax_name: string;
@Column('decimal', { name: 'taxt_value', nullable: true })
taxt_value: number;
@Column('decimal', { name: 'tax_total_value', nullable: true })
tax_total_value: number;
@Column('varchar', { name: 'transaction_id', nullable: true })
transaction_id: string;
@ManyToOne(() => TransactionItemBreakdownModel, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
@JoinColumn({ name: 'transaction_id' })
transaction: TransactionItemBreakdownModel;
}

View File

@ -0,0 +1,100 @@
import { Injectable } from '@nestjs/common';
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 { TransactionEntity } from '../../entities/transaction.entity';
import { calculateFormula } from 'src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper';
@Injectable()
export class PriceCalculator {
constructor(
private formulaService: SalesPriceFormulaDataService,
private taxService: TaxDataService,
) {}
private initialValue(tax: any) {
if (typeof tax !== 'object' || tax === null) {
return null; // Return null for non-object values
}
for (const key in tax) {
if (tax.hasOwnProperty(key)) {
tax[key] = null;
}
}
tax['dpp'] = null;
return tax;
}
async calculate(transaction: TransactionEntity) {
const taxes = await this.taxService.taxKeyValue();
const formulas = await this.formulaService.profitShareFormula();
const values = this.initialValue(taxes);
const dpp = formulas.find((formula) => formula.value_for == 'dpp');
const dppValue = calculateFormula(
dpp.formula_string,
taxes,
transaction.payment_total_net_profit,
);
values['dpp'] = dppValue;
do {
const valueFor = this.withNullValue(values);
const formula = formulas[valueFor];
let result;
try {
result = calculateFormula(
formula.formula_string,
values,
transaction.payment_total_net_profit,
);
} catch (error) {}
values[valueFor] = result;
values[`${valueFor}_value`] = result;
} while (this.containsNullValue(values));
return { dpp_value: dppValue, tax_datas: values };
}
containsNullValue(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj === null; // Return true if the value itself is null
}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if (this.containsNullValue(obj[key])) {
return true; // If any nested value is null, return true
}
}
}
return false; // If no null values are found, return false
}
withNullValue(obj) {
if (typeof obj !== 'object' || obj === null) {
return null; // Return null for non-object values
}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] === null) {
return key; // Found a null value, return the key
} else if (typeof obj[key] === 'object') {
const nestedKey = this.withNullValue(obj[key]);
if (nestedKey !== null) {
return `${key}.${nestedKey}`; // Found null in nested object, return nested key path
}
}
}
}
return null; // No null values found, return null
}
}

View File

@ -9,6 +9,7 @@ import { TransactionModel } from '../../../data/models/transaction.model';
import { calculateSalesFormula } from 'src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper';
import { CreateEventCalendarHelper } from 'src/modules/configuration/google-calendar/domain/usecases/managers/helpers/create-event-calanedar.helper';
import { TransactionUpdatedEvent } from '../../entities/event/transaction-updated.event';
import { PriceCalculator } from '../calculator/price.calculator';
@EventsHandler(TransactionChangeStatusEvent, TransactionUpdatedEvent)
export class SettledTransactionHandler
@ -18,6 +19,7 @@ export class SettledTransactionHandler
private formulaService: SalesPriceFormulaDataService,
private taxService: TaxDataService,
private dataService: TransactionDataService,
private calculator: PriceCalculator,
) {}
async handle(event: TransactionChangeStatusEvent) {
@ -62,11 +64,13 @@ 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 } = calculateSalesFormula(
sales_price.formula_string,
taxes,
data.payment_total_net_profit ?? 0,
);
const { dpp_value, tax_datas } = await this.calculator.calculate(data);
// calculateSalesFormula(
// sales_price.formula_string,
// taxes,
// data.payment_total_net_profit ?? 0,
// );
// console.log(data, 'dsa');
const google_calendar = await CreateEventCalendarHelper(data);