Merge pull request 'fix/transaction' (#23) from fix/transaction into development
continuous-integration/drone/tag Build is passing Details

Reviewed-on: #23
pull/24/head devel_10.6.6
aswin 2024-07-08 11:11:24 +00:00
commit d149a15530
28 changed files with 727 additions and 207 deletions

View File

@ -34,6 +34,7 @@
"@nestjs/cqrs": "^10.2.7",
"@nestjs/jwt": "^10.2.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/schedule": "^4.1.0",
"@nestjs/swagger": "^7.3.1",
"@nestjs/typeorm": "^10.0.2",
"bcrypt": "^5.1.1",

View File

@ -53,6 +53,7 @@ import { ReportBookmarkModule } from './modules/reports/report-bookmark/report-b
import { ReportExportModule } from './modules/reports/report-export/report-export.module';
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';
@Module({
imports: [
@ -97,6 +98,7 @@ import { ExportReportHistoryModel } from './modules/reports/shared/models/export
ConstantModule,
CqrsModule,
CouchModule,
CronModule,
GoogleCalendarModule,
LogModule,
SessionModule,

View File

@ -68,4 +68,8 @@ export abstract class BaseDataService<Entity> {
async getOneByOptions(findOneOptions): Promise<Entity> {
return await this.repository.findOne(findOneOptions);
}
async getManyByOptions(findOneOptions): Promise<Entity[]> {
return await this.repository.find(findOneOptions);
}
}

View File

@ -20,6 +20,18 @@ export abstract class BaseUpdateManager<Entity> extends BaseManager {
}
async prepareData(): Promise<void> {
this.oldData = await this.dataService.getOneByOptions({
where: { id: this.dataId },
});
if (!this.oldData) {
throw new NotFoundException({
statusCode: HttpStatus.NOT_FOUND,
message: `Failed! Entity with id ${this.dataId} not found`,
error: 'Entity Not Found',
});
}
Object.assign(this.data, {
editor_id: this.user.id,
editor_name: this.user.name,
@ -38,18 +50,6 @@ export abstract class BaseUpdateManager<Entity> extends BaseManager {
}
async process(): Promise<void> {
this.oldData = await this.dataService.getOneByOptions({
where: { id: this.dataId },
});
if (!this.oldData) {
throw new NotFoundException({
statusCode: HttpStatus.NOT_FOUND,
message: `Failed! Entity with id ${this.dataId} not found`,
error: 'Entity Not Found',
});
}
await new ValidateRelationHelper(
this.dataId,
this.dataService,

View File

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

View File

@ -34,28 +34,16 @@ export class ConstantController {
@Get('transaction-user-type')
async userType(): Promise<any> {
return [
'group',
'vip'
];
return ['group', 'vip'];
}
@Get('transaction-payment-type')
async transactionPaymentType(): Promise<any> {
return [
'midtrans',
'bank transfer',
'qris',
'counter',
];
return ['midtrans', 'bank transfer', 'qris', 'counter'];
}
@Get('transaction-type')
async transactionType(): Promise<any> {
return [
'counter',
'admin',
'online'
]
return ['counter', 'admin', 'online'];
}
}

View File

@ -29,15 +29,34 @@ import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'
import { UserModel } from 'src/modules/user-related/user/data/models/user.model';
import { UserDataService } from 'src/modules/user-related/user/data/services/user-data.service';
import { ItemDataService } from 'src/modules/item-related/item/data/services/item-data.service';
import {
BookingDeletedEvent,
BookingHandler,
} from './domain/managers/booking.handler';
import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { TransactionTaxModel } from 'src/modules/transaction/transaction/data/models/transaction-tax.model';
import { TransactionItemModel } from 'src/modules/transaction/transaction/data/models/transaction-item.model';
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forFeature([ItemModel, UserModel], CONNECTION_NAME.DEFAULT),
TypeOrmModule.forFeature(
[
ItemModel,
UserModel,
TransactionModel,
TransactionTaxModel,
TransactionItemModel,
],
CONNECTION_NAME.DEFAULT,
),
CqrsModule,
],
controllers: [CouchDataController],
providers: [
BookingHandler,
BookingDeletedEvent,
PaymentMethodDeletedHandler,
PaymentMethodUpdatedHandler,
VipCategoryDeletedHandler,
@ -49,6 +68,7 @@ import { ItemDataService } from 'src/modules/item-related/item/data/services/ite
UserDeletedHandler,
UserUpdatedHandler,
TransactionDataService,
UserDataService,
ItemDataService,
CouchService,

View File

@ -0,0 +1,73 @@
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service';
import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event';
import { CouchService } from '../../data/services/couch.service';
import { STATUS } from 'src/core/strings/constants/base.constants';
import { TransactionPaymentType } from 'src/modules/transaction/transaction/constants';
import { mappingTransaction } from 'src/modules/transaction/transaction/domain/usecases/managers/helpers/mapping-transaction.helper';
import { TransactionDeletedEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-deleted.event';
import { TransactionUpdatedEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-updated.event';
@EventsHandler(TransactionDeletedEvent)
export class BookingDeletedEvent
implements IEventHandler<TransactionDeletedEvent>
{
constructor(private couchService: CouchService) {}
async handle(event: TransactionDeletedEvent) {
await this.couchService.deleteDoc(
{
_id: event.data.id,
...event.data.data,
},
'item',
);
}
}
@EventsHandler(TransactionChangeStatusEvent, TransactionUpdatedEvent)
export class BookingHandler
implements IEventHandler<TransactionChangeStatusEvent>
{
constructor(
private bookingService: TransactionDataService,
private couchService: CouchService,
) {}
async handle(event: TransactionChangeStatusEvent) {
const old_data = event.data.old;
const data = event.data.data;
if (data.payment_type != TransactionPaymentType.COUNTER) return;
const booking = await this.bookingService.getOneByOptions({
where: {
id: data.id,
},
relations: ['items'],
});
mappingTransaction(booking);
if (
old_data?.status != data.status &&
[STATUS.PENDING, STATUS.ACTIVE].includes(data.status)
) {
await this.couchService.createDoc(
{
_id: booking.id,
...booking,
},
'booking',
);
} else {
await this.couchService.updateDoc(
{
_id: booking.id,
...booking,
},
'booking',
);
}
}
}

View File

@ -0,0 +1,11 @@
import { ConfigModule } from '@nestjs/config';
import { MidnightCronManager } from './domain/managers/midnight-cron.manager';
import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
@Module({
imports: [ConfigModule.forRoot(), CqrsModule],
controllers: [MidnightCronManager],
providers: [],
})
export class CronModule {}

View File

@ -0,0 +1,3 @@
export class CronMidnightEvent {
constructor(public readonly data: {}) {}
}

View File

@ -0,0 +1,21 @@
import { Controller } from '@nestjs/common';
import { EventBus } from '@nestjs/cqrs';
import { Cron } from '@nestjs/schedule';
import { CronMidnightEvent } from '../entities/cron-midnight.event';
@Controller()
export class MidnightCronManager {
constructor(public eventBus: EventBus) {}
@Cron(`${process.env.CRON_MIDNIGHT}`)
async scheduler(): Promise<void> {
const local_time = new Date().toLocaleDateString('en-US', {
timeZone: 'Asia/Jakarta',
});
const now = new Date(local_time).getTime();
console.log('Cron Event executed every 00:00 minutes.', now);
this.eventBus.publishAll([new CronMidnightEvent({})]);
}
}

View File

@ -3,79 +3,84 @@ import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-
import { SeasonPeriodEntity } from '../../entities/season-period.entity';
import { SelectQueryBuilder } from 'typeorm';
import {
Param,
RelationParam,
Param,
RelationParam,
} from 'src/core/modules/domain/entities/base-filter.entity';
import { STATUS } from 'src/core/strings/constants/base.constants';
@Injectable()
export class CurrentSeasonPeriodManager extends BaseIndexManager<SeasonPeriodEntity> {
async prepareData(): Promise<void> {
return;
}
async prepareData(): Promise<void> {
return;
}
async beforeProcess(): Promise<void> {
return;
}
async beforeProcess(): Promise<void> {
return;
}
async afterProcess(): Promise<void> {
Object.assign(this.result, {
data: this.result.data.sort((a, b) => a.priority - b.priority)
})
return;
}
async afterProcess(): Promise<void> {
Object.assign(this.result, {
data: this.result.data.sort((a, b) => a.priority - b.priority),
});
return;
}
get relations(): RelationParam {
return {
// relation only join (for query purpose)
joinRelations: ['season_type'],
get relations(): RelationParam {
return {
// relation only join (for query purpose)
joinRelations: ['season_type'],
// relation join and select (relasi yang ingin ditampilkan),
selectRelations: [],
// relation join and select (relasi yang ingin ditampilkan),
selectRelations: [],
// relation yang hanya ingin dihitung (akan return number)
countRelations: [],
};
}
// relation yang hanya ingin dihitung (akan return number)
countRelations: [],
};
}
get selects(): string[] {
return [
`${ this.tableName }.id`,
`${ this.tableName }.priority`,
`${ this.tableName }.created_at`,
`${ this.tableName }.creator_name`,
`${ this.tableName }.editor_name`,
`${ this.tableName }.updated_at`,
`${ this.tableName }.status`,
`${ this.tableName }.start_date`,
`${ this.tableName }.end_date`,
`${ this.tableName }.days`,
`${ this.tableName }.holiday_name`,
get selects(): string[] {
return [
`${this.tableName}.id`,
`${this.tableName}.priority`,
`${this.tableName}.created_at`,
`${this.tableName}.creator_name`,
`${this.tableName}.editor_name`,
`${this.tableName}.updated_at`,
`${this.tableName}.status`,
`${this.tableName}.start_date`,
`${this.tableName}.end_date`,
`${this.tableName}.days`,
`${this.tableName}.holiday_name`,
'season_type.id',
'season_type.name',
];
}
'season_type.id',
'season_type.name',
];
}
get specificFilter(): Param[] {
return [
{
cols: `${ this.tableName }.holiday_name`,
data: this.filterParam.holiday_names,
},
];
}
get specificFilter(): Param[] {
return [
{
cols: `${this.tableName}.holiday_name`,
data: this.filterParam.holiday_names,
},
];
}
setQueryFilter(
queryBuilder: SelectQueryBuilder<SeasonPeriodEntity>,
): SelectQueryBuilder<SeasonPeriodEntity> {
queryBuilder.andWhere(
`${ this.tableName }.start_date BETWEEN :from AND :to`,
{
from: new Date().toLocaleDateString(),
to: new Date().toLocaleDateString(),
},
);
setQueryFilter(
queryBuilder: SelectQueryBuilder<SeasonPeriodEntity>,
): SelectQueryBuilder<SeasonPeriodEntity> {
queryBuilder.andWhere(
`${this.tableName}.start_date BETWEEN :from AND :to`,
{
from: new Date().toLocaleDateString(),
to: new Date().toLocaleDateString(),
},
);
return queryBuilder;
}
queryBuilder.andWhere(`${this.tableName}.status In (:...statuses)`, {
statuses: [STATUS.ACTIVE],
});
return queryBuilder;
}
}

View File

@ -42,10 +42,13 @@ export class SeasonPeriodReadOrchestrator extends BaseReadOrchestrator<SeasonPer
async currentPeriod(): Promise<SeasonPeriodEntity> {
const params = new FilterSeasonPeriodDto();
this.currentPeriodManager.setFilterParam(params);
this.currentPeriodManager.setService(this.serviceData, TABLE_NAME.SEASON_PERIOD);
this.currentPeriodManager.setService(
this.serviceData,
TABLE_NAME.SEASON_PERIOD,
);
await this.currentPeriodManager.execute();
const data = this.currentPeriodManager.getResult();
return data.data[0]
return data.data[0];
}
async indexItem(params): Promise<PaginationResponse<ItemRateEntity>> {

View File

@ -0,0 +1,24 @@
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { CronMidnightEvent } from 'src/modules/configuration/cron/domain/entities/cron-midnight.event';
import { RecapReconciliationManager } from '../managers/recap-reconciliation.manager';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service';
@EventsHandler(CronMidnightEvent)
export class RecapPosTransactionHandler
implements IEventHandler<CronMidnightEvent>
{
constructor(
private recapManager: RecapReconciliationManager,
private dataService: TransactionDataService,
) {}
async handle(event: CronMidnightEvent) {
const data = new TransactionModel();
this.recapManager.setData(data);
this.recapManager.setService(this.dataService, TABLE_NAME.TRANSACTION);
await this.recapManager.execute();
return this.recapManager.getResult();
}
}

View File

@ -0,0 +1,138 @@
import { Injectable } from '@nestjs/common';
import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base-custom.manager';
import { EventTopics } from 'src/core/strings/constants/interface.constants';
import { TransactionType } from 'src/modules/transaction/transaction/constants';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
import { Between } from 'typeorm';
import * as _ from 'lodash';
import * as moment from 'moment';
import { STATUS } from 'src/core/strings/constants/base.constants';
@Injectable()
export class RecapReconciliationManager extends BaseCustomManager<TransactionEntity> {
private recapTransactions = {};
private startOfDay = moment().startOf('day').unix();
private endOfDay = moment().endOf('day').unix();
get entityTarget(): any {
return TransactionModel;
}
getResult() {
return;
}
get eventTopics(): EventTopics[] {
return [];
}
async validateProcess(): Promise<void> {
return;
}
async beforeProcess(): Promise<void> {
const transactions = await this.dataService.getManyByOptions({
where: {
is_recap_transaction: false,
type: TransactionType.COUNTER,
created_at: Between(this.startOfDay, this.endOfDay),
},
});
for (const transaction of transactions) {
const {
creator_counter_no,
payment_type_method_name,
payment_type_method_number,
} = transaction;
const group_by =
creator_counter_no +
'-' +
payment_type_method_name +
'-' +
payment_type_method_number;
if (!this.recapTransactions[group_by]) {
this.recapTransactions[group_by] = [];
}
this.recapTransactions[group_by].push(transaction);
}
return;
}
async process(): Promise<void> {
const total_recap = Object.keys(this.recapTransactions);
for (const recap of total_recap) {
const first_transaction = this.recapTransactions[recap][0];
const {
creator_counter_no,
payment_type_method_number,
payment_type_method_name,
} = first_transaction;
const exist = await this.dataService.getOneByOptions({
where: {
is_recap_transaction: true,
created_at: Between(this.startOfDay, this.endOfDay),
creator_counter_no: creator_counter_no,
payment_type_method_number: payment_type_method_number,
payment_type_method_name: payment_type_method_name,
},
});
const new_recap = new TransactionModel();
if (exist) {
Object.assign(exist, {
payment_total: _.sumBy(this.recapTransactions[recap], (recap) =>
parseFloat(recap.payment_total),
),
payment_total_net_profit: _.sumBy(
this.recapTransactions[recap],
(recap) => parseFloat(recap.payment_total),
),
editor_id: this.user.id,
editor_name: this.user.name,
updated_at: new Date().getTime(),
});
} else {
Object.assign(new_recap, {
is_recap_transaction: true,
payment_total: _.sumBy(this.recapTransactions[recap], (recap) =>
parseFloat(recap.payment_total),
),
payment_total_net_profit: _.sumBy(
this.recapTransactions[recap],
(recap) => parseFloat(recap.payment_total),
),
reconciliation_status: STATUS.PENDING,
status: STATUS.SETTLED,
type: TransactionType.COUNTER,
booking_date: first_transaction.booking_date,
creator_counter_no: first_transaction.creator_counter_no,
payment_type: first_transaction.payment_type,
payment_type_method_id: first_transaction.payment_type_method_id,
payment_type_method_number:
first_transaction.payment_type_method_number,
payment_type_method_name: first_transaction.payment_type_method_name,
payment_type_method_qr: first_transaction.payment_type_method_qr,
creator_id: this.user.id,
creator_name: this.user.name,
created_at: new Date().getTime(),
updated_at: new Date().getTime(),
});
}
await this.dataService.create(
this.queryRunner,
TransactionModel,
exist ?? new_recap,
);
}
return;
}
async afterProcess(): Promise<void> {
return;
}
}

View File

@ -23,7 +23,7 @@ export class UpdateReconciliationManager extends BaseUpdateManager<TransactionEn
Object.assign(this.data, {
reconciliation_mdr: this.data.reconciliation_mdr ?? null,
payment_total_net_profit: net_profit,
payment_date: this.data.payment_date ?? this.oldData.payment_total,
payment_date: this.data.payment_date ?? this.oldData.payment_date,
});
return;

View File

@ -9,6 +9,8 @@ import { CancelReconciliationManager } from './managers/cancel-reconciliation.ma
import { BatchCancelReconciliationManager } from './managers/batch-cancel-reconciliation.manager';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service';
import { RecapReconciliationManager } from './managers/recap-reconciliation.manager';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
@Injectable()
export class ReconciliationDataOrchestrator {
@ -18,6 +20,7 @@ export class ReconciliationDataOrchestrator {
private cancelManager: CancelReconciliationManager,
private batchConfirmManager: BatchConfirmReconciliationManager,
private batchCancelManager: BatchCancelReconciliationManager,
private recapManager: RecapReconciliationManager,
private serviceData: TransactionDataService,
) {}
@ -28,6 +31,14 @@ export class ReconciliationDataOrchestrator {
return this.updateManager.getResult();
}
async recap() {
const data = new TransactionModel();
this.recapManager.setData(data);
this.recapManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);
await this.recapManager.execute();
return this.recapManager.getResult();
}
async confirm(dataId): Promise<String> {
this.confirmManager.setData(dataId, STATUS.CONFIRMED);
this.confirmManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);

View File

@ -23,6 +23,11 @@ import { UpdateReconciliationDto } from './dto/reconciliation.dto';
export class ReconciliationDataController {
constructor(private orchestrator: ReconciliationDataOrchestrator) {}
@Post('/recap-transaction')
async recap(): Promise<any> {
return await this.orchestrator.recap();
}
@Put('/batch-confirm')
async batchConfirm(@Body() body: BatchIdsDto): Promise<BatchResult> {
return await this.orchestrator.batchConfirm(body.ids);

View File

@ -17,6 +17,8 @@ import { TransactionReadService } from '../transaction/data/services/transaction
import { CancelReconciliationManager } from './domain/usecases/managers/cancel-reconciliation.manager';
import { BatchCancelReconciliationManager } from './domain/usecases/managers/batch-cancel-reconciliation.manager';
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';
@Module({
imports: [
@ -26,6 +28,8 @@ import { BatchConfirmReconciliationManager } from './domain/usecases/managers/ba
],
controllers: [ReconciliationDataController, ReconciliationReadController],
providers: [
RecapPosTransactionHandler,
IndexReconciliationManager,
DetailReconciliationManager,
UpdateReconciliationManager,
@ -33,6 +37,7 @@ import { BatchConfirmReconciliationManager } from './domain/usecases/managers/ba
BatchConfirmReconciliationManager,
CancelReconciliationManager,
BatchCancelReconciliationManager,
RecapReconciliationManager,
TransactionDataService,
TransactionReadService,

View File

@ -15,9 +15,12 @@ export class TransactionTaxModel
@Column('varchar', { name: 'tax_name', nullable: true })
tax_name: string;
@Column('varchar', { name: 'taxt_value', nullable: true })
@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(() => TransactionModel, (model) => model.taxes, {

View File

@ -4,4 +4,5 @@ export interface TransactionTaxEntity extends BaseCoreEntity {
tax_id: string;
tax_name: string;
taxt_value: number;
tax_total_value: number;
}

View File

@ -0,0 +1,83 @@
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { ChangeDocEvent } from 'src/modules/configuration/couch/domain/events/change-doc.event';
import { TransactionType } from '../../../constants';
import { TransactionDataService } from '../../../data/services/transaction-data.service';
import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-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 { STATUS } from 'src/core/strings/constants/base.constants';
import { TransactionModel } from '../../../data/models/transaction.model';
import { mappingRevertTransaction } from '../managers/helpers/mapping-transaction.helper';
@EventsHandler(ChangeDocEvent)
export class PosTransactionHandler implements IEventHandler<ChangeDocEvent> {
constructor(
private dataService: TransactionDataService,
private taxService: TaxDataService,
private formulaService: SalesPriceFormulaDataService,
) {}
async handle(event: ChangeDocEvent) {
try {
const database = event.data.database;
const data = event.data.data;
// jika bukan database transaksi, return langsung
if (database != 'transaction') return;
const sales_formula = await this.formulaService.getOneByOptions({
where: {
type: FormulaType.SALES_PRICE,
},
});
const profit_formula = await this.formulaService.getOneByOptions({
where: {
type: FormulaType.PROFIT_SHARE,
},
});
const taxes = await this.taxService.getManyByOptions({
where: {
status: STATUS.ACTIVE,
},
});
const queryRunner = this.dataService
.getRepository()
.manager.connection.createQueryRunner();
// jika delete
if (data._deleted ?? false) {
await this.dataService.deleteById(
queryRunner,
TransactionModel,
data._id,
);
}
// jika update // create
else {
const tax_datas = taxes?.map((tax) => {
return {
tax_id: tax.id,
tax_name: tax.name,
tax_value: tax.value,
};
});
mappingRevertTransaction(data, TransactionType.COUNTER);
Object.assign(data, {
taxes: tax_datas,
profit_share_formula: profit_formula.formula_string,
sales_price_formula: sales_formula.formula_string,
});
await this.dataService.create(queryRunner, TransactionModel, data);
}
} catch (error) {
console.log('error handling pos transaction couch');
}
}
}

View File

@ -9,33 +9,12 @@ import { TransactionModel } from '../../../data/models/transaction.model';
import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager';
import { TransactionCreatedEvent } from '../../entities/event/transaction-created.event';
import { TransactionType } from '../../../constants';
import { mappingRevertTransaction } from './helpers/mapping-transaction.helper';
@Injectable()
export class CreateTransactionManager extends BaseCreateManager<TransactionEntity> {
async beforeProcess(): Promise<void> {
Object.assign(this.data, {
type: TransactionType.ADMIN,
customer_category_id: this.data.customer_category?.id ?? null,
customer_category_name: this.data.customer_category?.name ?? null,
season_period_id: this.data.season_period?.id ?? null,
season_period_name: this.data.season_period?.holiday_name ?? null,
season_period_type_id: this.data.season_period?.season_type?.id ?? null,
season_period_type_name:
this.data.season_period?.season_type?.name ?? null,
});
this.data.items?.map((item) => {
Object.assign(item, {
item_id: item.item.id,
item_name: item.item.name,
item_type: item.item.item_type,
item_price: item.item.base_price,
item_tenant_id: item.item.tenant?.id ?? null,
item_tenant_name: item.item.tenant?.id ?? null,
item_tenant_percentage: item.item.tenant?.share_margin ?? null,
});
});
mappingRevertTransaction(this.data, TransactionType.ADMIN);
return;
}

View File

@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { BaseDetailManager } from 'src/core/modules/domain/usecase/managers/base-detail.manager';
import { TransactionEntity } from '../../entities/transaction.entity';
import { RelationParam } from 'src/core/modules/domain/entities/base-filter.entity';
import { mappingTransaction } from './helpers/mapping-transaction.helper';
@Injectable()
export class DetailTransactionManager extends BaseDetailManager<TransactionEntity> {
@ -14,69 +15,7 @@ export class DetailTransactionManager extends BaseDetailManager<TransactionEntit
}
async afterProcess(): Promise<void> {
let payment_type_bank;
const season_period = {
id: this.result.season_period_id,
holiday_name: this.result.season_period_name,
season_type: {
id: this.result.season_period_type_id,
name: this.result.season_period_type_name,
},
};
if (this.result.payment_type_method_id) {
payment_type_bank = {
id: this.result.payment_type_method_id,
issuer_name: this.result.payment_type_method_name,
account_number: this.result.payment_type_method_number,
};
}
const items = this.result?.['items']?.map((itemData) => {
let tenant;
if (itemData.item_tenant_id) {
tenant = {
id: itemData.item_tenant_id,
name: itemData.item_tenant_name,
share_margin: itemData.item_tenant_share_margin,
};
}
return {
item: {
id: itemData.item_id,
name: itemData.item_name,
item_type: itemData.item_type,
base_price: itemData.item_price,
hpp: itemData.item_hpp,
tenant: tenant,
item_category: {
id: itemData.item_category_id,
name: itemData.item_category_name,
},
},
qty: itemData.qty,
total_price: itemData.total_price,
};
});
Object.assign(this.result, {
season_period: season_period,
items: items,
payment_type_bank: payment_type_bank,
});
delete this.result.season_period_id;
delete this.result.season_period_name;
delete this.result.season_period_type_id;
delete this.result.season_period_type_name;
delete this.result.payment_type_method_id;
delete this.result.payment_type_method_name;
delete this.result.payment_type_method_number;
mappingTransaction(this.result);
return;
}

View File

@ -0,0 +1,133 @@
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;
const season_period = {
id: data.season_period_id,
holiday_name: data.season_period_name,
season_type: {
id: data.season_period_type_id,
name: data.season_period_type_name,
},
};
if (data.payment_type_method_id) {
payment_type_bank = {
id: data.payment_type_method_id,
issuer_name: data.payment_type_method_name,
account_number: data.payment_type_method_number,
};
}
const items = data?.['items']?.map((itemData) => {
let tenant;
if (itemData.item_tenant_id) {
tenant = {
id: itemData.item_tenant_id,
name: itemData.item_tenant_name,
share_margin: itemData.item_tenant_share_margin,
};
}
return {
item: {
id: itemData.item_id,
name: itemData.item_name,
item_type: itemData.item_type,
base_price: itemData.item_price,
hpp: itemData.item_hpp,
tenant: tenant,
item_category: {
id: itemData.item_category_id,
name: itemData.item_category_name,
},
},
qty: itemData.qty,
total_price: itemData.total_price,
};
});
Object.assign(data, {
season_period: season_period,
items: items,
payment_type_bank: payment_type_bank,
});
delete data.season_period_id;
delete data.season_period_name;
delete data.season_period_type_id;
delete data.season_period_type_name;
delete data.payment_type_method_id;
delete data.payment_type_method_name;
delete data.payment_type_method_number;
}
export function mappingRevertTransaction(data, type) {
if (type == TransactionType.COUNTER) {
Object.assign(data, {
id: data._id,
creator_counter_no: data.pos_number,
creator_id: data.pos_admin?.id,
creator_name: data.pos_admin?.name,
status: STATUS.SETTLED,
booking_date: data.created_at,
settlement_date: data.created_at,
payment_type: data.payment_type,
payment_type_method_id: data.payment_type_bank?._id,
payment_type_method_name: data.payment_type_bank?.issuer_name,
payment_type_method_number: data.payment_type_bank?.account_number,
payment_card_information: data.card_information,
payment_code_reference: data.payment_code,
discount_code_id: data.discount_code?.id,
discount_code: data.discount_code?.code,
discount_percentage: data.discount_code?.discount,
});
} else {
// Object.assign(data, {
// payment_type:
// })
}
Object.assign(data, {
type: type,
customer_category_id: data.customer_category?.id ?? null,
customer_category_name: data.customer_category?.name ?? null,
season_period_id: data.season_period?.id ?? null,
season_period_name: data.season_period?.holiday_name ?? null,
season_period_type_id: data.season_period?.season_type?.id ?? null,
season_period_type_name: data.season_period?.season_type?.name ?? null,
});
data.items?.map((item) => {
const total_price = Number(item.item.base_price) * Number(item.qty);
const share_margin = item.item.tenant?.share_margin ?? 0;
const total_share_tenant =
share_margin > 0 ? (Number(share_margin) / 100) * total_price : 0;
Object.assign(item, {
item_id: item.item.id,
item_name: item.item.name,
item_type: item.item.item_type,
item_price: item.item.base_price,
item_hpp: item.item.hpp,
item_category_id: item.item.item_category?.id,
item_category_name: item.item.item_category?.name,
item_bundlings: item.item.bundling_items?.map(
(bundling) => bundling.name,
),
item_tenant_id: item.item.tenant?.id ?? null,
item_tenant_name: item.item.tenant?.id ?? null,
item_tenant_share_margin: item.item.tenant?.share_margin ?? null,
total_price: total_price,
total_hpp: Number(item.item.item_hpp) * Number(item.qty),
total_share_tenant: total_share_tenant,
total_profit: total_price - Number(total_share_tenant),
});
});
}

View File

@ -9,6 +9,7 @@ import {
validateRelations,
} from 'src/core/strings/constants/interface.constants';
import { TransactionType } from '../../../constants';
import { mappingRevertTransaction } from './helpers/mapping-transaction.helper';
@Injectable()
export class UpdateTransactionManager extends BaseUpdateManager<TransactionEntity> {
@ -17,29 +18,7 @@ export class UpdateTransactionManager extends BaseUpdateManager<TransactionEntit
}
async beforeProcess(): Promise<void> {
Object.assign(this.data, {
type: TransactionType.ADMIN,
customer_category_id: this.data.customer_category?.id ?? null,
customer_category_name: this.data.customer_category?.name ?? null,
season_period_id: this.data.season_period?.id ?? null,
season_period_name: this.data.season_period?.holiday_name ?? null,
season_period_type_id: this.data.season_period?.season_type?.id ?? null,
season_period_type_name:
this.data.season_period?.season_type?.name ?? null,
});
this.data.items?.map((item) => {
Object.assign(item, {
item_id: item.item.id,
item_name: item.item.name,
item_type: item.item.item_type,
item_price: item.item.base_price,
item_tenant_id: item.item.tenant?.id ?? null,
item_tenant_name: item.item.tenant?.id ?? null,
item_tenant_percentage: item.item.tenant?.share_margin ?? null,
});
});
mappingRevertTransaction(this.data, TransactionType.ADMIN);
return;
}

View File

@ -24,18 +24,31 @@ import { CancelTransactionManager } from './domain/usecases/managers/cancel-tran
import { BatchCancelTransactionManager } from './domain/usecases/managers/batch-cancel-transaction.manager';
import { ConfirmDataTransactionManager } from './domain/usecases/managers/confirm-data-transaction.manager';
import { BatchConfirmDataTransactionManager } from './domain/usecases/managers/batch-confirm-data-transaction.manager';
import { PosTransactionHandler } from './domain/usecases/handlers/pos-transaction.handler';
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';
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forFeature(
[TransactionModel, TransactionItemModel, TransactionTaxModel],
[
TransactionModel,
TransactionItemModel,
TransactionTaxModel,
TaxModel,
SalesPriceFormulaModel,
],
CONNECTION_NAME.DEFAULT,
),
CqrsModule,
],
controllers: [TransactionDataController, TransactionReadController],
providers: [
PosTransactionHandler,
IndexTransactionManager,
DetailTransactionManager,
CreateTransactionManager,
@ -51,6 +64,8 @@ import { BatchConfirmDataTransactionManager } from './domain/usecases/managers/b
TransactionDataService,
TransactionReadService,
TaxDataService,
SalesPriceFormulaDataService,
TransactionDataOrchestrator,
TransactionReadOrchestrator,

View File

@ -832,6 +832,14 @@
multer "1.4.4-lts.1"
tslib "2.6.2"
"@nestjs/schedule@^4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@nestjs/schedule/-/schedule-4.1.0.tgz#b0ae64519365821f4186416915e502d225836048"
integrity sha512-WEc96WTXZW+VI/Ng+uBpiBUwm6TWtAbQ4RKWkfbmzKvmbRGzA/9k/UyAWDS9k0pp+ZcbC+MaZQtt7TjQHrwX6g==
dependencies:
cron "3.1.7"
uuid "10.0.0"
"@nestjs/schematics@^10.0.0", "@nestjs/schematics@^10.0.1":
version "10.1.1"
resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-10.1.1.tgz#a67fb178a7ad6025ccc3314910b077ac454fcdf3"
@ -1148,6 +1156,11 @@
"@types/fined" "*"
"@types/node" "*"
"@types/luxon@~3.4.0":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.4.2.tgz#e4fc7214a420173cea47739c33cdf10874694db7"
integrity sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==
"@types/methods@^1.1.4":
version "1.1.4"
resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547"
@ -2514,6 +2527,14 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
cron@3.1.7:
version "3.1.7"
resolved "https://registry.yarnpkg.com/cron/-/cron-3.1.7.tgz#3423d618ba625e78458fff8cb67001672d49ba0d"
integrity sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==
dependencies:
"@types/luxon" "~3.4.0"
luxon "~3.4.0"
cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@ -4949,6 +4970,11 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
luxon@~3.4.0:
version "3.4.4"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af"
integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==
magic-string@0.30.5:
version "0.30.5"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9"
@ -6476,7 +6502,16 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -6517,7 +6552,14 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -7034,6 +7076,11 @@ utils-merge@1.0.1:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
uuid@10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294"
integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==
uuid@9.0.1, uuid@^9.0.0, uuid@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
@ -7198,7 +7245,7 @@ wordwrap@^1.0.0:
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -7216,6 +7263,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"