Merge pull request 'feat/transaction' (#6) from feat/transaction into development

Reviewed-on: #6
pull/7/head^2
aswin 2024-07-02 17:09:18 +00:00
commit 0a7678471d
58 changed files with 2912 additions and 8 deletions

View File

@ -42,6 +42,11 @@ import { SeasonPeriodModel } from './modules/season-related/season-period/data/m
import { ItemRateModule } from './modules/item-related/item-rate/item-rate.module'; import { ItemRateModule } from './modules/item-related/item-rate/item-rate.module';
import { ItemRateModel } from './modules/item-related/item-rate/data/models/item-rate.model'; import { ItemRateModel } from './modules/item-related/item-rate/data/models/item-rate.model';
import { GoogleCalendarModule } from './modules/configuration/google-calendar/google-calendar.module'; import { GoogleCalendarModule } from './modules/configuration/google-calendar/google-calendar.module';
import { TransactionModule } from './modules/transaction/transaction/transaction.module';
import { TransactionModel } from './modules/transaction/transaction/data/models/transaction.model';
import { TransactionItemModel } from './modules/transaction/transaction/data/models/transaction-item.model';
import { TransactionTaxModel } from './modules/transaction/transaction/data/models/transaction-tax.model';
import { ReconciliationModule } from './modules/transaction/reconciliation/reconciliation.module';
@Module({ @Module({
imports: [ imports: [
@ -69,6 +74,9 @@ import { GoogleCalendarModule } from './modules/configuration/google-calendar/go
SeasonPeriodModel, SeasonPeriodModel,
SeasonTypeModel, SeasonTypeModel,
TaxModel, TaxModel,
TransactionModel,
TransactionItemModel,
TransactionTaxModel,
UserModel, UserModel,
VipCategoryModel, VipCategoryModel,
VipCodeModel, VipCodeModel,
@ -96,8 +104,10 @@ import { GoogleCalendarModule } from './modules/configuration/google-calendar/go
// transaction // transaction
PaymentMethodModule, PaymentMethodModule,
ProfitShareFormulaModule, ProfitShareFormulaModule,
ReconciliationModule,
SalesPriceFormulaModule, SalesPriceFormulaModule,
TaxModule, TaxModule,
TransactionModule,
VipCategoryModule, VipCategoryModule,
VipCodeModule, VipCodeModule,
@ -135,4 +145,4 @@ import { GoogleCalendarModule } from './modules/configuration/google-calendar/go
}, },
], ],
}) })
export class AppModule {} export class AppModule { }

View File

@ -4,11 +4,13 @@ import { HttpStatus, NotFoundException } from '@nestjs/common';
import { OPERATION, STATUS } from 'src/core/strings/constants/base.constants'; import { OPERATION, STATUS } from 'src/core/strings/constants/base.constants';
import { ValidateRelationHelper } from 'src/core/helpers/validation/validate-relation.helper'; import { ValidateRelationHelper } from 'src/core/helpers/validation/validate-relation.helper';
import { RecordLog } from 'src/modules/configuration/log/domain/entities/log.event'; import { RecordLog } from 'src/modules/configuration/log/domain/entities/log.event';
import * as _ from 'lodash';
export abstract class BaseBatchUpdateStatusManager<Entity> extends BaseManager { export abstract class BaseBatchUpdateStatusManager<Entity> extends BaseManager {
protected dataIds: string[]; protected dataIds: string[];
protected result: BatchResult; protected result: BatchResult;
protected dataStatus: STATUS; protected dataStatus: STATUS;
protected oldData: Entity;
abstract get entityTarget(): any; abstract get entityTarget(): any;
setData(ids: string[], status: STATUS): void { setData(ids: string[], status: STATUS): void {
@ -44,6 +46,13 @@ export abstract class BaseBatchUpdateStatusManager<Entity> extends BaseManager {
error: 'Entity Not Found', error: 'Entity Not Found',
}); });
} }
this.oldData = _.cloneDeep(entity);
Object.assign(entity, {
status: this.dataStatus,
editor_id: this.user.id,
editor_name: this.user.name,
updated_at: new Date().getTime(),
});
await this.validateData(entity); await this.validateData(entity);
await new ValidateRelationHelper( await new ValidateRelationHelper(
@ -57,15 +66,10 @@ export abstract class BaseBatchUpdateStatusManager<Entity> extends BaseManager {
this.queryRunner, this.queryRunner,
this.entityTarget, this.entityTarget,
{ id: id }, { id: id },
{ entity,
status: this.dataStatus,
editor_id: this.user.id,
editor_name: this.user.name,
updated_at: new Date().getTime(),
},
); );
this.publishEvents(entity, result); this.publishEvents(this.oldData, result);
totalSuccess = totalSuccess + 1; totalSuccess = totalSuccess + 1;
} catch (error) { } catch (error) {

View File

@ -3,10 +3,12 @@ export enum MODULE_NAME {
ITEM_CATEGORY = 'item-categories', ITEM_CATEGORY = 'item-categories',
ITEM_RATE = 'item-rates', ITEM_RATE = 'item-rates',
PAYMENT_METHOD = 'payment-methods', PAYMENT_METHOD = 'payment-methods',
RECONCILIATION = 'reconciliations',
SEASON_TYPE = 'season-types', SEASON_TYPE = 'season-types',
SEASON_PERIOD = 'season-periods', SEASON_PERIOD = 'season-periods',
TAX = 'taxes', TAX = 'taxes',
TENANT = 'tenants', TENANT = 'tenants',
TRANSACTION = 'transactions',
USER = 'users', USER = 'users',
USER_PRIVILEGE = 'user-privileges', USER_PRIVILEGE = 'user-privileges',
USER_PRIVILEGE_CONFIGURATION = 'user-privilege-configurations', USER_PRIVILEGE_CONFIGURATION = 'user-privilege-configurations',

View File

@ -10,6 +10,9 @@ export enum TABLE_NAME {
SEASON_PERIOD = 'season_periods', SEASON_PERIOD = 'season_periods',
TAX = 'taxes', TAX = 'taxes',
TENANT = 'tenants', TENANT = 'tenants',
TRANSACTION = 'transactions',
TRANSACTION_ITEM = 'transaction_items',
TRANSACTION_TAX = 'transaction_taxes',
USER = 'users', USER = 'users',
USER_PRIVILEGE = 'user_privileges', USER_PRIVILEGE = 'user_privileges',
USER_PRIVILEGE_CONFIGURATION = 'user_privilege_configurations', USER_PRIVILEGE_CONFIGURATION = 'user_privilege_configurations',

View File

@ -0,0 +1,55 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Transaction1719572714752 implements MigrationInterface {
name = 'Transaction1719572714752';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "transaction_items" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "item_id" character varying, "item_name" character varying, "item_type" character varying, "item_price" bigint, "item_tenant_id" character varying, "item_tenant_name" character varying, "item_tenant_share_margin" numeric, "total_price" numeric, "total_hpp" numeric, "total_profit" numeric, "total_share_tenant" numeric, "qty" integer, "transaction_id" uuid, CONSTRAINT "PK_ff5a487ad820dccafd53bebf578" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "transaction_taxes" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "tax_id" character varying, "tax_name" character varying, "taxt_value" character varying, "transaction_id" uuid, CONSTRAINT "PK_208b2abdb10640e1991972fc754" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_type_enum" AS ENUM('counter', 'admin', 'online')`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_customer_type_enum" AS ENUM('group', 'vip')`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_payment_type_enum" AS ENUM('midtrans', 'bank transfer', 'qris', 'counter', 'cash', 'credit card', 'debit', 'e-money')`,
);
await queryRunner.query(
`CREATE TABLE "transactions" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "creator_id" character varying(36), "creator_name" character varying(125), "editor_id" character varying(36), "editor_name" character varying(125), "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL, "status" "public"."transactions_status_enum" NOT NULL DEFAULT 'draft', "type" "public"."transactions_type_enum" NOT NULL DEFAULT 'admin', "invoice_code" character varying, "creator_counter_no" integer, "season_period_id" character varying, "season_period_name" character varying, "season_period_type_id" character varying, "season_period_type_name" character varying, "customer_type" "public"."transactions_customer_type_enum", "customer_category_id" character varying, "customer_category_name" character varying, "customer_name" character varying, "customer_phone" character varying, "customer_email" character varying, "customer_description" character varying, "no_of_group" character varying, "booking_date" date, "settlement_date" date, "invoice_date" date, "discount_code_id" character varying, "discount_code" character varying, "discount_percentage" integer, "discount_value" numeric, "payment_type" "public"."transactions_payment_type_enum" NOT NULL DEFAULT 'bank transfer', "payment_type_method_id" character varying, "payment_type_method_name" character varying, "payment_type_method_qr" character varying, "payment_card_information" character varying, "payment_code_reference" character varying, "payment_date" date, "payment_sub_total" numeric, "payment_discount_total" numeric, "payment_total" numeric, "payment_total_pay" numeric, "payment_change" numeric, "payment_total_share" numeric, "payment_total_tax" numeric, "payment_total_profit" numeric, "profit_share_formula" character varying, "sales_price_formula" character varying, CONSTRAINT "PK_a219afd8dd77ed80f5a862f1db9" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" ADD CONSTRAINT "FK_5926425896b30c0d681fe879af0" FOREIGN KEY ("transaction_id") REFERENCES "transactions"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE "transaction_taxes" ADD CONSTRAINT "FK_d21db1756c6656efc7c082fbaa6" FOREIGN KEY ("transaction_id") REFERENCES "transactions"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transaction_taxes" DROP CONSTRAINT "FK_d21db1756c6656efc7c082fbaa6"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" DROP CONSTRAINT "FK_5926425896b30c0d681fe879af0"`,
);
await queryRunner.query(`DROP TABLE "transactions"`);
await queryRunner.query(
`DROP TYPE "public"."transactions_payment_type_enum"`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_customer_type_enum"`,
);
await queryRunner.query(`DROP TYPE "public"."transactions_type_enum"`);
await queryRunner.query(`DROP TYPE "public"."transactions_status_enum"`);
await queryRunner.query(`DROP TABLE "transaction_taxes"`);
await queryRunner.query(`DROP TABLE "transaction_items"`);
}
}

View File

@ -0,0 +1,61 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddReconciliationToTransaction1719925690145
implements MigrationInterface
{
name = 'AddReconciliationToTransaction1719925690145';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" ADD "is_recap_transaction" boolean NOT NULL DEFAULT true`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "payment_type_method_number" character varying`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "reconciliation_mdr" numeric`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_reconciliation_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "reconciliation_status" "public"."transactions_reconciliation_status_enum" NOT NULL DEFAULT 'draft'`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "reconciliation_confirm_date" character varying`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "reconciliation_confirm_by" character varying`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "payment_total_net_profit" numeric`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "payment_total_net_profit"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "reconciliation_confirm_by"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "reconciliation_confirm_date"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "reconciliation_status"`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_reconciliation_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "reconciliation_mdr"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "payment_type_method_number"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "is_recap_transaction"`,
);
}
}

View File

@ -0,0 +1,71 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UpdateTableTransaction1719934464407 implements MigrationInterface {
name = 'UpdateTableTransaction1719934464407';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transaction_items" ADD "item_hpp" bigint`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" ADD "item_category_id" character varying`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" ADD "item_category_name" character varying`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" ADD "item_bundlings" json`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_sending_invoice_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "sending_invoice_status" "public"."transactions_sending_invoice_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "sending_invoice_at" bigint`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_sending_qr_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "sending_qr_status" "public"."transactions_sending_qr_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "sending_qr_at" bigint`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "sending_qr_at"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "sending_qr_status"`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_sending_qr_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "sending_invoice_at"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "sending_invoice_status"`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_sending_invoice_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" DROP COLUMN "item_bundlings"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" DROP COLUMN "item_category_name"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" DROP COLUMN "item_category_id"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" DROP COLUMN "item_hpp"`,
);
}
}

View File

@ -0,0 +1,61 @@
import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager';
import {
EventTopics,
validateRelations,
} from 'src/core/strings/constants/interface.constants';
import { BatchResult } from 'src/core/response/domain/ok-response.interface';
import {
HttpStatus,
Injectable,
UnprocessableEntityException,
} from '@nestjs/common';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
@Injectable()
export class BatchCancelReconciliationManager extends BaseBatchUpdateStatusManager<TransactionEntity> {
validateData(data: TransactionEntity): Promise<void> {
Object.assign(data, {
reconciliation_mdr: this.data.reconciliation_mdr ?? null,
reconciliation_confirm_by: this.user.name,
reconciliation_confirm_date: new Date().getTime(),
status: this.dataStatus,
reconciliation_status: this.dataStatus,
payment_date: this.data.payment_date,
});
if (data.is_recap_transaction) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! cant cancel recap data`,
error: 'Unprocessable Entity',
});
}
return;
}
beforeProcess(): Promise<void> {
return;
}
afterProcess(): Promise<void> {
return;
}
get validateRelations(): validateRelations[] {
return [];
}
get entityTarget(): any {
return TransactionModel;
}
get eventTopics(): EventTopics[] {
return [];
}
getResult(): BatchResult {
return this.result;
}
}

View File

@ -0,0 +1,53 @@
import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager';
import {
EventTopics,
validateRelations,
} from 'src/core/strings/constants/interface.constants';
import { BatchResult } from 'src/core/response/domain/ok-response.interface';
import { Injectable } from '@nestjs/common';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
@Injectable()
export class BatchConfirmReconciliationManager extends BaseBatchUpdateStatusManager<TransactionEntity> {
validateData(data: TransactionEntity): Promise<void> {
const net_profit = data.reconciliation_mdr
? Number(this.data.payment_total) - Number(this.data.reconciliation_mdr)
: null;
Object.assign(data, {
reconciliation_mdr: this.data.reconciliation_mdr ?? null,
reconciliation_confirm_by: this.user.name,
reconciliation_confirm_date: new Date().getTime(),
status: this.oldData.status,
reconciliation_status: this.dataStatus,
payment_total_net_profit: net_profit,
payment_date: this.data.payment_date,
});
return;
}
beforeProcess(): Promise<void> {
return;
}
afterProcess(): Promise<void> {
return;
}
get validateRelations(): validateRelations[] {
return [];
}
get entityTarget(): any {
return TransactionModel;
}
get eventTopics(): EventTopics[] {
return [];
}
getResult(): BatchResult {
return this.result;
}
}

View File

@ -0,0 +1,59 @@
import {
HttpStatus,
Injectable,
UnprocessableEntityException,
} from '@nestjs/common';
import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager';
import {
EventTopics,
validateRelations,
} from 'src/core/strings/constants/interface.constants';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
@Injectable()
export class CancelReconciliationManager extends BaseUpdateStatusManager<TransactionEntity> {
getResult(): string {
return `Success active data ${this.result.id}`;
}
async validateProcess(): Promise<void> {
if (this.data.is_recap_transaction) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! cant cancel recap data`,
error: 'Unprocessable Entity',
});
}
return;
}
async beforeProcess(): Promise<void> {
Object.assign(this.data, {
reconciliation_mdr: this.data.reconciliation_mdr ?? null,
reconciliation_confirm_by: this.user.name,
reconciliation_confirm_date: new Date().getTime(),
status: this.dataStatus,
reconciliation_status: this.dataStatus,
payment_date: this.data.payment_date,
});
return;
}
async afterProcess(): Promise<void> {
return;
}
get validateRelations(): validateRelations[] {
return [];
}
get entityTarget(): any {
return TransactionModel;
}
get eventTopics(): EventTopics[] {
return [];
}
}

View File

@ -0,0 +1,46 @@
import { Injectable } from '@nestjs/common';
import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager';
import {
EventTopics,
validateRelations,
} from 'src/core/strings/constants/interface.constants';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
@Injectable()
export class ConfirmReconciliationManager extends BaseUpdateStatusManager<TransactionEntity> {
getResult(): string {
return `Success active data ${this.result.id}`;
}
async validateProcess(): Promise<void> {
return;
}
async beforeProcess(): Promise<void> {
Object.assign(this.data, {
reconciliation_confirm_by: this.user.name,
reconciliation_confirm_date: new Date().getTime(),
status: this.oldData.status,
reconciliation_status: this.dataStatus,
});
return;
}
async afterProcess(): Promise<void> {
return;
}
get validateRelations(): validateRelations[] {
return [];
}
get entityTarget(): any {
return TransactionModel;
}
get eventTopics(): EventTopics[] {
return [];
}
}

View File

@ -0,0 +1,55 @@
import { Injectable } from '@nestjs/common';
import { BaseDetailManager } from 'src/core/modules/domain/usecase/managers/base-detail.manager';
import { RelationParam } from 'src/core/modules/domain/entities/base-filter.entity';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
@Injectable()
export class DetailReconciliationManager extends BaseDetailManager<TransactionEntity> {
async prepareData(): Promise<void> {
return;
}
async beforeProcess(): Promise<void> {
return;
}
async afterProcess(): Promise<void> {
return;
}
get relations(): RelationParam {
return {
// relation only join (for query purpose)
joinRelations: [],
// relation join and select (relasi yang ingin ditampilkan),
selectRelations: [],
// relation yang hanya ingin dihitung (akan return number)
countRelations: [],
};
}
get selects(): string[] {
return [
`${this.tableName}.id`,
`${this.tableName}.reconciliation_mdr`,
`${this.tableName}.payment_type`,
`${this.tableName}.payment_type_method_id`,
`${this.tableName}.payment_type_method_name`,
`${this.tableName}.payment_type_method_number`,
`${this.tableName}.payment_code_reference`,
`${this.tableName}.payment_date`,
`${this.tableName}.payment_total`,
`${this.tableName}.payment_total_net_profit`,
];
}
get setFindProperties(): any {
return {
id: this.dataId,
};
}
}

View File

@ -0,0 +1,151 @@
import { Injectable } from '@nestjs/common';
import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager';
import { SelectQueryBuilder } from 'typeorm';
import {
Param,
RelationParam,
} from 'src/core/modules/domain/entities/base-filter.entity';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
import { BetweenQueryHelper } from 'src/core/helpers/query/between-query.helper';
@Injectable()
export class IndexReconciliationManager extends BaseIndexManager<TransactionEntity> {
async prepareData(): Promise<void> {
return;
}
async beforeProcess(): Promise<void> {
return;
}
async afterProcess(): Promise<void> {
return;
}
get relations(): RelationParam {
return {
// relation only join (for query purpose)
joinRelations: [],
// relation join and select (relasi yang ingin ditampilkan),
selectRelations: [],
// relation yang hanya ingin dihitung (akan return number)
countRelations: [],
};
}
get selects(): string[] {
return [
`${this.tableName}.id`,
`${this.tableName}.type`,
`${this.tableName}.reconciliation_status`,
`${this.tableName}.reconciliation_mdr`,
`${this.tableName}.reconciliation_confirm_date`,
`${this.tableName}.reconciliation_confirm_by`,
`${this.tableName}.customer_name`,
`${this.tableName}.payment_type`,
`${this.tableName}.payment_type_method_id`,
`${this.tableName}.payment_type_method_name`,
`${this.tableName}.payment_type_method_number`,
`${this.tableName}.payment_code_reference`,
`${this.tableName}.payment_date`,
`${this.tableName}.payment_total`,
`${this.tableName}.payment_total_net_profit`,
];
}
get specificFilter(): Param[] {
return [
{
cols: `${this.tableName}.customer_name`,
data: this.filterParam.customer_names,
},
{
cols: `${this.tableName}.reconciliation_confirm_by`,
data: this.filterParam.confirm_by_names,
},
{
cols: `${this.tableName}.payment_type_method_name`,
data: this.filterParam.payment_banks,
},
{
cols: `${this.tableName}.payment_type_method_number`,
data: this.filterParam.payment_bank_numbers,
},
];
}
setQueryFilter(
queryBuilder: SelectQueryBuilder<TransactionEntity>,
): SelectQueryBuilder<TransactionEntity> {
if (this.filterParam.payment_date_from) {
new BetweenQueryHelper(
queryBuilder,
this.tableName,
'payment_date',
this.filterParam.payment_date_from,
this.filterParam.payment_date_to,
'payment_created',
).getQuery();
}
if (this.filterParam.transaction_type) {
queryBuilder.andWhere(`${this.tableName}.type In (:...types)`, {
types: [this.filterParam.transaction_type],
});
}
if (this.filterParam.couner_no) {
queryBuilder.andWhere(
`${this.tableName}.creator_counter_no In (:...counters)`,
{
counters: [this.filterParam.couner_no],
},
);
}
if (this.filterParam.payment_type) {
queryBuilder.andWhere(
`${this.tableName}.creator_counter_no In (:...counters)`,
{
counters: [this.filterParam.couner_no],
},
);
}
if (this.filterParam.payment_via) {
queryBuilder.andWhere(`${this.tableName}.payment_type In (:...type)`, {
type: [this.filterParam.payment_via],
});
}
if (this.filterParam.payment_bank) {
queryBuilder.andWhere(
`${this.tableName}.payment_type_method_name In (:...banks)`,
{
banks: [this.filterParam.payment_bank],
},
);
}
if (this.filterParam.confirmation_date_from) {
new BetweenQueryHelper(
queryBuilder,
this.tableName,
'payment_date',
this.filterParam.confirmation_date_from,
this.filterParam.confirmation_date_to,
'payment_created',
).getQuery();
}
queryBuilder.andWhere(
`${this.tableName}.reconciliation_status Is Not Null`,
);
return queryBuilder;
}
}

View File

@ -0,0 +1,51 @@
import { Injectable } from '@nestjs/common';
import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager';
import {
EventTopics,
columnUniques,
validateRelations,
} from 'src/core/strings/constants/interface.constants';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
@Injectable()
export class UpdateReconciliationManager extends BaseUpdateManager<TransactionEntity> {
async validateProcess(): Promise<void> {
return;
}
async beforeProcess(): Promise<void> {
const net_profit = this.data.reconciliation_mdr
? Number(this.oldData.payment_total) -
Number(this.data.reconciliation_mdr)
: null;
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,
});
return;
}
async afterProcess(): Promise<void> {
return;
}
get validateRelations(): validateRelations[] {
return [];
}
get uniqueColumns(): columnUniques[] {
return [];
}
get entityTarget(): any {
return TransactionModel;
}
get eventTopics(): EventTopics[] {
return [];
}
}

View File

@ -0,0 +1,64 @@
import { Injectable } from '@nestjs/common';
import { UpdateReconciliationManager } from './managers/update-reconciliation.manager';
import { ConfirmReconciliationManager } from './managers/confirm-reconciliation.manager';
import { STATUS } from 'src/core/strings/constants/base.constants';
import { BatchResult } from 'src/core/response/domain/ok-response.interface';
import { BatchConfirmReconciliationManager } from './managers/batch-confirm-reconciliation.manager';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { CancelReconciliationManager } from './managers/cancel-reconciliation.manager';
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';
@Injectable()
export class ReconciliationDataOrchestrator {
constructor(
private updateManager: UpdateReconciliationManager,
private confirmManager: ConfirmReconciliationManager,
private cancelManager: CancelReconciliationManager,
private batchConfirmManager: BatchConfirmReconciliationManager,
private batchCancelManager: BatchCancelReconciliationManager,
private serviceData: TransactionDataService,
) {}
async update(dataId, data): Promise<TransactionEntity> {
this.updateManager.setData(dataId, data);
this.updateManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);
await this.updateManager.execute();
return this.updateManager.getResult();
}
async confirm(dataId): Promise<String> {
this.confirmManager.setData(dataId, STATUS.CONFIRMED);
this.confirmManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);
await this.confirmManager.execute();
return this.confirmManager.getResult();
}
async batchConfirm(dataIds: string[]): Promise<BatchResult> {
this.batchConfirmManager.setData(dataIds, STATUS.CONFIRMED);
this.batchConfirmManager.setService(
this.serviceData,
TABLE_NAME.TRANSACTION,
);
await this.batchConfirmManager.execute();
return this.batchConfirmManager.getResult();
}
async cancel(dataId): Promise<String> {
this.cancelManager.setData(dataId, STATUS.REJECTED);
this.cancelManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);
await this.cancelManager.execute();
return this.cancelManager.getResult();
}
async batchCancel(dataIds: string[]): Promise<BatchResult> {
this.batchCancelManager.setData(dataIds, STATUS.REJECTED);
this.batchCancelManager.setService(
this.serviceData,
TABLE_NAME.TRANSACTION,
);
await this.batchCancelManager.execute();
return this.batchCancelManager.getResult();
}
}

View File

@ -0,0 +1,33 @@
import { Injectable } from '@nestjs/common';
import { IndexReconciliationManager } from './managers/index-reconciliation.manager';
import { PaginationResponse } from 'src/core/response/domain/ok-response.interface';
import { BaseReadOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-read.orchestrator';
import { DetailReconciliationManager } from './managers/detail-reconciliation.manager';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
import { TransactionReadService } from 'src/modules/transaction/transaction/data/services/transaction-read.service';
@Injectable()
export class ReconciliationReadOrchestrator extends BaseReadOrchestrator<TransactionEntity> {
constructor(
private indexManager: IndexReconciliationManager,
private detailManager: DetailReconciliationManager,
private serviceData: TransactionReadService,
) {
super();
}
async index(params): Promise<PaginationResponse<TransactionEntity>> {
this.indexManager.setFilterParam(params);
this.indexManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);
await this.indexManager.execute();
return this.indexManager.getResult();
}
async detail(dataId: string): Promise<TransactionEntity> {
this.detailManager.setData(dataId);
this.detailManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);
await this.detailManager.execute();
return this.detailManager.getResult();
}
}

View File

@ -0,0 +1,60 @@
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.dto';
import { FilterTransactionEntity } from 'src/modules/transaction/transaction/domain/entities/filter-transaction.entity';
export class FilterReconciliationDto
extends BaseFilterDto
implements FilterTransactionEntity
{
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];
})
customer_names: string[];
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];
})
confirm_by_names: string[];
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];
})
payment_banks: string[];
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];
})
payment_bank_numbers: string[];
@ApiProperty({ type: Date, required: false })
payment_date_from: Date;
@ApiProperty({ type: Date, required: false })
payment_date_to: Date;
@ApiProperty({ type: Number, required: false })
couner_no: number;
@ApiProperty({ type: String, required: false })
payment_type: string;
@ApiProperty({ type: Date, required: false })
confirmation_date_from: Date;
@ApiProperty({ type: Date, required: false })
confirmation_date_to: Date;
@ApiProperty({ type: String, required: false })
transaction_type: string;
@ApiProperty({ type: String, required: false })
payment_via: string;
@ApiProperty({ type: String, required: false })
payment_bank: string;
}

View File

@ -0,0 +1,17 @@
import { ApiProperty } from '@nestjs/swagger';
export class UpdateReconciliationDto {
@ApiProperty({
type: Date,
required: true,
example: '2024/06/06',
})
payment_date: Date;
@ApiProperty({
type: Number,
required: false,
example: 350000,
})
reconciliation_mdr: number;
}

View File

@ -0,0 +1,53 @@
import {
Body,
Controller,
Delete,
Param,
Patch,
Post,
Put,
} from '@nestjs/common';
import { ReconciliationDataOrchestrator } from '../domain/usecases/reconciliation-data.orchestrator';
import { MODULE_NAME } from 'src/core/strings/constants/module.constants';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { BatchResult } from 'src/core/response/domain/ok-response.interface';
import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto';
import { Public } from 'src/core/guards';
import { TransactionEntity } from '../../transaction/domain/entities/transaction.entity';
import { UpdateReconciliationDto } from './dto/reconciliation.dto';
@ApiTags(`${MODULE_NAME.RECONCILIATION.split('-').join(' ')} - data`)
@Controller(`v1/${MODULE_NAME.RECONCILIATION}`)
@Public(false)
@ApiBearerAuth('JWT')
export class ReconciliationDataController {
constructor(private orchestrator: ReconciliationDataOrchestrator) {}
@Put('/batch-confirm')
async batchConfirm(@Body() body: BatchIdsDto): Promise<BatchResult> {
return await this.orchestrator.batchConfirm(body.ids);
}
@Patch(':id/confirm')
async confirm(@Param('id') dataId: string): Promise<String> {
return await this.orchestrator.confirm(dataId);
}
@Patch(':id/cancel')
async cancel(@Param('id') dataId: string): Promise<String> {
return await this.orchestrator.cancel(dataId);
}
@Put('/batch-cancel')
async batchCancel(@Body() body: BatchIdsDto): Promise<BatchResult> {
return await this.orchestrator.batchCancel(body.ids);
}
@Put(':id')
async update(
@Param('id') dataId: string,
@Body() data: UpdateReconciliationDto,
): Promise<TransactionEntity> {
return await this.orchestrator.update(dataId, data);
}
}

View File

@ -0,0 +1,30 @@
import { Controller, Get, Param, Query } from '@nestjs/common';
import { FilterReconciliationDto } from './dto/filter-reconciliation.dto';
import { Pagination } from 'src/core/response';
import { PaginationResponse } from 'src/core/response/domain/ok-response.interface';
import { ReconciliationReadOrchestrator } from '../domain/usecases/reconciliation-read.orchestrator';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { MODULE_NAME } from 'src/core/strings/constants/module.constants';
import { Public } from 'src/core/guards';
import { TransactionEntity } from '../../transaction/domain/entities/transaction.entity';
@ApiTags(`${MODULE_NAME.RECONCILIATION.split('-').join(' ')} - read`)
@Controller(`v1/${MODULE_NAME.RECONCILIATION}`)
@Public(false)
@ApiBearerAuth('JWT')
export class ReconciliationReadController {
constructor(private orchestrator: ReconciliationReadOrchestrator) {}
@Get()
@Pagination()
async index(
@Query() params: FilterReconciliationDto,
): Promise<PaginationResponse<TransactionEntity>> {
return await this.orchestrator.index(params);
}
@Get(':id')
async detail(@Param('id') id: string): Promise<TransactionEntity> {
return await this.orchestrator.detail(id);
}
}

View File

@ -0,0 +1,44 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { ReconciliationReadController } from './infrastructure/reconciliation-read.controller';
import { ReconciliationReadOrchestrator } from './domain/usecases/reconciliation-read.orchestrator';
import { ReconciliationDataController } from './infrastructure/reconciliation-data.controller';
import { ReconciliationDataOrchestrator } from './domain/usecases/reconciliation-data.orchestrator';
import { CqrsModule } from '@nestjs/cqrs';
import { IndexReconciliationManager } from './domain/usecases/managers/index-reconciliation.manager';
import { UpdateReconciliationManager } from './domain/usecases/managers/update-reconciliation.manager';
import { ConfirmReconciliationManager } from './domain/usecases/managers/confirm-reconciliation.manager';
import { DetailReconciliationManager } from './domain/usecases/managers/detail-reconciliation.manager';
import { TransactionModel } from '../transaction/data/models/transaction.model';
import { TransactionDataService } from '../transaction/data/services/transaction-data.service';
import { TransactionReadService } from '../transaction/data/services/transaction-read.service';
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';
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forFeature([TransactionModel], CONNECTION_NAME.DEFAULT),
CqrsModule,
],
controllers: [ReconciliationDataController, ReconciliationReadController],
providers: [
IndexReconciliationManager,
DetailReconciliationManager,
UpdateReconciliationManager,
ConfirmReconciliationManager,
BatchConfirmReconciliationManager,
CancelReconciliationManager,
BatchCancelReconciliationManager,
TransactionDataService,
TransactionReadService,
ReconciliationDataOrchestrator,
ReconciliationReadOrchestrator,
],
})
export class ReconciliationModule {}

View File

@ -0,0 +1,31 @@
import { TransactionItemModel } from './data/models/transaction-item.model';
import { TransactionTaxModel } from './data/models/transaction-tax.model';
import { TransactionModel } from './data/models/transaction.model';
export enum TransactionPaymentType {
MIDTRANS = 'midtrans',
BANK_TRANSFER = 'bank transfer',
QRIS = 'qris',
COUNTER = 'counter',
CASH = 'cash',
CC = 'credit card',
DEBIT = 'debit',
EMONEY = 'e-money',
}
export enum TransactionType {
COUNTER = 'counter', // transaksi yang dibuat dari POS / Counter
ADMIN = 'admin', // transaksi yang dibuat dari ADMIN page booking
ONLINE = 'online', // transaksi yang dibuat dari USER booking online
}
export enum TransactionUserType {
GROUP = 'group',
VIP = 'vip',
}
export const TransactionModels = [
TransactionModel,
TransactionItemModel,
TransactionTaxModel,
];

View File

@ -0,0 +1,71 @@
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
import { BaseCoreModel } from 'src/core/modules/data/model/base-core.model';
import { TransactionItemEntity } from '../../domain/entities/transaction-item.entity';
import { TransactionModel } from './transaction.model';
@Entity(TABLE_NAME.TRANSACTION_ITEM)
export class TransactionItemModel
extends BaseCoreModel<TransactionItemEntity>
implements TransactionItemEntity
{
// item data
@Column('varchar', { name: 'item_id', nullable: true })
item_id: string;
@Column('varchar', { name: 'item_name', nullable: true })
item_name: string;
@Column('varchar', { name: 'item_type', nullable: true })
item_type: string;
@Column('bigint', { name: 'item_price', nullable: true })
item_price: number;
@Column('bigint', { name: 'item_hpp', nullable: true })
item_hpp: number;
@Column('varchar', { name: 'item_category_id', nullable: true })
item_category_id: string;
@Column('varchar', { name: 'item_category_name', nullable: true })
item_category_name: string;
@Column('json', { name: 'item_bundlings', nullable: true })
item_bundlings: string;
// item tenant data
@Column('varchar', { name: 'item_tenant_id', nullable: true })
item_tenant_id: string;
@Column('varchar', { name: 'item_tenant_name', nullable: true })
item_tenant_name: string;
@Column('decimal', { name: 'item_tenant_share_margin', nullable: true })
item_tenant_share_margin: number;
// calculation data
@Column('decimal', { name: 'total_price', nullable: true })
total_price: number;
@Column('decimal', { name: 'total_hpp', nullable: true })
total_hpp: number;
@Column('decimal', { name: 'total_profit', nullable: true })
total_profit: number;
@Column('decimal', { name: 'total_share_tenant', nullable: true })
total_share_tenant: number;
@Column('int', { name: 'qty', nullable: true })
qty: number;
@Column('varchar', { name: 'transaction_id', nullable: true })
transaction_id: string;
@ManyToOne(() => TransactionModel, (model) => model.items, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
@JoinColumn({ name: 'transaction_id' })
transaction: TransactionModel;
}

View File

@ -0,0 +1,29 @@
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
import { TransactionTaxEntity } from '../../domain/entities/transaction-tax.entity';
import { BaseCoreModel } from 'src/core/modules/data/model/base-core.model';
import { TransactionModel } from './transaction.model';
@Entity(TABLE_NAME.TRANSACTION_TAX)
export class TransactionTaxModel
extends BaseCoreModel<TransactionTaxEntity>
implements TransactionTaxEntity
{
@Column('varchar', { name: 'tax_id', nullable: true })
tax_id: string;
@Column('varchar', { name: 'tax_name', nullable: true })
tax_name: string;
@Column('varchar', { name: 'taxt_value', nullable: true })
taxt_value: number;
@Column('varchar', { name: 'transaction_id', nullable: true })
transaction_id: string;
@ManyToOne(() => TransactionModel, (model) => model.taxes, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
@JoinColumn({ name: 'transaction_id' })
transaction: TransactionModel;
}

View File

@ -0,0 +1,214 @@
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { TransactionEntity } from '../../domain/entities/transaction.entity';
import { Column, Entity, OneToMany } from 'typeorm';
import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model';
import {
TransactionType,
TransactionUserType,
TransactionPaymentType,
} from '../../constants';
import { TransactionItemEntity } from '../../domain/entities/transaction-item.entity';
import { TransactionItemModel } from './transaction-item.model';
import { TransactionTaxModel } from './transaction-tax.model';
import { STATUS } from 'src/core/strings/constants/base.constants';
@Entity(TABLE_NAME.TRANSACTION)
export class TransactionModel
extends BaseStatusModel<TransactionEntity>
implements TransactionEntity
{
// general info
@Column('bool', { name: 'is_recap_transaction', default: true })
is_recap_transaction: boolean;
@Column('enum', {
name: 'type',
enum: TransactionType,
default: TransactionType.ADMIN,
})
type: TransactionType;
@Column('varchar', { name: 'invoice_code', nullable: true })
invoice_code: string;
@Column('int', { name: 'creator_counter_no', nullable: true })
creator_counter_no: number;
// season data
@Column('varchar', { name: 'season_period_id', nullable: true })
season_period_id: string;
@Column('varchar', { name: 'season_period_name', nullable: true })
season_period_name: string;
@Column('varchar', { name: 'season_period_type_id', nullable: true })
season_period_type_id: string;
@Column('varchar', { name: 'season_period_type_name', nullable: true })
season_period_type_name: string;
// customer info
@Column('enum', {
name: 'customer_type',
enum: TransactionUserType,
nullable: true,
})
customer_type: TransactionUserType;
@Column('varchar', { name: 'customer_category_id', nullable: true })
customer_category_id: string;
@Column('varchar', { name: 'customer_category_name', nullable: true })
customer_category_name: string;
@Column('varchar', { name: 'customer_name', nullable: true })
customer_name: string;
@Column('varchar', { name: 'customer_phone', nullable: true })
customer_phone: string;
@Column('varchar', { name: 'customer_email', nullable: true })
customer_email: string;
@Column('varchar', { name: 'customer_description', nullable: true })
customer_description: string;
@Column('varchar', { name: 'no_of_group', nullable: true })
no_of_group: number;
@Column('date', { name: 'booking_date', nullable: true })
booking_date: Date;
@Column('date', { name: 'settlement_date', nullable: true })
settlement_date: Date;
@Column('date', { name: 'invoice_date', nullable: true })
invoice_date: Date;
// discount data
@Column('varchar', { name: 'discount_code_id', nullable: true })
discount_code_id: string;
@Column('varchar', { name: 'discount_code', nullable: true })
discount_code: string;
@Column('int', { name: 'discount_percentage', nullable: true })
discount_percentage: number;
@Column('decimal', { name: 'discount_value', nullable: true })
discount_value: number;
// payment data
@Column('enum', {
name: 'payment_type',
enum: TransactionPaymentType,
default: TransactionPaymentType.BANK_TRANSFER,
})
payment_type: TransactionPaymentType;
@Column('varchar', { name: 'payment_type_method_id', nullable: true })
payment_type_method_id: string;
@Column('varchar', { name: 'payment_type_method_name', nullable: true })
payment_type_method_name: string;
@Column('varchar', { name: 'payment_type_method_number', nullable: true })
payment_type_method_number: string;
@Column('varchar', { name: 'payment_type_method_qr', nullable: true })
payment_type_method_qr: string;
@Column('varchar', { name: 'payment_card_information', nullable: true })
payment_card_information: string;
@Column('varchar', { name: 'payment_code_reference', nullable: true })
payment_code_reference: string;
@Column('date', { name: 'payment_date', nullable: true })
payment_date: Date;
// calculation data
@Column('decimal', { name: 'payment_sub_total', nullable: true })
payment_sub_total: number;
@Column('decimal', { name: 'payment_discount_total', nullable: true })
payment_discount_total: number;
@Column('decimal', { name: 'payment_total', nullable: true })
payment_total: number;
@Column('decimal', { name: 'payment_total_pay', nullable: true })
payment_total_pay: number;
@Column('decimal', { name: 'payment_change', nullable: true })
payment_change: number;
// share and profit data
@Column('decimal', { name: 'payment_total_share', nullable: true })
payment_total_share: number;
@Column('decimal', { name: 'payment_total_tax', nullable: true })
payment_total_tax: number;
@Column('decimal', { name: 'payment_total_profit', nullable: true })
payment_total_profit: number;
@Column('varchar', { name: 'profit_share_formula', nullable: true })
profit_share_formula: string;
@Column('varchar', { name: 'sales_price_formula', nullable: true })
sales_price_formula: string;
// mdr
@Column('decimal', { name: 'reconciliation_mdr', nullable: true })
reconciliation_mdr: number;
@Column('enum', {
name: 'reconciliation_status',
enum: STATUS,
default: STATUS.DRAFT,
})
reconciliation_status: STATUS;
@Column('varchar', { name: 'reconciliation_confirm_date', nullable: true })
reconciliation_confirm_date: string;
@Column('varchar', { name: 'reconciliation_confirm_by', nullable: true })
reconciliation_confirm_by: string;
@Column('decimal', { name: 'payment_total_net_profit', nullable: true })
payment_total_net_profit: number;
// sending data
@Column('enum', {
name: 'sending_invoice_status',
enum: STATUS,
nullable: true,
})
sending_invoice_status: STATUS;
@Column({ name: 'sending_invoice_at', type: 'bigint', nullable: true })
sending_invoice_at: number;
@Column('enum', { name: 'sending_qr_status', enum: STATUS, nullable: true })
sending_qr_status: STATUS;
@Column({ name: 'sending_qr_at', type: 'bigint', nullable: true })
sending_qr_at: number;
// relations to item
@OneToMany(() => TransactionItemModel, (model) => model.transaction, {
cascade: true,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
items: TransactionItemEntity[];
// relations to tax data
@OneToMany(() => TransactionTaxModel, (model) => model.transaction, {
cascade: true,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
taxes: TransactionTaxModel[];
}

View File

@ -0,0 +1,17 @@
import { Injectable } from '@nestjs/common';
import { BaseDataService } from 'src/core/modules/data/service/base-data.service';
import { TransactionEntity } from '../../domain/entities/transaction.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { TransactionModel } from '../models/transaction.model';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { Repository } from 'typeorm';
@Injectable()
export class TransactionDataService extends BaseDataService<TransactionEntity> {
constructor(
@InjectRepository(TransactionModel, CONNECTION_NAME.DEFAULT)
private repo: Repository<TransactionModel>,
) {
super(repo);
}
}

View File

@ -0,0 +1,17 @@
import { Injectable } from '@nestjs/common';
import { TransactionEntity } from '../../domain/entities/transaction.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { TransactionModel } from '../models/transaction.model';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { Repository } from 'typeorm';
import { BaseReadService } from 'src/core/modules/data/service/base-read.service';
@Injectable()
export class TransactionReadService extends BaseReadService<TransactionEntity> {
constructor(
@InjectRepository(TransactionModel, CONNECTION_NAME.DEFAULT)
private repo: Repository<TransactionModel>,
) {
super(repo);
}
}

View File

@ -0,0 +1,5 @@
import { IEvent } from 'src/core/strings/constants/interface.constants';
export class TransactionChangeStatusEvent {
constructor(public readonly data: IEvent) {}
}

View File

@ -0,0 +1,5 @@
import { IEvent } from 'src/core/strings/constants/interface.constants';
export class TransactionCreatedEvent {
constructor(public readonly data: IEvent) {}
}

View File

@ -0,0 +1,5 @@
import { IEvent } from 'src/core/strings/constants/interface.constants';
export class TransactionDeletedEvent {
constructor(public readonly data: IEvent) {}
}

View File

@ -0,0 +1,5 @@
import { IEvent } from 'src/core/strings/constants/interface.constants';
export class TransactionUpdatedEvent {
constructor(public readonly data: IEvent) {}
}

View File

@ -0,0 +1,36 @@
import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity';
export interface FilterTransactionEntity extends BaseFilterEntity {
// search mdr
customer_names?: string[];
confirm_by_names?: string[];
payment_banks?: string[];
payment_bank_numbers?: string[];
// seatch booking;
payment_types?: string[];
types?: string[];
customer_types?: string[];
invoice_codes?: string[];
refund_codes?: string[];
creator_names?: string[];
// filter mdr
payment_date_from?: Date;
payment_date_to?: Date;
transaction_type?: string;
couner_no?: number;
payment_type?: string;
payment_via?: string;
payment_bank?: string;
confirmation_date_from?: Date;
confirmation_date_to?: Date;
// filter booking
booking_date_from?: Date;
booking_date_to?: Date;
invoice_date_from?: Date;
invoice_date_to?: Date;
settlement_date_from?: Date;
settlement_date_to?: Date;
}

View File

@ -0,0 +1,25 @@
import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity';
export interface TransactionItemEntity extends BaseCoreEntity {
// item detail
item_id: string;
item_name: string;
item_type: string;
item_price: number;
item_hpp: number;
item_category_id: string;
item_category_name: string;
item_bundlings: string;
// item tenant data
item_tenant_id: string;
item_tenant_name: string;
item_tenant_share_margin: number;
// calculation data
total_price: number;
total_hpp: number;
total_profit: number;
total_share_tenant: number;
qty: number;
}

View File

@ -0,0 +1,7 @@
import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity';
export interface TransactionTaxEntity extends BaseCoreEntity {
tax_id: string;
tax_name: string;
taxt_value: number;
}

View File

@ -0,0 +1,78 @@
import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity';
import {
TransactionPaymentType,
TransactionType,
TransactionUserType,
} from '../../constants';
import { STATUS } from 'src/core/strings/constants/base.constants';
export interface TransactionEntity extends BaseStatusEntity {
// general info
is_recap_transaction: boolean;
type: TransactionType;
invoice_code: string;
creator_counter_no: number; // nomor pos transaksi dibuat
// season data
season_period_id: string;
season_period_name: string;
season_period_type_id: string;
season_period_type_name: string;
// customer info
customer_category_id: string;
customer_category_name: string;
customer_type: TransactionUserType;
customer_name: string;
customer_phone: string;
customer_email: string;
customer_description: string;
no_of_group: number;
booking_date: Date; // tnaggal untuk booking
settlement_date: Date; // tanggal status berubah menjadi settlement
invoice_date: Date; // tanggal invoice terkirim
// discount data
discount_code_id: string;
discount_code: string;
discount_percentage: number;
discount_value: number;
// payment data
payment_type: TransactionPaymentType;
payment_type_method_id: string;
payment_type_method_name: string;
payment_type_method_number: string;
payment_type_method_qr: string;
payment_card_information: string;
payment_code_reference: string;
payment_date: Date;
// calculation data
payment_sub_total: number; // total invoice tanpa discount
payment_discount_total: number; // total discount
payment_total: number; // total invoice
payment_total_pay: number; // total pembayaran user
payment_change: number; // total kembalian
// share and profit data
payment_total_share: number; // total share untuk para tenant
payment_total_tax: number; // total untuk tax
payment_total_profit: number; // total untuk profit perusahan
profit_share_formula: string;
sales_price_formula: string;
// mdr data
reconciliation_mdr: number;
reconciliation_status: STATUS;
reconciliation_confirm_date: string;
reconciliation_confirm_by: string;
payment_total_net_profit: number; // net pendapatan
// sending data
sending_invoice_at: number;
sending_invoice_status: STATUS;
sending_qr_at: number;
sending_qr_status: STATUS;
}

View File

@ -0,0 +1,58 @@
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 {
HttpStatus,
Injectable,
UnprocessableEntityException,
} from '@nestjs/common';
import { STATUS } from 'src/core/strings/constants/base.constants';
@Injectable()
export class BatchCancelTransactionManager extends BaseBatchUpdateStatusManager<TransactionEntity> {
validateData(data: TransactionEntity): Promise<void> {
if (![STATUS.EXPIRED, STATUS.PENDING].includes(data.status)) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! only data booking with status ${STATUS.ACTIVE} can be confirm`,
error: 'Unprocessable Entity',
});
}
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;
}
}

View File

@ -0,0 +1,58 @@
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 {
HttpStatus,
Injectable,
UnprocessableEntityException,
} from '@nestjs/common';
import { STATUS } from 'src/core/strings/constants/base.constants';
@Injectable()
export class BatchConfirmDataTransactionManager extends BaseBatchUpdateStatusManager<TransactionEntity> {
validateData(data: TransactionEntity): Promise<void> {
if (data.status != STATUS.DRAFT) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! only data booking with status ${STATUS.ACTIVE} can be confirm`,
error: 'Unprocessable Entity',
});
}
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;
}
}

View File

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

View File

@ -0,0 +1,45 @@
import { BaseBatchDeleteManager } from 'src/core/modules/domain/usecase/managers/base-batch-delete.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 { TransactionDeletedEvent } from '../../entities/event/transaction-deleted.event';
import { BatchResult } from 'src/core/response/domain/ok-response.interface';
import { Injectable } from '@nestjs/common';
@Injectable()
export class BatchDeleteTransactionManager extends BaseBatchDeleteManager<TransactionEntity> {
async beforeProcess(): Promise<void> {
return;
}
async validateData(data: TransactionEntity): Promise<void> {
return;
}
async afterProcess(): Promise<void> {
return;
}
get validateRelations(): validateRelations[] {
return [];
}
get entityTarget(): any {
return TransactionModel;
}
get eventTopics(): EventTopics[] {
return [
{
topic: TransactionDeletedEvent,
},
];
}
getResult(): BatchResult {
return this.result;
}
}

View File

@ -0,0 +1,61 @@
import {
HttpStatus,
Injectable,
UnprocessableEntityException,
} 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';
import { STATUS } from 'src/core/strings/constants/base.constants';
@Injectable()
export class CancelTransactionManager extends BaseUpdateStatusManager<TransactionEntity> {
getResult(): string {
return `Success active data ${this.result.invoice_code}`;
}
async validateProcess(): Promise<void> {
if (![STATUS.EXPIRED, STATUS.PENDING].includes(this.data.status)) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! only data booking with status ${STATUS.ACTIVE} can be confirm`,
error: 'Unprocessable Entity',
});
}
return;
}
async beforeProcess(): Promise<void> {
const freeTransaction = this.data.payment_total < 1;
Object.assign(this.data, {
status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING,
});
return;
}
async afterProcess(): Promise<void> {
return;
}
get validateRelations(): validateRelations[] {
return [];
}
get entityTarget(): any {
return TransactionModel;
}
get eventTopics(): EventTopics[] {
return [
{
topic: TransactionChangeStatusEvent,
data: this.data,
},
];
}
}

View File

@ -0,0 +1,72 @@
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';
import { STATUS } from 'src/core/strings/constants/base.constants';
@Injectable()
export class ConfirmDataTransactionManager extends BaseUpdateStatusManager<TransactionEntity> {
getResult(): string {
return `Success active data ${this.result.invoice_code}`;
}
async validateProcess(): Promise<void> {
return;
}
async beforeProcess(): Promise<void> {
const old_status = this.oldData.status;
switch (old_status) {
// jika confirm status pending
// maka akan kebuat reconsiliasi
case STATUS.PENDING:
this.data.reconciliation_status = STATUS.PENDING;
break;
// jika confirm status rejected
case STATUS.REJECTED:
this.data.reconciliation_status = STATUS.PENDING;
break;
// jika confirm status expired
case STATUS.EXPIRED:
break;
default:
this.data.reconciliation_status = STATUS.PENDING;
break;
}
const freeTransaction = this.data.payment_total < 1;
Object.assign(this.data, {
status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING,
});
return;
}
async afterProcess(): Promise<void> {
return;
}
get validateRelations(): validateRelations[] {
return [];
}
get entityTarget(): any {
return TransactionModel;
}
get eventTopics(): EventTopics[] {
return [
{
topic: TransactionChangeStatusEvent,
data: this.data,
},
];
}
}

View File

@ -0,0 +1,61 @@
import {
HttpStatus,
Injectable,
UnprocessableEntityException,
} 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';
import { STATUS } from 'src/core/strings/constants/base.constants';
@Injectable()
export class ConfirmTransactionManager extends BaseUpdateStatusManager<TransactionEntity> {
getResult(): string {
return `Success active data ${this.result.invoice_code}`;
}
async validateProcess(): Promise<void> {
if (this.data.status != STATUS.DRAFT) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! only data booking with status ${STATUS.ACTIVE} can be confirm`,
error: 'Unprocessable Entity',
});
}
return;
}
async beforeProcess(): Promise<void> {
const freeTransaction = this.data.payment_total < 1;
Object.assign(this.data, {
status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING,
});
return;
}
async afterProcess(): Promise<void> {
return;
}
get validateRelations(): validateRelations[] {
return [];
}
get entityTarget(): any {
return TransactionModel;
}
get eventTopics(): EventTopics[] {
return [
{
topic: TransactionChangeStatusEvent,
data: this.data,
},
];
}
}

View File

@ -0,0 +1,66 @@
import { Injectable } from '@nestjs/common';
import {
EventTopics,
columnUniques,
validateRelations,
} from 'src/core/strings/constants/interface.constants';
import { TransactionEntity } from '../../entities/transaction.entity';
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';
@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,
});
});
return;
}
async afterProcess(): Promise<void> {
return;
}
get validateRelations(): validateRelations[] {
return [];
}
get uniqueColumns(): columnUniques[] {
return [];
}
get eventTopics(): EventTopics[] {
return [
{
topic: TransactionCreatedEvent,
data: this.data,
},
];
}
get entityTarget(): any {
return TransactionModel;
}
}

View File

@ -0,0 +1,45 @@
import { Injectable } from '@nestjs/common';
import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.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 { TransactionDeletedEvent } from '../../entities/event/transaction-deleted.event';
@Injectable()
export class DeleteTransactionManager extends BaseDeleteManager<TransactionEntity> {
getResult(): string {
return `Success`;
}
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: TransactionDeletedEvent,
data: this.data,
},
];
}
}

View File

@ -0,0 +1,141 @@
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';
@Injectable()
export class DetailTransactionManager extends BaseDetailManager<TransactionEntity> {
async prepareData(): Promise<void> {
return;
}
async beforeProcess(): Promise<void> {
return;
}
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,
},
},
id: itemData.item_category_id,
name: itemData.item_category_name,
};
});
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;
return;
}
get relations(): RelationParam {
return {
// relation only join (for query purpose)
joinRelations: [],
// relation join and select (relasi yang ingin ditampilkan),
selectRelations: ['items'],
// relation yang hanya ingin dihitung (akan return number)
countRelations: [],
};
}
get selects(): string[] {
return [
`${this.tableName}.id`,
`${this.tableName}.creator_name`,
`${this.tableName}.created_at`,
`${this.tableName}.updated_at`,
`${this.tableName}.editor_name`,
`${this.tableName}.invoice_code`,
`${this.tableName}.season_period_id`,
`${this.tableName}.season_period_name`,
`${this.tableName}.season_period_type_id`,
`${this.tableName}.season_period_type_name`,
`${this.tableName}.status`,
`${this.tableName}.no_of_group`,
`${this.tableName}.customer_type`,
`${this.tableName}.customer_name`,
`${this.tableName}.customer_phone`,
`${this.tableName}.customer_email`,
`${this.tableName}.customer_description`,
`${this.tableName}.booking_date`,
`${this.tableName}.discount_percentage`,
`${this.tableName}.discount_value`,
`${this.tableName}.payment_type`,
`${this.tableName}.payment_date`,
`${this.tableName}.payment_total_pay`,
`${this.tableName}.payment_type_method_id`,
`${this.tableName}.payment_type_method_name`,
`${this.tableName}.payment_type_method_number`,
`${this.tableName}.payment_sub_total`,
`${this.tableName}.payment_discount_total`,
`${this.tableName}.payment_total`,
'items',
];
}
get setFindProperties(): any {
return {
id: this.dataId,
};
}
}

View File

@ -0,0 +1,75 @@
import { Injectable } from '@nestjs/common';
import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager';
import { TransactionEntity } from '../../entities/transaction.entity';
import { SelectQueryBuilder } from 'typeorm';
import {
Param,
RelationParam,
} from 'src/core/modules/domain/entities/base-filter.entity';
@Injectable()
export class IndexTransactionManager extends BaseIndexManager<TransactionEntity> {
async prepareData(): Promise<void> {
return;
}
async beforeProcess(): Promise<void> {
return;
}
async afterProcess(): Promise<void> {
return;
}
get relations(): RelationParam {
return {
// relation only join (for query purpose)
joinRelations: [],
// relation join and select (relasi yang ingin ditampilkan),
selectRelations: ['items'],
// relation yang hanya ingin dihitung (akan return number)
countRelations: [],
};
}
get selects(): string[] {
return [
`${this.tableName}.id`,
`${this.tableName}.status`,
`${this.tableName}.invoice_code`,
`${this.tableName}.booking_date`,
`${this.tableName}.no_of_group`,
`${this.tableName}.type`,
`${this.tableName}.payment_total`,
`${this.tableName}.customer_type`,
`${this.tableName}.customer_name`,
`${this.tableName}.customer_phone`,
`${this.tableName}.customer_description`,
`${this.tableName}.customer_email`,
`${this.tableName}.invoice_date`,
`${this.tableName}.settlement_date`,
`${this.tableName}.created_at`,
`${this.tableName}.creator_name`,
`${this.tableName}.editor_id`,
`${this.tableName}.editor_name`,
];
}
get specificFilter(): Param[] {
return [
{
cols: `${this.tableName}.name`,
data: this.filterParam.names,
},
];
}
setQueryFilter(
queryBuilder: SelectQueryBuilder<TransactionEntity>,
): SelectQueryBuilder<TransactionEntity> {
queryBuilder.andWhere(`${this.tableName}.is_recap_transaction is false`);
return queryBuilder;
}
}

View File

@ -0,0 +1,46 @@
import { Injectable } from '@nestjs/common';
import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager';
import { TransactionEntity } from '../../entities/transaction.entity';
import { TransactionModel } from '../../../data/models/transaction.model';
import { TransactionUpdatedEvent } from '../../entities/event/transaction-updated.event';
import {
EventTopics,
columnUniques,
validateRelations,
} from 'src/core/strings/constants/interface.constants';
@Injectable()
export class UpdateTransactionManager extends BaseUpdateManager<TransactionEntity> {
async validateProcess(): Promise<void> {
return;
}
async beforeProcess(): Promise<void> {
return;
}
async afterProcess(): Promise<void> {
return;
}
get validateRelations(): validateRelations[] {
return [];
}
get uniqueColumns(): columnUniques[] {
return [];
}
get entityTarget(): any {
return TransactionModel;
}
get eventTopics(): EventTopics[] {
return [
{
topic: TransactionUpdatedEvent,
data: this.data,
},
];
}
}

View File

@ -0,0 +1,118 @@
import { Injectable } from '@nestjs/common';
import { CreateTransactionManager } from './managers/create-transaction.manager';
import { TransactionDataService } from '../../data/services/transaction-data.service';
import { TransactionEntity } from '../entities/transaction.entity';
import { DeleteTransactionManager } from './managers/delete-transaction.manager';
import { UpdateTransactionManager } from './managers/update-transaction.manager';
import { ConfirmTransactionManager } from './managers/confirm-transaction.manager';
import { STATUS } from 'src/core/strings/constants/base.constants';
import { BatchResult } from 'src/core/response/domain/ok-response.interface';
import { BatchConfirmTransactionManager } from './managers/batch-confirm-transaction.manager';
import { BatchDeleteTransactionManager } from './managers/batch-delete-transaction.manager';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { CancelTransactionManager } from './managers/cancel-transaction.manager';
import { BatchCancelTransactionManager } from './managers/batch-cancel-transaction.manager';
import { ConfirmDataTransactionManager } from './managers/confirm-data-transaction.manager';
import { BatchConfirmDataTransactionManager } from './managers/batch-confirm-data-transaction.manager';
@Injectable()
export class TransactionDataOrchestrator {
constructor(
private createManager: CreateTransactionManager,
private updateManager: UpdateTransactionManager,
private confirmManager: ConfirmTransactionManager,
private batchConfirmManager: BatchConfirmTransactionManager,
private confirmDataManager: ConfirmDataTransactionManager,
private batchConfirmDataManager: BatchConfirmDataTransactionManager,
private deleteManager: DeleteTransactionManager,
private batchDeleteManager: BatchDeleteTransactionManager,
private cancelManager: CancelTransactionManager,
private batchCancelManager: BatchCancelTransactionManager,
private serviceData: TransactionDataService,
) {}
async create(data): Promise<TransactionEntity> {
this.createManager.setData(data);
this.createManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);
await this.createManager.execute();
return this.createManager.getResult();
}
async update(dataId, data): Promise<TransactionEntity> {
this.updateManager.setData(dataId, data);
this.updateManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);
await this.updateManager.execute();
return this.updateManager.getResult();
}
async delete(dataId): Promise<String> {
this.deleteManager.setData(dataId);
this.deleteManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);
await this.deleteManager.execute();
return this.deleteManager.getResult();
}
async batchDelete(dataIds: string[]): Promise<BatchResult> {
this.batchDeleteManager.setData(dataIds);
this.batchDeleteManager.setService(
this.serviceData,
TABLE_NAME.TRANSACTION,
);
await this.batchDeleteManager.execute();
return this.batchDeleteManager.getResult();
}
async cancel(dataId): Promise<String> {
this.cancelManager.setData(dataId, STATUS.CANCEL);
this.cancelManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);
await this.cancelManager.execute();
return this.cancelManager.getResult();
}
async batchCancel(dataIds: string[]): Promise<BatchResult> {
this.batchCancelManager.setData(dataIds, STATUS.CANCEL);
this.batchCancelManager.setService(
this.serviceData,
TABLE_NAME.TRANSACTION,
);
await this.batchCancelManager.execute();
return this.batchCancelManager.getResult();
}
async confirm(dataId): Promise<String> {
this.confirmManager.setData(dataId, STATUS.ACTIVE);
this.confirmManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);
await this.confirmManager.execute();
return this.confirmManager.getResult();
}
async batchConfirm(dataIds: string[]): Promise<BatchResult> {
this.batchConfirmManager.setData(dataIds, STATUS.ACTIVE);
this.batchConfirmManager.setService(
this.serviceData,
TABLE_NAME.TRANSACTION,
);
await this.batchConfirmManager.execute();
return this.batchConfirmManager.getResult();
}
async confirmData(dataId): Promise<String> {
this.confirmDataManager.setData(dataId, STATUS.ACTIVE);
this.confirmDataManager.setService(
this.serviceData,
TABLE_NAME.TRANSACTION,
);
await this.confirmDataManager.execute();
return this.confirmDataManager.getResult();
}
async batchConfirmData(dataIds: string[]): Promise<BatchResult> {
this.batchConfirmDataManager.setData(dataIds, STATUS.ACTIVE);
this.batchConfirmDataManager.setService(
this.serviceData,
TABLE_NAME.TRANSACTION,
);
await this.batchConfirmDataManager.execute();
return this.batchConfirmDataManager.getResult();
}
}

View File

@ -0,0 +1,33 @@
import { Injectable } from '@nestjs/common';
import { IndexTransactionManager } from './managers/index-transaction.manager';
import { TransactionReadService } from '../../data/services/transaction-read.service';
import { TransactionEntity } from '../entities/transaction.entity';
import { PaginationResponse } from 'src/core/response/domain/ok-response.interface';
import { BaseReadOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-read.orchestrator';
import { DetailTransactionManager } from './managers/detail-transaction.manager';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
@Injectable()
export class TransactionReadOrchestrator extends BaseReadOrchestrator<TransactionEntity> {
constructor(
private indexManager: IndexTransactionManager,
private detailManager: DetailTransactionManager,
private serviceData: TransactionReadService,
) {
super();
}
async index(params): Promise<PaginationResponse<TransactionEntity>> {
this.indexManager.setFilterParam(params);
this.indexManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);
await this.indexManager.execute();
return this.indexManager.getResult();
}
async detail(dataId: string): Promise<TransactionEntity> {
this.detailManager.setData(dataId);
this.detailManager.setService(this.serviceData, TABLE_NAME.TRANSACTION);
await this.detailManager.execute();
return this.detailManager.getResult();
}
}

View File

@ -0,0 +1,75 @@
import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.dto';
import { FilterTransactionEntity } from '../../domain/entities/filter-transaction.entity';
import { Transform } from 'class-transformer';
import { ApiProperty } from '@nestjs/swagger';
export class FilterTransactionDto
extends BaseFilterDto
implements FilterTransactionEntity
{
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];
})
types?: string[];
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];
})
customer_types?: string[];
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];
})
customer_names?: string[];
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];
})
payment_types?: string[];
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];
})
payment_banks?: string[];
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];
})
invoice_codes?: string[];
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];
})
refund_codes?: string[];
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];
})
creator_names?: string[];
@ApiProperty({ type: Date, required: false })
booking_date_from?: Date;
@ApiProperty({ type: Date, required: false })
booking_date_to?: Date;
@ApiProperty({ type: Date, required: false })
invoice_date_from?: Date;
@ApiProperty({ type: Date, required: false })
invoice_date_to?: Date;
@ApiProperty({ type: Date, required: false })
settlement_date_from?: Date;
@ApiProperty({ type: Date, required: false })
settlement_date_to?: Date;
}

View File

@ -0,0 +1,39 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsObject } from 'class-validator';
import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity';
export class TransactionItemDto {
@ApiProperty({
type: Object,
required: true,
example: {
id: 'uuid',
name: 'Bundling 1',
price: 10000,
hpp: 1000,
tenant: {
id: 'uuid',
name: 'tenant 1',
share_margin: 10,
},
},
})
@IsObject()
item: ItemEntity;
@ApiProperty({
type: Number,
required: true,
example: 1,
})
@IsNumber()
total_price: number;
@ApiProperty({
type: Number,
required: true,
example: 1,
})
@IsNumber()
qty: number;
}

View File

@ -0,0 +1,171 @@
import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.dto';
import { TransactionUserType, TransactionPaymentType } from '../../constants';
import { ApiProperty } from '@nestjs/swagger';
import {
IsArray,
IsEmail,
IsNumber,
IsObject,
IsString,
ValidateIf,
} from 'class-validator';
import { SeasonPeriodEntity } from 'src/modules/season-related/season-period/domain/entities/season-period.entity';
import { TransactionItemEntity } from '../../domain/entities/transaction-item.entity';
export class TransactionDto extends BaseStatusDto {
@ApiProperty({
type: Object,
required: false,
example: {
id: 'uuid',
season_type: {
id: 'uuid',
name: 'high season',
},
},
})
@IsObject()
@ValidateIf((body) => body.season_period)
season_period: SeasonPeriodEntity;
@ApiProperty({
type: String,
required: true,
example: TransactionUserType.GROUP,
})
@IsString()
customer_type: TransactionUserType;
@ApiProperty({
type: String,
required: true,
example: 'Andika',
})
@IsString()
customer_name: string;
@ApiProperty({
type: String,
required: false,
example: '0823...',
})
@ValidateIf((body) => body.customer_phone)
customer_phone: string;
@ApiProperty({ required: false, example: 'andika@mail.com' })
@IsEmail({ ignore_max_length: true })
@ValidateIf((body) => body.customer_email)
customer_email: string;
@ApiProperty({
type: String,
required: false,
example: 'Influencer',
})
@ValidateIf((body) => body.customer_description)
customer_description: string;
@ApiProperty({
type: Number,
required: true,
example: 1,
})
@IsNumber()
no_of_group: number;
@ApiProperty({
type: Date,
required: true,
example: '2024-01-01',
})
booking_date: Date;
@ApiProperty({
type: String,
required: false,
})
@ValidateIf((body) => body.discount_code)
discount_code: string;
@ApiProperty({
type: Number,
required: false,
})
@ValidateIf((body) => body.discount_percentage)
discount_percentage: number;
@ApiProperty({
type: Number,
required: false,
})
@ValidateIf((body) => body.discount_value)
discount_value: number;
@ApiProperty({
type: String,
required: true,
example: TransactionPaymentType.MIDTRANS,
})
@IsString()
payment_type: TransactionPaymentType;
@ApiProperty({
type: Number,
required: true,
example: 7000000,
})
@IsNumber()
payment_sub_total: number;
@ApiProperty({
type: Number,
required: false,
example: 3500000,
})
@IsNumber()
@ValidateIf((body) => body.payment_discount_total)
payment_discount_total: number;
@ApiProperty({
type: Number,
required: true,
example: 3500000,
})
@IsNumber()
payment_total: number;
@ApiProperty({
type: [Object],
required: true,
example: [
{
item: {
id: '68aa12f7-2cce-422b-9bae-185eb1343b94',
created_at: '1718876384378',
status: 'active',
name: 'tes',
item_type: 'bundling',
hpp: '100000',
base_price: '100000',
limit_type: 'no limit',
limit_value: 0,
item_category: {
id: 'ab15981a-a656-4efc-856c-b2abfbe30979',
name: 'Kategori Bundling 2',
},
bundling_items: [
{
id: 'bd5a7a38-df25-4203-a1cd-bf94867946b2',
name: 'Wahana 21 panjangggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg',
},
],
tenant: null,
},
qty: 40,
total_price: 4000000,
},
],
})
@IsArray()
items: TransactionItemEntity[];
}

View File

@ -0,0 +1,78 @@
import {
Body,
Controller,
Delete,
Param,
Patch,
Post,
Put,
} from '@nestjs/common';
import { TransactionDataOrchestrator } from '../domain/usecases/transaction-data.orchestrator';
import { TransactionDto } from './dto/transaction.dto';
import { MODULE_NAME } from 'src/core/strings/constants/module.constants';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { TransactionEntity } from '../domain/entities/transaction.entity';
import { BatchResult } from 'src/core/response/domain/ok-response.interface';
import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto';
import { Public } from 'src/core/guards';
@ApiTags(`${MODULE_NAME.TRANSACTION.split('-').join(' ')} - data`)
@Controller(`v1/${MODULE_NAME.TRANSACTION}`)
@Public(false)
@ApiBearerAuth('JWT')
export class TransactionDataController {
constructor(private orchestrator: TransactionDataOrchestrator) {}
@Post()
async create(@Body() data: TransactionDto): Promise<TransactionEntity> {
return await this.orchestrator.create(data);
}
@Put('/batch-delete')
async batchDeleted(@Body() body: BatchIdsDto): Promise<BatchResult> {
return await this.orchestrator.batchDelete(body.ids);
}
@Patch(':id/confirm-data')
async confirmData(@Param('id') dataId: string): Promise<String> {
return await this.orchestrator.confirmData(dataId);
}
@Put('/batch-confirm-data')
async batchConfirmData(@Body() body: BatchIdsDto): Promise<BatchResult> {
return await this.orchestrator.batchConfirmData(body.ids);
}
@Patch(':id/confirm')
async confirm(@Param('id') dataId: string): Promise<String> {
return await this.orchestrator.confirm(dataId);
}
@Put('/batch-confirm')
async batchConfirm(@Body() body: BatchIdsDto): Promise<BatchResult> {
return await this.orchestrator.batchConfirm(body.ids);
}
@Patch(':id/cancel')
async cancel(@Param('id') dataId: string): Promise<String> {
return await this.orchestrator.cancel(dataId);
}
@Put('/batch-cancel')
async batchCancel(@Body() body: BatchIdsDto): Promise<BatchResult> {
return await this.orchestrator.batchCancel(body.ids);
}
@Put(':id')
async update(
@Param('id') dataId: string,
@Body() data: TransactionDto,
): Promise<TransactionEntity> {
return await this.orchestrator.update(dataId, data);
}
@Delete(':id')
async delete(@Param('id') dataId: string): Promise<String> {
return await this.orchestrator.delete(dataId);
}
}

View File

@ -0,0 +1,30 @@
import { Controller, Get, Param, Query } from '@nestjs/common';
import { FilterTransactionDto } from './dto/filter-transaction.dto';
import { Pagination } from 'src/core/response';
import { PaginationResponse } from 'src/core/response/domain/ok-response.interface';
import { TransactionEntity } from '../domain/entities/transaction.entity';
import { TransactionReadOrchestrator } from '../domain/usecases/transaction-read.orchestrator';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { MODULE_NAME } from 'src/core/strings/constants/module.constants';
import { Public } from 'src/core/guards';
@ApiTags(`${MODULE_NAME.TRANSACTION.split('-').join(' ')} - read`)
@Controller(`v1/${MODULE_NAME.TRANSACTION}`)
@Public(false)
@ApiBearerAuth('JWT')
export class TransactionReadController {
constructor(private orchestrator: TransactionReadOrchestrator) {}
@Get()
@Pagination()
async index(
@Query() params: FilterTransactionDto,
): Promise<PaginationResponse<TransactionEntity>> {
return await this.orchestrator.index(params);
}
@Get(':id')
async detail(@Param('id') id: string): Promise<TransactionEntity> {
return await this.orchestrator.detail(id);
}
}

View File

@ -0,0 +1,59 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { TransactionDataService } from './data/services/transaction-data.service';
import { TransactionReadService } from './data/services/transaction-read.service';
import { TransactionReadController } from './infrastructure/transaction-read.controller';
import { TransactionReadOrchestrator } from './domain/usecases/transaction-read.orchestrator';
import { TransactionDataController } from './infrastructure/transaction-data.controller';
import { TransactionDataOrchestrator } from './domain/usecases/transaction-data.orchestrator';
import { CreateTransactionManager } from './domain/usecases/managers/create-transaction.manager';
import { CqrsModule } from '@nestjs/cqrs';
import { IndexTransactionManager } from './domain/usecases/managers/index-transaction.manager';
import { DeleteTransactionManager } from './domain/usecases/managers/delete-transaction.manager';
import { UpdateTransactionManager } from './domain/usecases/managers/update-transaction.manager';
import { ConfirmTransactionManager } from './domain/usecases/managers/confirm-transaction.manager';
import { DetailTransactionManager } from './domain/usecases/managers/detail-transaction.manager';
import { BatchDeleteTransactionManager } from './domain/usecases/managers/batch-delete-transaction.manager';
import { BatchConfirmTransactionManager } from './domain/usecases/managers/batch-confirm-transaction.manager';
import { TransactionModel } from './data/models/transaction.model';
import { TransactionItemModel } from './data/models/transaction-item.model';
import { TransactionTaxModel } from './data/models/transaction-tax.model';
import { CancelTransactionManager } from './domain/usecases/managers/cancel-transaction.manager';
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';
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forFeature(
[TransactionModel, TransactionItemModel, TransactionTaxModel],
CONNECTION_NAME.DEFAULT,
),
CqrsModule,
],
controllers: [TransactionDataController, TransactionReadController],
providers: [
IndexTransactionManager,
DetailTransactionManager,
CreateTransactionManager,
DeleteTransactionManager,
UpdateTransactionManager,
ConfirmTransactionManager,
BatchDeleteTransactionManager,
BatchConfirmTransactionManager,
CancelTransactionManager,
BatchCancelTransactionManager,
ConfirmDataTransactionManager,
BatchConfirmDataTransactionManager,
TransactionDataService,
TransactionReadService,
TransactionDataOrchestrator,
TransactionReadOrchestrator,
],
})
export class TransactionModule {}