Merge pull request 'fix/data' (#33) from fix/data into development

Reviewed-on: #33
pull/34/head^2
aswin 2024-07-22 04:06:42 +00:00
commit 000c3f1800
31 changed files with 356 additions and 128 deletions

View File

@ -10,39 +10,14 @@ export function setQueryFilterDefault(
baseFilter: BaseFilterEntity, baseFilter: BaseFilterEntity,
tableName: TABLE_NAME, tableName: TABLE_NAME,
): SelectQueryBuilder<any> { ): SelectQueryBuilder<any> {
// filter berdasarkan statuses
if (!!baseFilter.statuses) {
queryBuilder.andWhere(
new Brackets((qb) => {
baseFilter.statuses.map((status) => {
// trim search
const statusData = status.includes("'")
? status.trim().replace(/'/g, "''").replace(/\s+/g, ' ')
: status.trim().replace(/\s+/g, ' ');
// jika searching status terdapat dalam enum, maka dia mencari specific data
// ? karena jika tidak, ketika dia search "active" maka "inactive" juga ikut
if (STATUS[statusData.toUpperCase()])
qb.orWhere(`${tableName}.status = :statusData`, {
statusData: statusData,
});
else
qb['orWhere'](
`${tableName}.status::text ILIKE '%${[statusData]}%'`,
);
});
}),
);
}
// filter berdasarkan id pembuat // filter berdasarkan id pembuat
if (!!baseFilter.created_ids) if (!!baseFilter.created_ids)
new WhereInQueryHelper( new WhereInQueryHelper(
queryBuilder, queryBuilder,
tableName, tableName,
'created_id', 'creator_id',
baseFilter.created_ids, baseFilter.created_ids,
'created_ids', 'creator_ids',
).getQuery(); ).getQuery();
// filter berdasarkan tanggal terakhir dibuat // filter berdasarkan tanggal terakhir dibuat

View File

@ -107,7 +107,7 @@ export abstract class BaseBatchUpdateStatusManager<Entity> extends BaseManager {
if (!this.eventTopics.length) return; if (!this.eventTopics.length) return;
for (const topic of this.eventTopics) { for (const topic of this.eventTopics) {
let data; let data;
if (!topic.relations) { if (topic.relations?.length) {
data = await this.dataService.getOneByOptions({ data = await this.dataService.getOneByOptions({
where: { where: {
id: dataNew.id, id: dataNew.id,

View File

@ -8,6 +8,7 @@ import {
} from 'src/core/helpers/query/default-filter.helper'; } from 'src/core/helpers/query/default-filter.helper';
import { Param } from '../../entities/base-filter.entity'; import { Param } from '../../entities/base-filter.entity';
import { joinRelationHelper } from 'src/core/helpers/query/join-relations.helper'; import { joinRelationHelper } from 'src/core/helpers/query/join-relations.helper';
import { STATUS } from 'src/core/strings/constants/base.constants';
export abstract class BaseIndexManager<Entity> extends BaseReadManager { export abstract class BaseIndexManager<Entity> extends BaseReadManager {
protected result: PaginationResponse<Entity>; protected result: PaginationResponse<Entity>;
@ -19,6 +20,7 @@ export abstract class BaseIndexManager<Entity> extends BaseReadManager {
} }
async process(): Promise<void> { async process(): Promise<void> {
const specificFilter = this.specificFilter;
const { joinRelations, selectRelations, countRelations } = this.relations; const { joinRelations, selectRelations, countRelations } = this.relations;
if (joinRelations?.length) if (joinRelations?.length)
@ -40,10 +42,26 @@ export abstract class BaseIndexManager<Entity> extends BaseReadManager {
if (this.selects?.length) this.queryBuilder.select(this.selects); if (this.selects?.length) this.queryBuilder.select(this.selects);
if (this.filterParam.statuses?.length > 0) {
const data = this.filterParam.statuses.map((status) => {
const statusData = status.includes("'")
? status.trim().replace(/'/g, "''").replace(/\s+/g, ' ')
: status.trim().replace(/\s+/g, ' ');
// jika searching status terdapat dalam enum, maka dia mencari specific data
// ? karena jika tidak, ketika dia search "active" maka "inactive" juga ikut
return STATUS[statusData.toUpperCase()] ?? statusData;
});
specificFilter.push({
cols: `${this.tableName}.status::text`,
data: data,
});
}
new SpecificSearchFilter<Entity>( new SpecificSearchFilter<Entity>(
this.queryBuilder, this.queryBuilder,
this.tableName, this.tableName,
this.specificFilter, specificFilter,
).getFilter(); ).getFilter();
getOrderBy(this.filterParam, this.queryBuilder, this.tableName); getOrderBy(this.filterParam, this.queryBuilder, this.tableName);

View File

@ -1,25 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UpdateMidtransColumnTransaction1721284234428
implements MigrationInterface
{
name = 'UpdateMidtransColumnTransaction1721284234428';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "payment_midtrans_url"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "payment_midtrans_token"`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" ADD "payment_midtrans_token" character varying`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "payment_midtrans_url" character varying`,
);
}
}

View File

@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UpdateColumnTransaction1721385120750
implements MigrationInterface
{
name = 'UpdateColumnTransaction1721385120750';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "discount_percentage"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "discount_percentage" numeric`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "discount_percentage"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "discount_percentage" integer`,
);
}
}

View File

@ -24,7 +24,8 @@ export class PaymentTransactionHandler
if ( if (
old_data.status == STATUS.DRAFT && old_data.status == STATUS.DRAFT &&
current_data.status == STATUS.PENDING && current_data.status == STATUS.PENDING &&
current_data.payment_type != TransactionPaymentType.COUNTER current_data.payment_type != TransactionPaymentType.COUNTER &&
!!current_data.customer_email
) { ) {
if (current_data.payment_type != TransactionPaymentType.MIDTRANS) { if (current_data.payment_type != TransactionPaymentType.MIDTRANS) {
payments = await this.paymentService.getManyByOptions({ payments = await this.paymentService.getManyByOptions({

View File

@ -72,6 +72,7 @@ export class IndexItemRateManager extends BaseIndexManager<ItemEntity> {
get selects(): string[] { get selects(): string[] {
return [ return [
`${this.tableName}.id`, `${this.tableName}.id`,
`${this.tableName}.status`,
`${this.tableName}.created_at`, `${this.tableName}.created_at`,
`${this.tableName}.name`, `${this.tableName}.name`,
`${this.tableName}.base_price`, `${this.tableName}.base_price`,
@ -126,6 +127,9 @@ export class IndexItemRateManager extends BaseIndexManager<ItemEntity> {
}); });
} }
queryBuilder.andWhere(`${this.tableName}.status In (:...statuses)`, {
statuses: [STATUS.ACTIVE],
});
return queryBuilder; return queryBuilder;
} }
} }

View File

@ -85,6 +85,8 @@ export class IndexItemManager extends BaseIndexManager<ItemEntity> {
queryBuilder.andWhere(`${this.tableName}.tenant_id In (:...tenantIds)`, { queryBuilder.andWhere(`${this.tableName}.tenant_id In (:...tenantIds)`, {
tenantIds: this.filterParam.tenant_ids, tenantIds: this.filterParam.tenant_ids,
}); });
} else if (!this.filterParam.all_item) {
queryBuilder.andWhere(`${this.tableName}.tenant_id Is Null`);
} }
return queryBuilder; return queryBuilder;

View File

@ -11,6 +11,7 @@ import { STATUS } from 'src/core/strings/constants/base.constants';
@Injectable() @Injectable()
export class CurrentSeasonPeriodManager extends BaseIndexManager<SeasonPeriodEntity> { export class CurrentSeasonPeriodManager extends BaseIndexManager<SeasonPeriodEntity> {
async prepareData(): Promise<void> { async prepareData(): Promise<void> {
this.filterParam.limit = 10;
return; return;
} }

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

@ -11,10 +11,25 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { STATUS } from 'src/core/strings/constants/base.constants';
@Injectable() @Injectable()
export class BatchCancelReconciliationManager extends BaseBatchUpdateStatusManager<TransactionEntity> { export class BatchCancelReconciliationManager extends BaseBatchUpdateStatusManager<TransactionEntity> {
validateData(data: TransactionEntity): Promise<void> { async validateData(data: TransactionEntity): Promise<void> {
const transaction = await this.dataService.getOneByOptions({
where: {
id: data.id,
},
});
if (transaction.status != STATUS.SETTLED) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! cant cancel transaction not settled`,
error: 'Unprocessable Entity',
});
}
Object.assign(data, { Object.assign(data, {
reconciliation_mdr: this.data.reconciliation_mdr ?? null, reconciliation_mdr: this.data.reconciliation_mdr ?? null,
reconciliation_confirm_by: this.user.name, reconciliation_confirm_by: this.user.name,

View File

@ -4,6 +4,7 @@ import {
UnprocessableEntityException, UnprocessableEntityException,
} from '@nestjs/common'; } from '@nestjs/common';
import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager';
import { STATUS } from 'src/core/strings/constants/base.constants';
import { import {
EventTopics, EventTopics,
validateRelations, validateRelations,
@ -18,7 +19,20 @@ export class CancelReconciliationManager extends BaseUpdateStatusManager<Transac
} }
async validateProcess(): Promise<void> { async validateProcess(): Promise<void> {
if (this.data.is_recap_transaction) { // untuk dapat current status
const transaction = await this.dataService.getOneByOptions({
where: {
id: this.dataId,
},
});
if (transaction.status != STATUS.SETTLED) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! cant cancel transaction not settled`,
error: 'Unprocessable Entity',
});
} else if (this.data.is_recap_transaction) {
throw new UnprocessableEntityException({ throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY, statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! cant cancel recap data`, message: `Failed! cant cancel recap data`,

View File

@ -16,8 +16,14 @@ import { STATUS } from 'src/core/strings/constants/base.constants';
@Injectable() @Injectable()
export class BatchConfirmRefundManager extends BaseBatchUpdateStatusManager<RefundEntity> { export class BatchConfirmRefundManager extends BaseBatchUpdateStatusManager<RefundEntity> {
validateData(data: RefundEntity): Promise<void> { async validateData(data: RefundEntity): Promise<void> {
if (![STATUS.DRAFT, STATUS.PENDING].includes(data.status)) { if (data?.['transaction']?.status != STATUS.SETTLED) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`,
error: 'Unprocessable Entity',
});
} else if (![STATUS.DRAFT, STATUS.PENDING].includes(data.status)) {
throw new UnprocessableEntityException({ throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY, statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! only data with status ${STATUS.DRAFT} and ${STATUS.PENDING} can be confirmed`, message: `Failed! only data with status ${STATUS.DRAFT} and ${STATUS.PENDING} can be confirmed`,

View File

@ -38,6 +38,14 @@ export class ConfirmRefundManager extends BaseUpdateStatusManager<RefundEntity>
relations: ['transaction'], relations: ['transaction'],
}); });
if (data.transaction.status != STATUS.SETTLED) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`,
error: 'Unprocessable Entity',
});
}
if (data.status == STATUS.DRAFT) { if (data.status == STATUS.DRAFT) {
Object.assign(this.data, { Object.assign(this.data, {
code: `RF-${data.transaction?.invoice_code?.split('-')[1]}`, code: `RF-${data.transaction?.invoice_code?.split('-')[1]}`,

View File

@ -30,7 +30,14 @@ export class CreateRefundManager extends BaseCreateManager<RefundEntity> {
refund_items: refund_items, refund_items: refund_items,
}); });
if (this.data.transaction?.status != STATUS.SETTLED) { const transaction = await this.dataServiceFirstOpt.getOneByOptions({
where: {
id: this.data.transaction.id,
status: STATUS.SETTLED,
},
});
if (!transaction) {
throw new UnprocessableEntityException({ throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY, statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`, message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`,

View File

@ -17,7 +17,14 @@ import { STATUS } from 'src/core/strings/constants/base.constants';
@Injectable() @Injectable()
export class UpdateRefundManager extends BaseUpdateManager<RefundEntity> { export class UpdateRefundManager extends BaseUpdateManager<RefundEntity> {
async validateProcess(): Promise<void> { async validateProcess(): Promise<void> {
if (this.data.transaction?.status != STATUS.SETTLED) { const transaction = await this.dataServiceFirstOpt.getOneByOptions({
where: {
id: this.data.transaction.id,
status: STATUS.SETTLED,
},
});
if (!transaction) {
throw new UnprocessableEntityException({ throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY, statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`, message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`,

View File

@ -12,6 +12,7 @@ import { BatchDeleteRefundManager } from './managers/batch-delete-refund.manager
import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { CancelRefundManager } from './managers/cancel-refund.manager'; import { CancelRefundManager } from './managers/cancel-refund.manager';
import { BatchCancelRefundManager } from './managers/batch-cancel-refund.manager'; import { BatchCancelRefundManager } from './managers/batch-cancel-refund.manager';
import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service';
@Injectable() @Injectable()
export class RefundDataOrchestrator { export class RefundDataOrchestrator {
@ -25,18 +26,27 @@ export class RefundDataOrchestrator {
private batchCancelManager: BatchCancelRefundManager, private batchCancelManager: BatchCancelRefundManager,
private batchConfirmManager: BatchConfirmRefundManager, private batchConfirmManager: BatchConfirmRefundManager,
private serviceData: RefundDataService, private serviceData: RefundDataService,
private transactionDataService: TransactionDataService,
) {} ) {}
async create(data): Promise<RefundEntity> { async create(data): Promise<RefundEntity> {
this.createManager.setData(data); this.createManager.setData(data);
this.createManager.setService(this.serviceData, TABLE_NAME.REFUND); this.createManager.setService(
this.serviceData,
TABLE_NAME.REFUND,
this.transactionDataService,
);
await this.createManager.execute(); await this.createManager.execute();
return this.createManager.getResult(); return this.createManager.getResult();
} }
async update(dataId, data): Promise<RefundEntity> { async update(dataId, data): Promise<RefundEntity> {
this.updateManager.setData(dataId, data); this.updateManager.setData(dataId, data);
this.updateManager.setService(this.serviceData, TABLE_NAME.REFUND); this.updateManager.setService(
this.serviceData,
TABLE_NAME.REFUND,
this.transactionDataService,
);
await this.updateManager.execute(); await this.updateManager.execute();
return this.updateManager.getResult(); return this.updateManager.getResult();
} }

View File

@ -21,12 +21,14 @@ import { RefundModel } from './data/models/refund.model';
import { BatchCancelRefundManager } from './domain/usecases/managers/batch-cancel-refund.manager'; import { BatchCancelRefundManager } from './domain/usecases/managers/batch-cancel-refund.manager';
import { CancelRefundManager } from './domain/usecases/managers/cancel-refund.manager'; import { CancelRefundManager } from './domain/usecases/managers/cancel-refund.manager';
import { RefundItemModel } from './data/models/refund-item.model'; import { RefundItemModel } from './data/models/refund-item.model';
import { TransactionDataService } from '../transaction/data/services/transaction-data.service';
import { TransactionModel } from '../transaction/data/models/transaction.model';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot(), ConfigModule.forRoot(),
TypeOrmModule.forFeature( TypeOrmModule.forFeature(
[RefundModel, RefundItemModel], [RefundModel, RefundItemModel, TransactionModel],
CONNECTION_NAME.DEFAULT, CONNECTION_NAME.DEFAULT,
), ),
CqrsModule, CqrsModule,
@ -46,6 +48,7 @@ import { RefundItemModel } from './data/models/refund-item.model';
RefundDataService, RefundDataService,
RefundReadService, RefundReadService,
TransactionDataService,
RefundDataOrchestrator, RefundDataOrchestrator,
RefundReadOrchestrator, RefundReadOrchestrator,

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

@ -93,7 +93,7 @@ export class TransactionModel
@Column('varchar', { name: 'discount_code', nullable: true }) @Column('varchar', { name: 'discount_code', nullable: true })
discount_code: string; discount_code: string;
@Column('int', { name: 'discount_percentage', nullable: true }) @Column('decimal', { name: 'discount_percentage', nullable: true })
discount_percentage: number; discount_percentage: number;
@Column('decimal', { name: 'discount_value', nullable: true }) @Column('decimal', { name: 'discount_value', nullable: true })

View File

@ -18,7 +18,7 @@ export class RefundUpdatedHandler
if ( if (
old_data.status != current_data.data || old_data.status != current_data.data ||
event.data.op == OPERATION.DELETE (event.data.op == OPERATION.DELETE && current_data.status != STATUS.DRAFT)
) { ) {
const queryRunner = this.dataService const queryRunner = this.dataService
.getRepository() .getRepository()

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

View File

@ -56,7 +56,10 @@ export class ConfirmTransactionManager extends BaseUpdateStatusManager<Transacti
} }
Object.assign(this.data, { Object.assign(this.data, {
invoice_code: await generateInvoiceCodeHelper(this.dataService), invoice_code:
this.data.payment_type == TransactionPaymentType.COUNTER
? null
: await generateInvoiceCodeHelper(this.dataService),
status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING, status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING,
}); });
return; return;

View File

@ -35,11 +35,14 @@ export class DetailTransactionManager extends BaseDetailManager<TransactionEntit
get selects(): string[] { get selects(): string[] {
return [ return [
`${this.tableName}.id`, `${this.tableName}.id`,
`${this.tableName}.creator_counter_no`,
`${this.tableName}.creator_name`, `${this.tableName}.creator_name`,
`${this.tableName}.created_at`, `${this.tableName}.created_at`,
`${this.tableName}.updated_at`, `${this.tableName}.updated_at`,
`${this.tableName}.editor_name`, `${this.tableName}.editor_name`,
`${this.tableName}.invoice_code`, `${this.tableName}.invoice_code`,
`${this.tableName}.invoice_date`,
`${this.tableName}.settlement_date`,
`${this.tableName}.season_period_id`, `${this.tableName}.season_period_id`,
`${this.tableName}.season_period_name`, `${this.tableName}.season_period_name`,

View File

@ -1,5 +1,8 @@
import { STATUS } from 'src/core/strings/constants/base.constants'; import { STATUS } from 'src/core/strings/constants/base.constants';
import { TransactionType } from 'src/modules/transaction/transaction/constants'; import {
TransactionPaymentType,
TransactionType,
} from 'src/modules/transaction/transaction/constants';
export function mappingTransaction(data) { export function mappingTransaction(data) {
let payment_type_bank: any = null; let payment_type_bank: any = null;
@ -73,15 +76,31 @@ export function mappingTransaction(data) {
export function mappingRevertTransaction(data, type) { export function mappingRevertTransaction(data, type) {
if (type == TransactionType.COUNTER) { if (type == TransactionType.COUNTER) {
if (data.booking_id) {
Object.assign(data, {
editor_id: data.pos_admin?.id,
editor_name: data.pos_admin?.name,
edited_at: new Date(data.created_at),
});
} else {
Object.assign(data, { Object.assign(data, {
id: data._id,
creator_counter_no: data.pos_number,
creator_id: data.pos_admin?.id, creator_id: data.pos_admin?.id,
creator_name: data.pos_admin?.name, creator_name: data.pos_admin?.name,
});
}
Object.assign(data, {
id: data.booking_id ?? data._id,
invoice_code: data.code,
creator_counter_no: Number(data.pos_number),
status: STATUS.SETTLED, status: STATUS.SETTLED,
booking_date: data.created_at, settlement_date: new Date(data.created_at),
settlement_date: data.created_at, payment_date: new Date(data.created_at),
payment_type: data.payment_type, invoice_date: new Date(data.created_at),
payment_type:
data.payment_type == 'cc'
? TransactionPaymentType.CC
: data.payment_type,
payment_card_information: data.card_information, payment_card_information: data.card_information,
payment_code_reference: data.payment_code, payment_code_reference: data.payment_code,
discount_code_id: data.discount_code?.id, discount_code_id: data.discount_code?.id,
@ -116,7 +135,8 @@ export function mappingRevertTransaction(data, type) {
}); });
data.items?.map((item) => { data.items?.map((item) => {
const total_price = Number(item.item.base_price) * Number(item.qty); const total_price =
Number(item.item.price ?? item.item.base_price) * Number(item.qty);
const share_margin = item.item.tenant?.share_margin ?? 0; const share_margin = item.item.tenant?.share_margin ?? 0;
const total_share_tenant = const total_share_tenant =
share_margin > 0 ? (Number(share_margin) / 100) * total_price : 0; share_margin > 0 ? (Number(share_margin) / 100) * total_price : 0;

View File

@ -48,6 +48,7 @@ export class IndexTransactionManager extends BaseIndexManager<TransactionEntity>
`${this.tableName}.id`, `${this.tableName}.id`,
`${this.tableName}.status`, `${this.tableName}.status`,
`${this.tableName}.invoice_code`, `${this.tableName}.invoice_code`,
`${this.tableName}.creator_counter_no`,
`${this.tableName}.booking_date`, `${this.tableName}.booking_date`,
`${this.tableName}.no_of_group`, `${this.tableName}.no_of_group`,
`${this.tableName}.type`, `${this.tableName}.type`,