Merge pull request 'fix(SPG-595) Pemesanan - Penyesuaian flow booking' (#26) from fix/transaction into development
continuous-integration/drone/tag Build is passing
Details
continuous-integration/drone/tag Build is passing
Details
Reviewed-on: #26pull/27/head devel_10.6.10
commit
eb32584205
|
@ -18,4 +18,14 @@ ELASTIC_APM_ACTIVATE=true
|
|||
ELASTIC_APM_SERVICE_NAME="Skyworld POS"
|
||||
ELASTIC_APM_SERVER_URL="http://172.10.10.10:8200"
|
||||
|
||||
CRON_MIDNIGHT="55 11 * * *"
|
||||
CRON_EVERY_MINUTE="55 11 * * *"
|
||||
CRON_EVERY_HOUR="0 * * * *"
|
||||
|
||||
MIDTRANS_URL=https://app.sandbox.midtrans.com
|
||||
MIDTRANS_PRODUCTION=false
|
||||
MIDTRANS_SERVER_KEY=SB-Mid-server-ITmSD6C0nXfIcmgi4TXm6J7i
|
||||
MIDTRANS_CLIENT_KEY=SB-Mid-client-VFaU_cPL6kh2DKir
|
||||
|
||||
EXPORT_LIMIT_PARTITION=200
|
||||
ASSETS="https://asset.sky.eigen.co.id/"
|
|
@ -0,0 +1,31 @@
|
|||
PORT="3346"
|
||||
|
||||
JWT_SECRET="ftyYM4t4kjuj/0ixvIrS18gpdvBJw42NnW71GrFrEhcn0alQkkH7TQIHU5MFFJ1e"
|
||||
JWT_EXPIRES="24h"
|
||||
JWT_REFRESH_EXPIRES="7d"
|
||||
ENC_KEY="921c83f3b90c92dca4ba9b947f99b4c9"
|
||||
IV="a671a96159e97a4f"
|
||||
|
||||
COUCHDB_CONFIG="http://root:password@172.10.10.2:5970"
|
||||
|
||||
DEFAULT_DB_HOST="postgres"
|
||||
DEFAULT_DB_PORT="5432"
|
||||
DEFAULT_DB_USER="root"
|
||||
DEFAULT_DB_PASS="password"
|
||||
DEFAULT_DB_NAME="pos"
|
||||
|
||||
ELASTIC_APM_ACTIVATE=true
|
||||
ELASTIC_APM_SERVICE_NAME="Skyworld POS"
|
||||
ELASTIC_APM_SERVER_URL="http://172.10.10.10:8200"
|
||||
|
||||
CRON_MIDNIGHT="55 11 * * *"
|
||||
CRON_EVERY_MINUTE="55 11 * * *"
|
||||
CRON_EVERY_HOUR="0 * * * *"
|
||||
|
||||
MIDTRANS_URL=https://app.midtrans.com
|
||||
MIDTRANS_PRODUCTION=true
|
||||
MIDTRANS_SERVER_KEY=Mid-server-6lA4Nnmov2BSOcwVq1sLSOpC
|
||||
MIDTRANS_CLIENT_KEY=Mid-client-JiPIkIPd_RGooF8U
|
||||
|
||||
EXPORT_LIMIT_PARTITION=200
|
||||
ASSETS="https://asset.sky.eigen.co.id/"
|
|
@ -37,6 +37,7 @@
|
|||
"@nestjs/schedule": "^4.1.0",
|
||||
"@nestjs/swagger": "^7.3.1",
|
||||
"@nestjs/typeorm": "^10.0.2",
|
||||
"algebra.js": "^0.2.6",
|
||||
"bcrypt": "^5.1.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
|
@ -44,6 +45,8 @@
|
|||
"elastic-apm-node": "^4.5.4",
|
||||
"exceljs": "^4.4.0",
|
||||
"googleapis": "^140.0.0",
|
||||
"mathjs": "^13.0.2",
|
||||
"midtrans-client": "^1.3.1",
|
||||
"moment": "^2.30.1",
|
||||
"nano": "^10.1.3",
|
||||
"pg": "^8.11.5",
|
||||
|
|
|
@ -54,6 +54,7 @@ import { ReportExportModule } from './modules/reports/report-export/report-expor
|
|||
import { ReportBookmarkModel } from './modules/reports/shared/models/report-bookmark.model';
|
||||
import { ExportReportHistoryModel } from './modules/reports/shared/models/export-report-history.model';
|
||||
import { CronModule } from './modules/configuration/cron/cron.module';
|
||||
import { MidtransModule } from './modules/configuration/midtrans/midtrans.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
@ -101,6 +102,7 @@ import { CronModule } from './modules/configuration/cron/cron.module';
|
|||
CronModule,
|
||||
GoogleCalendarModule,
|
||||
LogModule,
|
||||
MidtransModule,
|
||||
SessionModule,
|
||||
|
||||
// user
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { EventBus } from '@nestjs/cqrs';
|
||||
const midtransClient = require('midtrans-client');
|
||||
|
||||
@Injectable()
|
||||
export class MidtransService {
|
||||
constructor(private eventBus: EventBus) {}
|
||||
|
||||
get midtransInstance() {
|
||||
return new midtransClient.Snap({
|
||||
isProduction: false,
|
||||
serverKey: process.env.MIDTRANS_SERVER_KEY,
|
||||
clientKey: process.env.MIDTRANS_CLIENT_KEY,
|
||||
});
|
||||
}
|
||||
|
||||
async getStatus(orderId: string): Promise<any> {
|
||||
return await this.midtransInstance.transaction.status(orderId);
|
||||
}
|
||||
|
||||
async create(body): Promise<any> {
|
||||
return await this.midtransInstance.createTransaction({
|
||||
transaction_details: {
|
||||
order_id: '123',
|
||||
gross_amount: 10000,
|
||||
},
|
||||
credit_card: {
|
||||
secure: true,
|
||||
},
|
||||
customer_details: {
|
||||
first_name: 'budd',
|
||||
last_name: 'pratama',
|
||||
email: 'budi.pra@mail.com',
|
||||
phone: '0822111111',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export class MidtransCallbackEvent {
|
||||
constructor(public readonly data: IEventMidtrans) {}
|
||||
}
|
||||
|
||||
export interface IEventMidtrans {
|
||||
id: string;
|
||||
data: any;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class MidtransDto {
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
example: '123',
|
||||
})
|
||||
order_id: string;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import { Body, Controller, Get, Injectable, Param, Post } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Public } from 'src/core/guards';
|
||||
import { MidtransService } from '../data/services/midtrans.service';
|
||||
import { EventBus } from '@nestjs/cqrs';
|
||||
import { MidtransCallbackEvent } from '../domain/entities/midtrans-callback.event';
|
||||
import { MidtransDto } from './dto/midtrans.dto';
|
||||
|
||||
@ApiTags(`midtrans`)
|
||||
@Controller('v1/midtrans')
|
||||
@Public()
|
||||
@Injectable()
|
||||
export class MidtransController {
|
||||
constructor(
|
||||
private dataService: MidtransService,
|
||||
private eventBus: EventBus,
|
||||
) {}
|
||||
|
||||
@Get(':id/status')
|
||||
async getStatus(@Param('id') id: string) {
|
||||
return await this.dataService.getStatus(id);
|
||||
}
|
||||
|
||||
@Post('callback')
|
||||
async callback(@Body() callback: MidtransDto) {
|
||||
const data = await this.dataService.getStatus(callback?.order_id);
|
||||
this.eventBus.publishAll([
|
||||
new MidtransCallbackEvent({
|
||||
id: data.order_id,
|
||||
data: data,
|
||||
}),
|
||||
]);
|
||||
console.log(`midtrans callback for order ${data.order_id}`);
|
||||
return 'success listen callback';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { ConfigModule } from '@nestjs/config';
|
||||
import { CqrsModule } from '@nestjs/cqrs';
|
||||
import { MidtransController } from './infrastructure/midtrans.controller';
|
||||
import { MidtransService } from './data/services/midtrans.service';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot(),
|
||||
// TypeOrmModule.forFeature(
|
||||
// [
|
||||
// ],
|
||||
// CONNECTION_NAME.DEFAULT,
|
||||
// ),
|
||||
CqrsModule,
|
||||
],
|
||||
controllers: [MidtransController],
|
||||
providers: [MidtransService],
|
||||
})
|
||||
export class MidtransModule {}
|
|
@ -11,7 +11,7 @@ import { STATUS } from 'src/core/strings/constants/base.constants';
|
|||
|
||||
@Injectable()
|
||||
export class BatchConfirmReconciliationManager extends BaseBatchUpdateStatusManager<TransactionEntity> {
|
||||
validateData(data: TransactionEntity): Promise<void> {
|
||||
async validateData(data: TransactionEntity): Promise<void> {
|
||||
const net_profit = data.reconciliation_mdr
|
||||
? Number(this.data.payment_total) - Number(this.data.reconciliation_mdr)
|
||||
: null;
|
||||
|
|
|
@ -19,6 +19,7 @@ import { BatchCancelReconciliationManager } from './domain/usecases/managers/bat
|
|||
import { BatchConfirmReconciliationManager } from './domain/usecases/managers/batch-confirm-reconciliation.manager';
|
||||
import { RecapReconciliationManager } from './domain/usecases/managers/recap-reconciliation.manager';
|
||||
import { RecapPosTransactionHandler } from './domain/usecases/handlers/recap-pos-transaction.handler';
|
||||
import { SalesPriceFormulaDataService } from '../sales-price-formula/data/services/sales-price-formula-data.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
|
||||
import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event';
|
||||
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';
|
||||
|
||||
@EventsHandler(TransactionChangeStatusEvent)
|
||||
export class SettledTransactionHandler
|
||||
implements IEventHandler<TransactionChangeStatusEvent>
|
||||
{
|
||||
constructor(
|
||||
private formulaService: SalesPriceFormulaDataService,
|
||||
private taxService: TaxDataService,
|
||||
private dataService: TransactionDataService,
|
||||
) {}
|
||||
|
||||
async handle(event: TransactionChangeStatusEvent) {
|
||||
const old_data = event.data.old;
|
||||
const data = event.data.data;
|
||||
|
||||
if (
|
||||
old_data.status == data.status ||
|
||||
![STATUS.ACTIVE, STATUS.SETTLED].includes(data.status)
|
||||
)
|
||||
return;
|
||||
|
||||
const profit_formula = await this.formulaService.getOneByOptions({
|
||||
where: {
|
||||
type: FormulaType.PROFIT_SHARE,
|
||||
},
|
||||
});
|
||||
|
||||
const sales_price = await this.formulaService.getOneByOptions({
|
||||
where: {
|
||||
type: FormulaType.SALES_PRICE,
|
||||
},
|
||||
});
|
||||
|
||||
const taxes = await this.taxService.getManyByOptions({
|
||||
where: {
|
||||
status: STATUS.ACTIVE,
|
||||
},
|
||||
});
|
||||
|
||||
const queryRunner = this.dataService
|
||||
.getRepository()
|
||||
.manager.connection.createQueryRunner();
|
||||
|
||||
// const profit_share_value = this.calculateFormula(profit_formula.formula_string, taxes, data.payment_total_net_profit ?? 0);
|
||||
// const sale_price_value = this.calculateFormula(sales_price.formula_string, taxes, data.payment_total_net_profit ?? 0);
|
||||
|
||||
Object.assign(data, {
|
||||
profit_share_formula: profit_formula.formula_string,
|
||||
sales_price_formula: sales_price.formula_string,
|
||||
});
|
||||
|
||||
await this.dataService.create(queryRunner, TransactionModel, data);
|
||||
}
|
||||
|
||||
calculateFormula(formula, taxes, total) {
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
const value = math.evaluate(result);
|
||||
console.log(value);
|
||||
return value;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager';
|
||||
import { TransactionEntity } from '../../entities/transaction.entity';
|
||||
import {
|
||||
EventTopics,
|
||||
validateRelations,
|
||||
} from 'src/core/strings/constants/interface.constants';
|
||||
import { TransactionModel } from '../../../data/models/transaction.model';
|
||||
import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event';
|
||||
|
||||
@Injectable()
|
||||
export class ActiveTransactionManager extends BaseUpdateStatusManager<TransactionEntity> {
|
||||
getResult(): string {
|
||||
return `Success active data ${this.result.invoice_code}`;
|
||||
}
|
||||
|
||||
async validateProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
async beforeProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
async afterProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
get validateRelations(): validateRelations[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
get entityTarget(): any {
|
||||
return TransactionModel;
|
||||
}
|
||||
|
||||
get eventTopics(): EventTopics[] {
|
||||
return [
|
||||
{
|
||||
topic: TransactionChangeStatusEvent,
|
||||
data: this.data,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager';
|
||||
import { TransactionEntity } from '../../entities/transaction.entity';
|
||||
import {
|
||||
EventTopics,
|
||||
validateRelations,
|
||||
} from 'src/core/strings/constants/interface.constants';
|
||||
import { TransactionModel } from '../../../data/models/transaction.model';
|
||||
import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event';
|
||||
import { BatchResult } from 'src/core/response/domain/ok-response.interface';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class BatchActiveTransactionManager extends BaseBatchUpdateStatusManager<TransactionEntity> {
|
||||
validateData(data: TransactionEntity): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
beforeProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
afterProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
get validateRelations(): validateRelations[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
get entityTarget(): any {
|
||||
return TransactionModel;
|
||||
}
|
||||
|
||||
get eventTopics(): EventTopics[] {
|
||||
return [
|
||||
{
|
||||
topic: TransactionChangeStatusEvent,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getResult(): BatchResult {
|
||||
return this.result;
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ import { STATUS } from 'src/core/strings/constants/base.constants';
|
|||
|
||||
@Injectable()
|
||||
export class BatchConfirmTransactionManager extends BaseBatchUpdateStatusManager<TransactionEntity> {
|
||||
validateData(data: TransactionEntity): Promise<void> {
|
||||
async validateData(data: TransactionEntity): Promise<void> {
|
||||
if (data.status != STATUS.DRAFT) {
|
||||
throw new UnprocessableEntityException({
|
||||
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager';
|
||||
import { TransactionEntity } from '../../entities/transaction.entity';
|
||||
import {
|
||||
EventTopics,
|
||||
validateRelations,
|
||||
} from 'src/core/strings/constants/interface.constants';
|
||||
import { TransactionModel } from '../../../data/models/transaction.model';
|
||||
import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event';
|
||||
import { BatchResult } from 'src/core/response/domain/ok-response.interface';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class BatchInactiveTransactionManager extends BaseBatchUpdateStatusManager<TransactionEntity> {
|
||||
validateData(data: TransactionEntity): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
beforeProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
afterProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
get validateRelations(): validateRelations[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
get entityTarget(): any {
|
||||
return TransactionModel;
|
||||
}
|
||||
|
||||
get eventTopics(): EventTopics[] {
|
||||
return [
|
||||
{
|
||||
topic: TransactionChangeStatusEvent,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getResult(): BatchResult {
|
||||
return this.result;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ import { STATUS } from 'src/core/strings/constants/base.constants';
|
|||
import { TransactionType } from 'src/modules/transaction/transaction/constants';
|
||||
|
||||
export function mappingTransaction(data) {
|
||||
let payment_type_bank;
|
||||
let payment_type_bank: any = null;
|
||||
const season_period = {
|
||||
id: data.season_period_id,
|
||||
holiday_name: data.season_period_name,
|
||||
|
@ -12,7 +12,7 @@ export function mappingTransaction(data) {
|
|||
},
|
||||
};
|
||||
|
||||
if (data.payment_type_method_id) {
|
||||
if (data.payment_type_method_id || data.payment_type_method_name) {
|
||||
payment_type_bank = {
|
||||
id: data.payment_type_method_id,
|
||||
issuer_name: data.payment_type_method_name,
|
||||
|
@ -93,6 +93,7 @@ export function mappingRevertTransaction(data, type) {
|
|||
|
||||
Object.assign(data, {
|
||||
type: type,
|
||||
payment_total_net_profit: data.payment_total,
|
||||
customer_category_id: data.customer_category?.id ?? null,
|
||||
customer_category_name: data.customer_category?.name ?? null,
|
||||
season_period_id: data.season_period?.id ?? null,
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager';
|
||||
import { TransactionEntity } from '../../entities/transaction.entity';
|
||||
import {
|
||||
EventTopics,
|
||||
validateRelations,
|
||||
} from 'src/core/strings/constants/interface.constants';
|
||||
import { TransactionModel } from '../../../data/models/transaction.model';
|
||||
import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event';
|
||||
|
||||
@Injectable()
|
||||
export class InactiveTransactionManager extends BaseUpdateStatusManager<TransactionEntity> {
|
||||
getResult(): string {
|
||||
return `Success inactive data ${this.result.invoice_code}`;
|
||||
}
|
||||
|
||||
async validateProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
async beforeProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
async afterProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
get validateRelations(): validateRelations[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
get entityTarget(): any {
|
||||
return TransactionModel;
|
||||
}
|
||||
|
||||
get eventTopics(): EventTopics[] {
|
||||
return [
|
||||
{
|
||||
topic: TransactionChangeStatusEvent,
|
||||
data: this.data,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ import { TaxDataService } from '../tax/data/services/tax-data.service';
|
|||
import { SalesPriceFormulaDataService } from '../sales-price-formula/data/services/sales-price-formula-data.service';
|
||||
import { SalesPriceFormulaModel } from '../sales-price-formula/data/models/sales-price-formula.model';
|
||||
import { TaxModel } from '../tax/data/models/tax.model';
|
||||
import { SettledTransactionHandler } from './domain/usecases/handlers/settled-transaction.handler';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
@ -48,6 +49,7 @@ import { TaxModel } from '../tax/data/models/tax.model';
|
|||
controllers: [TransactionDataController, TransactionReadController],
|
||||
providers: [
|
||||
PosTransactionHandler,
|
||||
SettledTransactionHandler,
|
||||
|
||||
IndexTransactionManager,
|
||||
DetailTransactionManager,
|
||||
|
|
Loading…
Reference in New Issue