From 2ed4ce01990071d4df2f060ef35608429bdc9af4 Mon Sep 17 00:00:00 2001 From: Aswin Ashar Abdullah Date: Fri, 12 Jul 2024 17:30:15 +0700 Subject: [PATCH] feat(SPG-389) REST API CUD Refund --- src/app.module.ts | 6 + .../base-batch-update-status.manager.ts | 2 + .../usecase/managers/base-delete.manager.ts | 2 +- src/core/strings/constants/base.constants.ts | 2 + .../strings/constants/module.constants.ts | 1 + src/core/strings/constants/table.constants.ts | 2 + .../{{dashCase name}}-read.controller.ts.hbs | 2 +- .../{{dashCase name}}-data.controller.ts.hbs | 2 +- .../{{dashCase name}}-data.controller.ts.hbs | 2 +- .../1720767689625-update-table-transaction.ts | 27 + .../migrations/1720768975877-refund.ts | 45 ++ .../1720774145470-update-enum-status.ts | 461 ++++++++++++++++++ .../infrastructure/constant.controller.ts | 6 + .../couch/infrastructure/couch.controller.ts | 13 +- src/modules/transaction/refund/constants.ts | 4 + .../refund/data/models/refund-item.model.ts | 38 ++ .../refund/data/models/refund.model.ts | 60 +++ .../data/services/refund-data.service.ts | 17 + .../event/refund-change-status.event.ts | 5 + .../entities/event/refund-created.event.ts | 5 + .../entities/event/refund-deleted.event.ts | 5 + .../entities/event/refund-updated.event.ts | 5 + .../domain/entities/refund-item.entity.ts | 7 + .../refund/domain/entities/refund.entity.ts | 16 + .../managers/batch-cancel-refund.manager.ts | 57 +++ .../managers/batch-confirm-refund.manager.ts | 72 +++ .../managers/batch-delete-refund.manager.ts | 45 ++ .../managers/cancel-refund.manager.ts | 57 +++ .../managers/confirm-refund.manager.ts | 77 +++ .../managers/create-refund.manager.ts | 54 ++ .../managers/delete-refund.manager.ts | 45 ++ .../managers/update-refund.manager.ts | 58 +++ .../usecases/refund-data.orchestrator.ts | 85 ++++ src/modules/transaction/refund/index.ts | 0 .../refund/infrastructure/dto/refund.dto.ts | 67 +++ .../infrastructure/refund-data.controller.ts | 68 +++ .../transaction/refund/refund.module.ts | 54 ++ .../data/models/transaction-item.model.ts | 17 +- .../data/models/transaction.model.ts | 11 +- .../entities/transaction-item.entity.ts | 2 + .../handlers/refund-update.handler.ts | 52 ++ .../transaction/transaction.module.ts | 2 + 42 files changed, 1542 insertions(+), 16 deletions(-) create mode 100644 src/database/migrations/1720767689625-update-table-transaction.ts create mode 100644 src/database/migrations/1720768975877-refund.ts create mode 100644 src/database/migrations/1720774145470-update-enum-status.ts create mode 100644 src/modules/transaction/refund/constants.ts create mode 100644 src/modules/transaction/refund/data/models/refund-item.model.ts create mode 100644 src/modules/transaction/refund/data/models/refund.model.ts create mode 100644 src/modules/transaction/refund/data/services/refund-data.service.ts create mode 100644 src/modules/transaction/refund/domain/entities/event/refund-change-status.event.ts create mode 100644 src/modules/transaction/refund/domain/entities/event/refund-created.event.ts create mode 100644 src/modules/transaction/refund/domain/entities/event/refund-deleted.event.ts create mode 100644 src/modules/transaction/refund/domain/entities/event/refund-updated.event.ts create mode 100644 src/modules/transaction/refund/domain/entities/refund-item.entity.ts create mode 100644 src/modules/transaction/refund/domain/entities/refund.entity.ts create mode 100644 src/modules/transaction/refund/domain/usecases/managers/batch-cancel-refund.manager.ts create mode 100644 src/modules/transaction/refund/domain/usecases/managers/batch-confirm-refund.manager.ts create mode 100644 src/modules/transaction/refund/domain/usecases/managers/batch-delete-refund.manager.ts create mode 100644 src/modules/transaction/refund/domain/usecases/managers/cancel-refund.manager.ts create mode 100644 src/modules/transaction/refund/domain/usecases/managers/confirm-refund.manager.ts create mode 100644 src/modules/transaction/refund/domain/usecases/managers/create-refund.manager.ts create mode 100644 src/modules/transaction/refund/domain/usecases/managers/delete-refund.manager.ts create mode 100644 src/modules/transaction/refund/domain/usecases/managers/update-refund.manager.ts create mode 100644 src/modules/transaction/refund/domain/usecases/refund-data.orchestrator.ts create mode 100644 src/modules/transaction/refund/index.ts create mode 100644 src/modules/transaction/refund/infrastructure/dto/refund.dto.ts create mode 100644 src/modules/transaction/refund/infrastructure/refund-data.controller.ts create mode 100644 src/modules/transaction/refund/refund.module.ts create mode 100644 src/modules/transaction/transaction/domain/usecases/handlers/refund-update.handler.ts diff --git a/src/app.module.ts b/src/app.module.ts index 9ea5bf5..8a49cd4 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -55,6 +55,9 @@ import { ReportBookmarkModel } from './modules/reports/shared/models/report-book import { ExportReportHistoryModel } from './modules/reports/shared/models/export-report-history.model'; import { CronModule } from './modules/configuration/cron/cron.module'; import { MidtransModule } from './modules/configuration/midtrans/midtrans.module'; +import { RefundModule } from './modules/transaction/refund/refund.module'; +import { RefundModel } from './modules/transaction/refund/data/models/refund.model'; +import { RefundItemModel } from './modules/transaction/refund/data/models/refund-item.model'; @Module({ imports: [ @@ -78,6 +81,8 @@ import { MidtransModule } from './modules/configuration/midtrans/midtrans.module ItemRateModel, LogModel, PaymentMethodModel, + RefundModel, + RefundItemModel, SalesPriceFormulaModel, SeasonPeriodModel, SeasonTypeModel, @@ -119,6 +124,7 @@ import { MidtransModule } from './modules/configuration/midtrans/midtrans.module PaymentMethodModule, ProfitShareFormulaModule, ReconciliationModule, + RefundModule, SalesPriceFormulaModule, TaxModule, TransactionModule, diff --git a/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts b/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts index afc2680..127875a 100644 --- a/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts @@ -8,6 +8,7 @@ import * as _ from 'lodash'; export abstract class BaseBatchUpdateStatusManager extends BaseManager { protected dataIds: string[]; + protected relations: string[] = []; protected result: BatchResult; protected dataStatus: STATUS; protected oldData: Entity; @@ -37,6 +38,7 @@ export abstract class BaseBatchUpdateStatusManager extends BaseManager { where: { id: id, }, + relations: this.relations, }); if (!entity) { diff --git a/src/core/modules/domain/usecase/managers/base-delete.manager.ts b/src/core/modules/domain/usecase/managers/base-delete.manager.ts index 4d0692b..3f6e484 100644 --- a/src/core/modules/domain/usecase/managers/base-delete.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-delete.manager.ts @@ -67,7 +67,7 @@ export abstract class BaseDeleteManager extends BaseManager { this.eventBus.publishAll([ new topic.topic({ id: topic.data['id'], - old: null, + old: this.data, data: topic.data, user: this.user, description: '', diff --git a/src/core/strings/constants/base.constants.ts b/src/core/strings/constants/base.constants.ts index bab60da..127b142 100644 --- a/src/core/strings/constants/base.constants.ts +++ b/src/core/strings/constants/base.constants.ts @@ -6,7 +6,9 @@ export enum STATUS { DRAFT = 'draft', EXPIRED = 'expired', INACTIVE = 'inactive', + PARTIAL_REFUND = 'partial refund', PENDING = 'pending', + PROCESS_REFUND = 'proses refund', REFUNDED = 'refunded', REJECTED = 'rejected', SETTLED = 'settled', diff --git a/src/core/strings/constants/module.constants.ts b/src/core/strings/constants/module.constants.ts index daf358b..a53b60e 100644 --- a/src/core/strings/constants/module.constants.ts +++ b/src/core/strings/constants/module.constants.ts @@ -4,6 +4,7 @@ export enum MODULE_NAME { ITEM_RATE = 'item-rates', PAYMENT_METHOD = 'payment-methods', RECONCILIATION = 'reconciliations', + REFUND = 'refunds', SEASON_TYPE = 'season-types', SEASON_PERIOD = 'season-periods', TAX = 'taxes', diff --git a/src/core/strings/constants/table.constants.ts b/src/core/strings/constants/table.constants.ts index f803516..3d3230d 100644 --- a/src/core/strings/constants/table.constants.ts +++ b/src/core/strings/constants/table.constants.ts @@ -6,6 +6,8 @@ export enum TABLE_NAME { LOG = 'logs', PAYMENT_METHOD = 'payment_methods', PRICE_FORMULA = 'price_formulas', + REFUND = 'refunds', + REFUND_ITEM = 'refund_items', SEASON_TYPE = 'season_types', SEASON_PERIOD = 'season_periods', TAX = 'taxes', diff --git a/src/core/templates/controllers/base-read/{{dashCase name}}-read.controller.ts.hbs b/src/core/templates/controllers/base-read/{{dashCase name}}-read.controller.ts.hbs index 50598ec..01b8800 100644 --- a/src/core/templates/controllers/base-read/{{dashCase name}}-read.controller.ts.hbs +++ b/src/core/templates/controllers/base-read/{{dashCase name}}-read.controller.ts.hbs @@ -9,7 +9,7 @@ import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; import { Public } from 'src/core/guards'; @ApiTags(`${MODULE_NAME.{{constantCase name}}.split('-').join(' ')} - read`) -@Controller(`v1/${MODULE_NAME.{{constantCase name}}}`) +@Controller(`v1/${MODULE_NAME.{{constantCase name}} }`) @Public(false) @ApiBearerAuth('JWT') export class {{pascalCase name}}ReadController { diff --git a/src/core/templates/controllers/base-status/{{dashCase name}}-data.controller.ts.hbs b/src/core/templates/controllers/base-status/{{dashCase name}}-data.controller.ts.hbs index 229c14d..755d5a3 100644 --- a/src/core/templates/controllers/base-status/{{dashCase name}}-data.controller.ts.hbs +++ b/src/core/templates/controllers/base-status/{{dashCase name}}-data.controller.ts.hbs @@ -17,7 +17,7 @@ import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto' import { Public } from 'src/core/guards'; @ApiTags(`${MODULE_NAME.{{constantCase name}}.split('-').join(' ')} - data`) -@Controller(`v1/${MODULE_NAME.{{constantCase name}}}`) +@Controller(`v1/${MODULE_NAME.{{constantCase name}} }`) @Public(false) @ApiBearerAuth('JWT') export class {{pascalCase name}}DataController { diff --git a/src/core/templates/controllers/base/{{dashCase name}}-data.controller.ts.hbs b/src/core/templates/controllers/base/{{dashCase name}}-data.controller.ts.hbs index cf4487d..706f9df 100644 --- a/src/core/templates/controllers/base/{{dashCase name}}-data.controller.ts.hbs +++ b/src/core/templates/controllers/base/{{dashCase name}}-data.controller.ts.hbs @@ -16,7 +16,7 @@ import { import { Public } from 'src/core/guards'; @ApiTags(`${MODULE_NAME.{{constantCase name}}.split('-').join(' ')} - data`) - @Controller(`v1/${MODULE_NAME.{{constantCase name}}}`) + @Controller(`v1/${MODULE_NAME.{{constantCase name}} }`) @Public(false) @ApiBearerAuth('JWT') export class {{pascalCase name}}DataController { diff --git a/src/database/migrations/1720767689625-update-table-transaction.ts b/src/database/migrations/1720767689625-update-table-transaction.ts new file mode 100644 index 0000000..2885e26 --- /dev/null +++ b/src/database/migrations/1720767689625-update-table-transaction.ts @@ -0,0 +1,27 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateTableTransaction1720767689625 implements MigrationInterface { + name = 'UpdateTableTransaction1720767689625'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transaction_items" ADD "qty_remaining" integer`, + ); + await queryRunner.query(`ALTER TABLE "transaction_items" ADD "taxes" json`); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "payment_total_dpp" numeric`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "payment_total_dpp"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" DROP COLUMN "taxes"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" DROP COLUMN "qty_remaining"`, + ); + } +} diff --git a/src/database/migrations/1720768975877-refund.ts b/src/database/migrations/1720768975877-refund.ts new file mode 100644 index 0000000..5e00472 --- /dev/null +++ b/src/database/migrations/1720768975877-refund.ts @@ -0,0 +1,45 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Refund1720768975877 implements MigrationInterface { + name = 'Refund1720768975877'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."refunds_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `CREATE TYPE "public"."refunds_type_enum" AS ENUM('pengembalian booking', 'pengembalian wahana')`, + ); + await queryRunner.query( + `CREATE TABLE "refunds" ("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"."refunds_status_enum" NOT NULL DEFAULT 'draft', "type" "public"."refunds_type_enum" NOT NULL DEFAULT 'pengembalian booking', "code" character varying, "request_date" date, "refund_date" date, "refund_total" numeric, "bank_name" character varying, "bank_account_name" character varying, "bank_account_number" character varying, "transaction_id" uuid, CONSTRAINT "REL_8bb3b7579f49990d2e77684acd" UNIQUE ("transaction_id"), CONSTRAINT "PK_5106efb01eeda7e49a78b869738" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE TABLE "refund_items" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "qty_refund" numeric, "refund_total" numeric, "refund_item_id" uuid, "transaction_item_id" uuid, CONSTRAINT "REL_07b481a163c219f5de8fb1c90b" UNIQUE ("transaction_item_id"), CONSTRAINT "PK_ef892918375a6101948b90f1140" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ADD CONSTRAINT "FK_8bb3b7579f49990d2e77684acd4" FOREIGN KEY ("transaction_id") REFERENCES "transactions"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "refund_items" ADD CONSTRAINT "FK_2a4bd60fb8a9c37f902f4f3da67" FOREIGN KEY ("refund_item_id") REFERENCES "refunds"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "refund_items" ADD CONSTRAINT "FK_07b481a163c219f5de8fb1c90b3" FOREIGN KEY ("transaction_item_id") REFERENCES "transaction_items"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "refund_items" DROP CONSTRAINT "FK_07b481a163c219f5de8fb1c90b3"`, + ); + await queryRunner.query( + `ALTER TABLE "refund_items" DROP CONSTRAINT "FK_2a4bd60fb8a9c37f902f4f3da67"`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" DROP CONSTRAINT "FK_8bb3b7579f49990d2e77684acd4"`, + ); + await queryRunner.query(`DROP TABLE "refund_items"`); + await queryRunner.query(`DROP TABLE "refunds"`); + await queryRunner.query(`DROP TYPE "public"."refunds_type_enum"`); + await queryRunner.query(`DROP TYPE "public"."refunds_status_enum"`); + } +} diff --git a/src/database/migrations/1720774145470-update-enum-status.ts b/src/database/migrations/1720774145470-update-enum-status.ts new file mode 100644 index 0000000..3c44eb3 --- /dev/null +++ b/src/database/migrations/1720774145470-update-enum-status.ts @@ -0,0 +1,461 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateEnumStatus1720774145470 implements MigrationInterface { + name = 'UpdateEnumStatus1720774145470'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TYPE "public"."item_categories_status_enum" RENAME TO "item_categories_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."item_categories_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "item_categories" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "item_categories" ALTER COLUMN "status" TYPE "public"."item_categories_status_enum" USING "status"::"text"::"public"."item_categories_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "item_categories" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."item_categories_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."season_types_status_enum" RENAME TO "season_types_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."season_types_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "season_types" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "season_types" ALTER COLUMN "status" TYPE "public"."season_types_status_enum" USING "status"::"text"::"public"."season_types_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "season_types" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."season_types_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."season_periods_status_enum" RENAME TO "season_periods_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."season_periods_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "season_periods" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "season_periods" ALTER COLUMN "status" TYPE "public"."season_periods_status_enum" USING "status"::"text"::"public"."season_periods_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "season_periods" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."season_periods_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."items_status_enum" RENAME TO "items_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."items_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "items" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "items" ALTER COLUMN "status" TYPE "public"."items_status_enum" USING "status"::"text"::"public"."items_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "items" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."items_status_enum_old"`); + await queryRunner.query( + `ALTER TYPE "public"."users_status_enum" RENAME TO "users_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."users_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "status" TYPE "public"."users_status_enum" USING "status"::"text"::"public"."users_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."users_status_enum_old"`); + await queryRunner.query( + `ALTER TYPE "public"."user_privileges_status_enum" RENAME TO "user_privileges_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."user_privileges_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "user_privileges" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "user_privileges" ALTER COLUMN "status" TYPE "public"."user_privileges_status_enum" USING "status"::"text"::"public"."user_privileges_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "user_privileges" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."user_privileges_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."refunds_status_enum" RENAME TO "refunds_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."refunds_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ALTER COLUMN "status" TYPE "public"."refunds_status_enum" USING "status"::"text"::"public"."refunds_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."refunds_status_enum_old"`); + await queryRunner.query( + `ALTER TYPE "public"."transactions_status_enum" RENAME TO "transactions_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "status" TYPE "public"."transactions_status_enum" USING "status"::"text"::"public"."transactions_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."transactions_reconciliation_status_enum" RENAME TO "transactions_reconciliation_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_reconciliation_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" TYPE "public"."transactions_reconciliation_status_enum" USING "reconciliation_status"::"text"::"public"."transactions_reconciliation_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_reconciliation_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."transactions_sending_invoice_status_enum" RENAME TO "transactions_sending_invoice_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_sending_invoice_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "sending_invoice_status" TYPE "public"."transactions_sending_invoice_status_enum" USING "sending_invoice_status"::"text"::"public"."transactions_sending_invoice_status_enum"`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_sending_invoice_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."transactions_sending_qr_status_enum" RENAME TO "transactions_sending_qr_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_sending_qr_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "sending_qr_status" TYPE "public"."transactions_sending_qr_status_enum" USING "sending_qr_status"::"text"::"public"."transactions_sending_qr_status_enum"`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_sending_qr_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."vip_categories_status_enum" RENAME TO "vip_categories_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."vip_categories_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "vip_categories" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "vip_categories" ALTER COLUMN "status" TYPE "public"."vip_categories_status_enum" USING "status"::"text"::"public"."vip_categories_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "vip_categories" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."vip_categories_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."taxes_status_enum" RENAME TO "taxes_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."taxes_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "taxes" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "taxes" ALTER COLUMN "status" TYPE "public"."taxes_status_enum" USING "status"::"text"::"public"."taxes_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "taxes" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."taxes_status_enum_old"`); + await queryRunner.query( + `ALTER TYPE "public"."payment_methods_status_enum" RENAME TO "payment_methods_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."payment_methods_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "payment_methods" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "payment_methods" ALTER COLUMN "status" TYPE "public"."payment_methods_status_enum" USING "status"::"text"::"public"."payment_methods_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "payment_methods" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."payment_methods_status_enum_old"`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."payment_methods_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "payment_methods" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "payment_methods" ALTER COLUMN "status" TYPE "public"."payment_methods_status_enum_old" USING "status"::"text"::"public"."payment_methods_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "payment_methods" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."payment_methods_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."payment_methods_status_enum_old" RENAME TO "payment_methods_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."taxes_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "taxes" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "taxes" ALTER COLUMN "status" TYPE "public"."taxes_status_enum_old" USING "status"::"text"::"public"."taxes_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "taxes" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."taxes_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."taxes_status_enum_old" RENAME TO "taxes_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."vip_categories_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "vip_categories" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "vip_categories" ALTER COLUMN "status" TYPE "public"."vip_categories_status_enum_old" USING "status"::"text"::"public"."vip_categories_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "vip_categories" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."vip_categories_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."vip_categories_status_enum_old" RENAME TO "vip_categories_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_sending_qr_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "sending_qr_status" TYPE "public"."transactions_sending_qr_status_enum_old" USING "sending_qr_status"::"text"::"public"."transactions_sending_qr_status_enum_old"`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_sending_qr_status_enum"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."transactions_sending_qr_status_enum_old" RENAME TO "transactions_sending_qr_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_sending_invoice_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "sending_invoice_status" TYPE "public"."transactions_sending_invoice_status_enum_old" USING "sending_invoice_status"::"text"::"public"."transactions_sending_invoice_status_enum_old"`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_sending_invoice_status_enum"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."transactions_sending_invoice_status_enum_old" RENAME TO "transactions_sending_invoice_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_reconciliation_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" TYPE "public"."transactions_reconciliation_status_enum_old" USING "reconciliation_status"::"text"::"public"."transactions_reconciliation_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_reconciliation_status_enum"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."transactions_reconciliation_status_enum_old" RENAME TO "transactions_reconciliation_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "status" TYPE "public"."transactions_status_enum_old" USING "status"::"text"::"public"."transactions_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."transactions_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."transactions_status_enum_old" RENAME TO "transactions_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."refunds_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ALTER COLUMN "status" TYPE "public"."refunds_status_enum_old" USING "status"::"text"::"public"."refunds_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."refunds_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."refunds_status_enum_old" RENAME TO "refunds_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."user_privileges_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "user_privileges" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "user_privileges" ALTER COLUMN "status" TYPE "public"."user_privileges_status_enum_old" USING "status"::"text"::"public"."user_privileges_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "user_privileges" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."user_privileges_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."user_privileges_status_enum_old" RENAME TO "user_privileges_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."users_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "status" TYPE "public"."users_status_enum_old" USING "status"::"text"::"public"."users_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."users_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."users_status_enum_old" RENAME TO "users_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."items_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "items" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "items" ALTER COLUMN "status" TYPE "public"."items_status_enum_old" USING "status"::"text"::"public"."items_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "items" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."items_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."items_status_enum_old" RENAME TO "items_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."season_periods_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "season_periods" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "season_periods" ALTER COLUMN "status" TYPE "public"."season_periods_status_enum_old" USING "status"::"text"::"public"."season_periods_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "season_periods" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."season_periods_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."season_periods_status_enum_old" RENAME TO "season_periods_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."season_types_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "season_types" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "season_types" ALTER COLUMN "status" TYPE "public"."season_types_status_enum_old" USING "status"::"text"::"public"."season_types_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "season_types" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."season_types_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."season_types_status_enum_old" RENAME TO "season_types_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."item_categories_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "item_categories" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "item_categories" ALTER COLUMN "status" TYPE "public"."item_categories_status_enum_old" USING "status"::"text"::"public"."item_categories_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "item_categories" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."item_categories_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."item_categories_status_enum_old" RENAME TO "item_categories_status_enum"`, + ); + } +} diff --git a/src/modules/configuration/constant/infrastructure/constant.controller.ts b/src/modules/configuration/constant/infrastructure/constant.controller.ts index 61bba1f..99c6d99 100644 --- a/src/modules/configuration/constant/infrastructure/constant.controller.ts +++ b/src/modules/configuration/constant/infrastructure/constant.controller.ts @@ -5,6 +5,7 @@ import { STATUS } from 'src/core/strings/constants/base.constants'; import { ItemType } from 'src/modules/item-related/item-category/constants'; import { LimitType } from 'src/modules/item-related/item/constants'; import { PaymentMethodType } from 'src/modules/transaction/payment-method/constants'; +import { RefundType } from 'src/modules/transaction/refund/constants'; @ApiTags('configuration - constant') @Controller('v1/constant') @@ -44,4 +45,9 @@ export class ConstantController { async transactionType(): Promise { return ['counter', 'admin', 'online']; } + + @Get('refund-type') + async refundType(): Promise { + return Object.values(RefundType); + } } diff --git a/src/modules/configuration/couch/infrastructure/couch.controller.ts b/src/modules/configuration/couch/infrastructure/couch.controller.ts index 195f8e4..c7a9d5c 100644 --- a/src/modules/configuration/couch/infrastructure/couch.controller.ts +++ b/src/modules/configuration/couch/infrastructure/couch.controller.ts @@ -23,9 +23,7 @@ export class CouchDataController { try { const n = this.nanoInstance; await n.db.create(entity.name); - } catch (error) { - console.log(error, 'dsa'); - } + } catch (error) {} } @Post('doc') @@ -33,7 +31,6 @@ export class CouchDataController { try { const nano = this.nanoInstance; const people = nano.db.use('string'); - console.log(await people.info(), entity); // const data = { // id: '1212', // name: 'dsadas', @@ -54,9 +51,7 @@ export class CouchDataController { .on('error', (e) => { console.error('error', e); }); - } catch (error) { - console.log(error, 'dsa'); - } + } catch (error) {} } @Get() @@ -67,8 +62,6 @@ export class CouchDataController { return people; // return people.get(); - } catch (error) { - console.log(error, 'dsa'); - } + } catch (error) {} } } diff --git a/src/modules/transaction/refund/constants.ts b/src/modules/transaction/refund/constants.ts new file mode 100644 index 0000000..0c9b67b --- /dev/null +++ b/src/modules/transaction/refund/constants.ts @@ -0,0 +1,4 @@ +export enum RefundType { + BOOKING = 'pengembalian booking', + WAHANA = 'pengembalian wahana', +} diff --git a/src/modules/transaction/refund/data/models/refund-item.model.ts b/src/modules/transaction/refund/data/models/refund-item.model.ts new file mode 100644 index 0000000..5ab17c8 --- /dev/null +++ b/src/modules/transaction/refund/data/models/refund-item.model.ts @@ -0,0 +1,38 @@ +import { BaseCoreModel } from 'src/core/modules/data/model/base-core.model'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from 'typeorm'; +import { RefundItemEntity } from '../../domain/entities/refund-item.entity'; +import { TransactionItemModel } from 'src/modules/transaction/transaction/data/models/transaction-item.model'; +import { RefundModel } from './refund.model'; + +@Entity(TABLE_NAME.REFUND_ITEM) +export class RefundItemModel + extends BaseCoreModel + implements RefundItemEntity +{ + @Column('decimal', { name: 'qty_refund', nullable: true }) + qty_refund: number; + + @Column('decimal', { name: 'refund_total', nullable: true }) + refund_total: number; + + // transaction to refund + @Column('decimal', { name: 'refund_item_id', nullable: true }) + refund_item_id: number; + @ManyToOne(() => RefundModel, (model) => model.refund_items, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'refund_item_id' }) + refund: RefundModel; + + // transaction to transaction item + @Column('varchar', { name: 'transaction_item_id', nullable: true }) + transaction_item_id: string; + @OneToOne(() => TransactionItemModel, (model) => model.refund, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'transaction_item_id' }) + transaction_item: TransactionItemModel; +} diff --git a/src/modules/transaction/refund/data/models/refund.model.ts b/src/modules/transaction/refund/data/models/refund.model.ts new file mode 100644 index 0000000..ae222cf --- /dev/null +++ b/src/modules/transaction/refund/data/models/refund.model.ts @@ -0,0 +1,60 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { RefundEntity } from '../../domain/entities/refund.entity'; +import { Column, Entity, JoinColumn, OneToMany, OneToOne } from 'typeorm'; +import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { RefundItemModel } from './refund-item.model'; +import { RefundType } from '../../constants'; + +@Entity(TABLE_NAME.REFUND) +export class RefundModel + extends BaseStatusModel + implements RefundEntity +{ + @Column('enum', { + name: 'type', + enum: RefundType, + default: RefundType.BOOKING, + }) + type: RefundType; + + @Column('varchar', { name: 'code', nullable: true }) + code: string; + + @Column('date', { name: 'request_date', nullable: true }) + request_date: Date; + + @Column('date', { name: 'refund_date', nullable: true }) + refund_date: Date; + + @Column('decimal', { name: 'refund_total', nullable: true }) + refund_total: number; + + // bank info + @Column('varchar', { name: 'bank_name', nullable: true }) + bank_name: string; + + @Column('varchar', { name: 'bank_account_name', nullable: true }) + bank_account_name: string; + + @Column('varchar', { name: 'bank_account_number', nullable: true }) + bank_account_number: string; + + // relations to item + @OneToMany(() => RefundItemModel, (model) => model.refund, { + cascade: true, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + refund_items: RefundItemModel[]; + + // relation to transaction + @Column('varchar', { name: 'transaction_id', nullable: true }) + transaction_id: string; + @OneToOne(() => TransactionModel, (model) => model.refund, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'transaction_id' }) + transaction: TransactionModel; +} diff --git a/src/modules/transaction/refund/data/services/refund-data.service.ts b/src/modules/transaction/refund/data/services/refund-data.service.ts new file mode 100644 index 0000000..34e196e --- /dev/null +++ b/src/modules/transaction/refund/data/services/refund-data.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import { RefundEntity } from '../../domain/entities/refund.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { RefundModel } from '../models/refund.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; + +@Injectable() +export class RefundDataService extends BaseDataService { + constructor( + @InjectRepository(RefundModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/transaction/refund/domain/entities/event/refund-change-status.event.ts b/src/modules/transaction/refund/domain/entities/event/refund-change-status.event.ts new file mode 100644 index 0000000..66e8ef2 --- /dev/null +++ b/src/modules/transaction/refund/domain/entities/event/refund-change-status.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class RefundChangeStatusEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/refund/domain/entities/event/refund-created.event.ts b/src/modules/transaction/refund/domain/entities/event/refund-created.event.ts new file mode 100644 index 0000000..2621c9d --- /dev/null +++ b/src/modules/transaction/refund/domain/entities/event/refund-created.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class RefundCreatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/refund/domain/entities/event/refund-deleted.event.ts b/src/modules/transaction/refund/domain/entities/event/refund-deleted.event.ts new file mode 100644 index 0000000..dce6938 --- /dev/null +++ b/src/modules/transaction/refund/domain/entities/event/refund-deleted.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class RefundDeletedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/refund/domain/entities/event/refund-updated.event.ts b/src/modules/transaction/refund/domain/entities/event/refund-updated.event.ts new file mode 100644 index 0000000..f41534f --- /dev/null +++ b/src/modules/transaction/refund/domain/entities/event/refund-updated.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class RefundUpdatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/refund/domain/entities/refund-item.entity.ts b/src/modules/transaction/refund/domain/entities/refund-item.entity.ts new file mode 100644 index 0000000..880dc80 --- /dev/null +++ b/src/modules/transaction/refund/domain/entities/refund-item.entity.ts @@ -0,0 +1,7 @@ +import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity'; + +export interface RefundItemEntity extends BaseCoreEntity { + qty_refund: number; + + transaction_item_id?: string; +} diff --git a/src/modules/transaction/refund/domain/entities/refund.entity.ts b/src/modules/transaction/refund/domain/entities/refund.entity.ts new file mode 100644 index 0000000..85c0420 --- /dev/null +++ b/src/modules/transaction/refund/domain/entities/refund.entity.ts @@ -0,0 +1,16 @@ +import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity'; +import { RefundType } from '../../constants'; + +export interface RefundEntity extends BaseStatusEntity { + type: RefundType; + code: string; + request_date: Date; + refund_date: Date; + refund_total: number; + + bank_name: string; + bank_account_name: string; + bank_account_number: string; + + transaction_id: string; +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/batch-cancel-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/batch-cancel-refund.manager.ts new file mode 100644 index 0000000..6197a2d --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/batch-cancel-refund.manager.ts @@ -0,0 +1,57 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundChangeStatusEvent } from '../../entities/event/refund-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 BatchCancelRefundManager extends BaseBatchUpdateStatusManager { + validateData(data: RefundEntity): Promise { + if (![STATUS.REFUNDED, STATUS.PENDING].includes(data.status)) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data with status ${STATUS.REFUNDED} and ${STATUS.PENDING} can be cancelled`, + error: 'Unprocessable Entity', + }); + } + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/batch-confirm-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/batch-confirm-refund.manager.ts new file mode 100644 index 0000000..e904591 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/batch-confirm-refund.manager.ts @@ -0,0 +1,72 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundChangeStatusEvent } from '../../entities/event/refund-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 BatchConfirmRefundManager extends BaseBatchUpdateStatusManager { + validateData(data: RefundEntity): Promise { + if (![STATUS.DRAFT, STATUS.PENDING].includes(data.status)) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data with status ${STATUS.DRAFT} and ${STATUS.PENDING} can be confirmed`, + error: 'Unprocessable Entity', + }); + } + + if (this.data.status == STATUS.DRAFT) { + Object.assign(this.data, { + code: `RF-${data?.['transaction']?.invoice_code.split('-')[1]}`, + request_date: new Date(), + status: STATUS.PENDING, + }); + } else if (this.data.status == STATUS.PENDING) { + Object.assign(this.data, { + refund_date: new Date(), + status: STATUS.REFUNDED, + }); + } + return; + } + + beforeProcess(): Promise { + this.relations = ['transaction']; + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundChangeStatusEvent, + relations: ['transaction'], + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/batch-delete-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/batch-delete-refund.manager.ts new file mode 100644 index 0000000..658d0b9 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/batch-delete-refund.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchDeleteManager } from 'src/core/modules/domain/usecase/managers/base-batch-delete.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundDeletedEvent } from '../../entities/event/refund-deleted.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchDeleteRefundManager extends BaseBatchDeleteManager { + async beforeProcess(): Promise { + return; + } + + async validateData(data: RefundEntity): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundDeletedEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/cancel-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/cancel-refund.manager.ts new file mode 100644 index 0000000..ec40de3 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/cancel-refund.manager.ts @@ -0,0 +1,57 @@ +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundChangeStatusEvent } from '../../entities/event/refund-change-status.event'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class CancelRefundManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.code}`; + } + + async validateProcess(): Promise { + if (![STATUS.REFUNDED, STATUS.PENDING].includes(this.data.status)) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data with status ${STATUS.REFUNDED} and ${STATUS.PENDING} can be cancelled`, + error: 'Unprocessable Entity', + }); + } + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/confirm-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/confirm-refund.manager.ts new file mode 100644 index 0000000..f56080f --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/confirm-refund.manager.ts @@ -0,0 +1,77 @@ +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundChangeStatusEvent } from '../../entities/event/refund-change-status.event'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class ConfirmRefundManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.code}`; + } + + async validateProcess(): Promise { + if (![STATUS.DRAFT, STATUS.PENDING].includes(this.oldData.status)) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data with status ${STATUS.DRAFT} and ${STATUS.PENDING} can be confirmed`, + error: 'Unprocessable Entity', + }); + } + return; + } + + async beforeProcess(): Promise { + const data = await this.dataService.getOneByOptions({ + where: { + id: this.data.id, + }, + relations: ['transaction'], + }); + + if (data.status == STATUS.DRAFT) { + Object.assign(this.data, { + code: `RF-${data.transaction?.invoice_code?.split('-')[1]}`, + request_date: new Date(), + status: STATUS.PENDING, + }); + } else if (data.status == STATUS.PENDING) { + Object.assign(this.data, { + refund_date: new Date(), + status: STATUS.REFUNDED, + }); + } + + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundChangeStatusEvent, + relations: ['transaction'], + }, + ]; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/create-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/create-refund.manager.ts new file mode 100644 index 0000000..e0f87ce --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/create-refund.manager.ts @@ -0,0 +1,54 @@ +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundEntity } from '../../entities/refund.entity'; +import { RefundModel } from '../../../data/models/refund.model'; +import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; +import { RefundCreatedEvent } from '../../entities/event/refund-created.event'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class CreateRefundManager extends BaseCreateManager { + async beforeProcess(): Promise { + // if (this.data.transaction?.status != STATUS.SETTLED) { + // throw new UnprocessableEntityException({ + // statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + // message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`, + // error: 'Unprocessable Entity', + // }); + // } + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundCreatedEvent, + data: this.data, + }, + ]; + } + + get entityTarget(): any { + return RefundModel; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/delete-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/delete-refund.manager.ts new file mode 100644 index 0000000..0015808 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/delete-refund.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundDeletedEvent } from '../../entities/event/refund-deleted.event'; + +@Injectable() +export class DeleteRefundManager extends BaseDeleteManager { + getResult(): string { + return `Success`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundDeletedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/update-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/update-refund.manager.ts new file mode 100644 index 0000000..cdb1cdc --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/update-refund.manager.ts @@ -0,0 +1,58 @@ +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundUpdatedEvent } from '../../entities/event/refund-updated.event'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class UpdateRefundManager extends BaseUpdateManager { + async validateProcess(): Promise { + if (this.data.transaction?.status != STATUS.SETTLED) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`, + error: 'Unprocessable Entity', + }); + } + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get entityTarget(): any { + return RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundUpdatedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/refund-data.orchestrator.ts b/src/modules/transaction/refund/domain/usecases/refund-data.orchestrator.ts new file mode 100644 index 0000000..065ef91 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/refund-data.orchestrator.ts @@ -0,0 +1,85 @@ +import { Injectable } from '@nestjs/common'; +import { CreateRefundManager } from './managers/create-refund.manager'; +import { RefundDataService } from '../../data/services/refund-data.service'; +import { RefundEntity } from '../entities/refund.entity'; +import { DeleteRefundManager } from './managers/delete-refund.manager'; +import { UpdateRefundManager } from './managers/update-refund.manager'; +import { ConfirmRefundManager } from './managers/confirm-refund.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchConfirmRefundManager } from './managers/batch-confirm-refund.manager'; +import { BatchDeleteRefundManager } from './managers/batch-delete-refund.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { CancelRefundManager } from './managers/cancel-refund.manager'; +import { BatchCancelRefundManager } from './managers/batch-cancel-refund.manager'; + +@Injectable() +export class RefundDataOrchestrator { + constructor( + private createManager: CreateRefundManager, + private updateManager: UpdateRefundManager, + private deleteManager: DeleteRefundManager, + private cancelManager: CancelRefundManager, + private confirmManager: ConfirmRefundManager, + private batchDeleteManager: BatchDeleteRefundManager, + private batchCancelManager: BatchCancelRefundManager, + private batchConfirmManager: BatchConfirmRefundManager, + private serviceData: RefundDataService, + ) {} + + async create(data): Promise { + this.createManager.setData(data); + this.createManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.createManager.execute(); + return this.createManager.getResult(); + } + + async update(dataId, data): Promise { + this.updateManager.setData(dataId, data); + this.updateManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.updateManager.execute(); + return this.updateManager.getResult(); + } + + async delete(dataId): Promise { + this.deleteManager.setData(dataId); + this.deleteManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.deleteManager.execute(); + return this.deleteManager.getResult(); + } + + async batchDelete(dataIds: string[]): Promise { + this.batchDeleteManager.setData(dataIds); + this.batchDeleteManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.batchDeleteManager.execute(); + return this.batchDeleteManager.getResult(); + } + + async cancel(dataId): Promise { + this.cancelManager.setData(dataId, STATUS.CANCEL); + this.cancelManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.cancelManager.execute(); + return this.cancelManager.getResult(); + } + + async batchCancel(dataIds: string[]): Promise { + this.batchCancelManager.setData(dataIds, STATUS.CANCEL); + this.batchCancelManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.batchCancelManager.execute(); + return this.batchCancelManager.getResult(); + } + + async confirm(dataId): Promise { + this.confirmManager.setData(dataId, STATUS.PENDING); + this.confirmManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.confirmManager.execute(); + return this.confirmManager.getResult(); + } + + async batchConfirm(dataIds: string[]): Promise { + this.batchConfirmManager.setData(dataIds, STATUS.PENDING); + this.batchConfirmManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.batchConfirmManager.execute(); + return this.batchConfirmManager.getResult(); + } +} diff --git a/src/modules/transaction/refund/index.ts b/src/modules/transaction/refund/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/transaction/refund/infrastructure/dto/refund.dto.ts b/src/modules/transaction/refund/infrastructure/dto/refund.dto.ts new file mode 100644 index 0000000..38164b0 --- /dev/null +++ b/src/modules/transaction/refund/infrastructure/dto/refund.dto.ts @@ -0,0 +1,67 @@ +import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.dto'; +import { RefundEntity } from '../../domain/entities/refund.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString, ValidateIf } from 'class-validator'; +import { Exclude } from 'class-transformer'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; +import { RefundType } from '../../constants'; + +export class RefundDto extends BaseStatusDto implements RefundEntity { + @ApiProperty({ + type: String, + required: true, + example: RefundType.BOOKING, + }) + @IsString() + type: RefundType; + + @ApiProperty({ + type: Number, + example: 1750000, + }) + @IsNumber() + refund_total: number; + + @ApiProperty({ + type: String, + required: false, + example: 'BCA', + }) + bank_name: string; + + @ApiProperty({ + type: String, + required: false, + example: 'andhika', + }) + bank_account_name: string; + + @ApiProperty({ + type: String, + required: false, + example: '64222456', + }) + bank_account_number: string; + + @ApiProperty({ + type: Object, + required: true, + example: { + id: 'uuid', + invoice_code: 'INV-', + }, + }) + transaction: TransactionEntity; + + @Exclude() + code: string; + + @Exclude() + request_date: Date; + + @Exclude() + refund_date: Date; + + @Exclude() + transaction_id: string; +} diff --git a/src/modules/transaction/refund/infrastructure/refund-data.controller.ts b/src/modules/transaction/refund/infrastructure/refund-data.controller.ts new file mode 100644 index 0000000..9a0fc4f --- /dev/null +++ b/src/modules/transaction/refund/infrastructure/refund-data.controller.ts @@ -0,0 +1,68 @@ +import { + Body, + Controller, + Delete, + Param, + Patch, + Post, + Put, +} from '@nestjs/common'; +import { RefundDataOrchestrator } from '../domain/usecases/refund-data.orchestrator'; +import { RefundDto } from './dto/refund.dto'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { RefundEntity } from '../domain/entities/refund.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.REFUND.split('-').join(' ')} - data`) +@Controller(`v1/${MODULE_NAME.REFUND}`) +@Public(false) +@ApiBearerAuth('JWT') +export class RefundDataController { + constructor(private orchestrator: RefundDataOrchestrator) {} + + @Post() + async create(@Body() data: RefundDto): Promise { + return await this.orchestrator.create(data); + } + + @Put('/batch-delete') + async batchDeleted(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchDelete(body.ids); + } + + @Patch(':id/cancel') + async cancel(@Param('id') dataId: string): Promise { + return await this.orchestrator.cancel(dataId); + } + + @Put('/batch-cancel') + async batchcancel(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchCancel(body.ids); + } + + @Patch(':id/confirm') + async confirm(@Param('id') dataId: string): Promise { + return await this.orchestrator.confirm(dataId); + } + + @Put('/batch-confirm') + async batchConfirm(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchConfirm(body.ids); + } + + @Put(':id') + async update( + @Param('id') dataId: string, + @Body() data: RefundDto, + ): Promise { + return await this.orchestrator.update(dataId, data); + } + + @Delete(':id') + async delete(@Param('id') dataId: string): Promise { + return await this.orchestrator.delete(dataId); + } +} diff --git a/src/modules/transaction/refund/refund.module.ts b/src/modules/transaction/refund/refund.module.ts new file mode 100644 index 0000000..fbae71e --- /dev/null +++ b/src/modules/transaction/refund/refund.module.ts @@ -0,0 +1,54 @@ +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 { RefundDataService } from './data/services/refund-data.service'; +import { RefundReadService } from './data/services/refund-read.service'; +import { RefundReadController } from './infrastructure/refund-read.controller'; +import { RefundReadOrchestrator } from './domain/usecases/refund-read.orchestrator'; +import { RefundDataController } from './infrastructure/refund-data.controller'; +import { RefundDataOrchestrator } from './domain/usecases/refund-data.orchestrator'; +import { CreateRefundManager } from './domain/usecases/managers/create-refund.manager'; +import { CqrsModule } from '@nestjs/cqrs'; +import { IndexRefundManager } from './domain/usecases/managers/index-refund.manager'; +import { DeleteRefundManager } from './domain/usecases/managers/delete-refund.manager'; +import { UpdateRefundManager } from './domain/usecases/managers/update-refund.manager'; +import { ConfirmRefundManager } from './domain/usecases/managers/confirm-refund.manager'; +import { DetailRefundManager } from './domain/usecases/managers/detail-refund.manager'; +import { BatchDeleteRefundManager } from './domain/usecases/managers/batch-delete-refund.manager'; +import { BatchConfirmRefundManager } from './domain/usecases/managers/batch-confirm-refund.manager'; +import { RefundModel } from './data/models/refund.model'; +import { BatchCancelRefundManager } from './domain/usecases/managers/batch-cancel-refund.manager'; +import { CancelRefundManager } from './domain/usecases/managers/cancel-refund.manager'; +import { RefundItemModel } from './data/models/refund-item.model'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature( + [RefundModel, RefundItemModel], + CONNECTION_NAME.DEFAULT, + ), + CqrsModule, + ], + controllers: [RefundDataController, RefundReadController], + providers: [ + IndexRefundManager, + DetailRefundManager, + CreateRefundManager, + DeleteRefundManager, + UpdateRefundManager, + ConfirmRefundManager, + CancelRefundManager, + BatchDeleteRefundManager, + BatchConfirmRefundManager, + BatchCancelRefundManager, + + RefundDataService, + RefundReadService, + + RefundDataOrchestrator, + RefundReadOrchestrator, + ], +}) +export class RefundModule {} diff --git a/src/modules/transaction/transaction/data/models/transaction-item.model.ts b/src/modules/transaction/transaction/data/models/transaction-item.model.ts index 68bfd21..d00e800 100644 --- a/src/modules/transaction/transaction/data/models/transaction-item.model.ts +++ b/src/modules/transaction/transaction/data/models/transaction-item.model.ts @@ -1,8 +1,9 @@ import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; -import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import { Column, Entity, JoinColumn, ManyToOne, OneToOne } 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'; +import { RefundItemModel } from 'src/modules/transaction/refund/data/models/refund-item.model'; @Entity(TABLE_NAME.TRANSACTION_ITEM) export class TransactionItemModel @@ -60,6 +61,12 @@ export class TransactionItemModel @Column('int', { name: 'qty', nullable: true }) qty: number; + @Column('int', { name: 'qty_remaining', nullable: true }) + qty_remaining: number; + + @Column('json', { name: 'taxes', nullable: true }) + taxes: string; + @Column('varchar', { name: 'transaction_id', nullable: true }) transaction_id: string; @ManyToOne(() => TransactionModel, (model) => model.items, { @@ -68,4 +75,12 @@ export class TransactionItemModel }) @JoinColumn({ name: 'transaction_id' }) transaction: TransactionModel; + + // relations to refund + @OneToOne(() => RefundItemModel, (model) => model.transaction_item, { + cascade: true, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + refund: RefundItemModel; } diff --git a/src/modules/transaction/transaction/data/models/transaction.model.ts b/src/modules/transaction/transaction/data/models/transaction.model.ts index a447efe..7ca536f 100644 --- a/src/modules/transaction/transaction/data/models/transaction.model.ts +++ b/src/modules/transaction/transaction/data/models/transaction.model.ts @@ -1,6 +1,6 @@ import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { TransactionEntity } from '../../domain/entities/transaction.entity'; -import { Column, Entity, OneToMany } from 'typeorm'; +import { Column, Entity, OneToMany, OneToOne } from 'typeorm'; import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; import { TransactionType, @@ -11,6 +11,7 @@ import { TransactionItemEntity } from '../../domain/entities/transaction-item.en import { TransactionItemModel } from './transaction-item.model'; import { TransactionTaxModel } from './transaction-tax.model'; import { STATUS } from 'src/core/strings/constants/base.constants'; +import { RefundModel } from 'src/modules/transaction/refund/data/models/refund.model'; @Entity(TABLE_NAME.TRANSACTION) export class TransactionModel @@ -214,4 +215,12 @@ export class TransactionModel onUpdate: 'CASCADE', }) taxes: TransactionTaxModel[]; + + // relations to refund + @OneToOne(() => RefundModel, (model) => model.transaction, { + cascade: true, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + refund: RefundModel; } diff --git a/src/modules/transaction/transaction/domain/entities/transaction-item.entity.ts b/src/modules/transaction/transaction/domain/entities/transaction-item.entity.ts index 1a638d2..102355a 100644 --- a/src/modules/transaction/transaction/domain/entities/transaction-item.entity.ts +++ b/src/modules/transaction/transaction/domain/entities/transaction-item.entity.ts @@ -22,4 +22,6 @@ export interface TransactionItemEntity extends BaseCoreEntity { total_profit: number; total_share_tenant: number; qty: number; + qty_remaining: number; + taxes: string; } diff --git a/src/modules/transaction/transaction/domain/usecases/handlers/refund-update.handler.ts b/src/modules/transaction/transaction/domain/usecases/handlers/refund-update.handler.ts new file mode 100644 index 0000000..3d83ed7 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/handlers/refund-update.handler.ts @@ -0,0 +1,52 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { RefundChangeStatusEvent } from 'src/modules/transaction/refund/domain/entities/event/refund-change-status.event'; +import { TransactionDataService } from '../../../data/services/transaction-data.service'; +import { OPERATION, STATUS } from 'src/core/strings/constants/base.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { RefundDeletedEvent } from 'src/modules/transaction/refund/domain/entities/event/refund-deleted.event'; + +@EventsHandler(RefundChangeStatusEvent, RefundDeletedEvent) +export class RefundUpdatedHandler + implements IEventHandler +{ + constructor(private dataService: TransactionDataService) {} + + async handle(event: RefundChangeStatusEvent) { + const old_data = event.data.old; + const current_data = event.data.data; + let status: STATUS; + + if ( + old_data.status != current_data.data || + event.data.op == OPERATION.DELETE + ) { + const queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); + + const data = new TransactionModel(); + const if_full_refund = + Number(current_data.refund_total ?? 0) == + Number(current_data.transaction?.payment_total); + + if (event.data.op == OPERATION.DELETE) status = STATUS.SETTLED; + else if (current_data.status == STATUS.PENDING) + status = STATUS.PROCESS_REFUND; + else if (current_data.status == STATUS.REFUNDED && if_full_refund) + status = STATUS.REFUNDED; + else if (current_data.status == STATUS.REFUNDED && !if_full_refund) + status = STATUS.PARTIAL_REFUND; + else if (current_data.status == STATUS.CANCEL) status = STATUS.SETTLED; + + await this.dataService.update( + queryRunner, + TransactionModel, + { id: current_data.transaction_id }, + { + ...data, + status: status, + }, + ); + } + } +} diff --git a/src/modules/transaction/transaction/transaction.module.ts b/src/modules/transaction/transaction/transaction.module.ts index 8e017d1..1ce5612 100644 --- a/src/modules/transaction/transaction/transaction.module.ts +++ b/src/modules/transaction/transaction/transaction.module.ts @@ -30,6 +30,7 @@ import { SalesPriceFormulaDataService } from '../sales-price-formula/data/servic import { SalesPriceFormulaModel } from '../sales-price-formula/data/models/sales-price-formula.model'; import { TaxModel } from '../tax/data/models/tax.model'; import { SettledTransactionHandler } from './domain/usecases/handlers/settled-transaction.handler'; +import { RefundUpdatedHandler } from './domain/usecases/handlers/refund-update.handler'; @Module({ imports: [ @@ -48,6 +49,7 @@ import { SettledTransactionHandler } from './domain/usecases/handlers/settled-tr ], controllers: [TransactionDataController, TransactionReadController], providers: [ + RefundUpdatedHandler, PosTransactionHandler, SettledTransactionHandler,