From 77a6afbbbc9023455ce2fb109c823006dea1dbe6 Mon Sep 17 00:00:00 2001 From: shancheas Date: Mon, 21 Oct 2024 12:59:44 +0700 Subject: [PATCH 01/94] fix: add video extension --- src/core/helpers/path/upload-store-path.helper.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/helpers/path/upload-store-path.helper.ts b/src/core/helpers/path/upload-store-path.helper.ts index 2fbb495..96ceed9 100644 --- a/src/core/helpers/path/upload-store-path.helper.ts +++ b/src/core/helpers/path/upload-store-path.helper.ts @@ -8,7 +8,9 @@ import { diskStorage } from 'multer'; const MB = 1024 * 1024; const fileFilter = (req, file, callback) => { - if (file.mimetype.match(/\/(jpg|jpeg|png)$/)) { + if ( + file.mimetype.match(/\/(jpg|jpeg|png|flv|mp4|m3u8|ts|3gp|mov|avi|wmv)$/) + ) { callback(null, true); } else { callback( From 77cf19c06eb4e33037687088ba4705a7ba5234cb Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 22 Oct 2024 11:45:44 +0700 Subject: [PATCH 02/94] fix: unused code --- .../modules/domain/usecase/managers/base-index.manager.ts | 2 +- src/modules/item-related/item/item.module.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/modules/domain/usecase/managers/base-index.manager.ts b/src/core/modules/domain/usecase/managers/base-index.manager.ts index fb5ece5..357fc04 100644 --- a/src/core/modules/domain/usecase/managers/base-index.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-index.manager.ts @@ -50,7 +50,7 @@ export abstract class BaseIndexManager extends BaseReadManager { // jika searching status terdapat dalam enum, maka dia mencari specific data // ? karena jika tidak, ketika dia search "active" maka "inactive" juga ikut - return `'${STATUS[statusData.toUpperCase()]}'` ?? `'%${statusData}%'`; + return `'${STATUS[statusData.toUpperCase()]}'`; }); const exist = specificFilter.find((item) => item.isStatus); diff --git a/src/modules/item-related/item/item.module.ts b/src/modules/item-related/item/item.module.ts index 76e0fe6..8e8c26a 100644 --- a/src/modules/item-related/item/item.module.ts +++ b/src/modules/item-related/item/item.module.ts @@ -6,7 +6,7 @@ import { ItemDataService } from './data/services/item-data.service'; import { ItemReadService } from './data/services/item-read.service'; import { ItemReadController, - ItemReadQueueController, + // ItemReadQueueController, } from './infrastructure/item-read.controller'; import { ItemReadOrchestrator } from './domain/usecases/item-read.orchestrator'; import { ItemDataController } from './infrastructure/item-data.controller'; @@ -44,7 +44,7 @@ import { IndexItemQueueManager } from './domain/usecases/managers/index-queue-it controllers: [ ItemDataController, ItemReadController, - ItemReadQueueController, + // ItemReadQueueController, ], providers: [ IndexItemManager, From 2d0ccf67f2a7bccc0568162f18f7e943c248647a Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 22 Oct 2024 11:47:22 +0700 Subject: [PATCH 03/94] WIP: module Queue --- .../strings/constants/module.constants.ts | 3 + src/core/strings/constants/table.constants.ts | 5 ++ .../migrations/1729151429165-queue-table.ts | 43 +++++++++ .../1729248576381-video-url-to-json.ts | 18 ++++ .../migrations/1729570177597-item-queues.ts | 33 +++++++ .../item-related/item-queue/constants.ts | 7 ++ .../infrastructure/item-read.controller.ts | 28 +++--- src/modules/queue/data/models/queue.model.ts | 89 +++++++++++++++++++ .../queue/data/services/ticket.service.ts | 25 ++++++ .../queue/domain/entities/order.entity.ts | 10 +++ .../domain/entities/queue-item.entity.ts | 10 +++ .../queue/domain/entities/ticket.entity.ts | 13 +++ .../queue/domain/queue.orchestrator.ts | 6 ++ src/modules/queue/index.ts | 0 .../controllers/queue.controller.ts | 24 +++++ src/modules/queue/queue.module.ts | 46 ++++++++++ 16 files changed, 346 insertions(+), 14 deletions(-) create mode 100644 src/database/migrations/1729151429165-queue-table.ts create mode 100644 src/database/migrations/1729248576381-video-url-to-json.ts create mode 100644 src/database/migrations/1729570177597-item-queues.ts create mode 100644 src/modules/item-related/item-queue/constants.ts create mode 100644 src/modules/queue/data/models/queue.model.ts create mode 100644 src/modules/queue/data/services/ticket.service.ts create mode 100644 src/modules/queue/domain/entities/order.entity.ts create mode 100644 src/modules/queue/domain/entities/queue-item.entity.ts create mode 100644 src/modules/queue/domain/entities/ticket.entity.ts create mode 100644 src/modules/queue/domain/queue.orchestrator.ts create mode 100644 src/modules/queue/index.ts create mode 100644 src/modules/queue/infrastructure/controllers/queue.controller.ts create mode 100644 src/modules/queue/queue.module.ts diff --git a/src/core/strings/constants/module.constants.ts b/src/core/strings/constants/module.constants.ts index f78ebca..b05838e 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 { GATE = 'gates', ITEM = 'items', ITEM_CATEGORY = 'item-categories', + ITEM_QUEUE = 'item-queues', ITEM_RATE = 'item-rates', NEWS = 'news', PAYMENT_METHOD = 'payment-methods', @@ -25,4 +26,6 @@ export enum MODULE_NAME { REPORT_BOOKMARK = 'report-bookmark', REPORT_EXPORT = 'report-export', REPORT_SUMMARY = 'report-summary', + + QUEUE = 'queue', } diff --git a/src/core/strings/constants/table.constants.ts b/src/core/strings/constants/table.constants.ts index 990c4cb..13eb804 100644 --- a/src/core/strings/constants/table.constants.ts +++ b/src/core/strings/constants/table.constants.ts @@ -4,6 +4,7 @@ export enum TABLE_NAME { FAQ = 'faqs', ITEM = 'items', ITEM_CATEGORY = 'item_categories', + ITEM_QUEUE = 'item_queues', ITEM_RATE = 'item_rates', GATE = 'gates', LOG = 'logs', @@ -35,4 +36,8 @@ export enum TABLE_NAME { REPORT_BOOKMARK = 'report_bookmark', EXPORT_REPORT_HISTORY = 'export_report_history', + + QUEUE_ORDER = 'queue_orders', + QUEUE_TICKET = 'queue_tickets', + QUEUE_ITEM = 'queue_items', } diff --git a/src/database/migrations/1729151429165-queue-table.ts b/src/database/migrations/1729151429165-queue-table.ts new file mode 100644 index 0000000..27f1dd7 --- /dev/null +++ b/src/database/migrations/1729151429165-queue-table.ts @@ -0,0 +1,43 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class QueueTable1729151429165 implements MigrationInterface { + name = 'QueueTable1729151429165'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "queue_orders" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "code" character varying NOT NULL, "customer" character varying, "phone" character varying, "date" bigint NOT NULL, CONSTRAINT "PK_b139e4cc9ca3e709c152f820d2e" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE TABLE "queue_tickets" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "code" character varying NOT NULL, "customer" character varying, "phone" character varying, "date" bigint NOT NULL, "order_id" uuid, CONSTRAINT "PK_1b903aa90bcc04136caa6540c55" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE TABLE "queue_items" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "qty" integer NOT NULL, "ticket_id" uuid, "item_id" uuid, CONSTRAINT "PK_2245e11ac3517494bacfe932773" PRIMARY KEY ("id"))`, + ); + + await queryRunner.query( + `ALTER TABLE "queue_tickets" ADD CONSTRAINT "FK_0e9823b8b7ca9523b3be73878e5" FOREIGN KEY ("order_id") REFERENCES "queue_orders"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "queue_items" ADD CONSTRAINT "FK_25352739034765f6917757df74b" FOREIGN KEY ("ticket_id") REFERENCES "queue_tickets"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "queue_items" ADD CONSTRAINT "FK_ab15c053aeb4f739ebf533b61cd" FOREIGN KEY ("item_id") REFERENCES "items"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "queue_items" DROP CONSTRAINT "FK_ab15c053aeb4f739ebf533b61cd"`, + ); + await queryRunner.query( + `ALTER TABLE "queue_items" DROP CONSTRAINT "FK_25352739034765f6917757df74b"`, + ); + await queryRunner.query( + `ALTER TABLE "queue_tickets" DROP CONSTRAINT "FK_0e9823b8b7ca9523b3be73878e5"`, + ); + + await queryRunner.query(`DROP TABLE "queue_items"`); + await queryRunner.query(`DROP TABLE "queue_tickets"`); + await queryRunner.query(`DROP TABLE "queue_orders"`); + } +} diff --git a/src/database/migrations/1729248576381-video-url-to-json.ts b/src/database/migrations/1729248576381-video-url-to-json.ts new file mode 100644 index 0000000..4761e3c --- /dev/null +++ b/src/database/migrations/1729248576381-video-url-to-json.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VideoUrlToJson1729248576381 implements MigrationInterface { + name = 'VideoUrlToJson1729248576381'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "items" DROP COLUMN "video_url"`); + await queryRunner.query(`ALTER TABLE "items" ADD "video_url" json`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "items" DROP COLUMN "video_url"`); + await queryRunner.query( + `ALTER TABLE "items" ADD "video_url" character varying`, + ); + await queryRunner.query(`DROP TABLE "item_queues"`); + } +} diff --git a/src/database/migrations/1729570177597-item-queues.ts b/src/database/migrations/1729570177597-item-queues.ts new file mode 100644 index 0000000..96acb18 --- /dev/null +++ b/src/database/migrations/1729570177597-item-queues.ts @@ -0,0 +1,33 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ItemQueues1729570177597 implements MigrationInterface { + name = 'ItemQueues1729570177597'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."item_queues_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `CREATE TYPE "public"."item_queues_item_type_enum" AS ENUM('tiket masuk', 'wahana', 'bundling', 'free gift', 'other')`, + ); + await queryRunner.query( + `CREATE TABLE "item_queues" ("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"."item_queues_status_enum" NOT NULL DEFAULT 'draft', "name" character varying NOT NULL, "item_type" "public"."item_queues_item_type_enum" NOT NULL DEFAULT 'tiket masuk', CONSTRAINT "PK_e19adb0b99d995e8f10c189985f" PRIMARY KEY ("id"))`, + ); + await queryRunner.query(`ALTER TABLE "items" ADD "item_queue_id" uuid`); + + await queryRunner.query( + `ALTER TABLE "items" ADD CONSTRAINT "FK_2cbbeb03e176addcf60d65f7c9c" FOREIGN KEY ("item_queue_id") REFERENCES "item_queues"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "items" DROP CONSTRAINT "FK_2cbbeb03e176addcf60d65f7c9c"`, + ); + + await queryRunner.query(`ALTER TABLE "items" DROP COLUMN "item_queue_id"`); + await queryRunner.query(`DROP TABLE "item_queues"`); + await queryRunner.query(`DROP TYPE "public"."item_queues_item_type_enum"`); + await queryRunner.query(`DROP TYPE "public"."item_queues_status_enum"`); + } +} diff --git a/src/modules/item-related/item-queue/constants.ts b/src/modules/item-related/item-queue/constants.ts new file mode 100644 index 0000000..9b62bb1 --- /dev/null +++ b/src/modules/item-related/item-queue/constants.ts @@ -0,0 +1,7 @@ +export enum ItemType { + TIKET_MASUK = 'tiket masuk', + WAHANA = 'wahana', + BUNDLING = 'bundling', + FREE_GIFT = 'free gift', + OTHER = 'other', +} diff --git a/src/modules/item-related/item/infrastructure/item-read.controller.ts b/src/modules/item-related/item/infrastructure/item-read.controller.ts index 91c7ee5..ae27dfb 100644 --- a/src/modules/item-related/item/infrastructure/item-read.controller.ts +++ b/src/modules/item-related/item/infrastructure/item-read.controller.ts @@ -41,18 +41,18 @@ export class ItemReadController { } } -@ApiTags(`Item Queue - Read`) -@Controller(`v1/item-queue`) -@Public(true) -export class ItemReadQueueController { - constructor(private orchestrator: ItemReadOrchestrator) {} +// @ApiTags(`Item Queue - Read`) +// @Controller(`v1/item-queue`) +// @Public(true) +// export class ItemReadQueueController { +// constructor(private orchestrator: ItemReadOrchestrator) {} - @Get() - @Pagination() - @ExcludePrivilege() - async indexQueue( - @Query() params: FilterItemDto, - ): Promise> { - return await this.orchestrator.indexQueue(params); - } -} +// @Get() +// @Pagination() +// @ExcludePrivilege() +// async indexQueue( +// @Query() params: FilterItemDto, +// ): Promise> { +// return await this.orchestrator.indexQueue(params); +// } +// } diff --git a/src/modules/queue/data/models/queue.model.ts b/src/modules/queue/data/models/queue.model.ts new file mode 100644 index 0000000..5e65c73 --- /dev/null +++ b/src/modules/queue/data/models/queue.model.ts @@ -0,0 +1,89 @@ +import { BaseCoreModel } from 'src/core/modules/data/model/base-core.model'; +import { QueueOrder } from '../../domain/entities/order.entity'; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { QueueTicket } from '../../domain/entities/ticket.entity'; +import { QueueItem } from '../../domain/entities/queue-item.entity'; +import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity'; + +import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; + +@Entity(TABLE_NAME.QUEUE_ORDER) +export class QueueOrderModel + extends BaseCoreModel + implements QueueOrder +{ + @Column('varchar') + code: string; + + @Column('varchar', { nullable: true }) + customer: string; + + @Column('varchar', { nullable: true }) + phone: string; + + @OneToMany(() => QueueTicketModel, (model) => model.order, { + cascade: true, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + tickets: QueueTicket[]; + + @Column({ type: 'bigint' }) + date: number; +} + +@Entity(TABLE_NAME.QUEUE_TICKET) +export class QueueTicketModel + extends BaseCoreModel + implements QueueTicket +{ + @Column('varchar') + code: string; + + @Column('varchar', { nullable: true }) + customer: string; + + @Column('varchar', { nullable: true }) + phone: string; + + @ManyToOne(() => QueueOrderModel, (model) => model.tickets, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'order_id' }) + order: QueueOrder; + + @Column({ type: 'bigint' }) + date: number; + + @OneToMany(() => QueueItemModel, (model) => model.ticket, { + cascade: true, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + items: QueueItem[]; +} + +@Entity(TABLE_NAME.QUEUE_ITEM) +export class QueueItemModel + extends BaseCoreModel + implements QueueItem +{ + @ManyToOne(() => QueueTicketModel, (model) => model.items, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'ticket_id' }) + ticket: QueueTicket; + + @Column('varchar') + item_id: string; + + @ManyToOne(() => ItemModel) + @JoinColumn({ name: 'item_id' }) + item: ItemEntity; + + @Column('int') + qty: number; +} diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts new file mode 100644 index 0000000..cd408d0 --- /dev/null +++ b/src/modules/queue/data/services/ticket.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import { InjectRepository } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; +import { QueueTicket } from '../../domain/entities/ticket.entity'; +import { QueueOrderModel, QueueTicketModel } from '../models/queue.model'; +import { QueueOrder } from '../../domain/entities/order.entity'; + +@Injectable() +export class TicketDataService extends BaseDataService { + constructor( + @InjectRepository(QueueTicketModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + + @InjectRepository(QueueOrderModel, CONNECTION_NAME.DEFAULT) + private order: Repository, + ) { + super(repo); + } + + async createQueueOrder(order: QueueOrder): Promise { + return await this.order.save(order); + } +} diff --git a/src/modules/queue/domain/entities/order.entity.ts b/src/modules/queue/domain/entities/order.entity.ts new file mode 100644 index 0000000..0ce59f2 --- /dev/null +++ b/src/modules/queue/domain/entities/order.entity.ts @@ -0,0 +1,10 @@ +import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity'; +import { QueueTicket } from './ticket.entity'; + +export interface QueueOrder extends BaseCoreEntity { + code: string; + customer: string; + phone: string; + date: number; + tickets: QueueTicket[]; +} diff --git a/src/modules/queue/domain/entities/queue-item.entity.ts b/src/modules/queue/domain/entities/queue-item.entity.ts new file mode 100644 index 0000000..f506ac4 --- /dev/null +++ b/src/modules/queue/domain/entities/queue-item.entity.ts @@ -0,0 +1,10 @@ +import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity'; +import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity'; +import { QueueTicket } from './ticket.entity'; + +export interface QueueItem extends BaseCoreEntity { + ticket?: QueueTicket; + item?: ItemEntity; + item_id: string; + qty: number; +} diff --git a/src/modules/queue/domain/entities/ticket.entity.ts b/src/modules/queue/domain/entities/ticket.entity.ts new file mode 100644 index 0000000..915990c --- /dev/null +++ b/src/modules/queue/domain/entities/ticket.entity.ts @@ -0,0 +1,13 @@ +import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity'; +import { QueueOrder } from './order.entity'; +import { QueueItem } from './queue-item.entity'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface QueueTicket extends BaseCoreEntity { + code: string; + customer: string; + phone: string; + date: number; + order?: QueueOrder; + items: QueueItem[]; +} diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts new file mode 100644 index 0000000..1126b28 --- /dev/null +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -0,0 +1,6 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class QueueOrchestrator { + // constructor() {} +} diff --git a/src/modules/queue/index.ts b/src/modules/queue/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts new file mode 100644 index 0000000..d5ab232 --- /dev/null +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -0,0 +1,24 @@ +import { + Body, + Controller, + Delete, + Param, + Patch, + Post, + Put, + Res, +} from '@nestjs/common'; + +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; + +import { Public } from 'src/core/guards'; +import { QueueOrchestrator } from '../../domain/queue.orchestrator'; + +@ApiTags(`Queue`) +@Controller(`v1/${MODULE_NAME.QUEUE}`) +@Public(false) +@ApiBearerAuth('JWT') +export class QueueController { + constructor(private orchestrator: QueueOrchestrator) {} +} diff --git a/src/modules/queue/queue.module.ts b/src/modules/queue/queue.module.ts new file mode 100644 index 0000000..3485f20 --- /dev/null +++ b/src/modules/queue/queue.module.ts @@ -0,0 +1,46 @@ +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 { CqrsModule } from '@nestjs/cqrs'; + +import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; +import { + QueueItemModel, + QueueOrderModel, + QueueTicketModel, +} from './data/models/queue.model'; +import { QueueController } from './infrastructure/controllers/queue.controller'; +import { QueueOrchestrator } from './domain/queue.orchestrator'; +import { QueueTransactionHandler } from './infrastructure/handlers/transaction.handler'; +import { TransactionDataService } from '../transaction/transaction/data/services/transaction-data.service'; +import { TransactionModel } from '../transaction/transaction/data/models/transaction.model'; +import { TicketDataService } from './data/services/ticket.service'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature( + [ + QueueOrderModel, + QueueTicketModel, + QueueItemModel, + ItemModel, + TransactionModel, + ], + CONNECTION_NAME.DEFAULT, + ), + CqrsModule, + ], + controllers: [QueueController], + providers: [ + QueueTransactionHandler, + + QueueOrchestrator, + + TransactionDataService, + TicketDataService, + ], +}) +export class QueueModule {} From efa245048a727f3a005d485d6561fe139cc33e59 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 22 Oct 2024 11:47:55 +0700 Subject: [PATCH 04/94] feat: item queue module --- src/app.module.ts | 17 +++ .../data/models/item-queue.model.ts | 28 ++++ .../data/services/item-queue-data.service.ts | 17 +++ .../data/services/item-queue-read.service.ts | 17 +++ .../entities/filter-item-queue.entity.ts | 5 + .../domain/entities/item-queue.entity.ts | 9 ++ .../usecases/item-queue-data.orchestrator.ts | 122 ++++++++++++++++++ .../usecases/item-queue-read.orchestrator.ts | 33 +++++ .../managers/active-item-queue.manager.ts | 39 ++++++ .../batch-active-item-queue.manager.ts | 40 ++++++ .../batch-confirm-item-queue.manager.ts | 40 ++++++ .../batch-delete-item-queue.manager.ts | 46 +++++++ .../batch-inactive-item-queue.manager.ts | 46 +++++++ .../managers/confirm-item-queue.manager.ts | 39 ++++++ .../managers/create-item-queue.manager.ts | 43 ++++++ .../managers/delete-item-queue.manager.ts | 45 +++++++ .../managers/detail-item-queue.manager.ts | 58 +++++++++ .../managers/inactive-item-queue.manager.ts | 45 +++++++ .../managers/index-item-queue.manager.ts | 75 +++++++++++ .../managers/update-item-queue.manager.ts | 53 ++++++++ src/modules/item-related/item-queue/index.ts | 0 .../dto/filter-item-queue.dto.ts | 15 +++ .../infrastructure/dto/item-queue.dto.ts | 32 +++++ .../item-queue-data.controller.ts | 78 +++++++++++ .../item-queue-read.controller.ts | 30 +++++ .../item-queue/item-queue.module.ts | 54 ++++++++ .../item/data/models/item.model.ts | 12 +- .../item/domain/entities/item.entity.ts | 2 +- .../item/infrastructure/dto/item.dto.ts | 12 +- 29 files changed, 1045 insertions(+), 7 deletions(-) create mode 100644 src/modules/item-related/item-queue/data/models/item-queue.model.ts create mode 100644 src/modules/item-related/item-queue/data/services/item-queue-data.service.ts create mode 100644 src/modules/item-related/item-queue/data/services/item-queue-read.service.ts create mode 100644 src/modules/item-related/item-queue/domain/entities/filter-item-queue.entity.ts create mode 100644 src/modules/item-related/item-queue/domain/entities/item-queue.entity.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/item-queue-data.orchestrator.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/item-queue-read.orchestrator.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/managers/active-item-queue.manager.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/managers/batch-active-item-queue.manager.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/managers/batch-confirm-item-queue.manager.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/managers/batch-delete-item-queue.manager.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/managers/batch-inactive-item-queue.manager.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/managers/confirm-item-queue.manager.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/managers/create-item-queue.manager.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/managers/delete-item-queue.manager.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/managers/detail-item-queue.manager.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/managers/inactive-item-queue.manager.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/managers/index-item-queue.manager.ts create mode 100644 src/modules/item-related/item-queue/domain/usecases/managers/update-item-queue.manager.ts create mode 100644 src/modules/item-related/item-queue/index.ts create mode 100644 src/modules/item-related/item-queue/infrastructure/dto/filter-item-queue.dto.ts create mode 100644 src/modules/item-related/item-queue/infrastructure/dto/item-queue.dto.ts create mode 100644 src/modules/item-related/item-queue/infrastructure/item-queue-data.controller.ts create mode 100644 src/modules/item-related/item-queue/infrastructure/item-queue-read.controller.ts create mode 100644 src/modules/item-related/item-queue/item-queue.module.ts diff --git a/src/app.module.ts b/src/app.module.ts index b8001b4..36efd42 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -83,6 +83,14 @@ import { UserLoginModel } from './modules/user-related/user/data/models/user-log import { LogUserLoginModel } from './modules/configuration/log/data/models/log-user-login.model'; import { AuthService } from './core/guards/domain/services/auth.service'; import { ReportSummaryModule } from './modules/reports/report-summary/report-summary.module'; +import { QueueModule } from './modules/queue/queue.module'; +import { + QueueOrderModel, + QueueTicketModel, + QueueItemModel, +} from './modules/queue/data/models/queue.model'; +import { ItemQueueModule } from './modules/item-related/item-queue/item-queue.module'; +import { ItemQueueModel } from './modules/item-related/item-queue/data/models/item-queue.model'; @Module({ imports: [ @@ -107,6 +115,7 @@ import { ReportSummaryModule } from './modules/reports/report-summary/report-sum ItemModel, ItemCategoryModel, ItemRateModel, + ItemQueueModel, LogModel, LogUserLoginModel, NewsModel, @@ -135,6 +144,11 @@ import { ReportSummaryModule } from './modules/reports/report-summary/report-sum // report ReportBookmarkModel, ExportReportHistoryModel, + + // Queue + QueueOrderModel, + QueueTicketModel, + QueueItemModel, ], synchronize: false, }), @@ -160,6 +174,7 @@ import { ReportSummaryModule } from './modules/reports/report-summary/report-sum ItemCategoryModule, ItemModule, ItemRateModule, + ItemQueueModule, // transaction PaymentMethodModule, @@ -193,6 +208,8 @@ import { ReportSummaryModule } from './modules/reports/report-summary/report-sum SupersetModule, GateScanModule, + + QueueModule, ], controllers: [], providers: [ diff --git a/src/modules/item-related/item-queue/data/models/item-queue.model.ts b/src/modules/item-related/item-queue/data/models/item-queue.model.ts new file mode 100644 index 0000000..293a849 --- /dev/null +++ b/src/modules/item-related/item-queue/data/models/item-queue.model.ts @@ -0,0 +1,28 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { ItemQueueEntity } from '../../domain/entities/item-queue.entity'; +import { Column, Entity, OneToMany } from 'typeorm'; +import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; +import { ItemType } from '../../constants'; +import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; + +@Entity(TABLE_NAME.ITEM_QUEUE) +export class ItemQueueModel + extends BaseStatusModel + implements ItemQueueEntity +{ + @Column('varchar', { name: 'name' }) + name: string; + + @Column('enum', { + name: 'item_type', + enum: ItemType, + default: ItemType.TIKET_MASUK, + }) + item_type: ItemType; + + @OneToMany(() => ItemModel, (model) => model.item_queue, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + items: ItemModel[]; +} diff --git a/src/modules/item-related/item-queue/data/services/item-queue-data.service.ts b/src/modules/item-related/item-queue/data/services/item-queue-data.service.ts new file mode 100644 index 0000000..7b67210 --- /dev/null +++ b/src/modules/item-related/item-queue/data/services/item-queue-data.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import { ItemQueueEntity } from '../../domain/entities/item-queue.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { ItemQueueModel } from '../models/item-queue.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; + +@Injectable() +export class ItemQueueDataService extends BaseDataService { + constructor( + @InjectRepository(ItemQueueModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/item-related/item-queue/data/services/item-queue-read.service.ts b/src/modules/item-related/item-queue/data/services/item-queue-read.service.ts new file mode 100644 index 0000000..64414f6 --- /dev/null +++ b/src/modules/item-related/item-queue/data/services/item-queue-read.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { ItemQueueEntity } from '../../domain/entities/item-queue.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { ItemQueueModel } from '../models/item-queue.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 ItemQueueReadService extends BaseReadService { + constructor( + @InjectRepository(ItemQueueModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/item-related/item-queue/domain/entities/filter-item-queue.entity.ts b/src/modules/item-related/item-queue/domain/entities/filter-item-queue.entity.ts new file mode 100644 index 0000000..e01cdcc --- /dev/null +++ b/src/modules/item-related/item-queue/domain/entities/filter-item-queue.entity.ts @@ -0,0 +1,5 @@ +import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; + +export interface FilterItemQueueEntity extends BaseFilterEntity { + item_types: string[]; +} diff --git a/src/modules/item-related/item-queue/domain/entities/item-queue.entity.ts b/src/modules/item-related/item-queue/domain/entities/item-queue.entity.ts new file mode 100644 index 0000000..e3b7708 --- /dev/null +++ b/src/modules/item-related/item-queue/domain/entities/item-queue.entity.ts @@ -0,0 +1,9 @@ +import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity'; +import { ItemType } from '../../constants'; +import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity'; + +export interface ItemQueueEntity extends BaseStatusEntity { + name: string; + item_type: ItemType; + items: ItemEntity[]; +} diff --git a/src/modules/item-related/item-queue/domain/usecases/item-queue-data.orchestrator.ts b/src/modules/item-related/item-queue/domain/usecases/item-queue-data.orchestrator.ts new file mode 100644 index 0000000..91dcd16 --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/item-queue-data.orchestrator.ts @@ -0,0 +1,122 @@ +import { Injectable } from '@nestjs/common'; +import { CreateItemQueueManager } from './managers/create-item-queue.manager'; +import { ItemQueueDataService } from '../../data/services/item-queue-data.service'; +import { ItemQueueEntity } from '../entities/item-queue.entity'; +import { DeleteItemQueueManager } from './managers/delete-item-queue.manager'; +import { UpdateItemQueueManager } from './managers/update-item-queue.manager'; +import { BaseDataTransactionOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator'; +import { ActiveItemQueueManager } from './managers/active-item-queue.manager'; +import { InactiveItemQueueManager } from './managers/inactive-item-queue.manager'; +import { ConfirmItemQueueManager } from './managers/confirm-item-queue.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchConfirmItemQueueManager } from './managers/batch-confirm-item-queue.manager'; +import { BatchInactiveItemQueueManager } from './managers/batch-inactive-item-queue.manager'; +import { BatchActiveItemQueueManager } from './managers/batch-active-item-queue.manager'; +import { BatchDeleteItemQueueManager } from './managers/batch-delete-item-queue.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class ItemQueueDataOrchestrator extends BaseDataTransactionOrchestrator { + constructor( + private createManager: CreateItemQueueManager, + private updateManager: UpdateItemQueueManager, + private deleteManager: DeleteItemQueueManager, + private activeManager: ActiveItemQueueManager, + private confirmManager: ConfirmItemQueueManager, + private inactiveManager: InactiveItemQueueManager, + private batchDeleteManager: BatchDeleteItemQueueManager, + private batchActiveManager: BatchActiveItemQueueManager, + private batchConfirmManager: BatchConfirmItemQueueManager, + private batchInactiveManager: BatchInactiveItemQueueManager, + private serviceData: ItemQueueDataService, + ) { + super(); + } + + async create(data): Promise { + data.items = data.item_ids.map((id) => { + return { id }; + }); + this.createManager.setData(data); + this.createManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY); + await this.createManager.execute(); + await this.createManager.generateConfig(); + return this.createManager.getResult(); + } + + async update(dataId, data): Promise { + this.updateManager.setData(dataId, data); + this.updateManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY); + await this.updateManager.execute(); + return this.updateManager.getResult(); + } + + async delete(dataId): Promise { + this.deleteManager.setData(dataId); + this.deleteManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY); + await this.deleteManager.execute(); + return this.deleteManager.getResult(); + } + + async batchDelete(dataIds: string[]): Promise { + this.batchDeleteManager.setData(dataIds); + this.batchDeleteManager.setService( + this.serviceData, + TABLE_NAME.ITEM_CATEGORY, + ); + await this.batchDeleteManager.execute(); + return this.batchDeleteManager.getResult(); + } + + async active(dataId): Promise { + this.activeManager.setData(dataId, STATUS.ACTIVE); + this.activeManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY); + await this.activeManager.execute(); + return this.activeManager.getResult(); + } + + async batchActive(dataIds: string[]): Promise { + this.batchActiveManager.setData(dataIds, STATUS.ACTIVE); + this.batchActiveManager.setService( + this.serviceData, + TABLE_NAME.ITEM_CATEGORY, + ); + await this.batchActiveManager.execute(); + return this.batchActiveManager.getResult(); + } + + async confirm(dataId): Promise { + this.confirmManager.setData(dataId, STATUS.ACTIVE); + this.confirmManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY); + await this.confirmManager.execute(); + return this.confirmManager.getResult(); + } + + async batchConfirm(dataIds: string[]): Promise { + this.batchConfirmManager.setData(dataIds, STATUS.ACTIVE); + this.batchConfirmManager.setService( + this.serviceData, + TABLE_NAME.ITEM_CATEGORY, + ); + await this.batchConfirmManager.execute(); + return this.batchConfirmManager.getResult(); + } + + async inactive(dataId): Promise { + this.inactiveManager.setData(dataId, STATUS.INACTIVE); + this.inactiveManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY); + await this.inactiveManager.execute(); + return this.inactiveManager.getResult(); + } + + async batchInactive(dataIds: string[]): Promise { + this.batchInactiveManager.setData(dataIds, STATUS.INACTIVE); + this.batchInactiveManager.setService( + this.serviceData, + TABLE_NAME.ITEM_CATEGORY, + ); + await this.batchInactiveManager.execute(); + return this.batchInactiveManager.getResult(); + } +} diff --git a/src/modules/item-related/item-queue/domain/usecases/item-queue-read.orchestrator.ts b/src/modules/item-related/item-queue/domain/usecases/item-queue-read.orchestrator.ts new file mode 100644 index 0000000..281d389 --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/item-queue-read.orchestrator.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { IndexItemQueueManager } from './managers/index-item-queue.manager'; +import { ItemQueueReadService } from '../../data/services/item-queue-read.service'; +import { ItemQueueEntity } from '../entities/item-queue.entity'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { BaseReadOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-read.orchestrator'; +import { DetailItemQueueManager } from './managers/detail-item-queue.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class ItemQueueReadOrchestrator extends BaseReadOrchestrator { + constructor( + private indexManager: IndexItemQueueManager, + private detailManager: DetailItemQueueManager, + private serviceData: ItemQueueReadService, + ) { + super(); + } + + async index(params): Promise> { + this.indexManager.setFilterParam(params); + this.indexManager.setService(this.serviceData, TABLE_NAME.ITEM_QUEUE); + await this.indexManager.execute(); + return this.indexManager.getResult(); + } + + async detail(dataId: string): Promise { + this.detailManager.setData(dataId); + this.detailManager.setService(this.serviceData, TABLE_NAME.ITEM_QUEUE); + await this.detailManager.execute(); + return this.detailManager.getResult(); + } +} diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/active-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/active-item-queue.manager.ts new file mode 100644 index 0000000..ae3baba --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/managers/active-item-queue.manager.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { ItemQueueEntity } from '../../entities/item-queue.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { ItemQueueModel } from '../../../data/models/item-queue.model'; + +@Injectable() +export class ActiveItemQueueManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.name}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return ItemQueueModel; + } + + get eventTopics(): EventTopics[] { + return []; + } +} diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/batch-active-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/batch-active-item-queue.manager.ts new file mode 100644 index 0000000..e70d534 --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/managers/batch-active-item-queue.manager.ts @@ -0,0 +1,40 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { ItemQueueEntity } from '../../entities/item-queue.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { ItemQueueModel } from '../../../data/models/item-queue.model'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchActiveItemQueueManager extends BaseBatchUpdateStatusManager { + validateData(data: ItemQueueEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return ItemQueueModel; + } + + get eventTopics(): EventTopics[] { + return []; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/batch-confirm-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/batch-confirm-item-queue.manager.ts new file mode 100644 index 0000000..02ec3d9 --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/managers/batch-confirm-item-queue.manager.ts @@ -0,0 +1,40 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { ItemQueueEntity } from '../../entities/item-queue.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { ItemQueueModel } from '../../../data/models/item-queue.model'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchConfirmItemQueueManager extends BaseBatchUpdateStatusManager { + validateData(data: ItemQueueEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return ItemQueueModel; + } + + get eventTopics(): EventTopics[] { + return []; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/batch-delete-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/batch-delete-item-queue.manager.ts new file mode 100644 index 0000000..a226ce6 --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/managers/batch-delete-item-queue.manager.ts @@ -0,0 +1,46 @@ +import { BaseBatchDeleteManager } from 'src/core/modules/domain/usecase/managers/base-batch-delete.manager'; +import { ItemQueueEntity } from '../../entities/item-queue.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { ItemQueueModel } from '../../../data/models/item-queue.model'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchDeleteItemQueueManager extends BaseBatchDeleteManager { + async beforeProcess(): Promise { + return; + } + + async validateData(data: ItemQueueEntity): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return [ + { + relation: 'items', + message: + 'Gagal! tidak dapat mengubah tipe item karena sudah berelasi dengan item', + }, + ]; + } + + get entityTarget(): any { + return ItemQueueModel; + } + + get eventTopics(): EventTopics[] { + return []; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/batch-inactive-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/batch-inactive-item-queue.manager.ts new file mode 100644 index 0000000..2d3c725 --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/managers/batch-inactive-item-queue.manager.ts @@ -0,0 +1,46 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { ItemQueueEntity } from '../../entities/item-queue.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { ItemQueueModel } from '../../../data/models/item-queue.model'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchInactiveItemQueueManager extends BaseBatchUpdateStatusManager { + validateData(data: ItemQueueEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return [ + { + relation: 'items', + message: + 'Gagal! tidak dapat mengubah tipe item karena sudah berelasi dengan item', + }, + ]; + } + + get entityTarget(): any { + return ItemQueueModel; + } + + get eventTopics(): EventTopics[] { + return []; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/confirm-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/confirm-item-queue.manager.ts new file mode 100644 index 0000000..d0ad2b8 --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/managers/confirm-item-queue.manager.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { ItemQueueEntity } from '../../entities/item-queue.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { ItemQueueModel } from '../../../data/models/item-queue.model'; + +@Injectable() +export class ConfirmItemQueueManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.name}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return ItemQueueModel; + } + + get eventTopics(): EventTopics[] { + return []; + } +} diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/create-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/create-item-queue.manager.ts new file mode 100644 index 0000000..09c3c21 --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/managers/create-item-queue.manager.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@nestjs/common'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { ItemQueueEntity } from '../../entities/item-queue.entity'; +import { ItemQueueModel } from '../../../data/models/item-queue.model'; +import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; + +@Injectable() +export class CreateItemQueueManager extends BaseCreateManager { + async beforeProcess(): Promise { + Object.assign(this.data, { + item_type: this.data.item_type.toLowerCase(), + }); + return; + } + + async afterProcess(): Promise { + return; + } + + async generateConfig(): Promise { + // TODO: Implement logic here + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return [{ column: 'name' }]; + } + + get eventTopics(): EventTopics[] { + return []; + } + + get entityTarget(): any { + return ItemQueueModel; + } +} diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/delete-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/delete-item-queue.manager.ts new file mode 100644 index 0000000..db63c0c --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/managers/delete-item-queue.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.manager'; +import { ItemQueueEntity } from '../../entities/item-queue.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { ItemQueueModel } from '../../../data/models/item-queue.model'; + +@Injectable() +export class DeleteItemQueueManager extends BaseDeleteManager { + getResult(): string { + return `Success`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return [ + { + relation: 'items', + message: + 'Gagal! tidak dapat mengubah tipe item karena sudah berelasi dengan item', + }, + ]; + } + + get entityTarget(): any { + return ItemQueueModel; + } + + get eventTopics(): EventTopics[] { + return []; + } +} diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/detail-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/detail-item-queue.manager.ts new file mode 100644 index 0000000..007c5b9 --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/managers/detail-item-queue.manager.ts @@ -0,0 +1,58 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDetailManager } from 'src/core/modules/domain/usecase/managers/base-detail.manager'; +import { ItemQueueEntity } from '../../entities/item-queue.entity'; +import { RelationParam } from 'src/core/modules/domain/entities/base-filter.entity'; + +@Injectable() +export class DetailItemQueueManager extends BaseDetailManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + joinRelations: [], + selectRelations: ['items'], + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.status`, + `${this.tableName}.name`, + `${this.tableName}.item_type`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + + `items.id`, + `items.created_at`, + `items.status`, + `items.item_type`, + `items.name`, + `items.hpp`, + `items.limit_type`, + `items.limit_value`, + `items.base_price`, + `items.share_profit`, + `items.play_estimation`, + ]; + } + + get setFindProperties(): any { + return { + id: this.dataId, + }; + } +} diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/inactive-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/inactive-item-queue.manager.ts new file mode 100644 index 0000000..106ab98 --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/managers/inactive-item-queue.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { ItemQueueEntity } from '../../entities/item-queue.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { ItemQueueModel } from '../../../data/models/item-queue.model'; + +@Injectable() +export class InactiveItemQueueManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success inactive data ${this.result.name}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return [ + { + relation: 'items', + message: + 'Gagal! tidak dapat mengubah tipe item karena sudah berelasi dengan item', + }, + ]; + } + + get entityTarget(): any { + return ItemQueueModel; + } + + get eventTopics(): EventTopics[] { + return []; + } +} diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/index-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/index-item-queue.manager.ts new file mode 100644 index 0000000..b1256d3 --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/managers/index-item-queue.manager.ts @@ -0,0 +1,75 @@ +import { Injectable } from '@nestjs/common'; +import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager'; +import { ItemQueueEntity } from '../../entities/item-queue.entity'; +import { SelectQueryBuilder } from 'typeorm'; +import { + Param, + RelationParam, +} from 'src/core/modules/domain/entities/base-filter.entity'; + +@Injectable() +export class IndexItemQueueManager extends BaseIndexManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + joinRelations: [], + selectRelations: ['items'], + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.status`, + `${this.tableName}.name`, + `${this.tableName}.item_type`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + + `items.id`, + `items.created_at`, + `items.status`, + `items.item_type`, + `items.name`, + `items.hpp`, + `items.limit_type`, + `items.limit_value`, + `items.base_price`, + `items.share_profit`, + `items.play_estimation`, + ]; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.name`, + data: this.filterParam.names, + }, + { + cols: `${this.tableName}.item_type::text`, + data: this.filterParam.item_types, + }, + ]; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + return queryBuilder; + } +} diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/update-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/update-item-queue.manager.ts new file mode 100644 index 0000000..2801c3b --- /dev/null +++ b/src/modules/item-related/item-queue/domain/usecases/managers/update-item-queue.manager.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager'; +import { ItemQueueEntity } from '../../entities/item-queue.entity'; +import { ItemQueueModel } from '../../../data/models/item-queue.model'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; + +@Injectable() +export class UpdateItemQueueManager extends BaseUpdateManager { + async validateProcess(): Promise { + Object.assign(this.data, { + item_type: this.data.item_type.toLowerCase(), + }); + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + if (this.data.item_type != this.oldData.item_type) { + return [ + { + relation: 'items', + message: + 'Gagal! tidak dapat mengubah tipe item karena sudah berelasi dengan item', + }, + ]; + } else { + return []; + } + } + + get uniqueColumns(): columnUniques[] { + return [{ column: 'name' }]; + } + + get entityTarget(): any { + return ItemQueueModel; + } + + get eventTopics(): EventTopics[] { + return []; + } +} diff --git a/src/modules/item-related/item-queue/index.ts b/src/modules/item-related/item-queue/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/item-related/item-queue/infrastructure/dto/filter-item-queue.dto.ts b/src/modules/item-related/item-queue/infrastructure/dto/filter-item-queue.dto.ts new file mode 100644 index 0000000..ab43721 --- /dev/null +++ b/src/modules/item-related/item-queue/infrastructure/dto/filter-item-queue.dto.ts @@ -0,0 +1,15 @@ +import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.dto'; +import { FilterItemQueueEntity } from '../../domain/entities/filter-item-queue.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; + +export class FilterItemQueueDto + extends BaseFilterDto + implements FilterItemQueueEntity +{ + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + item_types: string[]; +} diff --git a/src/modules/item-related/item-queue/infrastructure/dto/item-queue.dto.ts b/src/modules/item-related/item-queue/infrastructure/dto/item-queue.dto.ts new file mode 100644 index 0000000..cb1406c --- /dev/null +++ b/src/modules/item-related/item-queue/infrastructure/dto/item-queue.dto.ts @@ -0,0 +1,32 @@ +import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.dto'; +import { ItemQueueEntity } from '../../domain/entities/item-queue.entity'; +import { IsArray, IsString } from 'class-validator'; +import { ItemType } from '../../constants'; +import { ApiProperty } from '@nestjs/swagger'; +import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity'; +import { Exclude, Transform } from 'class-transformer'; + +export class ItemQueueDto extends BaseStatusDto implements ItemQueueEntity { + @Exclude() + items: ItemEntity[]; + + @ApiProperty({ name: 'name', required: true, example: 'Bundling w Entrance' }) + @IsString() + name: string; + + @ApiProperty({ + type: 'string', + required: true, + description: `Select (${JSON.stringify(Object.values(ItemType))})`, + example: ItemType.BUNDLING, + }) + item_type: ItemType; + + @ApiProperty({ type: [String], required: true }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + @IsArray() + @IsString({ each: true }) + item_ids: string[]; +} diff --git a/src/modules/item-related/item-queue/infrastructure/item-queue-data.controller.ts b/src/modules/item-related/item-queue/infrastructure/item-queue-data.controller.ts new file mode 100644 index 0000000..2639a8f --- /dev/null +++ b/src/modules/item-related/item-queue/infrastructure/item-queue-data.controller.ts @@ -0,0 +1,78 @@ +import { + Body, + Controller, + Delete, + Param, + Patch, + Post, + Put, +} from '@nestjs/common'; +import { ItemQueueDataOrchestrator } from '../domain/usecases/item-queue-data.orchestrator'; +import { ItemQueueDto } from './dto/item-queue.dto'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { ItemQueueEntity } from '../domain/entities/item-queue.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.ITEM_QUEUE.split('-').join(' ')} - data`) +@Controller(`v1/${MODULE_NAME.ITEM_QUEUE}`) +@Public(false) +@ApiBearerAuth('JWT') +export class ItemQueueDataController { + constructor(private orchestrator: ItemQueueDataOrchestrator) {} + + @Post() + async create(@Body() data: ItemQueueDto): 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/active') + async active(@Param('id') dataId: string): Promise { + return await this.orchestrator.active(dataId); + } + + @Put('/batch-active') + async batchActive(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchActive(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); + } + + @Patch(':id/inactive') + async inactive(@Param('id') dataId: string): Promise { + return await this.orchestrator.inactive(dataId); + } + + @Put('/batch-inactive') + async batchInactive(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchInactive(body.ids); + } + + @Put(':id') + async update( + @Param('id') dataId: string, + @Body() data: ItemQueueDto, + ): 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/item-related/item-queue/infrastructure/item-queue-read.controller.ts b/src/modules/item-related/item-queue/infrastructure/item-queue-read.controller.ts new file mode 100644 index 0000000..54490f7 --- /dev/null +++ b/src/modules/item-related/item-queue/infrastructure/item-queue-read.controller.ts @@ -0,0 +1,30 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { FilterItemQueueDto } from './dto/filter-item-queue.dto'; +import { Pagination } from 'src/core/response'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { ItemQueueEntity } from '../domain/entities/item-queue.entity'; +import { ItemQueueReadOrchestrator } from '../domain/usecases/item-queue-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.ITEM_QUEUE.split('-').join(' ')} - read`) +@Controller(`v1/${MODULE_NAME.ITEM_QUEUE}`) +@Public(false) +@ApiBearerAuth('JWT') +export class ItemQueueReadController { + constructor(private orchestrator: ItemQueueReadOrchestrator) {} + + @Get() + @Pagination() + async index( + @Query() params: FilterItemQueueDto, + ): Promise> { + return await this.orchestrator.index(params); + } + + @Get(':id') + async detail(@Param('id') id: string): Promise { + return await this.orchestrator.detail(id); + } +} diff --git a/src/modules/item-related/item-queue/item-queue.module.ts b/src/modules/item-related/item-queue/item-queue.module.ts new file mode 100644 index 0000000..c009f9f --- /dev/null +++ b/src/modules/item-related/item-queue/item-queue.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 { ItemQueueDataService } from './data/services/item-queue-data.service'; +import { ItemQueueReadService } from './data/services/item-queue-read.service'; +import { ItemQueueReadController } from './infrastructure/item-queue-read.controller'; +import { ItemQueueReadOrchestrator } from './domain/usecases/item-queue-read.orchestrator'; +import { ItemQueueDataController } from './infrastructure/item-queue-data.controller'; +import { ItemQueueDataOrchestrator } from './domain/usecases/item-queue-data.orchestrator'; +import { CreateItemQueueManager } from './domain/usecases/managers/create-item-queue.manager'; +import { CqrsModule } from '@nestjs/cqrs'; +import { IndexItemQueueManager } from './domain/usecases/managers/index-item-queue.manager'; +import { DeleteItemQueueManager } from './domain/usecases/managers/delete-item-queue.manager'; +import { UpdateItemQueueManager } from './domain/usecases/managers/update-item-queue.manager'; +import { ActiveItemQueueManager } from './domain/usecases/managers/active-item-queue.manager'; +import { ConfirmItemQueueManager } from './domain/usecases/managers/confirm-item-queue.manager'; +import { InactiveItemQueueManager } from './domain/usecases/managers/inactive-item-queue.manager'; +import { DetailItemQueueManager } from './domain/usecases/managers/detail-item-queue.manager'; +import { BatchDeleteItemQueueManager } from './domain/usecases/managers/batch-delete-item-queue.manager'; +import { BatchActiveItemQueueManager } from './domain/usecases/managers/batch-active-item-queue.manager'; +import { BatchConfirmItemQueueManager } from './domain/usecases/managers/batch-confirm-item-queue.manager'; +import { BatchInactiveItemQueueManager } from './domain/usecases/managers/batch-inactive-item-queue.manager'; +import { ItemQueueModel } from './data/models/item-queue.model'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature([ItemQueueModel], CONNECTION_NAME.DEFAULT), + CqrsModule, + ], + controllers: [ItemQueueDataController, ItemQueueReadController], + providers: [ + IndexItemQueueManager, + DetailItemQueueManager, + CreateItemQueueManager, + DeleteItemQueueManager, + UpdateItemQueueManager, + ActiveItemQueueManager, + ConfirmItemQueueManager, + InactiveItemQueueManager, + BatchDeleteItemQueueManager, + BatchActiveItemQueueManager, + BatchConfirmItemQueueManager, + BatchInactiveItemQueueManager, + + ItemQueueDataService, + ItemQueueReadService, + + ItemQueueDataOrchestrator, + ItemQueueReadOrchestrator, + ], +}) +export class ItemQueueModule {} diff --git a/src/modules/item-related/item/data/models/item.model.ts b/src/modules/item-related/item/data/models/item.model.ts index 7506733..14df792 100644 --- a/src/modules/item-related/item/data/models/item.model.ts +++ b/src/modules/item-related/item/data/models/item.model.ts @@ -16,6 +16,7 @@ import { ItemCategoryModel } from 'src/modules/item-related/item-category/data/m import { UserModel } from 'src/modules/user-related/user/data/models/user.model'; import { ItemRateModel } from 'src/modules/item-related/item-rate/data/models/item-rate.model'; import { GateModel } from 'src/modules/web-information/gate/data/models/gate.model'; +import { ItemQueueModel } from 'src/modules/item-related/item-queue/data/models/item-queue.model'; @Entity(TABLE_NAME.ITEM) export class ItemModel @@ -28,8 +29,8 @@ export class ItemModel @Column('varchar', { name: 'image_url', nullable: true }) image_url: string; - @Column('varchar', { nullable: true }) - video_url: string; + @Column('json', { nullable: true }) + video_url: string[]; @Column('enum', { name: 'item_type', @@ -85,6 +86,13 @@ export class ItemModel @JoinColumn({ name: 'item_category_id' }) item_category: ItemCategoryModel; + @ManyToOne(() => ItemQueueModel, (model) => model.items, { + onUpdate: 'CASCADE', + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'item_queue_id' }) + item_queue: ItemQueueModel; + // relation ke tenant // ? karena item bisajadi merupakan item dari tenant @Column('varchar', { name: 'tenant_id', nullable: true }) diff --git a/src/modules/item-related/item/domain/entities/item.entity.ts b/src/modules/item-related/item/domain/entities/item.entity.ts index 7ade51e..995b113 100644 --- a/src/modules/item-related/item/domain/entities/item.entity.ts +++ b/src/modules/item-related/item/domain/entities/item.entity.ts @@ -6,7 +6,7 @@ export interface ItemEntity extends BaseStatusEntity { name: string; item_type: ItemType; image_url: string; - video_url?: string; + video_url?: string[]; hpp: number; sales_margin: number; diff --git a/src/modules/item-related/item/infrastructure/dto/item.dto.ts b/src/modules/item-related/item/infrastructure/dto/item.dto.ts index c6f739e..c1add0f 100644 --- a/src/modules/item-related/item/infrastructure/dto/item.dto.ts +++ b/src/modules/item-related/item/infrastructure/dto/item.dto.ts @@ -12,6 +12,7 @@ import { ValidateIf, } from 'class-validator'; import { ItemCategoryEntity } from 'src/modules/item-related/item-category/domain/entities/item-category.entity'; +import { Transform } from 'class-transformer'; export class ItemDto extends BaseStatusDto implements ItemEntity { @ApiProperty({ @@ -32,13 +33,16 @@ export class ItemDto extends BaseStatusDto implements ItemEntity { image_url: string; @ApiProperty({ - type: String, + isArray: true, required: false, - example: '...', }) - @IsString() + // @IsString() @ValidateIf((body) => body.video_url) - video_url: string; + @Transform(({ value }) => { + if (!value) return []; + return Array.isArray(value) ? value : [value]; + }) + video_url: string[]; @ApiProperty({ type: 'string', From 4f0b378ec6a1b240e0f397716630daed7e152cc5 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 22 Oct 2024 13:30:49 +0700 Subject: [PATCH 05/94] fix: missing transaction handler --- .../handlers/transaction.handler.ts | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/modules/queue/infrastructure/handlers/transaction.handler.ts diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts new file mode 100644 index 0000000..9d168e9 --- /dev/null +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -0,0 +1,54 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; +import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event'; +import { TicketDataService } from '../../data/services/ticket.service'; +import { QueueOrder } from '../../domain/entities/order.entity'; +import { QueueTicket } from '../../domain/entities/ticket.entity'; +import { QueueItem } from '../../domain/entities/queue-item.entity'; + +@EventsHandler(TransactionChangeStatusEvent) +export class QueueTransactionHandler + implements IEventHandler +{ + constructor( + private dataService: TransactionDataService, + private queueService: TicketDataService, + ) {} + + async handle(event: TransactionChangeStatusEvent) { + const process_data = event.data.data; + + /** + * If data still in process (not settled) then don't create the queue order + */ + if (process_data?.status != 'settled') return; + + const transaction = await this.dataService.getOneByOptions({ + where: { + id: event.data.id, + }, + relations: ['items'], + }); + + const { customer_name, customer_phone, invoice_code } = transaction; + const current_date = new Date().valueOf(); + const customerOrder = { + code: invoice_code, + customer: customer_name, + phone: customer_phone, + date: current_date, + }; + + const items = transaction.items.map((item) => { + return { + item_id: item.item_id, + qty: item.qty, + }; + }); + + const ticket: QueueTicket = { ...customerOrder, items }; + const order: QueueOrder = { ...customerOrder, tickets: [ticket] }; + + this.queueService.createQueueOrder(order); + } +} From d6c02ac29ffdf57c94da6479758005a6a26e4502 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 22 Oct 2024 14:36:14 +0700 Subject: [PATCH 06/94] fix: on delete queue item --- .../1729582398827-item-queue-on-delete.ts | 25 +++++++++++++++++++ .../data/models/item-queue.model.ts | 1 - .../managers/delete-item-queue.manager.ts | 8 +----- .../item/data/models/item.model.ts | 2 +- 4 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 src/database/migrations/1729582398827-item-queue-on-delete.ts diff --git a/src/database/migrations/1729582398827-item-queue-on-delete.ts b/src/database/migrations/1729582398827-item-queue-on-delete.ts new file mode 100644 index 0000000..09fe090 --- /dev/null +++ b/src/database/migrations/1729582398827-item-queue-on-delete.ts @@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ItemQueueOnDelete1729582398827 implements MigrationInterface { + name = 'ItemQueueOnDelete1729582398827'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "items" DROP CONSTRAINT "FK_2cbbeb03e176addcf60d65f7c9c"`, + ); + + await queryRunner.query( + `ALTER TABLE "items" ADD CONSTRAINT "FK_2cbbeb03e176addcf60d65f7c9c" FOREIGN KEY ("item_queue_id") REFERENCES "item_queues"("id") ON DELETE SET NULL ON UPDATE CASCADE`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "items" DROP CONSTRAINT "FK_2cbbeb03e176addcf60d65f7c9c"`, + ); + + await queryRunner.query( + `ALTER TABLE "items" ADD CONSTRAINT "FK_2cbbeb03e176addcf60d65f7c9c" FOREIGN KEY ("item_queue_id") REFERENCES "item_queues"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } +} diff --git a/src/modules/item-related/item-queue/data/models/item-queue.model.ts b/src/modules/item-related/item-queue/data/models/item-queue.model.ts index 293a849..61e7d39 100644 --- a/src/modules/item-related/item-queue/data/models/item-queue.model.ts +++ b/src/modules/item-related/item-queue/data/models/item-queue.model.ts @@ -21,7 +21,6 @@ export class ItemQueueModel item_type: ItemType; @OneToMany(() => ItemModel, (model) => model.item_queue, { - onDelete: 'CASCADE', onUpdate: 'CASCADE', }) items: ItemModel[]; diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/delete-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/delete-item-queue.manager.ts index db63c0c..360aa89 100644 --- a/src/modules/item-related/item-queue/domain/usecases/managers/delete-item-queue.manager.ts +++ b/src/modules/item-related/item-queue/domain/usecases/managers/delete-item-queue.manager.ts @@ -26,13 +26,7 @@ export class DeleteItemQueueManager extends BaseDeleteManager { } get validateRelations(): validateRelations[] { - return [ - { - relation: 'items', - message: - 'Gagal! tidak dapat mengubah tipe item karena sudah berelasi dengan item', - }, - ]; + return []; } get entityTarget(): any { diff --git a/src/modules/item-related/item/data/models/item.model.ts b/src/modules/item-related/item/data/models/item.model.ts index 14df792..52f5f9f 100644 --- a/src/modules/item-related/item/data/models/item.model.ts +++ b/src/modules/item-related/item/data/models/item.model.ts @@ -88,7 +88,7 @@ export class ItemModel @ManyToOne(() => ItemQueueModel, (model) => model.items, { onUpdate: 'CASCADE', - onDelete: 'CASCADE', + onDelete: 'SET NULL', }) @JoinColumn({ name: 'item_queue_id' }) item_queue: ItemQueueModel; From e9d864c92258b42a3329060c3eed7698bbd6c62a Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 23 Oct 2024 10:30:14 +0700 Subject: [PATCH 07/94] feat: add login queue customer --- ...46392-add-transaction-id-to-queue-order.ts | 19 +++++++++++++++++++ src/modules/queue/data/models/queue.model.ts | 3 +++ .../queue/data/services/ticket.service.ts | 9 +++++++++ .../queue/domain/entities/order.entity.ts | 1 + .../queue/domain/queue.orchestrator.ts | 17 +++++++++++++++-- .../controllers/queue.controller.ts | 10 ++++++++-- .../handlers/transaction.handler.ts | 3 ++- 7 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 src/database/migrations/1729653046392-add-transaction-id-to-queue-order.ts diff --git a/src/database/migrations/1729653046392-add-transaction-id-to-queue-order.ts b/src/database/migrations/1729653046392-add-transaction-id-to-queue-order.ts new file mode 100644 index 0000000..c80ff83 --- /dev/null +++ b/src/database/migrations/1729653046392-add-transaction-id-to-queue-order.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTransactionIdToQueueOrder1729653046392 + implements MigrationInterface +{ + name = 'AddTransactionIdToQueueOrder1729653046392'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "queue_orders" ADD "transaction_id" character varying NOT NULL`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "queue_orders" DROP COLUMN "transaction_id"`, + ); + } +} diff --git a/src/modules/queue/data/models/queue.model.ts b/src/modules/queue/data/models/queue.model.ts index 5e65c73..5fbbebe 100644 --- a/src/modules/queue/data/models/queue.model.ts +++ b/src/modules/queue/data/models/queue.model.ts @@ -22,6 +22,9 @@ export class QueueOrderModel @Column('varchar', { nullable: true }) phone: string; + @Column('varchar', { nullable: false }) + transaction_id: string; + @OneToMany(() => QueueTicketModel, (model) => model.order, { cascade: true, onDelete: 'CASCADE', diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index cd408d0..3dd6894 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -22,4 +22,13 @@ export class TicketDataService extends BaseDataService { async createQueueOrder(order: QueueOrder): Promise { return await this.order.save(order); } + + async loginQueue(id: string): Promise { + return this.order.findOneOrFail({ + relations: ['tickets'], + where: { + transaction_id: id, + }, + }); + } } diff --git a/src/modules/queue/domain/entities/order.entity.ts b/src/modules/queue/domain/entities/order.entity.ts index 0ce59f2..32f843b 100644 --- a/src/modules/queue/domain/entities/order.entity.ts +++ b/src/modules/queue/domain/entities/order.entity.ts @@ -6,5 +6,6 @@ export interface QueueOrder extends BaseCoreEntity { customer: string; phone: string; date: number; + transaction_id: string; tickets: QueueTicket[]; } diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index 1126b28..f5d1624 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -1,6 +1,19 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { TicketDataService } from '../data/services/ticket.service'; +import { QueueOrder } from './entities/order.entity'; @Injectable() export class QueueOrchestrator { - // constructor() {} + constructor(private readonly dataService: TicketDataService) {} + + async loginCustomer(id: string): Promise { + try { + return await this.dataService.loginQueue(id); + } catch (error) { + throw new UnauthorizedException({ + message: 'Invoice tidak ditemukan', + error: 'INVOICE_NOT_FOUND', + }); + } + } } diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index d5ab232..b1932e1 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, - Delete, + Get, Param, Patch, Post, @@ -14,11 +14,17 @@ import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { Public } from 'src/core/guards'; import { QueueOrchestrator } from '../../domain/queue.orchestrator'; +import { QueueOrder } from '../../domain/entities/order.entity'; @ApiTags(`Queue`) @Controller(`v1/${MODULE_NAME.QUEUE}`) -@Public(false) +@Public(true) @ApiBearerAuth('JWT') export class QueueController { constructor(private orchestrator: QueueOrchestrator) {} + + @Get('login/:id') + async loginCustomer(@Param('id') id: string): Promise { + return await this.orchestrator.loginCustomer(id); + } } diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index 9d168e9..365a7ca 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -30,13 +30,14 @@ export class QueueTransactionHandler relations: ['items'], }); - const { customer_name, customer_phone, invoice_code } = transaction; + const { id, customer_name, customer_phone, invoice_code } = transaction; const current_date = new Date().valueOf(); const customerOrder = { code: invoice_code, customer: customer_name, phone: customer_phone, date: current_date, + transaction_id: id, }; const items = transaction.items.map((item) => { From 01b2796c26efef6f84703157b90c32302209011d Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 23 Oct 2024 11:56:21 +0700 Subject: [PATCH 08/94] feat: order tickets --- src/modules/queue/data/models/queue.model.ts | 7 +++++-- .../queue/data/services/ticket.service.ts | 9 +++++++++ .../queue/domain/entities/ticket.entity.ts | 1 + src/modules/queue/domain/queue.orchestrator.ts | 18 ++++++++++++++++++ .../controllers/queue.controller.ts | 5 +++++ 5 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/modules/queue/data/models/queue.model.ts b/src/modules/queue/data/models/queue.model.ts index 5fbbebe..c5377f4 100644 --- a/src/modules/queue/data/models/queue.model.ts +++ b/src/modules/queue/data/models/queue.model.ts @@ -57,6 +57,9 @@ export class QueueTicketModel @JoinColumn({ name: 'order_id' }) order: QueueOrder; + @Column('varchar') + order_id: string; + @Column({ type: 'bigint' }) date: number; @@ -65,7 +68,7 @@ export class QueueTicketModel onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - items: QueueItem[]; + items: QueueItemModel[]; } @Entity(TABLE_NAME.QUEUE_ITEM) @@ -85,7 +88,7 @@ export class QueueItemModel @ManyToOne(() => ItemModel) @JoinColumn({ name: 'item_id' }) - item: ItemEntity; + item: ItemModel; @Column('int') qty: number; diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index 3dd6894..c146531 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -31,4 +31,13 @@ export class TicketDataService extends BaseDataService { }, }); } + + async queueTickets(order_id: string): Promise { + return this.repo.find({ + relations: ['items', 'items.item', 'items.item.item_queue'], + where: { + order_id, + }, + }); + } } diff --git a/src/modules/queue/domain/entities/ticket.entity.ts b/src/modules/queue/domain/entities/ticket.entity.ts index 915990c..e5f60d5 100644 --- a/src/modules/queue/domain/entities/ticket.entity.ts +++ b/src/modules/queue/domain/entities/ticket.entity.ts @@ -9,5 +9,6 @@ export interface QueueTicket extends BaseCoreEntity { phone: string; date: number; order?: QueueOrder; + order_id?: string; items: QueueItem[]; } diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index f5d1624..0cc8997 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -1,6 +1,7 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { TicketDataService } from '../data/services/ticket.service'; import { QueueOrder } from './entities/order.entity'; +import { title } from 'process'; @Injectable() export class QueueOrchestrator { @@ -16,4 +17,21 @@ export class QueueOrchestrator { }); } } + + async queueTickets(order_id: string): Promise { + const tickets = await this.dataService.queueTickets(order_id); + const items = tickets[0].items; + return items.map((item) => { + return { + id: item.id, + title: item.item.name, + qty: item.qty, + available: true, + average: 12, + nearest: '13:10', + crowded_level: 20, + available_time: '15:00', + }; + }); + } } diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index b1932e1..dbf7198 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -27,4 +27,9 @@ export class QueueController { async loginCustomer(@Param('id') id: string): Promise { return await this.orchestrator.loginCustomer(id); } + + @Get('tickets/:id') + async queueTickets(@Param('id') id: string): Promise { + return await this.orchestrator.queueTickets(id); + } } From 32d4064f0a6935f26e5bcb29dbe88ec7a8fcd4a5 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 23 Oct 2024 17:16:28 +0700 Subject: [PATCH 09/94] feat: add detail queue item --- .../queue/data/services/ticket.service.ts | 13 +++ .../queue/domain/queue.orchestrator.ts | 85 +++++++++++++++++-- .../controllers/queue.controller.ts | 15 +++- 3 files changed, 106 insertions(+), 7 deletions(-) diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index c146531..a55a443 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -40,4 +40,17 @@ export class TicketDataService extends BaseDataService { }, }); } + + async queueItemTickets( + order_id: string, + item_id: string, + ): Promise { + return this.repo.find({ + relations: ['items', 'items.item', 'items.item.item_queue'], + where: { + order_id, + items: [{ item_id }, { item: { item_queue: { id: item_id } } }], + }, + }); + } } diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index 0cc8997..b6169d0 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -1,7 +1,7 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { TicketDataService } from '../data/services/ticket.service'; import { QueueOrder } from './entities/order.entity'; -import { title } from 'process'; +import { QueueItemModel } from '../data/models/queue.model'; @Injectable() export class QueueOrchestrator { @@ -20,12 +20,85 @@ export class QueueOrchestrator { async queueTickets(order_id: string): Promise { const tickets = await this.dataService.queueTickets(order_id); - const items = tickets[0].items; - return items.map((item) => { + return tickets.map((ticket) => { + const totalActivities = ticket.items.length; + const totalTickets = ticket.items.reduce( + (acc, item) => acc + item.qty, + 0, + ); + const totalUsed = 0; + const totalQueue = 0; return { - id: item.id, - title: item.item.name, - qty: item.qty, + id: ticket.id, + code: ticket.code, + customer: ticket.customer, + phone: ticket.phone, + date: ticket.date, + summary: { + total_activities: totalActivities, + total_tickets: totalTickets, + total_used: totalUsed, + total_queue: totalQueue, + }, + }; + }); + } + + async queueItemDetail(order_id: string, item_id: string): Promise { + const tickets = await this.dataService.queueItemTickets(order_id, item_id); + return tickets.map((ticket) => { + const totalActivities = ticket.items.length; + const totalTickets = ticket.items.reduce( + (acc, item) => acc + item.qty, + 0, + ); + const totalUsed = 0; + const totalQueue = 0; + return { + id: ticket.id, + code: ticket.code, + customer: ticket.customer, + phone: ticket.phone, + date: ticket.date, + summary: { + total_activities: totalActivities, + total_tickets: totalTickets, + total_used: totalUsed, + total_queue: totalQueue, + }, + queue: [ + { + code: 'A001', + qty: 1, + time: '15:00', + status: 'waiting', + }, + ], + }; + }); + } + + async queueItems(order_id: string): Promise { + const tickets = await this.dataService.queueTickets(order_id); + const ticketItems = {}; + + tickets.forEach((ticket) => { + ticket.items.forEach((item) => { + const item_id = item.item.item_queue?.id ?? item.item.id; + const currentItem = ticketItems[item_id]; + ticketItems[item_id] = currentItem ? [...currentItem, item] : [item]; + }); + }); + + return Object.values(ticketItems).map((items) => { + const item = items[0]; + const item_qty = items.reduce((acc, item) => acc + item.qty, 0); + return { + id: item.item_id, + title: item.item.item_queue?.name ?? item.item.name, + image_url: item.item.image_url, + qty: item_qty, + available: true, average: 12, nearest: '13:10', diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index dbf7198..8158d22 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -28,8 +28,21 @@ export class QueueController { return await this.orchestrator.loginCustomer(id); } - @Get('tickets/:id') + @Get(':id/tickets') async queueTickets(@Param('id') id: string): Promise { return await this.orchestrator.queueTickets(id); } + + @Get(':id/items') + async queueItems(@Param('id') id: string): Promise { + return await this.orchestrator.queueItems(id); + } + + @Get(':id/items/:item_id') + async queueItemTickets( + @Param('id') id: string, + @Param('item_id') item_id: string, + ): Promise { + return await this.orchestrator.queueItemDetail(id, item_id); + } } From ac8628918264b4305908344152e84b5a32fb7424 Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 24 Oct 2024 13:25:56 +0700 Subject: [PATCH 10/94] feat: add detail item ticket --- .../queue/data/services/ticket.service.ts | 13 +++++ .../queue/domain/queue.orchestrator.ts | 56 +++++++++++++++++++ .../controllers/queue.controller.ts | 19 +++---- 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index a55a443..f2cc833 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -41,6 +41,19 @@ export class TicketDataService extends BaseDataService { }); } + async queueTicketItems( + order_id: string, + ticket_id: string, + ): Promise { + return this.repo.find({ + relations: ['items', 'items.item', 'items.item.item_queue'], + where: { + order_id, + id: ticket_id, + }, + }); + } + async queueItemTickets( order_id: string, item_id: string, diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index b6169d0..2d40a93 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -44,6 +44,62 @@ export class QueueOrchestrator { }); } + async queueTicketDetail(order_id: string, ticket_id: string): Promise { + const tickets = await this.dataService.queueTicketItems( + order_id, + ticket_id, + ); + return tickets.map((ticket) => { + const totalActivities = ticket.items.length; + const totalTickets = ticket.items.reduce( + (acc, item) => acc + item.qty, + 0, + ); + const totalUsed = 0; + const totalQueue = 0; + return { + id: ticket.id, + code: ticket.code, + customer: ticket.customer, + phone: ticket.phone, + date: ticket.date, + summary: { + total_activities: totalActivities, + total_tickets: totalTickets, + total_used: totalUsed, + total_queue: totalQueue, + }, + items: ticket.items.map((item) => { + return { + id: item.item_id, + title: item.item.item_queue?.name ?? item.item.name, + image_url: item.item.image_url, + summary: { + total_tickets: item.qty, + total_used: 0, + total_queue: 1, + }, + queue: [ + { + code: 'A001', + qty: 1, + time: '15:00', + status: 'waiting', + }, + ], + queue_condition: { + available: true, + average: 12, + nearest: '13:10', + crowded_level: 20, + available_time: '15:00', + }, + }; + }), + }; + }); + } + async queueItemDetail(order_id: string, item_id: string): Promise { const tickets = await this.dataService.queueItemTickets(order_id, item_id); return tickets.map((ticket) => { diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index 8158d22..d849b56 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -1,13 +1,4 @@ -import { - Body, - Controller, - Get, - Param, - Patch, - Post, - Put, - Res, -} from '@nestjs/common'; +import { Controller, Get, Param } from '@nestjs/common'; import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; @@ -33,6 +24,14 @@ export class QueueController { return await this.orchestrator.queueTickets(id); } + @Get(':id/tickets/items') + async queueTicketItems( + @Param('id') id: string, + @Param('ticket_id') ticket_id: string, + ): Promise { + return await this.orchestrator.queueTicketDetail(id, ticket_id); + } + @Get(':id/items') async queueItems(@Param('id') id: string): Promise { return await this.orchestrator.queueItems(id); From 143bf764174c607dd28c2952580a43bce367c8ee Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 24 Oct 2024 16:05:42 +0700 Subject: [PATCH 11/94] fix: ticket item request url --- .../queue/infrastructure/controllers/queue.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index d849b56..8eccd69 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -24,7 +24,7 @@ export class QueueController { return await this.orchestrator.queueTickets(id); } - @Get(':id/tickets/items') + @Get(':id/tickets/:ticket_id/items') async queueTicketItems( @Param('id') id: string, @Param('ticket_id') ticket_id: string, From 5b507a1c3cce450b9d4de6f74fa1029d94e8610e Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 25 Oct 2024 14:49:56 +0700 Subject: [PATCH 12/94] feat: add queue admin list --- src/app.module.ts | 2 + src/core/strings/constants/table.constants.ts | 1 + .../1729756969674-add-queue-table.ts | 22 ++++++ .../1729838994129-add-queue-base-model.ts | 39 +++++++++++ .../data/services/item-queue-read.service.ts | 4 ++ .../usecases/item-queue-read.orchestrator.ts | 5 ++ .../item-queue-read.controller.ts | 13 ++++ src/modules/queue/data/models/queue.model.ts | 40 ++++++++++- .../queue/data/services/queue.service.ts | 17 +++++ .../queue/domain/entities/filter.entity.ts | 4 ++ .../queue/domain/entities/queue.entity.ts | 12 ++++ .../queue/domain/queue-admin.orchestrator.ts | 21 ++++++ .../domain/usecases/index-queue.manager.ts | 69 +++++++++++++++++++ .../controllers/dto/queue.filter.ts | 24 +++++++ .../controllers/queue-admin.controller.ts | 23 +++++++ src/modules/queue/queue.module.ts | 12 +++- 16 files changed, 306 insertions(+), 2 deletions(-) create mode 100644 src/database/migrations/1729756969674-add-queue-table.ts create mode 100644 src/database/migrations/1729838994129-add-queue-base-model.ts create mode 100644 src/modules/queue/data/services/queue.service.ts create mode 100644 src/modules/queue/domain/entities/filter.entity.ts create mode 100644 src/modules/queue/domain/entities/queue.entity.ts create mode 100644 src/modules/queue/domain/queue-admin.orchestrator.ts create mode 100644 src/modules/queue/domain/usecases/index-queue.manager.ts create mode 100644 src/modules/queue/infrastructure/controllers/dto/queue.filter.ts create mode 100644 src/modules/queue/infrastructure/controllers/queue-admin.controller.ts diff --git a/src/app.module.ts b/src/app.module.ts index 36efd42..e8696ac 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -88,6 +88,7 @@ import { QueueOrderModel, QueueTicketModel, QueueItemModel, + QueueModel, } from './modules/queue/data/models/queue.model'; import { ItemQueueModule } from './modules/item-related/item-queue/item-queue.module'; import { ItemQueueModel } from './modules/item-related/item-queue/data/models/item-queue.model'; @@ -149,6 +150,7 @@ import { ItemQueueModel } from './modules/item-related/item-queue/data/models/it QueueOrderModel, QueueTicketModel, QueueItemModel, + QueueModel, ], synchronize: false, }), diff --git a/src/core/strings/constants/table.constants.ts b/src/core/strings/constants/table.constants.ts index 13eb804..1e5129e 100644 --- a/src/core/strings/constants/table.constants.ts +++ b/src/core/strings/constants/table.constants.ts @@ -37,6 +37,7 @@ export enum TABLE_NAME { REPORT_BOOKMARK = 'report_bookmark', EXPORT_REPORT_HISTORY = 'export_report_history', + QUEUE = 'queues', QUEUE_ORDER = 'queue_orders', QUEUE_TICKET = 'queue_tickets', QUEUE_ITEM = 'queue_items', diff --git a/src/database/migrations/1729756969674-add-queue-table.ts b/src/database/migrations/1729756969674-add-queue-table.ts new file mode 100644 index 0000000..6b25c0c --- /dev/null +++ b/src/database/migrations/1729756969674-add-queue-table.ts @@ -0,0 +1,22 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddQueueTable1729756969674 implements MigrationInterface { + name = 'AddQueueTable1729756969674'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "queues" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "code" character varying NOT NULL, "status" character varying NOT NULL, "time" bigint NOT NULL, "call_time" bigint NOT NULL, "vip" boolean NOT NULL, "item_id" uuid NOT NULL, "qty" integer NOT NULL, CONSTRAINT "PK_d966f9eb39a9396658387071bb3" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `ALTER TABLE "queues" ADD CONSTRAINT "FK_435954e9a0d9967f17e043d54b4" FOREIGN KEY ("item_id") REFERENCES "queue_items"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "queues" DROP CONSTRAINT "FK_435954e9a0d9967f17e043d54b4"`, + ); + + await queryRunner.query(`DROP TABLE "queues"`); + } +} diff --git a/src/database/migrations/1729838994129-add-queue-base-model.ts b/src/database/migrations/1729838994129-add-queue-base-model.ts new file mode 100644 index 0000000..5a85010 --- /dev/null +++ b/src/database/migrations/1729838994129-add-queue-base-model.ts @@ -0,0 +1,39 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddQueueBaseModel1729838994129 implements MigrationInterface { + name = 'AddQueueBaseModel1729838994129'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "queues" ADD "creator_id" character varying(36)`, + ); + await queryRunner.query( + `ALTER TABLE "queues" ADD "creator_name" character varying(125)`, + ); + await queryRunner.query( + `ALTER TABLE "queues" ADD "editor_id" character varying(36)`, + ); + await queryRunner.query( + `ALTER TABLE "queues" ADD "editor_name" character varying(125)`, + ); + await queryRunner.query( + `ALTER TABLE "queues" ADD "created_at" bigint NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "queues" ADD "updated_at" bigint NOT NULL`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "queues" ALTER COLUMN "call_time" DROP NOT NULL`, + ); + + await queryRunner.query(`ALTER TABLE "queues" DROP COLUMN "updated_at"`); + await queryRunner.query(`ALTER TABLE "queues" DROP COLUMN "created_at"`); + await queryRunner.query(`ALTER TABLE "queues" DROP COLUMN "editor_name"`); + await queryRunner.query(`ALTER TABLE "queues" DROP COLUMN "editor_id"`); + await queryRunner.query(`ALTER TABLE "queues" DROP COLUMN "creator_name"`); + await queryRunner.query(`ALTER TABLE "queues" DROP COLUMN "creator_id"`); + } +} diff --git a/src/modules/item-related/item-queue/data/services/item-queue-read.service.ts b/src/modules/item-related/item-queue/data/services/item-queue-read.service.ts index 64414f6..40d3ab9 100644 --- a/src/modules/item-related/item-queue/data/services/item-queue-read.service.ts +++ b/src/modules/item-related/item-queue/data/services/item-queue-read.service.ts @@ -14,4 +14,8 @@ export class ItemQueueReadService extends BaseReadService { ) { super(repo); } + + async list(): Promise { + return this.repo.find(); + } } diff --git a/src/modules/item-related/item-queue/domain/usecases/item-queue-read.orchestrator.ts b/src/modules/item-related/item-queue/domain/usecases/item-queue-read.orchestrator.ts index 281d389..4c533fc 100644 --- a/src/modules/item-related/item-queue/domain/usecases/item-queue-read.orchestrator.ts +++ b/src/modules/item-related/item-queue/domain/usecases/item-queue-read.orchestrator.ts @@ -24,6 +24,11 @@ export class ItemQueueReadOrchestrator extends BaseReadOrchestrator { + const items = await this.serviceData.list(); + return items; + } + async detail(dataId: string): Promise { this.detailManager.setData(dataId); this.detailManager.setService(this.serviceData, TABLE_NAME.ITEM_QUEUE); diff --git a/src/modules/item-related/item-queue/infrastructure/item-queue-read.controller.ts b/src/modules/item-related/item-queue/infrastructure/item-queue-read.controller.ts index 54490f7..8932974 100644 --- a/src/modules/item-related/item-queue/infrastructure/item-queue-read.controller.ts +++ b/src/modules/item-related/item-queue/infrastructure/item-queue-read.controller.ts @@ -23,6 +23,19 @@ export class ItemQueueReadController { return await this.orchestrator.index(params); } + @Get('list') + @Public(true) + async list() { + const list = await this.orchestrator.list(); + return list.map(({ id, name, item_type }) => { + return { + id, + name, + item_type, + }; + }); + } + @Get(':id') async detail(@Param('id') id: string): Promise { return await this.orchestrator.detail(id); diff --git a/src/modules/queue/data/models/queue.model.ts b/src/modules/queue/data/models/queue.model.ts index c5377f4..dcdd18c 100644 --- a/src/modules/queue/data/models/queue.model.ts +++ b/src/modules/queue/data/models/queue.model.ts @@ -4,9 +4,10 @@ import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { QueueTicket } from '../../domain/entities/ticket.entity'; import { QueueItem } from '../../domain/entities/queue-item.entity'; -import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity'; import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; +import { Queue } from '../../domain/entities/queue.entity'; +import { BaseModel } from 'src/core/modules/data/model/base.model'; @Entity(TABLE_NAME.QUEUE_ORDER) export class QueueOrderModel @@ -83,6 +84,12 @@ export class QueueItemModel @JoinColumn({ name: 'ticket_id' }) ticket: QueueTicket; + @OneToMany(() => QueueModel, (model) => model.item, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + queue: QueueModel[]; + @Column('varchar') item_id: string; @@ -93,3 +100,34 @@ export class QueueItemModel @Column('int') qty: number; } + +@Entity(TABLE_NAME.QUEUE) +export class QueueModel extends BaseModel implements Queue { + @Column('varchar') + code: string; + + @Column('varchar') + status: string; + + @Column({ type: 'bigint' }) + time: number; + + @Column({ type: 'bigint' }) + call_time: number; + + @Column({ type: 'boolean' }) + vip: boolean; + + @ManyToOne(() => QueueItemModel, (model) => model.queue, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'item_id' }) + item: QueueItemModel; + + @Column('varchar') + item_id: string; + + @Column('int') + qty: number; +} diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts new file mode 100644 index 0000000..18ed187 --- /dev/null +++ b/src/modules/queue/data/services/queue.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; + +import { InjectRepository } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; +import { QueueModel } from '../models/queue.model'; +import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; + +@Injectable() +export class QueueDataService extends BaseReadService { + constructor( + @InjectRepository(QueueModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/queue/domain/entities/filter.entity.ts b/src/modules/queue/domain/entities/filter.entity.ts new file mode 100644 index 0000000..3bd24f5 --- /dev/null +++ b/src/modules/queue/domain/entities/filter.entity.ts @@ -0,0 +1,4 @@ +export interface FilterQueueEntity { + vip: boolean; + status: string[]; +} diff --git a/src/modules/queue/domain/entities/queue.entity.ts b/src/modules/queue/domain/entities/queue.entity.ts new file mode 100644 index 0000000..fea1945 --- /dev/null +++ b/src/modules/queue/domain/entities/queue.entity.ts @@ -0,0 +1,12 @@ +import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity'; +import { QueueItem } from './queue-item.entity'; + +export interface Queue extends BaseCoreEntity { + code: string; + qty: number; + status: string; + time: number; + vip: boolean; + call_time: number; + item: QueueItem; +} diff --git a/src/modules/queue/domain/queue-admin.orchestrator.ts b/src/modules/queue/domain/queue-admin.orchestrator.ts new file mode 100644 index 0000000..ef9664a --- /dev/null +++ b/src/modules/queue/domain/queue-admin.orchestrator.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; +import { QueueDataService } from '../data/services/queue.service'; +import { IndexQueueManager } from './usecases/index-queue.manager'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { Queue } from './entities/queue.entity'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class QueueAdminOrchestrator { + constructor( + private readonly dataService: QueueDataService, + private indexManager: IndexQueueManager, + ) {} + + async index(params): Promise> { + this.indexManager.setFilterParam(params); + this.indexManager.setService(this.dataService, TABLE_NAME.QUEUE); + await this.indexManager.execute(); + return this.indexManager.getResult(); + } +} diff --git a/src/modules/queue/domain/usecases/index-queue.manager.ts b/src/modules/queue/domain/usecases/index-queue.manager.ts new file mode 100644 index 0000000..f94d39f --- /dev/null +++ b/src/modules/queue/domain/usecases/index-queue.manager.ts @@ -0,0 +1,69 @@ +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 { Queue } from '../entities/queue.entity'; + +@Injectable() +export class IndexQueueManager extends BaseIndexManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + joinRelations: [], + selectRelations: ['item', 'item.ticket'], + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.code`, + `${this.tableName}.status`, + `${this.tableName}.time`, + `${this.tableName}.call_time`, + `${this.tableName}.vip`, + `${this.tableName}.item`, + `${this.tableName}.qty`, + `${this.tableName}.created_at`, + + `item.id`, + `ticket.customer`, + `ticket.phone`, + ]; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.status`, + data: this.filterParam.status, + }, + ]; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + if (this.filterParam.vip != null) { + queryBuilder.andWhere(`${this.tableName}.vip = :vip`, { + vip: this.filterParam.vip, + }); + } + return queryBuilder; + } +} diff --git a/src/modules/queue/infrastructure/controllers/dto/queue.filter.ts b/src/modules/queue/infrastructure/controllers/dto/queue.filter.ts new file mode 100644 index 0000000..c48bb27 --- /dev/null +++ b/src/modules/queue/infrastructure/controllers/dto/queue.filter.ts @@ -0,0 +1,24 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, ValidateIf } from 'class-validator'; +import { Transform } from 'class-transformer'; +import { FilterQueueEntity } from 'src/modules/queue/domain/entities/filter.entity'; + +export class QueueDto implements FilterQueueEntity { + @ApiProperty({ + isArray: true, + required: false, + }) + // @IsString() + @ValidateIf((body) => body.status) + @Transform(({ value }) => { + if (!value) return []; + return Array.isArray(value) ? value : [value]; + }) + status: string[]; + + @ApiProperty({ type: Boolean, required: false }) + @Transform((body) => body.value == 'true') + @IsBoolean() + @ValidateIf((body) => body.vip) + vip: boolean; +} diff --git a/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts b/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts new file mode 100644 index 0000000..2694854 --- /dev/null +++ b/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts @@ -0,0 +1,23 @@ +import { Controller, Get, Query } from '@nestjs/common'; + +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; + +import { Public } from 'src/core/guards'; +import { QueueAdminOrchestrator } from '../../domain/queue-admin.orchestrator'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { Queue } from '../../domain/entities/queue.entity'; +import { QueueDto } from './dto/queue.filter'; + +@ApiTags(`Queue Admin`) +@Controller(`v1/${MODULE_NAME.QUEUE}-admin`) +@Public(true) +@ApiBearerAuth('JWT') +export class QueueAdminController { + constructor(private orchestrator: QueueAdminOrchestrator) {} + + @Get('queues') + async index(@Query() params: QueueDto): Promise> { + return await this.orchestrator.index(params); + } +} diff --git a/src/modules/queue/queue.module.ts b/src/modules/queue/queue.module.ts index 3485f20..0f51e9e 100644 --- a/src/modules/queue/queue.module.ts +++ b/src/modules/queue/queue.module.ts @@ -8,6 +8,7 @@ import { CqrsModule } from '@nestjs/cqrs'; import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; import { QueueItemModel, + QueueModel, QueueOrderModel, QueueTicketModel, } from './data/models/queue.model'; @@ -17,6 +18,10 @@ import { QueueTransactionHandler } from './infrastructure/handlers/transaction.h import { TransactionDataService } from '../transaction/transaction/data/services/transaction-data.service'; import { TransactionModel } from '../transaction/transaction/data/models/transaction.model'; import { TicketDataService } from './data/services/ticket.service'; +import { QueueDataService } from './data/services/queue.service'; +import { QueueAdminController } from './infrastructure/controllers/queue-admin.controller'; +import { QueueAdminOrchestrator } from './domain/queue-admin.orchestrator'; +import { IndexQueueManager } from './domain/usecases/index-queue.manager'; @Module({ imports: [ @@ -26,6 +31,7 @@ import { TicketDataService } from './data/services/ticket.service'; QueueOrderModel, QueueTicketModel, QueueItemModel, + QueueModel, ItemModel, TransactionModel, ], @@ -33,14 +39,18 @@ import { TicketDataService } from './data/services/ticket.service'; ), CqrsModule, ], - controllers: [QueueController], + controllers: [QueueController, QueueAdminController], providers: [ QueueTransactionHandler, QueueOrchestrator, + QueueAdminOrchestrator, TransactionDataService, TicketDataService, + QueueDataService, + + IndexQueueManager, ], }) export class QueueModule {} From e9de46fff8104bf112ee55c694a59c2af4936bde Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 25 Oct 2024 16:11:43 +0700 Subject: [PATCH 13/94] fix: change response queue to pagination --- .../infrastructure/controllers/queue-admin.controller.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts b/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts index 2694854..02d4b5b 100644 --- a/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts @@ -3,20 +3,22 @@ import { Controller, Get, Query } from '@nestjs/common'; import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { Public } from 'src/core/guards'; import { QueueAdminOrchestrator } from '../../domain/queue-admin.orchestrator'; import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; import { Queue } from '../../domain/entities/queue.entity'; import { QueueDto } from './dto/queue.filter'; +import { Pagination } from 'src/core/response'; +import { Public } from 'src/core/guards'; @ApiTags(`Queue Admin`) @Controller(`v1/${MODULE_NAME.QUEUE}-admin`) -@Public(true) @ApiBearerAuth('JWT') +@Public(true) export class QueueAdminController { constructor(private orchestrator: QueueAdminOrchestrator) {} @Get('queues') + @Pagination() async index(@Query() params: QueueDto): Promise> { return await this.orchestrator.index(params); } From 487f59ae938fe5f5181d41966f93ef54e3982adf Mon Sep 17 00:00:00 2001 From: shancheas Date: Mon, 28 Oct 2024 15:09:27 +0700 Subject: [PATCH 14/94] feat: add register queue --- .../managers/base-update-status.manager.ts | 10 +++-- src/core/strings/constants/base.constants.ts | 5 +++ .../queue/data/services/queue.service.ts | 11 +++++ .../queue/domain/queue-admin.orchestrator.ts | 13 +++++- .../queue/domain/queue.orchestrator.ts | 17 ++++++- .../domain/usecases/call-queue.manager.ts | 40 +++++++++++++++++ .../domain/usecases/register-queue.manager.ts | 44 +++++++++++++++++++ .../controllers/dto/register-queue.dto.ts | 19 ++++++++ .../controllers/queue-admin.controller.ts | 7 ++- .../controllers/queue.controller.ts | 9 +++- src/modules/queue/queue.module.ts | 7 ++- 11 files changed, 174 insertions(+), 8 deletions(-) create mode 100644 src/modules/queue/domain/usecases/call-queue.manager.ts create mode 100644 src/modules/queue/domain/usecases/register-queue.manager.ts create mode 100644 src/modules/queue/infrastructure/controllers/dto/register-queue.dto.ts diff --git a/src/core/modules/domain/usecase/managers/base-update-status.manager.ts b/src/core/modules/domain/usecase/managers/base-update-status.manager.ts index 64516db..e585577 100644 --- a/src/core/modules/domain/usecase/managers/base-update-status.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-update-status.manager.ts @@ -1,6 +1,10 @@ import { ValidateRelationHelper } from 'src/core/helpers/validation/validate-relation.helper'; import { BaseManager } from '../base.manager'; -import { OPERATION, STATUS } from 'src/core/strings/constants/base.constants'; +import { + OPERATION, + QUEUE_STATUS, + STATUS, +} from 'src/core/strings/constants/base.constants'; import * as _ from 'lodash'; import { RecordLog } from 'src/modules/configuration/log/domain/entities/log.event'; @@ -8,12 +12,12 @@ export abstract class BaseUpdateStatusManager extends BaseManager { protected dataId: string; protected result: Entity; protected oldData: Entity; - protected dataStatus: STATUS; + protected dataStatus: STATUS | QUEUE_STATUS; protected relations = []; protected duplicateColumn: string[]; abstract get entityTarget(): any; - setData(id: string, status: STATUS): void { + setData(id: string, status: STATUS | QUEUE_STATUS): void { /** * // TODO: Handle case confirm multiple tabs; * Pola id yang dikirim dirubah menjadi data_id___updated_at diff --git a/src/core/strings/constants/base.constants.ts b/src/core/strings/constants/base.constants.ts index 0e1f7d4..466d2dc 100644 --- a/src/core/strings/constants/base.constants.ts +++ b/src/core/strings/constants/base.constants.ts @@ -15,6 +15,11 @@ export enum STATUS { WAITING = 'waiting', } +export enum QUEUE_STATUS { + DONE = 'done', + CALLED = 'called', +} + export enum ORDER_TYPE { ASC = 'ASC', DESC = 'DESC', diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index 18ed187..128fa46 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -5,6 +5,7 @@ import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; import { Repository } from 'typeorm'; import { QueueModel } from '../models/queue.model'; import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; @Injectable() export class QueueDataService extends BaseReadService { @@ -15,3 +16,13 @@ export class QueueDataService extends BaseReadService { super(repo); } } + +@Injectable() +export class QueueService extends BaseDataService { + constructor( + @InjectRepository(QueueModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/queue/domain/queue-admin.orchestrator.ts b/src/modules/queue/domain/queue-admin.orchestrator.ts index ef9664a..b39480e 100644 --- a/src/modules/queue/domain/queue-admin.orchestrator.ts +++ b/src/modules/queue/domain/queue-admin.orchestrator.ts @@ -1,15 +1,19 @@ import { Injectable } from '@nestjs/common'; -import { QueueDataService } from '../data/services/queue.service'; +import { QueueDataService, QueueService } from '../data/services/queue.service'; import { IndexQueueManager } from './usecases/index-queue.manager'; import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; import { Queue } from './entities/queue.entity'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { CallQueueManager } from './usecases/call-queue.manager'; +import { QUEUE_STATUS } from 'src/core/strings/constants/base.constants'; @Injectable() export class QueueAdminOrchestrator { constructor( private readonly dataService: QueueDataService, + private readonly service: QueueService, private indexManager: IndexQueueManager, + private callManager: CallQueueManager, ) {} async index(params): Promise> { @@ -18,4 +22,11 @@ export class QueueAdminOrchestrator { await this.indexManager.execute(); return this.indexManager.getResult(); } + + async call(dataId): Promise { + this.callManager.setData(dataId, QUEUE_STATUS.CALLED); + this.callManager.setService(this.service, TABLE_NAME.QUEUE); + await this.callManager.execute(); + return this.callManager.getResult(); + } } diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index 2d40a93..eadaf3a 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -2,10 +2,18 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { TicketDataService } from '../data/services/ticket.service'; import { QueueOrder } from './entities/order.entity'; import { QueueItemModel } from '../data/models/queue.model'; +import { Queue } from './entities/queue.entity'; +import { QueueService } from '../data/services/queue.service'; +import { RegisterQueueManager } from './usecases/register-queue.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; @Injectable() export class QueueOrchestrator { - constructor(private readonly dataService: TicketDataService) {} + constructor( + private readonly dataService: TicketDataService, + private readonly queueService: QueueService, + private readonly registerQueueManager: RegisterQueueManager, + ) {} async loginCustomer(id: string): Promise { try { @@ -18,6 +26,13 @@ export class QueueOrchestrator { } } + async create(data): Promise { + this.registerQueueManager.setData(data); + this.registerQueueManager.setService(this.queueService, TABLE_NAME.QUEUE); + await this.registerQueueManager.execute(); + return this.registerQueueManager.getResult(); + } + async queueTickets(order_id: string): Promise { const tickets = await this.dataService.queueTickets(order_id); return tickets.map((ticket) => { diff --git a/src/modules/queue/domain/usecases/call-queue.manager.ts b/src/modules/queue/domain/usecases/call-queue.manager.ts new file mode 100644 index 0000000..a49ca11 --- /dev/null +++ b/src/modules/queue/domain/usecases/call-queue.manager.ts @@ -0,0 +1,40 @@ +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 { Queue } from '../entities/queue.entity'; +import { QueueModel } from '../../data/models/queue.model'; + +@Injectable() +export class CallQueueManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success call Queue ${this.result.code}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + this.data.call_time = new Date().getTime(); + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return QueueModel; + } + + get eventTopics(): EventTopics[] { + return []; + } +} diff --git a/src/modules/queue/domain/usecases/register-queue.manager.ts b/src/modules/queue/domain/usecases/register-queue.manager.ts new file mode 100644 index 0000000..3a258b5 --- /dev/null +++ b/src/modules/queue/domain/usecases/register-queue.manager.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@nestjs/common'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; +import { Queue } from '../entities/queue.entity'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { QueueModel } from '../../data/models/queue.model'; +import { generateRandom } from 'src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper'; + +@Injectable() +export class RegisterQueueManager extends BaseCreateManager { + async beforeProcess(): Promise { + Object.assign(this.data, { + status: STATUS.WAITING, + time: new Date().getTime(), + vip: false, + code: `Q${generateRandom(4, true)}`, + }); + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get eventTopics(): EventTopics[] { + return []; + } + + get entityTarget(): any { + return QueueModel; + } +} diff --git a/src/modules/queue/infrastructure/controllers/dto/register-queue.dto.ts b/src/modules/queue/infrastructure/controllers/dto/register-queue.dto.ts new file mode 100644 index 0000000..6ebc95e --- /dev/null +++ b/src/modules/queue/infrastructure/controllers/dto/register-queue.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber, IsString, Min } from 'class-validator'; + +export class RegisterQueueDto { + @ApiProperty({ name: 'item_id', required: true }) + @IsString() + @IsNotEmpty() + item_id: string; + + @ApiProperty({ + type: Number, + required: true, + example: 1, + }) + @IsNumber() + @Min(1) + @IsNotEmpty() + qty: number; +} diff --git a/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts b/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts index 02d4b5b..eb5363e 100644 --- a/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Query } from '@nestjs/common'; +import { Controller, Get, Param, Post, Query } from '@nestjs/common'; import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; @@ -22,4 +22,9 @@ export class QueueAdminController { async index(@Query() params: QueueDto): Promise> { return await this.orchestrator.index(params); } + + @Post('queues/:id/call') + async call(@Param('id') id: string) { + return await this.orchestrator.call(id); + } } diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index 8eccd69..8019e1c 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Param } from '@nestjs/common'; +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; @@ -6,6 +6,8 @@ import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { Public } from 'src/core/guards'; import { QueueOrchestrator } from '../../domain/queue.orchestrator'; import { QueueOrder } from '../../domain/entities/order.entity'; +import { Queue } from '../../domain/entities/queue.entity'; +import { RegisterQueueDto } from './dto/register-queue.dto'; @ApiTags(`Queue`) @Controller(`v1/${MODULE_NAME.QUEUE}`) @@ -14,6 +16,11 @@ import { QueueOrder } from '../../domain/entities/order.entity'; export class QueueController { constructor(private orchestrator: QueueOrchestrator) {} + @Post('register') + async registerQueue(@Body() data: RegisterQueueDto): Promise { + return await this.orchestrator.create(data); + } + @Get('login/:id') async loginCustomer(@Param('id') id: string): Promise { return await this.orchestrator.loginCustomer(id); diff --git a/src/modules/queue/queue.module.ts b/src/modules/queue/queue.module.ts index 0f51e9e..ff62289 100644 --- a/src/modules/queue/queue.module.ts +++ b/src/modules/queue/queue.module.ts @@ -18,10 +18,12 @@ import { QueueTransactionHandler } from './infrastructure/handlers/transaction.h import { TransactionDataService } from '../transaction/transaction/data/services/transaction-data.service'; import { TransactionModel } from '../transaction/transaction/data/models/transaction.model'; import { TicketDataService } from './data/services/ticket.service'; -import { QueueDataService } from './data/services/queue.service'; +import { QueueDataService, QueueService } from './data/services/queue.service'; import { QueueAdminController } from './infrastructure/controllers/queue-admin.controller'; import { QueueAdminOrchestrator } from './domain/queue-admin.orchestrator'; import { IndexQueueManager } from './domain/usecases/index-queue.manager'; +import { CallQueueManager } from './domain/usecases/call-queue.manager'; +import { RegisterQueueManager } from './domain/usecases/register-queue.manager'; @Module({ imports: [ @@ -49,8 +51,11 @@ import { IndexQueueManager } from './domain/usecases/index-queue.manager'; TransactionDataService, TicketDataService, QueueDataService, + QueueService, IndexQueueManager, + CallQueueManager, + RegisterQueueManager, ], }) export class QueueModule {} From 9977a7456ad8cc89e916539dbda665f5c16db804 Mon Sep 17 00:00:00 2001 From: shancheas Date: Mon, 28 Oct 2024 16:03:13 +0700 Subject: [PATCH 15/94] feat: item queue id in response ticket --- src/modules/queue/domain/queue.orchestrator.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index eadaf3a..647b770 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -87,6 +87,7 @@ export class QueueOrchestrator { items: ticket.items.map((item) => { return { id: item.item_id, + item_queue_id: item.id, title: item.item.item_queue?.name ?? item.item.name, image_url: item.item.image_url, summary: { From 86251f43a23bc1e9da45a04968b0a5eda753497f Mon Sep 17 00:00:00 2001 From: shancheas Date: Mon, 28 Oct 2024 17:16:41 +0700 Subject: [PATCH 16/94] fix: change register ticket body --- src/modules/queue/data/models/queue.model.ts | 3 +++ src/modules/queue/data/services/queue.service.ts | 14 +++++++++++++- src/modules/queue/domain/queue.orchestrator.ts | 13 +++++++++++-- .../domain/usecases/register-queue.manager.ts | 2 +- .../controllers/dto/register-queue.dto.ts | 5 +++++ 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/modules/queue/data/models/queue.model.ts b/src/modules/queue/data/models/queue.model.ts index dcdd18c..04ba717 100644 --- a/src/modules/queue/data/models/queue.model.ts +++ b/src/modules/queue/data/models/queue.model.ts @@ -84,6 +84,9 @@ export class QueueItemModel @JoinColumn({ name: 'ticket_id' }) ticket: QueueTicket; + @Column('varchar') + ticket_id: string; + @OneToMany(() => QueueModel, (model) => model.item, { onDelete: 'CASCADE', onUpdate: 'CASCADE', diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index 128fa46..e688c7e 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; import { Repository } from 'typeorm'; -import { QueueModel } from '../models/queue.model'; +import { QueueItemModel, QueueModel } from '../models/queue.model'; import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; @@ -22,7 +22,19 @@ export class QueueService extends BaseDataService { constructor( @InjectRepository(QueueModel, CONNECTION_NAME.DEFAULT) private repo: Repository, + + @InjectRepository(QueueItemModel, CONNECTION_NAME.DEFAULT) + private item: Repository, ) { super(repo); } + + async getTicketItems(ticket_id: string, item_id: string) { + return this.item.findOneOrFail({ + where: { + ticket_id, + item_id, + }, + }); + } } diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index 647b770..c876fd3 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -6,6 +6,7 @@ import { Queue } from './entities/queue.entity'; import { QueueService } from '../data/services/queue.service'; import { RegisterQueueManager } from './usecases/register-queue.manager'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { RegisterQueueDto } from '../infrastructure/controllers/dto/register-queue.dto'; @Injectable() export class QueueOrchestrator { @@ -26,8 +27,16 @@ export class QueueOrchestrator { } } - async create(data): Promise { - this.registerQueueManager.setData(data); + async create(data: RegisterQueueDto): Promise { + const queue = await this.queueService.getTicketItems( + data.ticket_id, + data.item_id, + ); + const queueRequest: any = { + qty: data.qty, + item_id: queue.id, + }; + this.registerQueueManager.setData(queueRequest); this.registerQueueManager.setService(this.queueService, TABLE_NAME.QUEUE); await this.registerQueueManager.execute(); return this.registerQueueManager.getResult(); diff --git a/src/modules/queue/domain/usecases/register-queue.manager.ts b/src/modules/queue/domain/usecases/register-queue.manager.ts index 3a258b5..95b40d1 100644 --- a/src/modules/queue/domain/usecases/register-queue.manager.ts +++ b/src/modules/queue/domain/usecases/register-queue.manager.ts @@ -11,7 +11,7 @@ import { QueueModel } from '../../data/models/queue.model'; import { generateRandom } from 'src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper'; @Injectable() -export class RegisterQueueManager extends BaseCreateManager { +export class RegisterQueueManager extends BaseCreateManager { async beforeProcess(): Promise { Object.assign(this.data, { status: STATUS.WAITING, diff --git a/src/modules/queue/infrastructure/controllers/dto/register-queue.dto.ts b/src/modules/queue/infrastructure/controllers/dto/register-queue.dto.ts index 6ebc95e..8141b71 100644 --- a/src/modules/queue/infrastructure/controllers/dto/register-queue.dto.ts +++ b/src/modules/queue/infrastructure/controllers/dto/register-queue.dto.ts @@ -7,6 +7,11 @@ export class RegisterQueueDto { @IsNotEmpty() item_id: string; + @ApiProperty({ name: 'ticket_id', required: true }) + @IsString() + @IsNotEmpty() + ticket_id: string; + @ApiProperty({ type: Number, required: true, From 93b1208278bcd0004bad3120a0ef85c071b64564 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 29 Oct 2024 10:30:57 +0700 Subject: [PATCH 17/94] refactor: change code to manager --- .../queue/data/services/ticket.service.ts | 21 ++- .../queue/domain/queue.orchestrator.ts | 141 ++---------------- .../queue/customer-queue-detail.manager.ts | 51 +++++++ .../queue/customer-queue-item-list.manager.ts | 41 +++++ .../queue/customer-queue-item.manager.ts | 32 ++++ .../queue/customer-queue-summary.manager.ts | 21 +++ .../usecases/queue/customer-queue.manager.ts | 58 +++++++ 7 files changed, 233 insertions(+), 132 deletions(-) create mode 100644 src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts create mode 100644 src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts create mode 100644 src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts create mode 100644 src/modules/queue/domain/usecases/queue/customer-queue-summary.manager.ts create mode 100644 src/modules/queue/domain/usecases/queue/customer-queue.manager.ts diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index f2cc833..7d509e3 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -34,7 +34,12 @@ export class TicketDataService extends BaseDataService { async queueTickets(order_id: string): Promise { return this.repo.find({ - relations: ['items', 'items.item', 'items.item.item_queue'], + relations: [ + 'items', + 'items.queue', + 'items.item', + 'items.item.item_queue', + ], where: { order_id, }, @@ -46,7 +51,12 @@ export class TicketDataService extends BaseDataService { ticket_id: string, ): Promise { return this.repo.find({ - relations: ['items', 'items.item', 'items.item.item_queue'], + relations: [ + 'items', + 'items.queue', + 'items.item', + 'items.item.item_queue', + ], where: { order_id, id: ticket_id, @@ -59,7 +69,12 @@ export class TicketDataService extends BaseDataService { item_id: string, ): Promise { return this.repo.find({ - relations: ['items', 'items.item', 'items.item.item_queue'], + relations: [ + 'items', + 'items.queue', + 'items.item', + 'items.item.item_queue', + ], where: { order_id, items: [{ item_id }, { item: { item_queue: { id: item_id } } }], diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index c876fd3..aa61f48 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -1,12 +1,15 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { TicketDataService } from '../data/services/ticket.service'; import { QueueOrder } from './entities/order.entity'; -import { QueueItemModel } from '../data/models/queue.model'; import { Queue } from './entities/queue.entity'; import { QueueService } from '../data/services/queue.service'; import { RegisterQueueManager } from './usecases/register-queue.manager'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { RegisterQueueDto } from '../infrastructure/controllers/dto/register-queue.dto'; +import { CustomerQueueSummaryManager } from './usecases/queue/customer-queue-summary.manager'; +import { CustomerQueueDetailManager } from './usecases/queue/customer-queue-detail.manager'; +import { CustomerQueueItemManager } from './usecases/queue/customer-queue-item.manager'; +import { CustomerQueueItemListManager } from './usecases/queue/customer-queue-item-list.manager'; @Injectable() export class QueueOrchestrator { @@ -44,28 +47,8 @@ export class QueueOrchestrator { async queueTickets(order_id: string): Promise { const tickets = await this.dataService.queueTickets(order_id); - return tickets.map((ticket) => { - const totalActivities = ticket.items.length; - const totalTickets = ticket.items.reduce( - (acc, item) => acc + item.qty, - 0, - ); - const totalUsed = 0; - const totalQueue = 0; - return { - id: ticket.id, - code: ticket.code, - customer: ticket.customer, - phone: ticket.phone, - date: ticket.date, - summary: { - total_activities: totalActivities, - total_tickets: totalTickets, - total_used: totalUsed, - total_queue: totalQueue, - }, - }; - }); + const manager = new CustomerQueueSummaryManager(tickets); + return manager.data; } async queueTicketDetail(order_id: string, ticket_id: string): Promise { @@ -73,119 +56,19 @@ export class QueueOrchestrator { order_id, ticket_id, ); - return tickets.map((ticket) => { - const totalActivities = ticket.items.length; - const totalTickets = ticket.items.reduce( - (acc, item) => acc + item.qty, - 0, - ); - const totalUsed = 0; - const totalQueue = 0; - return { - id: ticket.id, - code: ticket.code, - customer: ticket.customer, - phone: ticket.phone, - date: ticket.date, - summary: { - total_activities: totalActivities, - total_tickets: totalTickets, - total_used: totalUsed, - total_queue: totalQueue, - }, - items: ticket.items.map((item) => { - return { - id: item.item_id, - item_queue_id: item.id, - title: item.item.item_queue?.name ?? item.item.name, - image_url: item.item.image_url, - summary: { - total_tickets: item.qty, - total_used: 0, - total_queue: 1, - }, - queue: [ - { - code: 'A001', - qty: 1, - time: '15:00', - status: 'waiting', - }, - ], - queue_condition: { - available: true, - average: 12, - nearest: '13:10', - crowded_level: 20, - available_time: '15:00', - }, - }; - }), - }; - }); + const manager = new CustomerQueueDetailManager(tickets); + return manager.data; } async queueItemDetail(order_id: string, item_id: string): Promise { const tickets = await this.dataService.queueItemTickets(order_id, item_id); - return tickets.map((ticket) => { - const totalActivities = ticket.items.length; - const totalTickets = ticket.items.reduce( - (acc, item) => acc + item.qty, - 0, - ); - const totalUsed = 0; - const totalQueue = 0; - return { - id: ticket.id, - code: ticket.code, - customer: ticket.customer, - phone: ticket.phone, - date: ticket.date, - summary: { - total_activities: totalActivities, - total_tickets: totalTickets, - total_used: totalUsed, - total_queue: totalQueue, - }, - queue: [ - { - code: 'A001', - qty: 1, - time: '15:00', - status: 'waiting', - }, - ], - }; - }); + const manager = new CustomerQueueItemManager(tickets); + return manager.data; } async queueItems(order_id: string): Promise { const tickets = await this.dataService.queueTickets(order_id); - const ticketItems = {}; - - tickets.forEach((ticket) => { - ticket.items.forEach((item) => { - const item_id = item.item.item_queue?.id ?? item.item.id; - const currentItem = ticketItems[item_id]; - ticketItems[item_id] = currentItem ? [...currentItem, item] : [item]; - }); - }); - - return Object.values(ticketItems).map((items) => { - const item = items[0]; - const item_qty = items.reduce((acc, item) => acc + item.qty, 0); - return { - id: item.item_id, - title: item.item.item_queue?.name ?? item.item.name, - image_url: item.item.image_url, - qty: item_qty, - - available: true, - average: 12, - nearest: '13:10', - crowded_level: 20, - available_time: '15:00', - }; - }); + const manager = new CustomerQueueItemListManager(tickets); + return manager.data; } } diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts new file mode 100644 index 0000000..0ce404d --- /dev/null +++ b/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts @@ -0,0 +1,51 @@ +import { CustomerQueueManager } from './customer-queue.manager'; + +export class CustomerQueueDetailManager extends CustomerQueueManager { + get data() { + return this.tickets.map((ticket) => { + return { + id: ticket.id, + code: ticket.code, + customer: ticket.customer, + phone: ticket.phone, + date: ticket.date, + summary: { + total_activities: this.totalActivities(ticket), + total_tickets: this.totalTickets(ticket), + total_used: this.totalUsedTickets(ticket), + total_queue: this.totalQueueTickets(ticket), + }, + items: ticket.items.map((item) => { + return { + id: item.item_id, + item_queue_id: item.id, + title: item.item.item_queue?.name ?? item.item.name, + image_url: item.item.image_url, + summary: { + total_tickets: item.qty, + total_used: this.totalUsedItems(item), + total_queue: this.totalQueueItems(item), + }, + queue: item.queue + .sort((a, b) => b.time - a.time) + .map((q) => { + return { + code: q.code, + qty: q.qty, + time: this.toTime(q.time), + status: q.status, + }; + }), + queue_condition: { + available: true, + average: 12, + nearest: '13:10', + crowded_level: 20, + available_time: '15:00', + }, + }; + }), + }; + }); + } +} diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts new file mode 100644 index 0000000..8f0bd69 --- /dev/null +++ b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts @@ -0,0 +1,41 @@ +import { QueueItemModel } from 'src/modules/queue/data/models/queue.model'; +import { CustomerQueueManager } from './customer-queue.manager'; + +export class CustomerQueueItemListManager extends CustomerQueueManager { + get data() { + const tickets = this.tickets; + const ticketItems = {}; + + tickets.forEach((ticket) => { + ticket.items.forEach((item) => { + const item_id = item.item.item_queue?.id ?? item.item.id; + const currentItem = ticketItems[item_id]; + ticketItems[item_id] = currentItem ? [...currentItem, item] : [item]; + }); + }); + + return Object.values(ticketItems).map((items) => { + const item = items[0]; + const item_qty = items.reduce((acc, item) => acc + item.qty, 0); + return { + id: item.item_id, + title: item.item.item_queue?.name ?? item.item.name, + image_url: item.item.image_url, + qty: item_qty, + + available: true, + average: 12, + nearest: '13:10', + crowded_level: 20, + available_time: '15:00', + queue_condition: { + available: true, + average: 12, + nearest: '13:10', + crowded_level: 20, + available_time: '15:00', + }, + }; + }); + } +} diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts new file mode 100644 index 0000000..af4aa9d --- /dev/null +++ b/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts @@ -0,0 +1,32 @@ +import { CustomerQueueManager } from './customer-queue.manager'; + +export class CustomerQueueItemManager extends CustomerQueueManager { + get data() { + return this.tickets.map((ticket) => { + const item = ticket.items[0]; + return { + id: ticket.id, + code: ticket.code, + customer: ticket.customer, + phone: ticket.phone, + date: ticket.date, + summary: { + total_activities: this.totalActivities(ticket), + total_tickets: this.totalTickets(ticket), + total_used: this.totalUsedTickets(ticket), + total_queue: this.totalQueueTickets(ticket), + }, + queue: item.queue + .sort((a, b) => b.time - a.time) + .map((q) => { + return { + code: q.code, + qty: q.qty, + time: this.toTime(q.time), + status: q.status, + }; + }), + }; + }); + } +} diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-summary.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-summary.manager.ts new file mode 100644 index 0000000..e56ffee --- /dev/null +++ b/src/modules/queue/domain/usecases/queue/customer-queue-summary.manager.ts @@ -0,0 +1,21 @@ +import { CustomerQueueManager } from './customer-queue.manager'; + +export class CustomerQueueSummaryManager extends CustomerQueueManager { + get data() { + return this.tickets.map((ticket) => { + return { + id: ticket.id, + code: ticket.code, + customer: ticket.customer, + phone: ticket.phone, + date: ticket.date, + summary: { + total_activities: this.totalActivities(ticket), + total_tickets: this.totalTickets(ticket), + total_used: this.totalUsedTickets(ticket), + total_queue: this.totalQueueTickets(ticket), + }, + }; + }); + } +} diff --git a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts new file mode 100644 index 0000000..5b95ba1 --- /dev/null +++ b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts @@ -0,0 +1,58 @@ +import { + QueueItemModel, + QueueTicketModel, +} from '../../../data/models/queue.model'; + +export class CustomerQueueManager { + constructor(protected readonly tickets: QueueTicketModel[]) {} + get data(): any { + return this.tickets; + } + + toTime(timestamp: number): string { + // js function to convert timestamp (1729739455000) to time with format HH:mm + const date = new Date(timestamp / 1000); + const hours = date.getHours() + 7; + const minutes = date.getMinutes(); + return `${hours < 10 ? '0' : ''}${hours}:${ + minutes < 10 ? '0' : '' + }${minutes}`; + } + + totalActivities(ticket: QueueTicketModel): number { + return ticket.items.length; + } + + totalTickets(ticket: QueueTicketModel): number { + return ticket.items.reduce((acc, item) => acc + item.qty, 0); + } + + totalUsedItems(item: QueueItemModel): number { + return item.queue + .filter((q) => ['done', 'called'].includes(q.status)) + .reduce((acc, item) => acc + item.qty, 0); + } + + totalUsedTickets(ticket: QueueTicketModel): number { + const tickets = ticket.items.map((item) => { + return this.totalUsedItems(item); + }); + + const reducer = (accumulator, currentValue) => accumulator + currentValue; + return tickets.reduce(reducer, 0); + } + + totalQueueItems(item: QueueItemModel): number { + return item.queue + .filter((q) => ['waiting'].includes(q.status)) + .reduce((acc, item) => acc + item.qty, 0); + } + totalQueueTickets(ticket: QueueTicketModel): number { + const tickets = ticket.items.map((item) => { + return this.totalQueueItems(item); + }); + + const reducer = (accumulator, currentValue) => accumulator + currentValue; + return tickets.reduce(reducer, 0); + } +} From c8d0b32cc6e04482611c62c586f195b90b7231f3 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 29 Oct 2024 14:15:37 +0700 Subject: [PATCH 18/94] feat: add list order queue --- .../queue/data/services/ticket.service.ts | 20 +++++++- .../queue/domain/queue.orchestrator.ts | 7 +++ .../queue/customer-queue-item-list.manager.ts | 1 + .../queue/customer-queue-item.manager.ts | 1 + .../queue/customer-queue-list.manager.ts | 46 +++++++++++++++++++ .../controllers/queue.controller.ts | 5 ++ 6 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index 7d509e3..4f1cd66 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -4,7 +4,11 @@ import { InjectRepository } from '@nestjs/typeorm'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; import { Repository } from 'typeorm'; import { QueueTicket } from '../../domain/entities/ticket.entity'; -import { QueueOrderModel, QueueTicketModel } from '../models/queue.model'; +import { + QueueItemModel, + QueueOrderModel, + QueueTicketModel, +} from '../models/queue.model'; import { QueueOrder } from '../../domain/entities/order.entity'; @Injectable() @@ -15,6 +19,9 @@ export class TicketDataService extends BaseDataService { @InjectRepository(QueueOrderModel, CONNECTION_NAME.DEFAULT) private order: Repository, + + @InjectRepository(QueueItemModel, CONNECTION_NAME.DEFAULT) + private item: Repository, ) { super(repo); } @@ -81,4 +88,15 @@ export class TicketDataService extends BaseDataService { }, }); } + + async queueItems(order_id: string): Promise { + return this.item.find({ + relations: ['queue', 'ticket'], + where: { + ticket: { + order_id, + }, + }, + }); + } } diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index aa61f48..be8f5fe 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -10,6 +10,7 @@ import { CustomerQueueSummaryManager } from './usecases/queue/customer-queue-sum import { CustomerQueueDetailManager } from './usecases/queue/customer-queue-detail.manager'; import { CustomerQueueItemManager } from './usecases/queue/customer-queue-item.manager'; import { CustomerQueueItemListManager } from './usecases/queue/customer-queue-item-list.manager'; +import { CustomerQueueListManager } from './usecases/queue/customer-queue-list.manager'; @Injectable() export class QueueOrchestrator { @@ -71,4 +72,10 @@ export class QueueOrchestrator { const manager = new CustomerQueueItemListManager(tickets); return manager.data; } + + async queueOrderItems(order_id: string): Promise { + const tickets = await this.dataService.queueTickets(order_id); + const manager = new CustomerQueueListManager(tickets); + return manager.data; + } } diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts index 8f0bd69..7692acf 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts @@ -28,6 +28,7 @@ export class CustomerQueueItemListManager extends CustomerQueueManager { nearest: '13:10', crowded_level: 20, available_time: '15:00', + queue_condition: { available: true, average: 12, diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts index af4aa9d..2e85a5d 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts @@ -10,6 +10,7 @@ export class CustomerQueueItemManager extends CustomerQueueManager { customer: ticket.customer, phone: ticket.phone, date: ticket.date, + image_url: item.item.image_url, summary: { total_activities: this.totalActivities(ticket), total_tickets: this.totalTickets(ticket), diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts new file mode 100644 index 0000000..849eea6 --- /dev/null +++ b/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts @@ -0,0 +1,46 @@ +import { QueueItemModel } from 'src/modules/queue/data/models/queue.model'; +import { CustomerQueueManager } from './customer-queue.manager'; + +export class CustomerQueueListManager extends CustomerQueueManager { + get data() { + const tickets = this.tickets; + const ticketItems = {}; + + tickets.forEach((ticket) => { + ticket.items.forEach((item) => { + const item_id = item.item.item_queue?.id ?? item.item.id; + const currentItem = ticketItems[item_id]; + item.ticket = ticket; + ticketItems[item_id] = currentItem ? [...currentItem, item] : [item]; + }); + }); + + return Object.values(ticketItems).map((items) => { + const item = items[0]; + const item_qty = items.reduce((acc, item) => acc + item.qty, 0); + return { + id: item.item_id, + title: item.item.item_queue?.name ?? item.item.name, + image_url: item.item.image_url, + qty: item_qty, + + items: items.map((item) => { + return { + code: item.ticket.code, + customer: item.ticket.customer, + phone: item.ticket.phone, + id: item.ticket.id, + queue: item.queue.map((q) => { + return { + code: q.code, + qty: q.qty, + time: this.toTime(q.time), + status: q.status, + }; + }), + }; + }), + }; + }); + } +} diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index 8019e1c..7d382dc 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -44,6 +44,11 @@ export class QueueController { return await this.orchestrator.queueItems(id); } + @Get(':id/queue') + async queueOrderItems(@Param('id') id: string): Promise { + return await this.orchestrator.queueOrderItems(id); + } + @Get(':id/items/:item_id') async queueItemTickets( @Param('id') id: string, From dea998991453858c81af051263c45b1cee9e2283 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 30 Oct 2024 13:54:09 +0700 Subject: [PATCH 19/94] fix: queue not generate from POS --- .../handlers/transaction.handler.ts | 7 +++++-- .../event/transaction-change-status.event.ts | 4 ++++ .../handlers/pos-transaction.handler.ts | 19 ++++++++++++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index 365a7ca..49d69c3 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -1,12 +1,15 @@ import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; -import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event'; +import { + TransactionChangeStatusEvent, + TransactionCreateQueueEvent, +} from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event'; import { TicketDataService } from '../../data/services/ticket.service'; import { QueueOrder } from '../../domain/entities/order.entity'; import { QueueTicket } from '../../domain/entities/ticket.entity'; import { QueueItem } from '../../domain/entities/queue-item.entity'; -@EventsHandler(TransactionChangeStatusEvent) +@EventsHandler(TransactionChangeStatusEvent, TransactionCreateQueueEvent) export class QueueTransactionHandler implements IEventHandler { diff --git a/src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event.ts b/src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event.ts index 53ffa35..a2bbab5 100644 --- a/src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event.ts +++ b/src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event.ts @@ -3,3 +3,7 @@ import { IEvent } from 'src/core/strings/constants/interface.constants'; export class TransactionChangeStatusEvent { constructor(public readonly data: IEvent) {} } + +export class TransactionCreateQueueEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/transaction/domain/usecases/handlers/pos-transaction.handler.ts b/src/modules/transaction/transaction/domain/usecases/handlers/pos-transaction.handler.ts index 1207198..fce39cc 100644 --- a/src/modules/transaction/transaction/domain/usecases/handlers/pos-transaction.handler.ts +++ b/src/modules/transaction/transaction/domain/usecases/handlers/pos-transaction.handler.ts @@ -14,7 +14,10 @@ import { TransactionModel } from '../../../data/models/transaction.model'; import { mappingRevertTransaction } from '../managers/helpers/mapping-transaction.helper'; import { apm } from '../../../../../../core/apm'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; -import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; +import { + TransactionChangeStatusEvent, + TransactionCreateQueueEvent, +} from '../../entities/event/transaction-change-status.event'; import { PriceCalculator } from '../calculator/price.calculator'; @EventsHandler(ChangeDocEvent) @@ -120,6 +123,20 @@ export class PosTransactionHandler implements IEventHandler { }), ); } + + if (data.status == STATUS.SETTLED) { + this.eventBus.publish( + new TransactionCreateQueueEvent({ + id: data.id, + old: event.data.data, + data: data, + user: BLANK_USER, + description: '', + module: TABLE_NAME.TRANSACTION, + op: OPERATION.UPDATE, + }), + ); + } apmTransactions.result = 'Success'; } } catch (error) { From 78fb5b7face84fcaa76b742d0218205688852a9d Mon Sep 17 00:00:00 2001 From: Supan Adit Pratama Date: Wed, 30 Oct 2024 14:33:50 +0700 Subject: [PATCH 20/94] ci: update pipeline for kustomize auto --- .drone.yml | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/.drone.yml b/.drone.yml index f9fa9bf..c9a2718 100644 --- a/.drone.yml +++ b/.drone.yml @@ -46,3 +46,56 @@ trigger: event: exclude: - promote +--- +kind: pipeline +type: docker +name: kustomize + +clone: + disable: true + +steps: + - name: kustomize-testing + image: registry.k8s.io/kustomize/kustomize:v5.0.0 + environment: + DEVOPS_SSH_PRIVATE: + from_secret: DEVOPS_SSH_PRIVATE + DEVOPS_SSH_PUBLIC: + from_secret: DEVOPS_SSH_PUBLIC + INFRASTRUCTURE_REPO: "k8s-kustomize-external" + DIRECTORY_NAME: "weplay-pos-testing" + commands: + - mkdir -p ~/.ssh && + - echo $DEVOPS_SSH_PRIVATE | base64 -d > ~/.ssh/id_rsa && + - echo $DEVOPS_SSH_PUBLIC | base64 -d > ~/.ssh/id_rsa.pub && + - ssh-keyscan -H -p 2222 git.eigen.co.id >> ~/.ssh/known_hosts && + - chmod 700 ~/.ssh/ && + - chmod 600 ~/.ssh/id_rsa && + - git clone ssh://git@git.eigen.co.id:2222/eigen/$INFRASTRUCTURE_REPO.git && + - cd $INFRASTRUCTURE_REPO/$DIRECTORY_NAME + - kustomize edit set image registry.eigen.co.id/eigen/$DRONE_REPO_NAME=registry.eigen.co.id/eigen/$DRONE_REPO_NAME:$DRONE_TAG && + - git add . && + - |- + git commit -m "feat: update $DRONE_REPO_NAME testing to $DRONE_TAG" && + - git push origin master + - name: send-message + image: harbor.eigen.co.id/docker.com/plugins/webhook + settings: + urls: https://mattermost.eigen.co.id/api/v4/posts + content_type: application/json + headers: + - Authorization=Bearer 5zubexudb38uuradfa36qy98ca + template: | + { + "channel_id": "s1ekqde1c3du5p35g6budnuotc", + "message": "ALERT: {{ repo.name }} gagal update dengan tag ${DRONE_TAG}" + } + when: + status: + - failure +trigger: + ref: + include: + - refs/tags/*-alpha.* +depends_on: + - build \ No newline at end of file From bd32d4fbdd20341bd40e90e88457a4556bfc0912 Mon Sep 17 00:00:00 2001 From: Supan Adit Pratama Date: Wed, 30 Oct 2024 14:34:27 +0700 Subject: [PATCH 21/94] ci: fix depends --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index c9a2718..991b258 100644 --- a/.drone.yml +++ b/.drone.yml @@ -98,4 +98,4 @@ trigger: include: - refs/tags/*-alpha.* depends_on: - - build \ No newline at end of file + - server \ No newline at end of file From 1d54b709b651e81261d6064ee858ab725274f677 Mon Sep 17 00:00:00 2001 From: Supan Adit Pratama Date: Wed, 30 Oct 2024 14:37:07 +0700 Subject: [PATCH 22/94] ci: build k8s only --- .drone.yml | 44 ++++++++++++++++++++++++++------------------ Dockerfile | 8 ++++---- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/.drone.yml b/.drone.yml index 991b258..fb0768c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,29 +2,37 @@ kind: pipeline type: docker name: server steps: - - name: build - image: appleboy/drone-ssh - settings: - host: - - 172.10.10.10 - username: eigen - key: - from_secret: DEVOPS_SSH_PRIVATE_OPEN - port: 22 - script: - - cd /home/eigen/PROJECT/POS/POS.DEV/BE - - sh build.sh - when: - ref: - - refs/tags/devel_* - - refs/tags/*-alpha.* + # - name: build + # image: appleboy/drone-ssh + # settings: + # host: + # - 172.10.10.10 + # username: eigen + # key: + # from_secret: DEVOPS_SSH_PRIVATE_OPEN + # port: 22 + # script: + # - cd /home/eigen/PROJECT/POS/POS.DEV/BE + # - sh build.sh + # when: + # ref: + # - refs/tags/devel_* + # - refs/tags/*-alpha.* + - name: build-production + image: plugins/docker + settings: + registry: registry.eigen.co.id + repo: registry.eigen.co.id/eigen/${DRONE_REPO_NAME} + tags: ${DRONE_TAG} + custom_dns: 172.10.10.16 + when: + ref: + - refs/tags/*-production.* - name: build-production image: plugins/docker settings: registry: registry.eigen.co.id repo: registry.eigen.co.id/eigen/${DRONE_REPO_NAME} - build_args: - - env_target=env.production tags: ${DRONE_TAG} custom_dns: 172.10.10.16 when: diff --git a/Dockerfile b/Dockerfile index 9b354f4..0b4ded3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,11 +5,11 @@ COPY . . RUN yarn install RUN yarn build FROM node:18.17-alpine -ARG env_target +# ARG env_target WORKDIR /app -RUN echo ${env_target} -COPY env/$env_target /app/.env -COPY --from=builder /app/env/$env_target .env +# RUN echo ${env_target} +# COPY env/$env_target /app/.env +# COPY --from=builder /app/env/$env_target .env COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist COPY --from=builder /app/assets ./assets From e6787aed891b44226a52d62b29f4b3ee386ed09b Mon Sep 17 00:00:00 2001 From: Supan Adit Pratama Date: Wed, 30 Oct 2024 14:37:42 +0700 Subject: [PATCH 23/94] ci: fix duplicate step name --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index fb0768c..4df5f7b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -18,7 +18,7 @@ steps: # ref: # - refs/tags/devel_* # - refs/tags/*-alpha.* - - name: build-production + - name: build-testing image: plugins/docker settings: registry: registry.eigen.co.id From 4ed7ecce5e7774c6d8f0f0b587538819b3f1feee Mon Sep 17 00:00:00 2001 From: Supan Adit Pratama Date: Wed, 30 Oct 2024 14:38:11 +0700 Subject: [PATCH 24/94] ci: fix filtering --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 4df5f7b..e2fe1f9 100644 --- a/.drone.yml +++ b/.drone.yml @@ -27,7 +27,7 @@ steps: custom_dns: 172.10.10.16 when: ref: - - refs/tags/*-production.* + - refs/tags/*-alpha.* - name: build-production image: plugins/docker settings: From 492a4ca2baf1ca6805edd1c1bd001ab7da947abc Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 1 Nov 2024 13:32:59 +0700 Subject: [PATCH 25/94] wip: queue bucket --- src/app.module.ts | 2 ++ src/core/strings/constants/table.constants.ts | 1 + .../queue/data/models/queue-bucket.model.ts | 21 ++++++++++++++++ .../queue/data/services/queue-bucket.ts | 25 +++++++++++++++++++ src/modules/queue/queue.module.ts | 13 +++++++++- 5 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/modules/queue/data/models/queue-bucket.model.ts create mode 100644 src/modules/queue/data/services/queue-bucket.ts diff --git a/src/app.module.ts b/src/app.module.ts index e8696ac..3015ab4 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -92,6 +92,7 @@ import { } from './modules/queue/data/models/queue.model'; import { ItemQueueModule } from './modules/item-related/item-queue/item-queue.module'; import { ItemQueueModel } from './modules/item-related/item-queue/data/models/item-queue.model'; +import { QueueBucketModel } from './modules/queue/data/models/queue-bucket.model'; @Module({ imports: [ @@ -151,6 +152,7 @@ import { ItemQueueModel } from './modules/item-related/item-queue/data/models/it QueueTicketModel, QueueItemModel, QueueModel, + QueueBucketModel, ], synchronize: false, }), diff --git a/src/core/strings/constants/table.constants.ts b/src/core/strings/constants/table.constants.ts index 1e5129e..27eb096 100644 --- a/src/core/strings/constants/table.constants.ts +++ b/src/core/strings/constants/table.constants.ts @@ -41,4 +41,5 @@ export enum TABLE_NAME { QUEUE_ORDER = 'queue_orders', QUEUE_TICKET = 'queue_tickets', QUEUE_ITEM = 'queue_items', + QUEUE_BUCKET = 'queue_bucket', } diff --git a/src/modules/queue/data/models/queue-bucket.model.ts b/src/modules/queue/data/models/queue-bucket.model.ts new file mode 100644 index 0000000..51f2e3a --- /dev/null +++ b/src/modules/queue/data/models/queue-bucket.model.ts @@ -0,0 +1,21 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { QueueBucket } from '../../domain/entities/queue-bucket.entity'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Entity(TABLE_NAME.QUEUE_BUCKET) +export class QueueBucketModel implements QueueBucket { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column('varchar') + queue_item_id: string; + + @Column({ type: 'bigint', nullable: false }) + date: number; + + @Column('int') + regular: number; + + @Column('int') + vip: number; +} diff --git a/src/modules/queue/data/services/queue-bucket.ts b/src/modules/queue/data/services/queue-bucket.ts new file mode 100644 index 0000000..9186137 --- /dev/null +++ b/src/modules/queue/data/services/queue-bucket.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; + +import { InjectRepository } from '@nestjs/typeorm'; +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'; +import { QueueBucketModel } from '../models/queue-bucket.model'; + +@Injectable() +export class QueueBucketReadService extends BaseReadService { + constructor( + @InjectRepository(QueueBucketModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } + + getQueue(item_id: string): Promise { + return this.repo.findOne({ + where: { + queue_item_id: item_id, + }, + }); + } +} diff --git a/src/modules/queue/queue.module.ts b/src/modules/queue/queue.module.ts index ff62289..e9eb803 100644 --- a/src/modules/queue/queue.module.ts +++ b/src/modules/queue/queue.module.ts @@ -18,12 +18,19 @@ import { QueueTransactionHandler } from './infrastructure/handlers/transaction.h import { TransactionDataService } from '../transaction/transaction/data/services/transaction-data.service'; import { TransactionModel } from '../transaction/transaction/data/models/transaction.model'; import { TicketDataService } from './data/services/ticket.service'; -import { QueueDataService, QueueService } from './data/services/queue.service'; +import { + QueueDataService, + QueueOrderService, + QueueService, +} from './data/services/queue.service'; import { QueueAdminController } from './infrastructure/controllers/queue-admin.controller'; import { QueueAdminOrchestrator } from './domain/queue-admin.orchestrator'; import { IndexQueueManager } from './domain/usecases/index-queue.manager'; import { CallQueueManager } from './domain/usecases/call-queue.manager'; import { RegisterQueueManager } from './domain/usecases/register-queue.manager'; +import { QueueBucketModel } from './data/models/queue-bucket.model'; +import { QueueBucketReadService } from './data/services/queue-bucket'; +import { SplitQueueManager } from './domain/usecases/split-queue.manager'; @Module({ imports: [ @@ -34,6 +41,7 @@ import { RegisterQueueManager } from './domain/usecases/register-queue.manager'; QueueTicketModel, QueueItemModel, QueueModel, + QueueBucketModel, ItemModel, TransactionModel, ], @@ -52,10 +60,13 @@ import { RegisterQueueManager } from './domain/usecases/register-queue.manager'; TicketDataService, QueueDataService, QueueService, + QueueBucketReadService, + QueueOrderService, IndexQueueManager, CallQueueManager, RegisterQueueManager, + SplitQueueManager, ], }) export class QueueModule {} From 50e7f66bb7a58735964020fc87b38a425aeebf8d Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 1 Nov 2024 13:33:25 +0700 Subject: [PATCH 26/94] feat: split QR customer --- .../queue/data/services/queue.service.ts | 28 +++++- .../queue/data/services/ticket.service.ts | 56 ++++++++++-- .../domain/entities/queue-bucket.entity.ts | 7 ++ .../queue/domain/queue.orchestrator.ts | 33 ++++++- .../queue/customer-queue-item-list.manager.ts | 1 + .../domain/usecases/register-queue.manager.ts | 22 ++++- .../domain/usecases/split-queue.manager.ts | 87 +++++++++++++++++++ .../controllers/dto/split-queue.dto.ts | 56 ++++++++++++ .../controllers/queue.controller.ts | 6 ++ 9 files changed, 285 insertions(+), 11 deletions(-) create mode 100644 src/modules/queue/domain/entities/queue-bucket.entity.ts create mode 100644 src/modules/queue/domain/usecases/split-queue.manager.ts create mode 100644 src/modules/queue/infrastructure/controllers/dto/split-queue.dto.ts diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index e688c7e..786820c 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -1,9 +1,13 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; -import { Repository } from 'typeorm'; -import { QueueItemModel, QueueModel } from '../models/queue.model'; +import { DataSource, Repository } from 'typeorm'; +import { + QueueItemModel, + QueueModel, + QueueOrderModel, +} from '../models/queue.model'; import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; @@ -25,6 +29,9 @@ export class QueueService extends BaseDataService { @InjectRepository(QueueItemModel, CONNECTION_NAME.DEFAULT) private item: Repository, + + @InjectDataSource(CONNECTION_NAME.DEFAULT) + private dataSource: DataSource, ) { super(repo); } @@ -37,4 +44,19 @@ export class QueueService extends BaseDataService { }, }); } + + async updateItemQty(item_id: string, qty: number): Promise { + const query = `UPDATE queue_items SET qty = qty - ${qty} WHERE id = '${item_id}'`; + this.dataSource.query(query); + } +} + +@Injectable() +export class QueueOrderService extends BaseDataService { + constructor( + @InjectRepository(QueueOrderModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } } diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index 4f1cd66..38e62a3 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; import { InjectRepository } from '@nestjs/typeorm'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; -import { Repository } from 'typeorm'; +import { In, Repository } from 'typeorm'; import { QueueTicket } from '../../domain/entities/ticket.entity'; import { QueueItemModel, @@ -39,7 +39,50 @@ export class TicketDataService extends BaseDataService { }); } + async orders(order_id: string): Promise { + const order = await this.order.findOneOrFail({ + where: { + id: order_id, + }, + }); + + if (order.transaction_id != null) { + return this.order.find({ + where: { + code: order.code, + }, + }); + } + + return [order]; + } + + async orderIds(order_id: string): Promise { + const orders = await this.orders(order_id); + + return orders.map((order) => order.id); + } + + async orderItems( + order_id: string, + item_ids: string[], + ): Promise { + const order = await this.orderIds(order_id); + return this.order.findOneOrFail({ + relations: ['tickets', 'tickets.items'], + where: { + tickets: { + order_id: In(order), + items: { + id: In(item_ids), + }, + }, + }, + }); + } + async queueTickets(order_id: string): Promise { + const order = await this.orderIds(order_id); return this.repo.find({ relations: [ 'items', @@ -48,7 +91,7 @@ export class TicketDataService extends BaseDataService { 'items.item.item_queue', ], where: { - order_id, + order_id: In(order), }, }); } @@ -57,6 +100,7 @@ export class TicketDataService extends BaseDataService { order_id: string, ticket_id: string, ): Promise { + const order = await this.orderIds(order_id); return this.repo.find({ relations: [ 'items', @@ -65,7 +109,7 @@ export class TicketDataService extends BaseDataService { 'items.item.item_queue', ], where: { - order_id, + order_id: In(order), id: ticket_id, }, }); @@ -75,6 +119,7 @@ export class TicketDataService extends BaseDataService { order_id: string, item_id: string, ): Promise { + const order = await this.orderIds(order_id); return this.repo.find({ relations: [ 'items', @@ -83,18 +128,19 @@ export class TicketDataService extends BaseDataService { 'items.item.item_queue', ], where: { - order_id, + order_id: In(order), items: [{ item_id }, { item: { item_queue: { id: item_id } } }], }, }); } async queueItems(order_id: string): Promise { + const order = await this.orderIds(order_id); return this.item.find({ relations: ['queue', 'ticket'], where: { ticket: { - order_id, + order_id: In(order), }, }, }); diff --git a/src/modules/queue/domain/entities/queue-bucket.entity.ts b/src/modules/queue/domain/entities/queue-bucket.entity.ts new file mode 100644 index 0000000..8cda117 --- /dev/null +++ b/src/modules/queue/domain/entities/queue-bucket.entity.ts @@ -0,0 +1,7 @@ +export interface QueueBucket { + id: string; + queue_item_id: string; + date: number; + regular: number; + vip: number; +} diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index be8f5fe..e3314ff 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -2,7 +2,10 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { TicketDataService } from '../data/services/ticket.service'; import { QueueOrder } from './entities/order.entity'; import { Queue } from './entities/queue.entity'; -import { QueueService } from '../data/services/queue.service'; +import { + QueueOrderService, + QueueService, +} from '../data/services/queue.service'; import { RegisterQueueManager } from './usecases/register-queue.manager'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { RegisterQueueDto } from '../infrastructure/controllers/dto/register-queue.dto'; @@ -11,13 +14,17 @@ import { CustomerQueueDetailManager } from './usecases/queue/customer-queue-deta import { CustomerQueueItemManager } from './usecases/queue/customer-queue-item.manager'; import { CustomerQueueItemListManager } from './usecases/queue/customer-queue-item-list.manager'; import { CustomerQueueListManager } from './usecases/queue/customer-queue-list.manager'; +import { SplitQueueDto } from '../infrastructure/controllers/dto/split-queue.dto'; +import { SplitQueueManager } from './usecases/split-queue.manager'; @Injectable() export class QueueOrchestrator { constructor( private readonly dataService: TicketDataService, private readonly queueService: QueueService, + private readonly queueOrderService: QueueOrderService, private readonly registerQueueManager: RegisterQueueManager, + private readonly splitQueueManager: SplitQueueManager, ) {} async loginCustomer(id: string): Promise { @@ -46,6 +53,30 @@ export class QueueOrchestrator { return this.registerQueueManager.getResult(); } + async split(data: SplitQueueDto): Promise { + const queueIds = data.items.map((i) => i.queue_item_id); + const queue = await this.dataService.orderItems(data.order_id, queueIds); + // queue.tickets = queue.tickets.map((ticket) => { + // ticket.items = ticket.items.map((item) => { + // const itemQty = data.items.find((i) => i.queue_item_id === item.id); + // return { + // ...item, + // qty: itemQty.qty, + // }; + // }); + // return ticket; + // }); + + this.splitQueueManager.setRequestData(data); + this.splitQueueManager.setData(queue); + this.splitQueueManager.setService( + this.queueOrderService, + TABLE_NAME.QUEUE_ORDER, + ); + await this.splitQueueManager.execute(); + return this.splitQueueManager.getResult(); + } + async queueTickets(order_id: string): Promise { const tickets = await this.dataService.queueTickets(order_id); const manager = new CustomerQueueSummaryManager(tickets); diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts index 7692acf..d43a070 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts @@ -19,6 +19,7 @@ export class CustomerQueueItemListManager extends CustomerQueueManager { const item_qty = items.reduce((acc, item) => acc + item.qty, 0); return { id: item.item_id, + queue_item_id: item.id, title: item.item.item_queue?.name ?? item.item.name, image_url: item.item.image_url, qty: item_qty, diff --git a/src/modules/queue/domain/usecases/register-queue.manager.ts b/src/modules/queue/domain/usecases/register-queue.manager.ts index 95b40d1..6d80b11 100644 --- a/src/modules/queue/domain/usecases/register-queue.manager.ts +++ b/src/modules/queue/domain/usecases/register-queue.manager.ts @@ -5,23 +5,41 @@ import { validateRelations, } from 'src/core/strings/constants/interface.constants'; import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; -import { Queue } from '../entities/queue.entity'; import { STATUS } from 'src/core/strings/constants/base.constants'; import { QueueModel } from '../../data/models/queue.model'; import { generateRandom } from 'src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper'; +import { QueueBucketReadService } from '../../data/services/queue-bucket'; @Injectable() export class RegisterQueueManager extends BaseCreateManager { + constructor(private readonly queueService: QueueBucketReadService) { + super(); + } + + async averageTime(): Promise { + const item = await this.getItem(); + return item.item.item.play_estimation; + } + async beforeProcess(): Promise { Object.assign(this.data, { status: STATUS.WAITING, time: new Date().getTime(), vip: false, - code: `Q${generateRandom(4, true)}`, + code: `A${generateRandom(4, true)}`, }); return; } + async getItem(): Promise { + return this.dataService.repo.findOne({ + relations: ['item', 'item.item'], + where: { + item_id: this.data.item_id, + }, + }); + } + async afterProcess(): Promise { return; } diff --git a/src/modules/queue/domain/usecases/split-queue.manager.ts b/src/modules/queue/domain/usecases/split-queue.manager.ts new file mode 100644 index 0000000..73427b0 --- /dev/null +++ b/src/modules/queue/domain/usecases/split-queue.manager.ts @@ -0,0 +1,87 @@ +import { Injectable } from '@nestjs/common'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; + +import { QueueOrderModel } from '../../data/models/queue.model'; +import { SplitQueueDto } from '../../infrastructure/controllers/dto/split-queue.dto'; +import { QueueService } from '../../data/services/queue.service'; + +@Injectable() +export class SplitQueueManager extends BaseCreateManager { + private dto: SplitQueueDto; + constructor(private readonly queueService: QueueService) { + super(); + } + + setRequestData(dto: SplitQueueDto): void { + this.dto = dto; + } + + prepareData(): Promise { + const { tickets, ...order } = this.data; + const ticket = tickets[0]; + const items = tickets + .map((ticket) => { + return ticket.items; + }) + .flat(); + this.data = { + code: order.code, + customer: order.customer, + phone: order.phone, + date: order.date, + updated_at: new Date().getTime(), + tickets: [ + { + code: `${order.code}-1`, + customer: this.dto.name, + phone: this.dto.phone, + date: ticket.date, + order_id: order.id, + items: items.map((item) => { + const itemQty = this.dto.items.find( + (i) => i.queue_item_id === item.id, + ); + return { + item_id: item.item_id, + qty: itemQty.qty, + }; + }), + }, + ], + }; + super.prepareData(); + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + this.dto.items.forEach((item) => { + this.queueService.updateItemQty(item.queue_item_id, item.qty); + }); + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get eventTopics(): EventTopics[] { + return []; + } + + get entityTarget(): any { + return QueueOrderModel; + } +} diff --git a/src/modules/queue/infrastructure/controllers/dto/split-queue.dto.ts b/src/modules/queue/infrastructure/controllers/dto/split-queue.dto.ts new file mode 100644 index 0000000..482267c --- /dev/null +++ b/src/modules/queue/infrastructure/controllers/dto/split-queue.dto.ts @@ -0,0 +1,56 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsArray, + IsNotEmpty, + IsNumber, + IsString, + Min, + ValidateNested, +} from 'class-validator'; + +export class ItemSplitQueueDto { + @ApiProperty({ name: 'queue_item_id', required: true }) + @IsString() + @IsNotEmpty() + queue_item_id: string; + @ApiProperty({ + type: Number, + required: true, + example: 1, + }) + @IsNumber() + @Min(1) + @IsNotEmpty() + qty: number; +} + +export class SplitQueueDto { + @ApiProperty({ name: 'order_id', required: true }) + @IsString() + @IsNotEmpty() + order_id: string; + + @ApiProperty({ name: 'name', required: true }) + @IsString() + @IsNotEmpty() + name: string; + + @ApiProperty({ name: 'phone', required: true }) + @IsString() + @IsNotEmpty() + phone: string; + + @ApiProperty({ + type: [ItemSplitQueueDto], + required: false, + example: [ + { + queue_item_id: 'string', + qty: 1, + }, + ], + }) + @ValidateNested({ each: true }) + @IsArray() + items: ItemSplitQueueDto[]; +} diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index 7d382dc..f132bd2 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -8,6 +8,7 @@ import { QueueOrchestrator } from '../../domain/queue.orchestrator'; import { QueueOrder } from '../../domain/entities/order.entity'; import { Queue } from '../../domain/entities/queue.entity'; import { RegisterQueueDto } from './dto/register-queue.dto'; +import { SplitQueueDto } from './dto/split-queue.dto'; @ApiTags(`Queue`) @Controller(`v1/${MODULE_NAME.QUEUE}`) @@ -21,6 +22,11 @@ export class QueueController { return await this.orchestrator.create(data); } + @Post('split') + async splitQueue(@Body() data: SplitQueueDto): Promise { + return await this.orchestrator.split(data); + } + @Get('login/:id') async loginCustomer(@Param('id') id: string): Promise { return await this.orchestrator.loginCustomer(id); From 5f46432327d9b68b37cdcc2a07f05b4d5d5d29b7 Mon Sep 17 00:00:00 2001 From: shancheas Date: Mon, 4 Nov 2024 12:35:53 +0700 Subject: [PATCH 27/94] feat: login customer --- .../queue/data/services/ticket.service.ts | 17 ++++++++++++- .../queue/domain/queue.orchestrator.ts | 25 +++++++++++-------- .../controllers/dto/login-queue.dto.ts | 20 +++++++++++++++ .../controllers/dto/split-queue.dto.ts | 9 ++++++- .../controllers/queue.controller.ts | 6 +++++ 5 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 src/modules/queue/infrastructure/controllers/dto/login-queue.dto.ts diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index 38e62a3..f0514d9 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; import { InjectRepository } from '@nestjs/typeorm'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; -import { In, Repository } from 'typeorm'; +import { Between, In, Repository } from 'typeorm'; import { QueueTicket } from '../../domain/entities/ticket.entity'; import { QueueItemModel, @@ -10,6 +10,7 @@ import { QueueTicketModel, } from '../models/queue.model'; import { QueueOrder } from '../../domain/entities/order.entity'; +import * as moment from 'moment'; @Injectable() export class TicketDataService extends BaseDataService { @@ -39,6 +40,20 @@ export class TicketDataService extends BaseDataService { }); } + async loginPhone(user: string, phone: string): Promise { + const start = moment().startOf('day').valueOf(); + const end = moment().endOf('day').valueOf(); + + return this.order.findOneOrFail({ + relations: ['tickets'], + where: { + customer: user, + phone: `+${phone}`, + date: Between(start, end), + }, + }); + } + async orders(order_id: string): Promise { const order = await this.order.findOneOrFail({ where: { diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index e3314ff..d600c31 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -16,6 +16,8 @@ import { CustomerQueueItemListManager } from './usecases/queue/customer-queue-it import { CustomerQueueListManager } from './usecases/queue/customer-queue-list.manager'; import { SplitQueueDto } from '../infrastructure/controllers/dto/split-queue.dto'; import { SplitQueueManager } from './usecases/split-queue.manager'; +import { LoginQueueDto } from '../infrastructure/controllers/dto/login-queue.dto'; +import * as moment from 'moment'; @Injectable() export class QueueOrchestrator { @@ -38,6 +40,19 @@ export class QueueOrchestrator { } } + async loginPhone(login: LoginQueueDto): Promise { + const { name, phone } = login; + const now = moment().format('DD/MM/YYYY'); + try { + return await this.dataService.loginPhone(name, phone); + } catch (error) { + throw new UnauthorizedException({ + message: `Antrian atas nama ${name} dan nomor telepon ${phone} pada tanggal ${now} tidak ditemukan`, + error: 'INVOICE_NOT_FOUND', + }); + } + } + async create(data: RegisterQueueDto): Promise { const queue = await this.queueService.getTicketItems( data.ticket_id, @@ -56,16 +71,6 @@ export class QueueOrchestrator { async split(data: SplitQueueDto): Promise { const queueIds = data.items.map((i) => i.queue_item_id); const queue = await this.dataService.orderItems(data.order_id, queueIds); - // queue.tickets = queue.tickets.map((ticket) => { - // ticket.items = ticket.items.map((item) => { - // const itemQty = data.items.find((i) => i.queue_item_id === item.id); - // return { - // ...item, - // qty: itemQty.qty, - // }; - // }); - // return ticket; - // }); this.splitQueueManager.setRequestData(data); this.splitQueueManager.setData(queue); diff --git a/src/modules/queue/infrastructure/controllers/dto/login-queue.dto.ts b/src/modules/queue/infrastructure/controllers/dto/login-queue.dto.ts new file mode 100644 index 0000000..6a0678d --- /dev/null +++ b/src/modules/queue/infrastructure/controllers/dto/login-queue.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString, Matches } from 'class-validator'; + +export class LoginQueueDto { + @ApiProperty({ name: 'name', required: true }) + @IsString() + @IsNotEmpty() + name: string; + + @ApiProperty({ + name: 'phone', + required: true, + description: 'Phone Number Must Start With 62, no + or 0', + example: '628123456789', + }) + @IsString() + @IsNotEmpty() + @Matches(/^628\d+$/i, { message: 'Nomor Telepon tidak valid' }) + phone: string; +} diff --git a/src/modules/queue/infrastructure/controllers/dto/split-queue.dto.ts b/src/modules/queue/infrastructure/controllers/dto/split-queue.dto.ts index 482267c..efdfecb 100644 --- a/src/modules/queue/infrastructure/controllers/dto/split-queue.dto.ts +++ b/src/modules/queue/infrastructure/controllers/dto/split-queue.dto.ts @@ -4,6 +4,7 @@ import { IsNotEmpty, IsNumber, IsString, + Matches, Min, ValidateNested, } from 'class-validator'; @@ -35,9 +36,15 @@ export class SplitQueueDto { @IsNotEmpty() name: string; - @ApiProperty({ name: 'phone', required: true }) + @ApiProperty({ + name: 'phone', + required: true, + description: 'Phone Number Must Start With 62, no + or 0', + example: '+628123456789', + }) @IsString() @IsNotEmpty() + @Matches(/^\+628\d+$/i, { message: 'Nomor Telepon tidak valid' }) phone: string; @ApiProperty({ diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index f132bd2..34a85af 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -9,6 +9,7 @@ import { QueueOrder } from '../../domain/entities/order.entity'; import { Queue } from '../../domain/entities/queue.entity'; import { RegisterQueueDto } from './dto/register-queue.dto'; import { SplitQueueDto } from './dto/split-queue.dto'; +import { LoginQueueDto } from './dto/login-queue.dto'; @ApiTags(`Queue`) @Controller(`v1/${MODULE_NAME.QUEUE}`) @@ -27,6 +28,11 @@ export class QueueController { return await this.orchestrator.split(data); } + @Post('login') + async loginQueue(@Body() data: LoginQueueDto): Promise { + return await this.orchestrator.loginPhone(data); + } + @Get('login/:id') async loginCustomer(@Param('id') id: string): Promise { return await this.orchestrator.loginCustomer(id); From 3ddd837622505f2beee1bb576bb8fafc33cc211b Mon Sep 17 00:00:00 2001 From: shancheas Date: Mon, 4 Nov 2024 15:06:09 +0700 Subject: [PATCH 28/94] fix: customer and phone in split queue --- src/modules/queue/domain/usecases/split-queue.manager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/queue/domain/usecases/split-queue.manager.ts b/src/modules/queue/domain/usecases/split-queue.manager.ts index 73427b0..c8a3c2c 100644 --- a/src/modules/queue/domain/usecases/split-queue.manager.ts +++ b/src/modules/queue/domain/usecases/split-queue.manager.ts @@ -31,8 +31,8 @@ export class SplitQueueManager extends BaseCreateManager { .flat(); this.data = { code: order.code, - customer: order.customer, - phone: order.phone, + customer: this.dto.name, + phone: this.dto.phone, date: order.date, updated_at: new Date().getTime(), tickets: [ From ba7b81c3201aec344202af108874b5af37189f3c Mon Sep 17 00:00:00 2001 From: shancheas Date: Mon, 4 Nov 2024 16:34:17 +0700 Subject: [PATCH 29/94] feat: add login by invoice code --- src/modules/queue/data/services/ticket.service.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index f0514d9..0014ef5 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -34,9 +34,7 @@ export class TicketDataService extends BaseDataService { async loginQueue(id: string): Promise { return this.order.findOneOrFail({ relations: ['tickets'], - where: { - transaction_id: id, - }, + where: [{ transaction_id: id }, { code: id }], }); } From 5ef7521e9b5c12c815783c63a33a8d930f4841ae Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 5 Nov 2024 14:43:04 +0700 Subject: [PATCH 30/94] feat: add queue active summary --- .../queue/data/services/ticket.service.ts | 19 +++++++++++++++ .../queue/domain/queue.orchestrator.ts | 11 +++++++++ .../queue/customer-queue-ticket.manager.ts | 23 +++++++++++++++++++ .../controllers/queue.controller.ts | 8 +++++++ 4 files changed, 61 insertions(+) create mode 100644 src/modules/queue/domain/usecases/queue/customer-queue-ticket.manager.ts diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index 0014ef5..2d11cee 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -128,6 +128,25 @@ export class TicketDataService extends BaseDataService { }); } + async queueTicketActive( + order_id: string, + ticket_id: string, + ): Promise { + const order = await this.orderIds(order_id); + return this.repo.find({ + relations: ['items', 'items.queue'], + where: { + order_id: In(order), + id: ticket_id, + items: { + queue: { + status: In(['waiting']), + }, + }, + }, + }); + } + async queueItemTickets( order_id: string, item_id: string, diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index d600c31..98050f2 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -18,6 +18,7 @@ import { SplitQueueDto } from '../infrastructure/controllers/dto/split-queue.dto import { SplitQueueManager } from './usecases/split-queue.manager'; import { LoginQueueDto } from '../infrastructure/controllers/dto/login-queue.dto'; import * as moment from 'moment'; +import { CustomerQueueTicketSummaryManager } from './usecases/queue/customer-queue-ticket.manager'; @Injectable() export class QueueOrchestrator { @@ -97,6 +98,16 @@ export class QueueOrchestrator { return manager.data; } + async queueTicketSummary(order_id: string, ticket_id: string): Promise { + const tickets = await this.dataService.queueTicketActive( + order_id, + ticket_id, + ); + + const manager = new CustomerQueueTicketSummaryManager(tickets); + return manager.data; + } + async queueItemDetail(order_id: string, item_id: string): Promise { const tickets = await this.dataService.queueItemTickets(order_id, item_id); const manager = new CustomerQueueItemManager(tickets); diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-ticket.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-ticket.manager.ts new file mode 100644 index 0000000..d2bbf14 --- /dev/null +++ b/src/modules/queue/domain/usecases/queue/customer-queue-ticket.manager.ts @@ -0,0 +1,23 @@ +import { CustomerQueueManager } from './customer-queue.manager'; + +export class CustomerQueueTicketSummaryManager extends CustomerQueueManager { + get data() { + const tickets = this.tickets.map((ticket) => { + return { + id: ticket.id, + code: ticket.code, + customer: ticket.customer, + phone: ticket.phone, + date: ticket.date, + summary: { + total_activities: this.totalActivities(ticket), + total_tickets: this.totalTickets(ticket), + total_used: this.totalUsedTickets(ticket), + total_queue: this.totalQueueTickets(ticket), + }, + }; + }); + + return tickets[0]; + } +} diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index 34a85af..982c994 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -51,6 +51,14 @@ export class QueueController { return await this.orchestrator.queueTicketDetail(id, ticket_id); } + @Get(':id/tickets/:ticket_id/summary') + async queueTicketActive( + @Param('id') id: string, + @Param('ticket_id') ticket_id: string, + ): Promise { + return await this.orchestrator.queueTicketSummary(id, ticket_id); + } + @Get(':id/items') async queueItems(@Param('id') id: string): Promise { return await this.orchestrator.queueItems(id); From dac42b754cf81f3b0e617ba9a357bec4aac5d6d4 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 6 Nov 2024 11:06:14 +0700 Subject: [PATCH 31/94] fix: missing logic --- .../1730859187883-add-queue-bucket.ts | 15 +++++++ .../queue/data/services/queue-bucket.ts | 39 +++++++++++++++++-- .../queue/data/services/queue.service.ts | 14 ++++++- .../queue/domain/helpers/time.helper.ts | 11 ++++++ .../formula/queue-condition.formula.ts | 38 ++++++++++++++++++ .../domain/usecases/index-queue.manager.ts | 9 +++++ .../queue/customer-queue-detail.manager.ts | 18 +++++---- .../queue/customer-queue-item-list.manager.ts | 30 +++++++------- .../usecases/queue/customer-queue.manager.ts | 9 +---- .../domain/usecases/register-queue.manager.ts | 8 ++-- .../handlers/transaction.handler.ts | 8 +++- .../helpers/generate-random.helper.ts | 4 ++ 12 files changed, 164 insertions(+), 39 deletions(-) create mode 100644 src/database/migrations/1730859187883-add-queue-bucket.ts create mode 100644 src/modules/queue/domain/helpers/time.helper.ts create mode 100644 src/modules/queue/domain/usecases/formula/queue-condition.formula.ts diff --git a/src/database/migrations/1730859187883-add-queue-bucket.ts b/src/database/migrations/1730859187883-add-queue-bucket.ts new file mode 100644 index 0000000..9e06f8d --- /dev/null +++ b/src/database/migrations/1730859187883-add-queue-bucket.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddQueueBucket1730859187883 implements MigrationInterface { + name = 'AddQueueBucket1730859187883'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "queue_bucket" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "queue_item_id" character varying NOT NULL, "date" bigint NOT NULL, "regular" integer NOT NULL, "vip" integer NOT NULL, CONSTRAINT "PK_cdd58b0d9e93e4be922da9d8bd6" PRIMARY KEY ("id"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "queue_bucket"`); + } +} diff --git a/src/modules/queue/data/services/queue-bucket.ts b/src/modules/queue/data/services/queue-bucket.ts index 9186137..b4c69de 100644 --- a/src/modules/queue/data/services/queue-bucket.ts +++ b/src/modules/queue/data/services/queue-bucket.ts @@ -2,9 +2,10 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; -import { Repository } from 'typeorm'; +import { Between, Repository } from 'typeorm'; import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; import { QueueBucketModel } from '../models/queue-bucket.model'; +import * as moment from 'moment'; @Injectable() export class QueueBucketReadService extends BaseReadService { @@ -15,11 +16,43 @@ export class QueueBucketReadService extends BaseReadService { super(repo); } - getQueue(item_id: string): Promise { - return this.repo.findOne({ + async getQueue(item_id: string, vip = false): Promise { + const start = moment().startOf('day').valueOf(); + const end = moment().endOf('day').valueOf(); + + const queue = await this.repo.findOne({ where: { queue_item_id: item_id, + date: Between(start, end), }, }); + + if (!queue) { + const regularNumber = vip ? 0 : 1; + const vipNumber = vip ? 1 : 0; + this.repo.save({ + queue_item_id: item_id, + date: start, + regular: regularNumber, + vip: vipNumber, + }); + return Promise.resolve(1); + } else { + const field = vip ? 'vip' : 'regular'; + const data = await this.repo + .createQueryBuilder('bucket') + .update(QueueBucketModel) + .set({ + [field]: () => `${field} + 1`, + }) + .where('id = :key', { + key: queue.id, + }) + .returning(field) + .updateEntity(true) + .execute(); + + return data.raw[0]?.[field] ?? 1; + } } } diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index 786820c..b015301 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; -import { DataSource, Repository } from 'typeorm'; +import { DataSource, In, Repository } from 'typeorm'; import { QueueItemModel, QueueModel, @@ -19,6 +19,18 @@ export class QueueDataService extends BaseReadService { ) { super(repo); } + + async queueItems(item_queue_id: string[]): Promise { + return this.repo.find({ + relations: ['item', 'item.item', 'item.item.item_queue'], + where: { + item: { item: { item_queue: { id: In(item_queue_id) } } }, + }, + order: { + time: 'DESC', + }, + }); + } } @Injectable() diff --git a/src/modules/queue/domain/helpers/time.helper.ts b/src/modules/queue/domain/helpers/time.helper.ts new file mode 100644 index 0000000..88f094e --- /dev/null +++ b/src/modules/queue/domain/helpers/time.helper.ts @@ -0,0 +1,11 @@ +import * as moment from 'moment'; + +export function toTime(timestamp: number): string { + const date = moment.unix(timestamp / 1000).add(7, 'hours'); + + const hours = date.hours(); + const minutes = date.minutes(); + return `${hours < 10 ? '0' : ''}${hours}:${ + minutes < 10 ? '0' : '' + }${minutes}`; +} diff --git a/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts b/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts new file mode 100644 index 0000000..358f548 --- /dev/null +++ b/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts @@ -0,0 +1,38 @@ +import { QueueModel } from 'src/modules/queue/data/models/queue.model'; +import { toTime } from '../../helpers/time.helper'; +import * as math from 'mathjs'; + +export class QueueCondition { + private ticketItems = {}; + constructor(readonly items: QueueModel[]) { + items.forEach((item) => { + const item_id = item.item.item.item_queue?.id ?? item.item.item.id; + const currentItem = this.ticketItems[item_id]; + this.ticketItems[item_id] = currentItem ? [...currentItem, item] : [item]; + }); + } + + condition(item_id: string) { + const queues: QueueModel[] = this.ticketItems[item_id] ?? []; + const time = queues[0]?.time; + const nearest = time ? toTime(time) : 0; + return { + available: queues.length == 0, + average: this.averageTime(queues), + nearest: nearest, + crowded_level: queues.length, + available_time: nearest, + }; + } + + averageTime(queues: QueueModel[]) { + if (queues.length == 0) return 0; + const calledQueue = queues.filter((q) => q.status === 'called'); + + const times = calledQueue.map((queue) => { + return queue.call_time - queue.time; + }); + + return math.sum(times) / times.length; + } +} diff --git a/src/modules/queue/domain/usecases/index-queue.manager.ts b/src/modules/queue/domain/usecases/index-queue.manager.ts index f94d39f..a379d99 100644 --- a/src/modules/queue/domain/usecases/index-queue.manager.ts +++ b/src/modules/queue/domain/usecases/index-queue.manager.ts @@ -6,6 +6,7 @@ import { RelationParam, } from 'src/core/modules/domain/entities/base-filter.entity'; import { Queue } from '../entities/queue.entity'; +import * as moment from 'moment'; @Injectable() export class IndexQueueManager extends BaseIndexManager { @@ -59,6 +60,14 @@ export class IndexQueueManager extends BaseIndexManager { setQueryFilter( queryBuilder: SelectQueryBuilder, ): SelectQueryBuilder { + const start = moment().startOf('day').valueOf(); + const end = moment().endOf('day').valueOf(); + + queryBuilder.andWhere(`${this.tableName}.time BETWEEN :start AND :end`, { + start, + end, + }); + if (this.filterParam.vip != null) { queryBuilder.andWhere(`${this.tableName}.vip = :vip`, { vip: this.filterParam.vip, diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts index 0ce404d..c54975e 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts @@ -1,7 +1,14 @@ +import { QueueModel } from 'src/modules/queue/data/models/queue.model'; import { CustomerQueueManager } from './customer-queue.manager'; +import { QueueCondition } from '../formula/queue-condition.formula'; export class CustomerQueueDetailManager extends CustomerQueueManager { + private queues: QueueModel[] = []; + currentQueues(queues: QueueModel[]) { + this.queues = queues; + } get data() { + const queueCondition = new QueueCondition(this.queues); return this.tickets.map((ticket) => { return { id: ticket.id, @@ -16,10 +23,11 @@ export class CustomerQueueDetailManager extends CustomerQueueManager { total_queue: this.totalQueueTickets(ticket), }, items: ticket.items.map((item) => { + const queueItem = item.item.item_queue ?? item.item; return { id: item.item_id, item_queue_id: item.id, - title: item.item.item_queue?.name ?? item.item.name, + title: queueItem.name, image_url: item.item.image_url, summary: { total_tickets: item.qty, @@ -36,13 +44,7 @@ export class CustomerQueueDetailManager extends CustomerQueueManager { status: q.status, }; }), - queue_condition: { - available: true, - average: 12, - nearest: '13:10', - crowded_level: 20, - available_time: '15:00', - }, + queue_condition: queueCondition.condition(queueItem.id), }; }), }; diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts index d43a070..e12625d 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts @@ -1,11 +1,21 @@ -import { QueueItemModel } from 'src/modules/queue/data/models/queue.model'; +import { + QueueItemModel, + QueueModel, +} from 'src/modules/queue/data/models/queue.model'; import { CustomerQueueManager } from './customer-queue.manager'; +import { QueueCondition } from '../formula/queue-condition.formula'; export class CustomerQueueItemListManager extends CustomerQueueManager { + private queues: QueueModel[] = []; + currentQueues(queues: QueueModel[]) { + this.queues = queues; + } get data() { const tickets = this.tickets; const ticketItems = {}; + const queueCondition = new QueueCondition(this.queues); + tickets.forEach((ticket) => { ticket.items.forEach((item) => { const item_id = item.item.item_queue?.id ?? item.item.id; @@ -17,26 +27,16 @@ export class CustomerQueueItemListManager extends CustomerQueueManager { return Object.values(ticketItems).map((items) => { const item = items[0]; const item_qty = items.reduce((acc, item) => acc + item.qty, 0); + const queueItem = item.item.item_queue ?? item.item; return { id: item.item_id, queue_item_id: item.id, - title: item.item.item_queue?.name ?? item.item.name, + title: queueItem.name, image_url: item.item.image_url, qty: item_qty, - available: true, - average: 12, - nearest: '13:10', - crowded_level: 20, - available_time: '15:00', - - queue_condition: { - available: true, - average: 12, - nearest: '13:10', - crowded_level: 20, - available_time: '15:00', - }, + queue_condition: queueCondition.condition(queueItem.id), + ...queueCondition.condition(queueItem.id), }; }); } diff --git a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts index 5b95ba1..2faaa8a 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts @@ -2,6 +2,7 @@ import { QueueItemModel, QueueTicketModel, } from '../../../data/models/queue.model'; +import { toTime } from '../../helpers/time.helper'; export class CustomerQueueManager { constructor(protected readonly tickets: QueueTicketModel[]) {} @@ -10,13 +11,7 @@ export class CustomerQueueManager { } toTime(timestamp: number): string { - // js function to convert timestamp (1729739455000) to time with format HH:mm - const date = new Date(timestamp / 1000); - const hours = date.getHours() + 7; - const minutes = date.getMinutes(); - return `${hours < 10 ? '0' : ''}${hours}:${ - minutes < 10 ? '0' : '' - }${minutes}`; + return toTime(timestamp); } totalActivities(ticket: QueueTicketModel): number { diff --git a/src/modules/queue/domain/usecases/register-queue.manager.ts b/src/modules/queue/domain/usecases/register-queue.manager.ts index 6d80b11..6ecd67f 100644 --- a/src/modules/queue/domain/usecases/register-queue.manager.ts +++ b/src/modules/queue/domain/usecases/register-queue.manager.ts @@ -7,12 +7,12 @@ import { import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; import { STATUS } from 'src/core/strings/constants/base.constants'; import { QueueModel } from '../../data/models/queue.model'; -import { generateRandom } from 'src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper'; +import { padCode } from 'src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper'; import { QueueBucketReadService } from '../../data/services/queue-bucket'; @Injectable() export class RegisterQueueManager extends BaseCreateManager { - constructor(private readonly queueService: QueueBucketReadService) { + constructor(private readonly bucketService: QueueBucketReadService) { super(); } @@ -22,11 +22,13 @@ export class RegisterQueueManager extends BaseCreateManager { } async beforeProcess(): Promise { + const queueNumber = await this.bucketService.getQueue(this.data.item_id); + const code = `A${padCode(queueNumber)}`; Object.assign(this.data, { status: STATUS.WAITING, time: new Date().getTime(), vip: false, - code: `A${generateRandom(4, true)}`, + code, }); return; } diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index 49d69c3..10c2105 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -8,6 +8,7 @@ import { TicketDataService } from '../../data/services/ticket.service'; import { QueueOrder } from '../../domain/entities/order.entity'; import { QueueTicket } from '../../domain/entities/ticket.entity'; import { QueueItem } from '../../domain/entities/queue-item.entity'; +import * as moment from 'moment'; @EventsHandler(TransactionChangeStatusEvent, TransactionCreateQueueEvent) export class QueueTransactionHandler @@ -33,13 +34,16 @@ export class QueueTransactionHandler relations: ['items'], }); + const date = transaction.booking_date ?? transaction.invoice_date; + const queue_date = moment(date, 'YYYY-MM-DD').unix(); + const { id, customer_name, customer_phone, invoice_code } = transaction; - const current_date = new Date().valueOf(); + const customerOrder = { code: invoice_code, customer: customer_name, phone: customer_phone, - date: current_date, + date: queue_date, transaction_id: id, }; diff --git a/src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper.ts b/src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper.ts index 4c65f72..8295e13 100644 --- a/src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper.ts +++ b/src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper.ts @@ -22,3 +22,7 @@ export function generateCodeDate() { return `${month}${year}`; } + +export function padCode(number: number, pad = 4): string { + return String(number).padStart(pad, '0'); +} From 4c403293db60b9cb25b23b592a606b476d502a77 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 6 Nov 2024 11:28:02 +0700 Subject: [PATCH 32/94] fix: admin queue order item --- src/modules/queue/domain/queue-admin.orchestrator.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/modules/queue/domain/queue-admin.orchestrator.ts b/src/modules/queue/domain/queue-admin.orchestrator.ts index b39480e..0e2967e 100644 --- a/src/modules/queue/domain/queue-admin.orchestrator.ts +++ b/src/modules/queue/domain/queue-admin.orchestrator.ts @@ -5,7 +5,10 @@ import { PaginationResponse } from 'src/core/response/domain/ok-response.interfa import { Queue } from './entities/queue.entity'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { CallQueueManager } from './usecases/call-queue.manager'; -import { QUEUE_STATUS } from 'src/core/strings/constants/base.constants'; +import { + ORDER_TYPE, + QUEUE_STATUS, +} from 'src/core/strings/constants/base.constants'; @Injectable() export class QueueAdminOrchestrator { @@ -17,7 +20,7 @@ export class QueueAdminOrchestrator { ) {} async index(params): Promise> { - this.indexManager.setFilterParam(params); + this.indexManager.setFilterParam({ ...params, order_type: ORDER_TYPE.ASC }); this.indexManager.setService(this.dataService, TABLE_NAME.QUEUE); await this.indexManager.execute(); return this.indexManager.getResult(); From 3ed25e136620816ac51dde5c3ace9a37d2ab7013 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 6 Nov 2024 11:28:54 +0700 Subject: [PATCH 33/94] fix: admin queue order item --- src/modules/queue/domain/queue-admin.orchestrator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/queue/domain/queue-admin.orchestrator.ts b/src/modules/queue/domain/queue-admin.orchestrator.ts index 0e2967e..21876c8 100644 --- a/src/modules/queue/domain/queue-admin.orchestrator.ts +++ b/src/modules/queue/domain/queue-admin.orchestrator.ts @@ -20,7 +20,7 @@ export class QueueAdminOrchestrator { ) {} async index(params): Promise> { - this.indexManager.setFilterParam({ ...params, order_type: ORDER_TYPE.ASC }); + this.indexManager.setFilterParam({ order_type: ORDER_TYPE.ASC, ...params }); this.indexManager.setService(this.dataService, TABLE_NAME.QUEUE); await this.indexManager.execute(); return this.indexManager.getResult(); From 28c1ab36da5aa3a0ae207f9531c59b269bed84da Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 6 Nov 2024 11:38:51 +0700 Subject: [PATCH 34/94] fix: summary ticket without order --- src/modules/queue/data/services/ticket.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index 2d11cee..0ceb0fb 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -132,11 +132,11 @@ export class TicketDataService extends BaseDataService { order_id: string, ticket_id: string, ): Promise { - const order = await this.orderIds(order_id); + // const order = await this.orderIds(order_id); return this.repo.find({ relations: ['items', 'items.queue'], where: { - order_id: In(order), + // order_id: In(order), id: ticket_id, items: { queue: { From bf5914af922075f19502a8d3f96fdb844c43c082 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 6 Nov 2024 12:01:49 +0700 Subject: [PATCH 35/94] fix: queue bucket item id --- .../item-related/item/data/models/item.model.ts | 3 +++ src/modules/queue/data/services/queue-bucket.ts | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/modules/item-related/item/data/models/item.model.ts b/src/modules/item-related/item/data/models/item.model.ts index 52f5f9f..90634a6 100644 --- a/src/modules/item-related/item/data/models/item.model.ts +++ b/src/modules/item-related/item/data/models/item.model.ts @@ -93,6 +93,9 @@ export class ItemModel @JoinColumn({ name: 'item_queue_id' }) item_queue: ItemQueueModel; + @Column('varchar', { name: 'item_queue_id', nullable: true }) + item_queue_id: string; + // relation ke tenant // ? karena item bisajadi merupakan item dari tenant @Column('varchar', { name: 'tenant_id', nullable: true }) diff --git a/src/modules/queue/data/services/queue-bucket.ts b/src/modules/queue/data/services/queue-bucket.ts index b4c69de..c261739 100644 --- a/src/modules/queue/data/services/queue-bucket.ts +++ b/src/modules/queue/data/services/queue-bucket.ts @@ -6,12 +6,16 @@ import { Between, Repository } from 'typeorm'; import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; import { QueueBucketModel } from '../models/queue-bucket.model'; import * as moment from 'moment'; +import { QueueItemModel } from '../models/queue.model'; @Injectable() export class QueueBucketReadService extends BaseReadService { constructor( @InjectRepository(QueueBucketModel, CONNECTION_NAME.DEFAULT) private repo: Repository, + + @InjectRepository(QueueItemModel, CONNECTION_NAME.DEFAULT) + private item: Repository, ) { super(repo); } @@ -20,9 +24,16 @@ export class QueueBucketReadService extends BaseReadService { const start = moment().startOf('day').valueOf(); const end = moment().endOf('day').valueOf(); + const queueItem = await this.item.findOne({ + relations: ['item'], + where: { + id: item_id, + }, + }); + const queue = await this.repo.findOne({ where: { - queue_item_id: item_id, + queue_item_id: queueItem.item.item_queue_id ?? queueItem.item.id, date: Between(start, end), }, }); @@ -31,7 +42,7 @@ export class QueueBucketReadService extends BaseReadService { const regularNumber = vip ? 0 : 1; const vipNumber = vip ? 1 : 0; this.repo.save({ - queue_item_id: item_id, + queue_item_id: queueItem.item.item_queue_id ?? queueItem.item.id, date: start, regular: regularNumber, vip: vipNumber, From d413f1fa7b0a3379765b1c7d0936b4b17ed06d2d Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 8 Nov 2024 15:13:33 +0700 Subject: [PATCH 36/94] fix: QR queue and date booking --- src/modules/queue/data/services/ticket.service.ts | 10 +++++----- .../infrastructure/handlers/transaction.handler.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index 0ceb0fb..0aa8b8f 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -138,11 +138,11 @@ export class TicketDataService extends BaseDataService { where: { // order_id: In(order), id: ticket_id, - items: { - queue: { - status: In(['waiting']), - }, - }, + // items: { + // queue: { + // status: In(['waiting']), + // }, + // }, }, }); } diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index 10c2105..0249adf 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -43,7 +43,7 @@ export class QueueTransactionHandler code: invoice_code, customer: customer_name, phone: customer_phone, - date: queue_date, + date: queue_date * 1000, transaction_id: id, }; From c129a59d47d990184dce321a712ea7f01f4bfe92 Mon Sep 17 00:00:00 2001 From: shancheas Date: Mon, 11 Nov 2024 14:29:17 +0700 Subject: [PATCH 37/94] feat: merge item queue --- .../domain/entities/queue-item.entity.ts | 10 +++++ .../queue/customer-queue-detail.manager.ts | 20 ++++----- .../queue/customer-queue-item-list.manager.ts | 3 +- .../usecases/queue/customer-queue.manager.ts | 43 ++++++++++++++++--- .../data/models/transaction.model.ts | 3 +- .../data/services/transaction-data.service.ts | 3 +- 6 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/modules/queue/domain/entities/queue-item.entity.ts b/src/modules/queue/domain/entities/queue-item.entity.ts index f506ac4..7cbf761 100644 --- a/src/modules/queue/domain/entities/queue-item.entity.ts +++ b/src/modules/queue/domain/entities/queue-item.entity.ts @@ -1,6 +1,7 @@ import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity'; import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity'; import { QueueTicket } from './ticket.entity'; +import { QueueModel } from '../../data/models/queue.model'; export interface QueueItem extends BaseCoreEntity { ticket?: QueueTicket; @@ -8,3 +9,12 @@ export interface QueueItem extends BaseCoreEntity { item_id: string; qty: number; } + +export interface MergedItemQueue extends BaseCoreEntity { + id: string; + queue_item_id: string; + title: string; + image_url: string; + qty: number; + queues: QueueModel[]; +} diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts index c54975e..cd2faae 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts @@ -10,6 +10,7 @@ export class CustomerQueueDetailManager extends CustomerQueueManager { get data() { const queueCondition = new QueueCondition(this.queues); return this.tickets.map((ticket) => { + const queueItems = this.mergeQueueItems(ticket); return { id: ticket.id, code: ticket.code, @@ -22,19 +23,18 @@ export class CustomerQueueDetailManager extends CustomerQueueManager { total_used: this.totalUsedTickets(ticket), total_queue: this.totalQueueTickets(ticket), }, - items: ticket.items.map((item) => { - const queueItem = item.item.item_queue ?? item.item; + items: queueItems.map((item) => { return { - id: item.item_id, - item_queue_id: item.id, - title: queueItem.name, - image_url: item.item.image_url, + id: item.id, + item_queue_id: item.queue_item_id, + title: item.title, + image_url: item.image_url, summary: { total_tickets: item.qty, - total_used: this.totalUsedItems(item), - total_queue: this.totalQueueItems(item), + total_used: this.totalUsedItems(item.queues), + total_queue: this.totalQueueItems(item.queues), }, - queue: item.queue + queue: item.queues .sort((a, b) => b.time - a.time) .map((q) => { return { @@ -44,7 +44,7 @@ export class CustomerQueueDetailManager extends CustomerQueueManager { status: q.status, }; }), - queue_condition: queueCondition.condition(queueItem.id), + queue_condition: queueCondition.condition(item.queue_item_id), }; }), }; diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts index e12625d..7a2f9fc 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts @@ -12,10 +12,9 @@ export class CustomerQueueItemListManager extends CustomerQueueManager { } get data() { const tickets = this.tickets; - const ticketItems = {}; - const queueCondition = new QueueCondition(this.queues); + const ticketItems = {}; tickets.forEach((ticket) => { ticket.items.forEach((item) => { const item_id = item.item.item_queue?.id ?? item.item.id; diff --git a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts index 2faaa8a..889e472 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts @@ -1,7 +1,9 @@ import { QueueItemModel, + QueueModel, QueueTicketModel, } from '../../../data/models/queue.model'; +import { MergedItemQueue } from '../../entities/queue-item.entity'; import { toTime } from '../../helpers/time.helper'; export class CustomerQueueManager { @@ -22,32 +24,61 @@ export class CustomerQueueManager { return ticket.items.reduce((acc, item) => acc + item.qty, 0); } - totalUsedItems(item: QueueItemModel): number { - return item.queue + totalUsedItems(queues: QueueModel[]): number { + return queues .filter((q) => ['done', 'called'].includes(q.status)) .reduce((acc, item) => acc + item.qty, 0); } totalUsedTickets(ticket: QueueTicketModel): number { const tickets = ticket.items.map((item) => { - return this.totalUsedItems(item); + return this.totalUsedItems(item.queue); }); const reducer = (accumulator, currentValue) => accumulator + currentValue; return tickets.reduce(reducer, 0); } - totalQueueItems(item: QueueItemModel): number { - return item.queue + totalQueueItems(queues: QueueModel[]): number { + return queues .filter((q) => ['waiting'].includes(q.status)) .reduce((acc, item) => acc + item.qty, 0); } totalQueueTickets(ticket: QueueTicketModel): number { const tickets = ticket.items.map((item) => { - return this.totalQueueItems(item); + return this.totalQueueItems(item.queue); }); const reducer = (accumulator, currentValue) => accumulator + currentValue; return tickets.reduce(reducer, 0); } + + mergeQueueItems(ticket: QueueTicketModel): MergedItemQueue[] { + const ticketItems = {}; + ticket.items.forEach((item) => { + const item_id = item.item.item_queue?.id ?? item.item.id; + const currentItem = ticketItems[item_id]; + ticketItems[item_id] = currentItem ? [...currentItem, item] : [item]; + }); + + return Object.values(ticketItems).map((items) => { + const item = items[0]; + const queues: QueueModel[] = []; + + items.forEach((item) => { + queues.push(...item.queue); + }); + + const item_qty = items.reduce((acc, item) => acc + item.qty, 0); + const queueItem = item.item.item_queue ?? item.item; + return { + id: item.item_id, + queue_item_id: item.id, + title: queueItem.name, + image_url: item.item.image_url, + qty: item_qty, + queues, + }; + }); + } } diff --git a/src/modules/transaction/transaction/data/models/transaction.model.ts b/src/modules/transaction/transaction/data/models/transaction.model.ts index 97d312a..7b17489 100644 --- a/src/modules/transaction/transaction/data/models/transaction.model.ts +++ b/src/modules/transaction/transaction/data/models/transaction.model.ts @@ -7,7 +7,6 @@ import { 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'; @@ -239,7 +238,7 @@ export class TransactionModel onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - items: TransactionItemEntity[]; + items: TransactionItemModel[]; // relations to tax data @OneToMany(() => TransactionTaxModel, (model) => model.transaction, { diff --git a/src/modules/transaction/transaction/data/services/transaction-data.service.ts b/src/modules/transaction/transaction/data/services/transaction-data.service.ts index f13c3b7..40b8f6a 100644 --- a/src/modules/transaction/transaction/data/services/transaction-data.service.ts +++ b/src/modules/transaction/transaction/data/services/transaction-data.service.ts @@ -1,13 +1,12 @@ 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 { +export class TransactionDataService extends BaseDataService { constructor( @InjectRepository(TransactionModel, CONNECTION_NAME.DEFAULT) private repo: Repository, From 6c53610ec49f71a4b3ccdaec06f62c4950a0caeb Mon Sep 17 00:00:00 2001 From: shancheas Date: Mon, 11 Nov 2024 14:43:15 +0700 Subject: [PATCH 38/94] feat: vip auto enter queue --- .../handlers/transaction.handler.ts | 38 +++++++++++++++---- .../data/models/transaction-item.model.ts | 5 +++ .../data/models/transaction.model.ts | 14 ++++++- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index 0249adf..0281e4c 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -9,6 +9,7 @@ import { QueueOrder } from '../../domain/entities/order.entity'; import { QueueTicket } from '../../domain/entities/ticket.entity'; import { QueueItem } from '../../domain/entities/queue-item.entity'; import * as moment from 'moment'; +import { QueueOrchestrator } from '../../domain/queue.orchestrator'; @EventsHandler(TransactionChangeStatusEvent, TransactionCreateQueueEvent) export class QueueTransactionHandler @@ -17,6 +18,7 @@ export class QueueTransactionHandler constructor( private dataService: TransactionDataService, private queueService: TicketDataService, + private orchestrator: QueueOrchestrator, ) {} async handle(event: TransactionChangeStatusEvent) { @@ -31,7 +33,7 @@ export class QueueTransactionHandler where: { id: event.data.id, }, - relations: ['items'], + relations: ['customer_category', 'items', 'items.item'], }); const date = transaction.booking_date ?? transaction.invoice_date; @@ -47,16 +49,36 @@ export class QueueTransactionHandler transaction_id: id, }; - const items = transaction.items.map((item) => { - return { - item_id: item.item_id, - qty: item.qty, - }; - }); + const items = transaction.items + .filter((item) => item.item.use_queue) + .map((item) => { + return { + item_id: item.item_id, + qty: item.qty, + }; + }); const ticket: QueueTicket = { ...customerOrder, items }; const order: QueueOrder = { ...customerOrder, tickets: [ticket] }; - this.queueService.createQueueOrder(order); + const queueOrder = await this.queueService.createQueueOrder(order); + if (transaction.customer_category?.has_vip_pass) { + queueOrder.tickets.forEach((ticket) => { + const ticket_id = ticket.id; + ticket.items.forEach((item) => { + const item_id = item.item_id; + + for (let i = 0; i < item.qty; i++) { + const payload = { + item_id, + ticket_id, + qty: 1, + }; + + this.orchestrator.create(payload); + } + }); + }); + } } } 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 034bb47..8248388 100644 --- a/src/modules/transaction/transaction/data/models/transaction-item.model.ts +++ b/src/modules/transaction/transaction/data/models/transaction-item.model.ts @@ -8,6 +8,7 @@ import { import { TransactionModel } from './transaction.model'; import { RefundItemModel } from 'src/modules/transaction/refund/data/models/refund-item.model'; import { TransactionTaxEntity } from '../../domain/entities/transaction-tax.entity'; +import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; @Entity(TABLE_NAME.TRANSACTION_ITEM) export class TransactionItemModel @@ -126,6 +127,10 @@ export class TransactionItemModel onUpdate: 'CASCADE', }) item_taxes: TransactionItemTaxModel[]; + + @ManyToOne(() => ItemModel) + @JoinColumn({ name: 'item_id' }) + item: ItemModel; } @Entity(TABLE_NAME.TRANSACTION_ITEM_BREAKDOWN) diff --git a/src/modules/transaction/transaction/data/models/transaction.model.ts b/src/modules/transaction/transaction/data/models/transaction.model.ts index 7b17489..8ca6aa2 100644 --- a/src/modules/transaction/transaction/data/models/transaction.model.ts +++ b/src/modules/transaction/transaction/data/models/transaction.model.ts @@ -1,6 +1,13 @@ import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { TransactionEntity } from '../../domain/entities/transaction.entity'; -import { Column, Entity, OneToMany, OneToOne } from 'typeorm'; +import { + Column, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + OneToOne, +} from 'typeorm'; import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; import { TransactionType, @@ -12,6 +19,7 @@ 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'; import { TransactionDemographyModel } from './transaction-demography.model'; +import { VipCategoryModel } from 'src/modules/transaction/vip-category/data/models/vip-category.model'; @Entity(TABLE_NAME.TRANSACTION) export class TransactionModel @@ -51,6 +59,10 @@ export class TransactionModel @Column('varchar', { name: 'season_period_type_name', nullable: true }) season_period_type_name: string; + @ManyToOne(() => TransactionDemographyModel) + @JoinColumn({ name: 'customer_category_id' }) + customer_category: VipCategoryModel; + // customer info @Column('enum', { name: 'customer_type', From 8e7e43b09ddd576fa21008a2f554a2205070dbc6 Mon Sep 17 00:00:00 2001 From: shancheas Date: Mon, 11 Nov 2024 16:34:58 +0700 Subject: [PATCH 39/94] fix: vip from booking generate queue --- .../queue/infrastructure/handlers/transaction.handler.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index 0281e4c..47110ce 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -10,6 +10,7 @@ import { QueueTicket } from '../../domain/entities/ticket.entity'; import { QueueItem } from '../../domain/entities/queue-item.entity'; import * as moment from 'moment'; import { QueueOrchestrator } from '../../domain/queue.orchestrator'; +import { TransactionUserType } from 'src/modules/transaction/transaction/constants'; @EventsHandler(TransactionChangeStatusEvent, TransactionCreateQueueEvent) export class QueueTransactionHandler @@ -62,7 +63,10 @@ export class QueueTransactionHandler const order: QueueOrder = { ...customerOrder, tickets: [ticket] }; const queueOrder = await this.queueService.createQueueOrder(order); - if (transaction.customer_category?.has_vip_pass) { + if ( + transaction.customer_category?.has_vip_pass || + transaction.customer_type === TransactionUserType.VIP + ) { queueOrder.tickets.forEach((ticket) => { const ticket_id = ticket.id; ticket.items.forEach((item) => { From ad3e3593fdfdcc2de9948b2da9238b9043d7478d Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 13 Nov 2024 10:00:37 +0700 Subject: [PATCH 40/94] feat: add gate log --- .../domain/entity/gate-request.entity.ts | 6 +++++ .../gates/infrastructure/dto/logs.dto.ts | 26 +++++++++++++++++++ .../gates/infrastructure/gate.controller.ts | 8 ++++++ 3 files changed, 40 insertions(+) create mode 100644 src/modules/gates/infrastructure/dto/logs.dto.ts diff --git a/src/modules/gates/domain/entity/gate-request.entity.ts b/src/modules/gates/domain/entity/gate-request.entity.ts index 299e086..37b62c1 100644 --- a/src/modules/gates/domain/entity/gate-request.entity.ts +++ b/src/modules/gates/domain/entity/gate-request.entity.ts @@ -3,3 +3,9 @@ export interface GateScanEntity { type: string; uuid: string; } + +export interface GateLogEntity { + gate_id: string; + code: string; + error: any; +} diff --git a/src/modules/gates/infrastructure/dto/logs.dto.ts b/src/modules/gates/infrastructure/dto/logs.dto.ts new file mode 100644 index 0000000..011e757 --- /dev/null +++ b/src/modules/gates/infrastructure/dto/logs.dto.ts @@ -0,0 +1,26 @@ +import { IsNotEmpty, IsString } from 'class-validator'; +import { GateLogEntity } from '../../domain/entity/gate-request.entity'; +import { ApiProperty } from '@nestjs/swagger'; + +export class GateLogDto implements GateLogEntity { + @ApiProperty({ + type: String, + required: true, + }) + @IsNotEmpty() + @IsString() + gate_id: string; + + @ApiProperty({ + type: String, + required: true, + }) + @IsNotEmpty() + @IsString() + code: string; + + @ApiProperty({ + required: false, + }) + error: any; +} diff --git a/src/modules/gates/infrastructure/gate.controller.ts b/src/modules/gates/infrastructure/gate.controller.ts index 74fa973..4da847b 100644 --- a/src/modules/gates/infrastructure/gate.controller.ts +++ b/src/modules/gates/infrastructure/gate.controller.ts @@ -8,6 +8,7 @@ import { GateResponseEntity, } from '../domain/entity/gate-response.entity'; import { Gate } from 'src/core/response'; +import { GateLogDto } from './dto/logs.dto'; const masterGates = [ '319b6d3e-b661-4d19-8695-0dd6fb76465e', @@ -72,6 +73,13 @@ export class GateController { return responseValue; } + @Post('logs') + async logs(@Body() data: GateLogDto): Promise { + console.log(data); + + return { code: 1, message: 'success' }; + } + @Get(':id/master') async detail(@Param('id') id: string): Promise { if (id == '1') return { codes: masterGates }; From 6807d00cbeb740ad1c8ad8bdc9b26d29b69a4d1a Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 13 Nov 2024 14:29:35 +0700 Subject: [PATCH 41/94] fix: add empty user to base --- src/core/modules/domain/usecase/base.manager.ts | 2 +- src/core/modules/domain/usecase/managers/base-create.manager.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/modules/domain/usecase/base.manager.ts b/src/core/modules/domain/usecase/base.manager.ts index 0afe79f..f47f3c2 100644 --- a/src/core/modules/domain/usecase/base.manager.ts +++ b/src/core/modules/domain/usecase/base.manager.ts @@ -32,7 +32,7 @@ export abstract class BaseManager { setUser() { try { - this.user = this.userProvider?.user; + this.user = this.userProvider?.user ?? BLANK_USER; } catch (error) { this.user = BLANK_USER; } diff --git a/src/core/modules/domain/usecase/managers/base-create.manager.ts b/src/core/modules/domain/usecase/managers/base-create.manager.ts index 79bded9..309be3b 100644 --- a/src/core/modules/domain/usecase/managers/base-create.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-create.manager.ts @@ -71,7 +71,7 @@ export abstract class BaseCreateManager extends BaseManager { } async publishEvents() { - this.eventBus.publish( + this.eventBus?.publish( new RecordLog({ id: this.result['id'], old: null, From 1878d03c0fa005dab0d2b9711dc85c0ebd780986 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 13 Nov 2024 14:30:10 +0700 Subject: [PATCH 42/94] feat: vip auto generate queue --- .../couch/domain/managers/booking.handler.ts | 2 -- .../queue/domain/queue.orchestrator.ts | 4 ++- .../queue/customer-queue-item-list.manager.ts | 2 +- .../queue/customer-queue-item.manager.ts | 4 ++- .../controllers/queue.controller.ts | 7 ++++ .../handlers/transaction.handler.ts | 32 +++++++++++++++---- src/modules/queue/queue.module.ts | 4 +-- .../data/models/transaction.model.ts | 2 +- 8 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/modules/configuration/couch/domain/managers/booking.handler.ts b/src/modules/configuration/couch/domain/managers/booking.handler.ts index 200afed..4e48be0 100644 --- a/src/modules/configuration/couch/domain/managers/booking.handler.ts +++ b/src/modules/configuration/couch/domain/managers/booking.handler.ts @@ -97,9 +97,7 @@ export class ChangeStatusBookingHandler }, relations: ['items', 'items.bundling_items'], }); - console.log('change status', { dataID, couchData, booking }); mappingTransaction(booking); - console.log('after mapping'); if (!couchData) { console.log('save data to couch'); diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index 98050f2..b1621db 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -19,11 +19,13 @@ import { SplitQueueManager } from './usecases/split-queue.manager'; import { LoginQueueDto } from '../infrastructure/controllers/dto/login-queue.dto'; import * as moment from 'moment'; import { CustomerQueueTicketSummaryManager } from './usecases/queue/customer-queue-ticket.manager'; +import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; @Injectable() export class QueueOrchestrator { constructor( - private readonly dataService: TicketDataService, + public readonly dataService: TicketDataService, + public readonly transactionService: TransactionDataService, private readonly queueService: QueueService, private readonly queueOrderService: QueueOrderService, private readonly registerQueueManager: RegisterQueueManager, diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts index 7a2f9fc..b385e68 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts @@ -28,7 +28,7 @@ export class CustomerQueueItemListManager extends CustomerQueueManager { const item_qty = items.reduce((acc, item) => acc + item.qty, 0); const queueItem = item.item.item_queue ?? item.item; return { - id: item.item_id, + id: queueItem.id, queue_item_id: item.id, title: queueItem.name, image_url: item.item.image_url, diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts index 2e85a5d..a88d41b 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts @@ -3,7 +3,9 @@ import { CustomerQueueManager } from './customer-queue.manager'; export class CustomerQueueItemManager extends CustomerQueueManager { get data() { return this.tickets.map((ticket) => { + const queueItems = this.mergeQueueItems(ticket); const item = ticket.items[0]; + return { id: ticket.id, code: ticket.code, @@ -17,7 +19,7 @@ export class CustomerQueueItemManager extends CustomerQueueManager { total_used: this.totalUsedTickets(ticket), total_queue: this.totalQueueTickets(ticket), }, - queue: item.queue + queue: queueItems[0].queues .sort((a, b) => b.time - a.time) .map((q) => { return { diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index 982c994..b4c2637 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -10,6 +10,7 @@ import { Queue } from '../../domain/entities/queue.entity'; import { RegisterQueueDto } from './dto/register-queue.dto'; import { SplitQueueDto } from './dto/split-queue.dto'; import { LoginQueueDto } from './dto/login-queue.dto'; +import { LoginReceiptDto } from './dto/login-receipt.dto'; @ApiTags(`Queue`) @Controller(`v1/${MODULE_NAME.QUEUE}`) @@ -33,6 +34,12 @@ export class QueueController { return await this.orchestrator.loginPhone(data); } + @Post('login/receipt') + async loginReceipt(@Body() { id }: LoginReceiptDto): Promise { + console.log(id); + return await this.orchestrator.loginCustomer(id); + } + @Get('login/:id') async loginCustomer(@Param('id') id: string): Promise { return await this.orchestrator.loginCustomer(id); diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index 47110ce..755869e 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -9,17 +9,22 @@ import { QueueOrder } from '../../domain/entities/order.entity'; import { QueueTicket } from '../../domain/entities/ticket.entity'; import { QueueItem } from '../../domain/entities/queue-item.entity'; import * as moment from 'moment'; -import { QueueOrchestrator } from '../../domain/queue.orchestrator'; import { TransactionUserType } from 'src/modules/transaction/transaction/constants'; +import { RegisterQueueManager } from '../../domain/usecases/register-queue.manager'; +import { RegisterQueueDto } from '../controllers/dto/register-queue.dto'; +import { QueueService } from '../../data/services/queue.service'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { QueueBucketReadService } from '../../data/services/queue-bucket'; @EventsHandler(TransactionChangeStatusEvent, TransactionCreateQueueEvent) export class QueueTransactionHandler implements IEventHandler { constructor( - private dataService: TransactionDataService, - private queueService: TicketDataService, - private orchestrator: QueueOrchestrator, + private readonly dataService: TransactionDataService, + private readonly ticketService: TicketDataService, + private readonly queueService: QueueService, + private readonly bucketService: QueueBucketReadService, ) {} async handle(event: TransactionChangeStatusEvent) { @@ -62,7 +67,7 @@ export class QueueTransactionHandler const ticket: QueueTicket = { ...customerOrder, items }; const order: QueueOrder = { ...customerOrder, tickets: [ticket] }; - const queueOrder = await this.queueService.createQueueOrder(order); + const queueOrder = await this.ticketService.createQueueOrder(order); if ( transaction.customer_category?.has_vip_pass || transaction.customer_type === TransactionUserType.VIP @@ -79,10 +84,25 @@ export class QueueTransactionHandler qty: 1, }; - this.orchestrator.create(payload); + this.create(payload); } }); }); } } + + async create(data: RegisterQueueDto): Promise { + const queue = await this.queueService.getTicketItems( + data.ticket_id, + data.item_id, + ); + const queueRequest: any = { + qty: data.qty, + item_id: queue.id, + }; + const registerQueueManager = new RegisterQueueManager(this.bucketService); + registerQueueManager.setData(queueRequest); + registerQueueManager.setService(this.queueService, TABLE_NAME.QUEUE); + await registerQueueManager.execute(); + } } diff --git a/src/modules/queue/queue.module.ts b/src/modules/queue/queue.module.ts index e9eb803..8dc43c9 100644 --- a/src/modules/queue/queue.module.ts +++ b/src/modules/queue/queue.module.ts @@ -51,11 +51,11 @@ import { SplitQueueManager } from './domain/usecases/split-queue.manager'; ], controllers: [QueueController, QueueAdminController], providers: [ - QueueTransactionHandler, - QueueOrchestrator, QueueAdminOrchestrator, + QueueTransactionHandler, + TransactionDataService, TicketDataService, QueueDataService, diff --git a/src/modules/transaction/transaction/data/models/transaction.model.ts b/src/modules/transaction/transaction/data/models/transaction.model.ts index 8ca6aa2..053503c 100644 --- a/src/modules/transaction/transaction/data/models/transaction.model.ts +++ b/src/modules/transaction/transaction/data/models/transaction.model.ts @@ -59,7 +59,7 @@ export class TransactionModel @Column('varchar', { name: 'season_period_type_name', nullable: true }) season_period_type_name: string; - @ManyToOne(() => TransactionDemographyModel) + @ManyToOne(() => VipCategoryModel) @JoinColumn({ name: 'customer_category_id' }) customer_category: VipCategoryModel; From 577f8ea9ea22e191a29d24c2a20f8b94b600b752 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 13 Nov 2024 14:30:23 +0700 Subject: [PATCH 43/94] feat: login by receipt --- ...26542-add-transaction-and-item-relation.ts | 50 +++++++++++++++++++ .../controllers/dto/login-receipt.dto.ts | 9 ++++ 2 files changed, 59 insertions(+) create mode 100644 src/database/migrations/1731383726542-add-transaction-and-item-relation.ts create mode 100644 src/modules/queue/infrastructure/controllers/dto/login-receipt.dto.ts diff --git a/src/database/migrations/1731383726542-add-transaction-and-item-relation.ts b/src/database/migrations/1731383726542-add-transaction-and-item-relation.ts new file mode 100644 index 0000000..4f9e288 --- /dev/null +++ b/src/database/migrations/1731383726542-add-transaction-and-item-relation.ts @@ -0,0 +1,50 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTransactionAndItemRelation1731383726542 + implements MigrationInterface +{ + name = 'AddTransactionAndItemRelation1731383726542'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transaction_items" DROP COLUMN "item_id"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" ADD "item_id" uuid`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "customer_category_id"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "customer_category_id" uuid`, + ); + + await queryRunner.query( + `ALTER TABLE "transaction_items" ADD CONSTRAINT "FK_edb934ab033f847e3f7ed4fc0fc" FOREIGN KEY ("item_id") REFERENCES "items"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD CONSTRAINT "FK_08dc8138714894a66e94820766d" FOREIGN KEY ("customer_category_id") REFERENCES "vip_categories"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP CONSTRAINT "FK_08dc8138714894a66e94820766d"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" DROP CONSTRAINT "FK_edb934ab033f847e3f7ed4fc0fc"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "customer_category_id"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "customer_category_id" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" DROP COLUMN "item_id"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" ADD "item_id" character varying`, + ); + } +} diff --git a/src/modules/queue/infrastructure/controllers/dto/login-receipt.dto.ts b/src/modules/queue/infrastructure/controllers/dto/login-receipt.dto.ts new file mode 100644 index 0000000..298fc1f --- /dev/null +++ b/src/modules/queue/infrastructure/controllers/dto/login-receipt.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class LoginReceiptDto { + @ApiProperty({ name: 'id', required: true }) + @IsString() + @IsNotEmpty() + id: string; +} From 2bcd7a34fb9d3f62fa7760e74812cd7c1b8d4d76 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 13 Nov 2024 15:48:30 +0700 Subject: [PATCH 44/94] feat: create summary ticket function --- .../usecases/queue/customer-queue-detail.manager.ts | 7 +------ .../usecases/queue/customer-queue-item.manager.ts | 7 +------ .../queue/customer-queue-summary.manager.ts | 7 +------ .../usecases/queue/customer-queue-ticket.manager.ts | 7 +------ .../domain/usecases/queue/customer-queue.manager.ts | 13 +++++++++++++ 5 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts index cd2faae..c656de8 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-detail.manager.ts @@ -17,12 +17,7 @@ export class CustomerQueueDetailManager extends CustomerQueueManager { customer: ticket.customer, phone: ticket.phone, date: ticket.date, - summary: { - total_activities: this.totalActivities(ticket), - total_tickets: this.totalTickets(ticket), - total_used: this.totalUsedTickets(ticket), - total_queue: this.totalQueueTickets(ticket), - }, + summary: this.summaryTicket(ticket), items: queueItems.map((item) => { return { id: item.id, diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts index a88d41b..68e9682 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-item.manager.ts @@ -13,12 +13,7 @@ export class CustomerQueueItemManager extends CustomerQueueManager { phone: ticket.phone, date: ticket.date, image_url: item.item.image_url, - summary: { - total_activities: this.totalActivities(ticket), - total_tickets: this.totalTickets(ticket), - total_used: this.totalUsedTickets(ticket), - total_queue: this.totalQueueTickets(ticket), - }, + summary: this.summaryTicket(ticket), queue: queueItems[0].queues .sort((a, b) => b.time - a.time) .map((q) => { diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-summary.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-summary.manager.ts index e56ffee..342baf3 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-summary.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-summary.manager.ts @@ -9,12 +9,7 @@ export class CustomerQueueSummaryManager extends CustomerQueueManager { customer: ticket.customer, phone: ticket.phone, date: ticket.date, - summary: { - total_activities: this.totalActivities(ticket), - total_tickets: this.totalTickets(ticket), - total_used: this.totalUsedTickets(ticket), - total_queue: this.totalQueueTickets(ticket), - }, + summary: this.summaryTicket(ticket), }; }); } diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-ticket.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-ticket.manager.ts index d2bbf14..d38c691 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-ticket.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-ticket.manager.ts @@ -9,12 +9,7 @@ export class CustomerQueueTicketSummaryManager extends CustomerQueueManager { customer: ticket.customer, phone: ticket.phone, date: ticket.date, - summary: { - total_activities: this.totalActivities(ticket), - total_tickets: this.totalTickets(ticket), - total_used: this.totalUsedTickets(ticket), - total_queue: this.totalQueueTickets(ticket), - }, + summary: this.summaryTicket(ticket), }; }); diff --git a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts index 889e472..128db18 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts @@ -53,6 +53,19 @@ export class CustomerQueueManager { return tickets.reduce(reducer, 0); } + summaryTicket(ticket: QueueTicketModel): any { + const total_tickets = this.totalTickets(ticket); + const total_used = this.totalUsedTickets(ticket); + const total_queue = this.totalQueueTickets(ticket); + return { + total_activities: this.totalActivities(ticket), + total_tickets, + total_used, + total_queue, + remaining: total_tickets - total_queue, + }; + } + mergeQueueItems(ticket: QueueTicketModel): MergedItemQueue[] { const ticketItems = {}; ticket.items.forEach((item) => { From af9818d44c49f5c3f31c547a7893149cd6f0291d Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 13 Nov 2024 15:48:54 +0700 Subject: [PATCH 45/94] feat: cancel order, delete queue --- .../queue/data/services/queue-bucket.ts | 2 +- .../queue/data/services/ticket.service.ts | 23 +++++++++++++++++++ .../handlers/cancel-transaction.handler.ts | 21 +++++++++++++++++ src/modules/queue/queue.module.ts | 2 ++ .../usecases/calculator/price.calculator.ts | 2 +- 5 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/modules/queue/infrastructure/handlers/cancel-transaction.handler.ts diff --git a/src/modules/queue/data/services/queue-bucket.ts b/src/modules/queue/data/services/queue-bucket.ts index c261739..0042c4a 100644 --- a/src/modules/queue/data/services/queue-bucket.ts +++ b/src/modules/queue/data/services/queue-bucket.ts @@ -41,7 +41,7 @@ export class QueueBucketReadService extends BaseReadService { if (!queue) { const regularNumber = vip ? 0 : 1; const vipNumber = vip ? 1 : 0; - this.repo.save({ + await this.repo.save({ queue_item_id: queueItem.item.item_queue_id ?? queueItem.item.id, date: start, regular: regularNumber, diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index 0aa8b8f..7d85aac 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -70,6 +70,29 @@ export class TicketDataService extends BaseDataService { return [order]; } + async transactions(transaction_id: string): Promise { + const order = await this.order.findOneOrFail({ + where: { + transaction_id, + }, + }); + + if (order.transaction_id != null) { + return this.order.find({ + where: { + code: order.code, + }, + }); + } + + return [order]; + } + + async deleteQueue(transaction_id: string): Promise { + const transactions = await this.transactions(transaction_id); + await this.order.remove(transactions); + } + async orderIds(order_id: string): Promise { const orders = await this.orders(order_id); diff --git a/src/modules/queue/infrastructure/handlers/cancel-transaction.handler.ts b/src/modules/queue/infrastructure/handlers/cancel-transaction.handler.ts new file mode 100644 index 0000000..84505cb --- /dev/null +++ b/src/modules/queue/infrastructure/handlers/cancel-transaction.handler.ts @@ -0,0 +1,21 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event'; +import { TicketDataService } from '../../data/services/ticket.service'; + +@EventsHandler(TransactionChangeStatusEvent) +export class QueueTransactionCancelHandler + implements IEventHandler +{ + constructor(private queueService: TicketDataService) {} + + async handle(event: TransactionChangeStatusEvent) { + const process_data = event.data.data; + + /** + * If data still in process (not settled) then don't create the queue order + */ + if (!['cancel', 'rejected'].includes(process_data?.status)) return; + + this.queueService.deleteQueue(event.data.id); + } +} diff --git a/src/modules/queue/queue.module.ts b/src/modules/queue/queue.module.ts index 8dc43c9..d6dee2a 100644 --- a/src/modules/queue/queue.module.ts +++ b/src/modules/queue/queue.module.ts @@ -31,6 +31,7 @@ import { RegisterQueueManager } from './domain/usecases/register-queue.manager'; import { QueueBucketModel } from './data/models/queue-bucket.model'; import { QueueBucketReadService } from './data/services/queue-bucket'; import { SplitQueueManager } from './domain/usecases/split-queue.manager'; +import { QueueTransactionCancelHandler } from './infrastructure/handlers/cancel-transaction.handler'; @Module({ imports: [ @@ -55,6 +56,7 @@ import { SplitQueueManager } from './domain/usecases/split-queue.manager'; QueueAdminOrchestrator, QueueTransactionHandler, + QueueTransactionCancelHandler, TransactionDataService, TicketDataService, diff --git a/src/modules/transaction/transaction/domain/usecases/calculator/price.calculator.ts b/src/modules/transaction/transaction/domain/usecases/calculator/price.calculator.ts index 72b8ce8..1148789 100644 --- a/src/modules/transaction/transaction/domain/usecases/calculator/price.calculator.ts +++ b/src/modules/transaction/transaction/domain/usecases/calculator/price.calculator.ts @@ -133,7 +133,7 @@ export class PriceCalculator { calledVariable = []; } catch (error) { calledVariable.push(valueFor); - console.log(error); + console.log(error.message); } values[valueFor] = result; From 9805b9903a5f372c9f67b9dadf32a1329a5bb5d6 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 13 Nov 2024 16:49:05 +0700 Subject: [PATCH 46/94] feat: add pos item ticket --- .../queue/data/services/ticket.service.ts | 14 +++++++++ .../queue/domain/queue.orchestrator.ts | 7 +++++ .../queue/customer-queue-pos-item.manager.ts | 30 +++++++++++++++++++ .../controllers/queue.controller.ts | 5 ++++ 4 files changed, 56 insertions(+) create mode 100644 src/modules/queue/domain/usecases/queue/customer-queue-pos-item.manager.ts diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index 7d85aac..e14150d 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -117,6 +117,20 @@ export class TicketDataService extends BaseDataService { }); } + async queuePosTickets(order_id: string): Promise { + return this.repo.find({ + relations: [ + 'items', + 'items.queue', + 'items.item', + 'items.item.item_queue', + ], + where: { + order_id, + }, + }); + } + async queueTickets(order_id: string): Promise { const order = await this.orderIds(order_id); return this.repo.find({ diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index b1621db..660d5e2 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -20,6 +20,7 @@ import { LoginQueueDto } from '../infrastructure/controllers/dto/login-queue.dto import * as moment from 'moment'; import { CustomerQueueTicketSummaryManager } from './usecases/queue/customer-queue-ticket.manager'; import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; +import { CustomerQueuePosItemManager } from './usecases/queue/customer-queue-pos-item.manager'; @Injectable() export class QueueOrchestrator { @@ -85,6 +86,12 @@ export class QueueOrchestrator { return this.splitQueueManager.getResult(); } + async queuePOSTickets(order_id: string): Promise { + const tickets = await this.dataService.queuePosTickets(order_id); + const manager = new CustomerQueuePosItemManager(tickets); + return manager.data; + } + async queueTickets(order_id: string): Promise { const tickets = await this.dataService.queueTickets(order_id); const manager = new CustomerQueueSummaryManager(tickets); diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-pos-item.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-pos-item.manager.ts new file mode 100644 index 0000000..9629493 --- /dev/null +++ b/src/modules/queue/domain/usecases/queue/customer-queue-pos-item.manager.ts @@ -0,0 +1,30 @@ +import { CustomerQueueManager } from './customer-queue.manager'; + +export class CustomerQueuePosItemManager extends CustomerQueueManager { + get data() { + const ticket = this.tickets[0]; + const queueItems = this.mergeQueueItems(ticket); + return { + id: ticket.id, + code: ticket.code, + customer: ticket.customer, + phone: ticket.phone, + date: ticket.date, + summary: this.summaryTicket(ticket), + items: queueItems.map((item) => { + return { + id: item.id, + item_queue_id: item.queue_item_id, + title: item.title, + image_url: item.image_url, + summary: { + total_tickets: item.qty, + total_used: this.totalUsedItems(item.queues), + total_queue: this.totalQueueItems(item.queues), + remaining: item.qty - this.totalQueueItems(item.queues), + }, + }; + }), + }; + } +} diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index b4c2637..65572de 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -71,6 +71,11 @@ export class QueueController { return await this.orchestrator.queueItems(id); } + @Get(':id/pos-items') + async queuePosItems(@Param('id') id: string): Promise { + return await this.orchestrator.queuePOSTickets(id); + } + @Get(':id/queue') async queueOrderItems(@Param('id') id: string): Promise { return await this.orchestrator.queueOrderItems(id); From 94696e765d90b05c8cb5711b10281d10eae3770d Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 13 Nov 2024 18:17:53 +0700 Subject: [PATCH 47/94] fix: request ticket item --- src/modules/queue/data/services/queue.service.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index b015301..7efe003 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -50,10 +50,17 @@ export class QueueService extends BaseDataService { async getTicketItems(ticket_id: string, item_id: string) { return this.item.findOneOrFail({ - where: { - ticket_id, - item_id, - }, + relations: ['item'], + where: [ + { + ticket_id, + item_id, + }, + { + ticket_id, + item: { item_queue_id: item_id }, + }, + ], }); } From e9535749d43b331c748f83b0701c81add3f65673 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 13 Nov 2024 18:18:04 +0700 Subject: [PATCH 48/94] fix: vip code prefix --- .../queue/domain/usecases/register-queue.manager.ts | 12 +++++++++--- .../infrastructure/handlers/transaction.handler.ts | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/modules/queue/domain/usecases/register-queue.manager.ts b/src/modules/queue/domain/usecases/register-queue.manager.ts index 6ecd67f..29d4537 100644 --- a/src/modules/queue/domain/usecases/register-queue.manager.ts +++ b/src/modules/queue/domain/usecases/register-queue.manager.ts @@ -22,12 +22,18 @@ export class RegisterQueueManager extends BaseCreateManager { } async beforeProcess(): Promise { - const queueNumber = await this.bucketService.getQueue(this.data.item_id); - const code = `A${padCode(queueNumber)}`; + const vip = this.data.vip ?? false; + const queueNumber = await this.bucketService.getQueue( + this.data.item_id, + vip, + ); + const prefix = vip ? 'B' : 'A'; + const code = `${prefix}${padCode(queueNumber)}`; + Object.assign(this.data, { status: STATUS.WAITING, time: new Date().getTime(), - vip: false, + vip, code, }); return; diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index 755869e..61c635e 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -99,6 +99,7 @@ export class QueueTransactionHandler const queueRequest: any = { qty: data.qty, item_id: queue.id, + vip: true, }; const registerQueueManager = new RegisterQueueManager(this.bucketService); registerQueueManager.setData(queueRequest); From 2b132c53a840e7c3052d5e477589e2f1e47a7e78 Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 14 Nov 2024 10:06:51 +0700 Subject: [PATCH 49/94] feat: add average item queue time --- .../1731498661938-add-item-queue-to-queue.ts | 15 +++++++++++++++ src/modules/queue/data/models/queue.model.ts | 3 +++ .../queue/data/services/queue.service.ts | 17 ++++++++++++++++- .../queue/data/services/ticket.service.ts | 3 +++ .../queue/domain/queue.orchestrator.ts | 18 ++++++++++++++++++ .../formula/queue-condition.formula.ts | 11 +++++++---- .../domain/usecases/register-queue.manager.ts | 19 ++++++++++++------- 7 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 src/database/migrations/1731498661938-add-item-queue-to-queue.ts diff --git a/src/database/migrations/1731498661938-add-item-queue-to-queue.ts b/src/database/migrations/1731498661938-add-item-queue-to-queue.ts new file mode 100644 index 0000000..a1151af --- /dev/null +++ b/src/database/migrations/1731498661938-add-item-queue-to-queue.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddItemQueueToQueue1731498661938 implements MigrationInterface { + name = 'AddItemQueueToQueue1731498661938'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "queues" ADD "item_queue_id" character varying`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "queues" DROP COLUMN "item_queue_id"`); + } +} diff --git a/src/modules/queue/data/models/queue.model.ts b/src/modules/queue/data/models/queue.model.ts index 04ba717..ec2083f 100644 --- a/src/modules/queue/data/models/queue.model.ts +++ b/src/modules/queue/data/models/queue.model.ts @@ -131,6 +131,9 @@ export class QueueModel extends BaseModel implements Queue { @Column('varchar') item_id: string; + @Column('varchar', { nullable: true }) + item_queue_id: string; + @Column('int') qty: number; } diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index 7efe003..fb34c5d 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; -import { DataSource, In, Repository } from 'typeorm'; +import { Between, DataSource, In, Repository } from 'typeorm'; import { QueueItemModel, QueueModel, @@ -10,6 +10,7 @@ import { } from '../models/queue.model'; import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import * as moment from 'moment'; @Injectable() export class QueueDataService extends BaseReadService { @@ -48,6 +49,20 @@ export class QueueService extends BaseDataService { super(repo); } + async queues(ids: string[]) { + const start = moment().startOf('day').valueOf(); + const end = moment().endOf('day').valueOf(); + return this.repo.find({ + where: { + item_queue_id: In(ids), + time: Between(start, end), + }, + order: { + time: 'ASC', + }, + }); + } + async getTicketItems(ticket_id: string, item_id: string) { return this.item.findOneOrFail({ relations: ['item'], diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index e14150d..36255de 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -133,6 +133,8 @@ export class TicketDataService extends BaseDataService { async queueTickets(order_id: string): Promise { const order = await this.orderIds(order_id); + const start = moment().startOf('day').valueOf(); + const end = moment().endOf('day').valueOf(); return this.repo.find({ relations: [ 'items', @@ -142,6 +144,7 @@ export class TicketDataService extends BaseDataService { ], where: { order_id: In(order), + date: Between(start, end), }, }); } diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index 660d5e2..87796d8 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -21,6 +21,7 @@ import * as moment from 'moment'; import { CustomerQueueTicketSummaryManager } from './usecases/queue/customer-queue-ticket.manager'; import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; import { CustomerQueuePosItemManager } from './usecases/queue/customer-queue-pos-item.manager'; +import { QueueTicketModel } from '../data/models/queue.model'; @Injectable() export class QueueOrchestrator { @@ -103,7 +104,11 @@ export class QueueOrchestrator { order_id, ticket_id, ); + + const queueItemIds = this.getQueueItemFromTickets(tickets); + const queues = await this.queueService.queues(queueItemIds); const manager = new CustomerQueueDetailManager(tickets); + manager.currentQueues(queues); return manager.data; } @@ -125,7 +130,10 @@ export class QueueOrchestrator { async queueItems(order_id: string): Promise { const tickets = await this.dataService.queueTickets(order_id); + const queueItemIds = this.getQueueItemFromTickets(tickets); + const queues = await this.queueService.queues(queueItemIds); const manager = new CustomerQueueItemListManager(tickets); + manager.currentQueues(queues); return manager.data; } @@ -134,4 +142,14 @@ export class QueueOrchestrator { const manager = new CustomerQueueListManager(tickets); return manager.data; } + + getQueueItemFromTickets(tickets: QueueTicketModel[]) { + const queueItemIds = tickets.map((ticket) => { + return ticket.items.map((item) => { + return item.item.item_queue_id; + }); + }); + + return queueItemIds.flat(); + } } diff --git a/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts b/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts index 358f548..bf64da0 100644 --- a/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts +++ b/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts @@ -6,7 +6,7 @@ export class QueueCondition { private ticketItems = {}; constructor(readonly items: QueueModel[]) { items.forEach((item) => { - const item_id = item.item.item.item_queue?.id ?? item.item.item.id; + const item_id = item.item_queue_id; const currentItem = this.ticketItems[item_id]; this.ticketItems[item_id] = currentItem ? [...currentItem, item] : [item]; }); @@ -14,14 +14,17 @@ export class QueueCondition { condition(item_id: string) { const queues: QueueModel[] = this.ticketItems[item_id] ?? []; - const time = queues[0]?.time; - const nearest = time ? toTime(time) : 0; + const time = queues[0]; + const nearest = time ? toTime(time.time) : 0; + + const last = [...queues].pop(); + const lastTime = last ? toTime(last.time) : 0; return { available: queues.length == 0, average: this.averageTime(queues), nearest: nearest, crowded_level: queues.length, - available_time: nearest, + available_time: lastTime, }; } diff --git a/src/modules/queue/domain/usecases/register-queue.manager.ts b/src/modules/queue/domain/usecases/register-queue.manager.ts index 29d4537..10176bd 100644 --- a/src/modules/queue/domain/usecases/register-queue.manager.ts +++ b/src/modules/queue/domain/usecases/register-queue.manager.ts @@ -6,9 +6,10 @@ import { } from 'src/core/strings/constants/interface.constants'; import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; import { STATUS } from 'src/core/strings/constants/base.constants'; -import { QueueModel } from '../../data/models/queue.model'; +import { QueueItemModel, QueueModel } from '../../data/models/queue.model'; import { padCode } from 'src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper'; import { QueueBucketReadService } from '../../data/services/queue-bucket'; +import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; @Injectable() export class RegisterQueueManager extends BaseCreateManager { @@ -17,12 +18,13 @@ export class RegisterQueueManager extends BaseCreateManager { } async averageTime(): Promise { - const item = await this.getItem(); - return item.item.item.play_estimation; + const item = await this.getItemMaster(); + return item.play_estimation; } async beforeProcess(): Promise { const vip = this.data.vip ?? false; + const item = await this.getItemMaster(); const queueNumber = await this.bucketService.getQueue( this.data.item_id, vip, @@ -33,19 +35,22 @@ export class RegisterQueueManager extends BaseCreateManager { Object.assign(this.data, { status: STATUS.WAITING, time: new Date().getTime(), + item_queue_id: item.item_queue_id, vip, code, }); return; } - async getItem(): Promise { - return this.dataService.repo.findOne({ - relations: ['item', 'item.item'], + async getItemMaster(): Promise { + const queueItem: QueueItemModel = await this.dataService.item.findOne({ + relations: ['item'], where: { - item_id: this.data.item_id, + id: this.data.item_id, }, }); + + return queueItem.item; } async afterProcess(): Promise { From fb7f925c78b6bb83ef7fcf9f16dd15b8b2afa732 Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 14 Nov 2024 14:24:56 +0700 Subject: [PATCH 50/94] feat: add session to admin queue --- .../entities/user-sessions.interface.ts | 1 + .../domain/usecases/index-queue.manager.ts | 4 ++ .../controllers/queue-admin.controller.ts | 37 +++++++++++++++++-- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/core/sessions/domain/entities/user-sessions.interface.ts b/src/core/sessions/domain/entities/user-sessions.interface.ts index 848d44e..c59f85b 100644 --- a/src/core/sessions/domain/entities/user-sessions.interface.ts +++ b/src/core/sessions/domain/entities/user-sessions.interface.ts @@ -6,5 +6,6 @@ export interface UsersSession { name: string; role: UserRole; source?: AppSource; + item_id?: string; user_privilege_id: string; } diff --git a/src/modules/queue/domain/usecases/index-queue.manager.ts b/src/modules/queue/domain/usecases/index-queue.manager.ts index a379d99..4740c44 100644 --- a/src/modules/queue/domain/usecases/index-queue.manager.ts +++ b/src/modules/queue/domain/usecases/index-queue.manager.ts @@ -68,6 +68,10 @@ export class IndexQueueManager extends BaseIndexManager { end, }); + queryBuilder.andWhere(`${this.tableName}.item_queue_id = :item_queue_id`, { + item_queue_id: this.filterParam.item_queue_id, + }); + if (this.filterParam.vip != null) { queryBuilder.andWhere(`${this.tableName}.vip = :vip`, { vip: this.filterParam.vip, diff --git a/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts b/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts index eb5363e..3916154 100644 --- a/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts @@ -1,4 +1,11 @@ -import { Controller, Get, Param, Post, Query } from '@nestjs/common'; +import { + Controller, + Get, + Param, + Post, + Query, + UnauthorizedException, +} from '@nestjs/common'; import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; @@ -9,18 +16,40 @@ import { Queue } from '../../domain/entities/queue.entity'; import { QueueDto } from './dto/queue.filter'; import { Pagination } from 'src/core/response'; import { Public } from 'src/core/guards'; +import { UserProvider } from 'src/core/sessions'; @ApiTags(`Queue Admin`) @Controller(`v1/${MODULE_NAME.QUEUE}-admin`) @ApiBearerAuth('JWT') -@Public(true) +@Public(false) export class QueueAdminController { - constructor(private orchestrator: QueueAdminOrchestrator) {} + constructor( + private orchestrator: QueueAdminOrchestrator, + private readonly userProvider: UserProvider, + ) {} @Get('queues') @Pagination() async index(@Query() params: QueueDto): Promise> { - return await this.orchestrator.index(params); + const queue_id = this.userProvider.user.item_id; + if (!queue_id) { + throw new UnauthorizedException({ + error: 'USER_IS_NOT_ADMIN', + message: "Login user isn't admin, please login in admin account", + code: 20001, + }); + } + return await this.orchestrator.index({ ...params, queue_id }); + } + + @Get('queues/:id') + @Public(true) + @Pagination() + async indexPublic( + @Param('id') id: string, + @Query() params: QueueDto, + ): Promise> { + return await this.orchestrator.index({ ...params, queue_id: id }); } @Post('queues/:id/call') From 86c73058fd5ff38bd50069b0d94c1503a437e720 Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 14 Nov 2024 15:05:39 +0700 Subject: [PATCH 51/94] feat: add information to queue item for display --- ...570311609-add-information-to-item-queue.ts | 19 +++++++++++++++++++ .../data/models/item-queue.model.ts | 3 +++ .../domain/entities/item-queue.entity.ts | 1 + .../managers/detail-item-queue.manager.ts | 10 ++++++++++ .../infrastructure/dto/item-queue.dto.ts | 8 ++++++++ .../item-queue-read.controller.ts | 6 ++++++ 6 files changed, 47 insertions(+) create mode 100644 src/database/migrations/1731570311609-add-information-to-item-queue.ts diff --git a/src/database/migrations/1731570311609-add-information-to-item-queue.ts b/src/database/migrations/1731570311609-add-information-to-item-queue.ts new file mode 100644 index 0000000..ccc0993 --- /dev/null +++ b/src/database/migrations/1731570311609-add-information-to-item-queue.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddInformationToItemQueue1731570311609 + implements MigrationInterface +{ + name = 'AddInformationToItemQueue1731570311609'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "item_queues" ADD "information" character varying`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "item_queues" DROP COLUMN "information"`, + ); + } +} diff --git a/src/modules/item-related/item-queue/data/models/item-queue.model.ts b/src/modules/item-related/item-queue/data/models/item-queue.model.ts index 61e7d39..58733b8 100644 --- a/src/modules/item-related/item-queue/data/models/item-queue.model.ts +++ b/src/modules/item-related/item-queue/data/models/item-queue.model.ts @@ -13,6 +13,9 @@ export class ItemQueueModel @Column('varchar', { name: 'name' }) name: string; + @Column('varchar', { name: 'information', nullable: true }) + information: string; + @Column('enum', { name: 'item_type', enum: ItemType, diff --git a/src/modules/item-related/item-queue/domain/entities/item-queue.entity.ts b/src/modules/item-related/item-queue/domain/entities/item-queue.entity.ts index e3b7708..2d706a7 100644 --- a/src/modules/item-related/item-queue/domain/entities/item-queue.entity.ts +++ b/src/modules/item-related/item-queue/domain/entities/item-queue.entity.ts @@ -5,5 +5,6 @@ import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.e export interface ItemQueueEntity extends BaseStatusEntity { name: string; item_type: ItemType; + information?: string; items: ItemEntity[]; } diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/detail-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/detail-item-queue.manager.ts index 007c5b9..6b815e1 100644 --- a/src/modules/item-related/item-queue/domain/usecases/managers/detail-item-queue.manager.ts +++ b/src/modules/item-related/item-queue/domain/usecases/managers/detail-item-queue.manager.ts @@ -30,6 +30,7 @@ export class DetailItemQueueManager extends BaseDetailManager { `${this.tableName}.id`, `${this.tableName}.status`, `${this.tableName}.name`, + `${this.tableName}.information`, `${this.tableName}.item_type`, `${this.tableName}.created_at`, `${this.tableName}.creator_name`, @@ -47,6 +48,7 @@ export class DetailItemQueueManager extends BaseDetailManager { `items.base_price`, `items.share_profit`, `items.play_estimation`, + `items.video_url`, ]; } @@ -55,4 +57,12 @@ export class DetailItemQueueManager extends BaseDetailManager { id: this.dataId, }; } + + getResult(): ItemQueueEntity { + const videos = this.result.items.map((item) => { + return item.video_url ?? []; + }); + this.result['videos'] = videos.flat(); + return this.result; + } } diff --git a/src/modules/item-related/item-queue/infrastructure/dto/item-queue.dto.ts b/src/modules/item-related/item-queue/infrastructure/dto/item-queue.dto.ts index cb1406c..fbbceb3 100644 --- a/src/modules/item-related/item-queue/infrastructure/dto/item-queue.dto.ts +++ b/src/modules/item-related/item-queue/infrastructure/dto/item-queue.dto.ts @@ -14,6 +14,14 @@ export class ItemQueueDto extends BaseStatusDto implements ItemQueueEntity { @IsString() name: string; + @ApiProperty({ + name: 'information', + required: false, + example: 'Running text untuk display antrian', + }) + @IsString() + information: string; + @ApiProperty({ type: 'string', required: true, diff --git a/src/modules/item-related/item-queue/infrastructure/item-queue-read.controller.ts b/src/modules/item-related/item-queue/infrastructure/item-queue-read.controller.ts index 8932974..b3a78b6 100644 --- a/src/modules/item-related/item-queue/infrastructure/item-queue-read.controller.ts +++ b/src/modules/item-related/item-queue/infrastructure/item-queue-read.controller.ts @@ -40,4 +40,10 @@ export class ItemQueueReadController { async detail(@Param('id') id: string): Promise { return await this.orchestrator.detail(id); } + + @Get('display/:id') + @Public(true) + async detailPublic(@Param('id') id: string): Promise { + return await this.orchestrator.detail(id); + } } From 37334ecb192e8638038e5dbd09e0a187143e3fff Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 14 Nov 2024 16:10:31 +0700 Subject: [PATCH 52/94] fix: queue admin item not show --- src/modules/queue/domain/usecases/index-queue.manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/queue/domain/usecases/index-queue.manager.ts b/src/modules/queue/domain/usecases/index-queue.manager.ts index 4740c44..f5c4789 100644 --- a/src/modules/queue/domain/usecases/index-queue.manager.ts +++ b/src/modules/queue/domain/usecases/index-queue.manager.ts @@ -69,7 +69,7 @@ export class IndexQueueManager extends BaseIndexManager { }); queryBuilder.andWhere(`${this.tableName}.item_queue_id = :item_queue_id`, { - item_queue_id: this.filterParam.item_queue_id, + item_queue_id: this.filterParam.queue_id, }); if (this.filterParam.vip != null) { From 0e9ae569baaf8e8144c1c4de9b109618723693cf Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 15 Nov 2024 10:23:06 +0700 Subject: [PATCH 53/94] debug: add log time to queue admin --- src/modules/queue/domain/usecases/index-queue.manager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/queue/domain/usecases/index-queue.manager.ts b/src/modules/queue/domain/usecases/index-queue.manager.ts index f5c4789..855df51 100644 --- a/src/modules/queue/domain/usecases/index-queue.manager.ts +++ b/src/modules/queue/domain/usecases/index-queue.manager.ts @@ -63,6 +63,8 @@ export class IndexQueueManager extends BaseIndexManager { const start = moment().startOf('day').valueOf(); const end = moment().endOf('day').valueOf(); + console.log({ start, end, item_queue_id: this.filterParam.queue_id }); + queryBuilder.andWhere(`${this.tableName}.time BETWEEN :start AND :end`, { start, end, From 72827aa83e42b1b1a4021860110db22932b3311e Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 15 Nov 2024 11:05:34 +0700 Subject: [PATCH 54/94] feat: calculate time queue --- .../queue/data/services/queue.service.ts | 40 +++++++++++++++---- .../domain/usecases/index-queue.manager.ts | 19 +++++++++ src/modules/queue/queue.module.ts | 2 + 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index fb34c5d..d7d2506 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -11,26 +11,52 @@ import { import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; import * as moment from 'moment'; +import { ItemQueueModel } from 'src/modules/item-related/item-queue/data/models/item-queue.model'; +import * as math from 'mathjs'; @Injectable() export class QueueDataService extends BaseReadService { constructor( @InjectRepository(QueueModel, CONNECTION_NAME.DEFAULT) private repo: Repository, + + @InjectRepository(ItemQueueModel, CONNECTION_NAME.DEFAULT) + private itemQueueRepo: Repository, ) { super(repo); } - async queueItems(item_queue_id: string[]): Promise { - return this.repo.find({ - relations: ['item', 'item.item', 'item.item.item_queue'], + async queueTimes(item_queue_id: string): Promise { + const queueTimes = {}; + let now = moment().valueOf(); + const itemQueue = await this.itemQueueRepo.findOne({ + relations: ['items'], where: { - item: { item: { item_queue: { id: In(item_queue_id) } } }, - }, - order: { - time: 'DESC', + id: item_queue_id, }, }); + + const times = itemQueue.items.map((item) => item.play_estimation ?? 0); + const average = math.mean(times) * 60 * 1000; // change average minute to milliseconds + const queues = await this.repo.find({ + where: { + item_queue_id, + }, + }); + + for (const queue of queues) { + // duration will be total qty multiple by average + const duration = queue.qty * average; + + // time to call will be now + duration + const time = now + duration; + queueTimes[queue.id] = time; + + // update now to last call time + now = time; + } + + return queueTimes; } } diff --git a/src/modules/queue/domain/usecases/index-queue.manager.ts b/src/modules/queue/domain/usecases/index-queue.manager.ts index 855df51..5afd54c 100644 --- a/src/modules/queue/domain/usecases/index-queue.manager.ts +++ b/src/modules/queue/domain/usecases/index-queue.manager.ts @@ -7,9 +7,11 @@ import { } from 'src/core/modules/domain/entities/base-filter.entity'; import { Queue } from '../entities/queue.entity'; import * as moment from 'moment'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; @Injectable() export class IndexQueueManager extends BaseIndexManager { + private queueTimes = {}; async prepareData(): Promise { return; } @@ -19,6 +21,9 @@ export class IndexQueueManager extends BaseIndexManager { } async afterProcess(): Promise { + this.queueTimes = await this.dataService.queueTimes( + this.filterParam.queue_id, + ); return; } @@ -30,6 +35,20 @@ export class IndexQueueManager extends BaseIndexManager { }; } + getResult(): PaginationResponse { + const result = this.result; + + return { + total: result.total, + data: result.data.map((queue) => { + return { + ...queue, + time: this.queueTimes[queue.id], + }; + }), + }; + } + get selects(): string[] { return [ `${this.tableName}.id`, diff --git a/src/modules/queue/queue.module.ts b/src/modules/queue/queue.module.ts index d6dee2a..ab95226 100644 --- a/src/modules/queue/queue.module.ts +++ b/src/modules/queue/queue.module.ts @@ -32,6 +32,7 @@ import { QueueBucketModel } from './data/models/queue-bucket.model'; import { QueueBucketReadService } from './data/services/queue-bucket'; import { SplitQueueManager } from './domain/usecases/split-queue.manager'; import { QueueTransactionCancelHandler } from './infrastructure/handlers/cancel-transaction.handler'; +import { ItemQueueModel } from '../item-related/item-queue/data/models/item-queue.model'; @Module({ imports: [ @@ -44,6 +45,7 @@ import { QueueTransactionCancelHandler } from './infrastructure/handlers/cancel- QueueModel, QueueBucketModel, ItemModel, + ItemQueueModel, TransactionModel, ], CONNECTION_NAME.DEFAULT, From ec916ab57405f73c81f3dca3779bad4d3646c7c3 Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 15 Nov 2024 11:16:29 +0700 Subject: [PATCH 55/94] fix: queue time calculation --- src/modules/queue/data/services/queue.service.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index d7d2506..048f18c 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -41,10 +41,14 @@ export class QueueDataService extends BaseReadService { const queues = await this.repo.find({ where: { item_queue_id, + status: 'waiting', }, }); - for (const queue of queues) { + queueTimes[queues[0].id] = now; // first queue will be now + + for (let i = 1; i < queues.length; i++) { + const queue = queues[i]; // duration will be total qty multiple by average const duration = queue.qty * average; From 53ef4656ebb9c18e32f96c579893b9efa6d7448b Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 15 Nov 2024 12:37:43 +0700 Subject: [PATCH 56/94] fix: change queue time formula to manager --- .../queue/data/services/queue.service.ts | 47 +++++++++++++++- .../usecases/formula/queue-time.formula.ts | 56 +++++++++++++++++++ .../domain/usecases/index-queue.manager.ts | 7 ++- src/modules/queue/queue.module.ts | 3 + 4 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 src/modules/queue/domain/usecases/formula/queue-time.formula.ts diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index 048f18c..7d33202 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -10,8 +10,8 @@ import { } from '../models/queue.model'; import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; -import * as moment from 'moment'; import { ItemQueueModel } from 'src/modules/item-related/item-queue/data/models/item-queue.model'; +import * as moment from 'moment'; import * as math from 'mathjs'; @Injectable() @@ -26,6 +26,39 @@ export class QueueDataService extends BaseReadService { super(repo); } + async waitingQueue(item_queue_id: string) { + const start = moment().startOf('day').valueOf(); + const end = moment().endOf('day').valueOf(); + return this.repo.find({ + where: { + time: Between(start, end), + item_queue_id, + status: 'waiting', + }, + }); + } + + async lastQueue(item_queue_id: string) { + const start = moment().startOf('day').valueOf(); + const end = moment().endOf('day').valueOf(); + return this.repo.findOne({ + where: { + time: Between(start, end), + item_queue_id, + status: 'called', + }, + order: { + call_time: 'DESC', + }, + }); + } + + /** + * @deprecated + * Change to QueueTimeFormula (queue-time.formula.ts) + * @param item_queue_id + * @returns + */ async queueTimes(item_queue_id: string): Promise { const queueTimes = {}; let now = moment().valueOf(); @@ -62,6 +95,18 @@ export class QueueDataService extends BaseReadService { return queueTimes; } + + async queueItems(item_queue_id: string[]): Promise { + return this.repo.find({ + relations: ['item', 'item.item', 'item.item.item_queue'], + where: { + item: { item: { item_queue: { id: In(item_queue_id) } } }, + }, + order: { + time: 'DESC', + }, + }); + } } @Injectable() diff --git a/src/modules/queue/domain/usecases/formula/queue-time.formula.ts b/src/modules/queue/domain/usecases/formula/queue-time.formula.ts new file mode 100644 index 0000000..e25c0a2 --- /dev/null +++ b/src/modules/queue/domain/usecases/formula/queue-time.formula.ts @@ -0,0 +1,56 @@ +import { InjectRepository } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { ItemQueueModel } from 'src/modules/item-related/item-queue/data/models/item-queue.model'; +import { QueueDataService } from 'src/modules/queue/data/services/queue.service'; +import { Repository } from 'typeorm'; +import * as moment from 'moment'; +import * as math from 'mathjs'; + +export class QueueTimeFormula { + constructor( + private readonly queueDataService: QueueDataService, + @InjectRepository(ItemQueueModel, CONNECTION_NAME.DEFAULT) + private itemQueueRepo: Repository, + ) {} + + async items(item_queue_id: string) { + const itemQueue = await this.itemQueueRepo.findOne({ + relations: ['items'], + where: { + id: item_queue_id, + }, + }); + + const times = itemQueue.items.map((item) => item.play_estimation ?? 0); + const average = math.mean(times) * 60 * 1000; // change average minute to milliseconds + + const queues = await this.queueDataService.waitingQueue(item_queue_id); + const calledQueue = await this.queueDataService.lastQueue(item_queue_id); + + const queueTimes = {}; + const timeNow = moment().valueOf(); + const lastCall = calledQueue?.call_time ?? timeNow - average; + + const callTime = lastCall + average; + const currentQueueCallTime = timeNow > callTime ? timeNow : callTime; + + queueTimes[queues[0].id] = currentQueueCallTime; + let now = currentQueueCallTime; + + for (let i = 1; i < queues.length; i++) { + const before = queues[i - 1]; + // duration will be total qty multiple by average from last queue + const duration = before.qty * average; + + const queue = queues[i]; + // time to call will be now + duration + const time = now + duration; + queueTimes[queue.id] = time; + + // update now to last call time + now = time; + } + + return queueTimes; + } +} diff --git a/src/modules/queue/domain/usecases/index-queue.manager.ts b/src/modules/queue/domain/usecases/index-queue.manager.ts index 5afd54c..0db811f 100644 --- a/src/modules/queue/domain/usecases/index-queue.manager.ts +++ b/src/modules/queue/domain/usecases/index-queue.manager.ts @@ -8,9 +8,14 @@ import { import { Queue } from '../entities/queue.entity'; import * as moment from 'moment'; import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { QueueTimeFormula } from './formula/queue-time.formula'; @Injectable() export class IndexQueueManager extends BaseIndexManager { + constructor(private readonly queueTimeFormula: QueueTimeFormula) { + super(); + } + private queueTimes = {}; async prepareData(): Promise { return; @@ -21,7 +26,7 @@ export class IndexQueueManager extends BaseIndexManager { } async afterProcess(): Promise { - this.queueTimes = await this.dataService.queueTimes( + this.queueTimes = await this.queueTimeFormula.items( this.filterParam.queue_id, ); return; diff --git a/src/modules/queue/queue.module.ts b/src/modules/queue/queue.module.ts index ab95226..19b2637 100644 --- a/src/modules/queue/queue.module.ts +++ b/src/modules/queue/queue.module.ts @@ -33,6 +33,7 @@ import { QueueBucketReadService } from './data/services/queue-bucket'; import { SplitQueueManager } from './domain/usecases/split-queue.manager'; import { QueueTransactionCancelHandler } from './infrastructure/handlers/cancel-transaction.handler'; import { ItemQueueModel } from '../item-related/item-queue/data/models/item-queue.model'; +import { QueueTimeFormula } from './domain/usecases/formula/queue-time.formula'; @Module({ imports: [ @@ -71,6 +72,8 @@ import { ItemQueueModel } from '../item-related/item-queue/data/models/item-queu CallQueueManager, RegisterQueueManager, SplitQueueManager, + + QueueTimeFormula, ], }) export class QueueModule {} From a0de16575fbc4e4595de0bf101952de50dbadbb9 Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 15 Nov 2024 13:51:12 +0700 Subject: [PATCH 57/94] fix: empty queue id in time formula --- .../queue/domain/usecases/formula/queue-time.formula.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/queue/domain/usecases/formula/queue-time.formula.ts b/src/modules/queue/domain/usecases/formula/queue-time.formula.ts index e25c0a2..9831e34 100644 --- a/src/modules/queue/domain/usecases/formula/queue-time.formula.ts +++ b/src/modules/queue/domain/usecases/formula/queue-time.formula.ts @@ -14,6 +14,11 @@ export class QueueTimeFormula { ) {} async items(item_queue_id: string) { + const queues = await this.queueDataService.waitingQueue(item_queue_id); + if (queues.length == 0) { + return {}; + } + const itemQueue = await this.itemQueueRepo.findOne({ relations: ['items'], where: { @@ -24,7 +29,6 @@ export class QueueTimeFormula { const times = itemQueue.items.map((item) => item.play_estimation ?? 0); const average = math.mean(times) * 60 * 1000; // change average minute to milliseconds - const queues = await this.queueDataService.waitingQueue(item_queue_id); const calledQueue = await this.queueDataService.lastQueue(item_queue_id); const queueTimes = {}; From eae529bce9a648387fcaa3f805b0f961eabe55e2 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 19 Nov 2024 11:13:53 +0700 Subject: [PATCH 58/94] fix: vip pass queue register for 1 queue --- .../handlers/transaction.handler.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index 61c635e..2f1ab55 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -77,15 +77,15 @@ export class QueueTransactionHandler ticket.items.forEach((item) => { const item_id = item.item_id; - for (let i = 0; i < item.qty; i++) { - const payload = { - item_id, - ticket_id, - qty: 1, - }; + // for (let i = 0; i < item.qty; i++) { + const payload = { + item_id, + ticket_id, + qty: item.qty, + }; - this.create(payload); - } + this.create(payload); + // } }); }); } From 9eacb7dca5bcd721a8a11624b32fd897c6e4b681 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 19 Nov 2024 13:16:52 +0700 Subject: [PATCH 59/94] feat: add done status queue --- src/modules/queue/domain/queue-admin.orchestrator.ts | 7 +++++++ .../infrastructure/controllers/queue-admin.controller.ts | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/modules/queue/domain/queue-admin.orchestrator.ts b/src/modules/queue/domain/queue-admin.orchestrator.ts index 21876c8..36be553 100644 --- a/src/modules/queue/domain/queue-admin.orchestrator.ts +++ b/src/modules/queue/domain/queue-admin.orchestrator.ts @@ -32,4 +32,11 @@ export class QueueAdminOrchestrator { await this.callManager.execute(); return this.callManager.getResult(); } + + async done(dataId): Promise { + this.callManager.setData(dataId, QUEUE_STATUS.DONE); + this.callManager.setService(this.service, TABLE_NAME.QUEUE); + await this.callManager.execute(); + return this.callManager.getResult(); + } } diff --git a/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts b/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts index 3916154..0ba3e4a 100644 --- a/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts @@ -56,4 +56,9 @@ export class QueueAdminController { async call(@Param('id') id: string) { return await this.orchestrator.call(id); } + + @Post('queues/:id/done') + async done(@Param('id') id: string) { + return await this.orchestrator.done(id); + } } From 3cf4fbdada89a39b92adfad98251d71b9abdb445 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 19 Nov 2024 13:33:29 +0700 Subject: [PATCH 60/94] fix: exclude privilege from admin queue --- .../queue/infrastructure/controllers/queue-admin.controller.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts b/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts index 0ba3e4a..9c08352 100644 --- a/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue-admin.controller.ts @@ -15,13 +15,14 @@ import { PaginationResponse } from 'src/core/response/domain/ok-response.interfa import { Queue } from '../../domain/entities/queue.entity'; import { QueueDto } from './dto/queue.filter'; import { Pagination } from 'src/core/response'; -import { Public } from 'src/core/guards'; +import { ExcludePrivilege, Public } from 'src/core/guards'; import { UserProvider } from 'src/core/sessions'; @ApiTags(`Queue Admin`) @Controller(`v1/${MODULE_NAME.QUEUE}-admin`) @ApiBearerAuth('JWT') @Public(false) +@ExcludePrivilege() export class QueueAdminController { constructor( private orchestrator: QueueAdminOrchestrator, From c0a68db9f12e1cda641486a9f25f64b2039b1cd8 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 19 Nov 2024 13:33:45 +0700 Subject: [PATCH 61/94] fix: total activities, now merge by item queue --- .../domain/usecases/queue/customer-queue.manager.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts index 128db18..9b386db 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts @@ -17,7 +17,14 @@ export class CustomerQueueManager { } totalActivities(ticket: QueueTicketModel): number { - return ticket.items.length; + const ticketItems = {}; + ticket.items.forEach((item) => { + const item_id = + item.item?.item_queue?.id ?? item.item?.id ?? item.item_id; + const currentItem = ticketItems[item_id]; + ticketItems[item_id] = currentItem ? [...currentItem, item] : [item]; + }); + return Object.keys(ticketItems).length; } totalTickets(ticket: QueueTicketModel): number { From 8d54c686fce6e920dcd4bcf854c791c9fa28c183 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 20 Nov 2024 10:40:00 +0700 Subject: [PATCH 62/94] fix(SPG-1027): Total QR not same with total ticket --- .../queue/data/services/ticket.service.ts | 18 ++++++++++++++++-- src/modules/queue/domain/queue.orchestrator.ts | 5 ++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index 36255de..c8275ef 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; import { InjectRepository } from '@nestjs/typeorm'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; -import { Between, In, Repository } from 'typeorm'; +import { Between, In, IsNull, Not, Repository } from 'typeorm'; import { QueueTicket } from '../../domain/entities/ticket.entity'; import { QueueItemModel, @@ -34,7 +34,21 @@ export class TicketDataService extends BaseDataService { async loginQueue(id: string): Promise { return this.order.findOneOrFail({ relations: ['tickets'], - where: [{ transaction_id: id }, { code: id }], + where: [ + { transaction_id: id }, + { code: id, transaction_id: Not(IsNull()) }, + ], + }); + } + + async ticketByCode(code: string): Promise { + return this.repo.find({ + relations: ['order'], + where: { + order: { + code, + }, + }, }); } diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index 87796d8..944cc98 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -36,7 +36,10 @@ export class QueueOrchestrator { async loginCustomer(id: string): Promise { try { - return await this.dataService.loginQueue(id); + const order = await this.dataService.loginQueue(id); + const tickets = await this.dataService.ticketByCode(order.code); + order.tickets = tickets; + return order; } catch (error) { throw new UnauthorizedException({ message: 'Invoice tidak ditemukan', From ac3fde14e3a1e1a45c1d6037374e595837585e49 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 20 Nov 2024 10:40:23 +0700 Subject: [PATCH 63/94] feat: add total_queue_item to summary ticket --- .../domain/usecases/queue/customer-queue.manager.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts index 9b386db..915e850 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts @@ -51,6 +51,7 @@ export class CustomerQueueManager { .filter((q) => ['waiting'].includes(q.status)) .reduce((acc, item) => acc + item.qty, 0); } + totalQueueTickets(ticket: QueueTicketModel): number { const tickets = ticket.items.map((item) => { return this.totalQueueItems(item.queue); @@ -60,15 +61,27 @@ export class CustomerQueueManager { return tickets.reduce(reducer, 0); } + totalQueueItemTickets(ticket: QueueTicketModel): number { + const tickets = ticket.items + .map((item) => { + return this.totalQueueItems(item.queue); + }) + .filter((item) => item > 0); + + return tickets.length; + } + summaryTicket(ticket: QueueTicketModel): any { const total_tickets = this.totalTickets(ticket); const total_used = this.totalUsedTickets(ticket); const total_queue = this.totalQueueTickets(ticket); + const total_queue_item = this.totalQueueItemTickets(ticket); return { total_activities: this.totalActivities(ticket), total_tickets, total_used, total_queue, + total_queue_item, remaining: total_tickets - total_queue, }; } From 99d0fc8560beb634910990f018aa9452c2a0e6ba Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 20 Nov 2024 11:38:23 +0700 Subject: [PATCH 64/94] fix(SPG-1035): grouping queue information --- .../queue/customer-queue-list.manager.ts | 76 +++++++++++++------ 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts index 849eea6..ae449f6 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts @@ -1,7 +1,22 @@ -import { QueueItemModel } from 'src/modules/queue/data/models/queue.model'; +import { + QueueModel, + QueueTicketModel, +} from 'src/modules/queue/data/models/queue.model'; import { CustomerQueueManager } from './customer-queue.manager'; export class CustomerQueueListManager extends CustomerQueueManager { + private ticketToQueues(tickets: QueueTicketModel[]): QueueModel[] { + const ticket = tickets.map((ticket) => { + const queue = ticket.items.map((item) => { + return item.queue; + }); + + return queue.flat(); + }); + + return ticket.flat(); + } + get data() { const tickets = this.tickets; const ticketItems = {}; @@ -10,36 +25,49 @@ export class CustomerQueueListManager extends CustomerQueueManager { ticket.items.forEach((item) => { const item_id = item.item.item_queue?.id ?? item.item.id; const currentItem = ticketItems[item_id]; - item.ticket = ticket; - ticketItems[item_id] = currentItem ? [...currentItem, item] : [item]; + const ticketQueue = { ...ticket, items: [item] }; + + ticketItems[item_id] = currentItem + ? [...currentItem, ticketQueue] + : [ticketQueue]; }); }); - return Object.values(ticketItems).map((items) => { - const item = items[0]; - const item_qty = items.reduce((acc, item) => acc + item.qty, 0); + return Object.keys(ticketItems).map((item_id) => { + const tickets: QueueTicketModel[] = ticketItems[item_id]; + const item = tickets[0].items[0]; + const uniqueTicket = tickets.filter( + (obj1, i, arr) => arr.findIndex((obj2) => obj2.id === obj1.id) === i, + ); + let totalQueue = 0; + + const currentTickets = uniqueTicket.map((ticket) => { + const currentTicket = tickets.filter((t) => t.id === ticket.id); + const queues = this.ticketToQueues(currentTicket); + const currentQueues = queues.map((q) => { + return { + code: q.code, + qty: q.qty, + time: this.toTime(q.time), + status: q.status, + }; + }); + totalQueue += currentQueues.length; + return { + code: ticket.code, + customer: ticket.customer, + phone: ticket.phone, + id: ticket.id, + queue: currentQueues, + }; + }); + return { id: item.item_id, title: item.item.item_queue?.name ?? item.item.name, image_url: item.item.image_url, - qty: item_qty, - - items: items.map((item) => { - return { - code: item.ticket.code, - customer: item.ticket.customer, - phone: item.ticket.phone, - id: item.ticket.id, - queue: item.queue.map((q) => { - return { - code: q.code, - qty: q.qty, - time: this.toTime(q.time), - status: q.status, - }; - }), - }; - }), + qty: totalQueue, + items: currentTickets, }; }); } From 4dc21c4ebdbac3e7f9f1990fae36a5a0944e8fca Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 20 Nov 2024 11:46:19 +0700 Subject: [PATCH 65/94] fix(SPG-1036): remove queue if empty --- .../queue/customer-queue-list.manager.ts | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts index ae449f6..2173a35 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts @@ -33,42 +33,48 @@ export class CustomerQueueListManager extends CustomerQueueManager { }); }); - return Object.keys(ticketItems).map((item_id) => { - const tickets: QueueTicketModel[] = ticketItems[item_id]; - const item = tickets[0].items[0]; - const uniqueTicket = tickets.filter( - (obj1, i, arr) => arr.findIndex((obj2) => obj2.id === obj1.id) === i, - ); - let totalQueue = 0; + return Object.keys(ticketItems) + .map((item_id) => { + const tickets: QueueTicketModel[] = ticketItems[item_id]; + const item = tickets[0].items[0]; + const uniqueTicket = tickets.filter( + (obj1, i, arr) => arr.findIndex((obj2) => obj2.id === obj1.id) === i, + ); + let totalQueue = 0; - const currentTickets = uniqueTicket.map((ticket) => { - const currentTicket = tickets.filter((t) => t.id === ticket.id); - const queues = this.ticketToQueues(currentTicket); - const currentQueues = queues.map((q) => { - return { - code: q.code, - qty: q.qty, - time: this.toTime(q.time), - status: q.status, - }; + const currentTickets = uniqueTicket.map((ticket) => { + const currentTicket = tickets.filter((t) => t.id === ticket.id); + const queues = this.ticketToQueues(currentTicket); + const currentQueues = queues.map((q) => { + return { + code: q.code, + qty: q.qty, + time: this.toTime(q.time), + status: q.status, + }; + }); + totalQueue += currentQueues.length; + return currentQueues.length > 0 + ? { + code: ticket.code, + customer: ticket.customer, + phone: ticket.phone, + id: ticket.id, + queue: currentQueues, + } + : null; }); - totalQueue += currentQueues.length; - return { - code: ticket.code, - customer: ticket.customer, - phone: ticket.phone, - id: ticket.id, - queue: currentQueues, - }; - }); - return { - id: item.item_id, - title: item.item.item_queue?.name ?? item.item.name, - image_url: item.item.image_url, - qty: totalQueue, - items: currentTickets, - }; - }); + return currentTickets.filter(Boolean).length > 0 + ? { + id: item.item_id, + title: item.item.item_queue?.name ?? item.item.name, + image_url: item.item.image_url, + qty: totalQueue, + items: currentTickets.filter(Boolean), + } + : null; + }) + .filter(Boolean); } } From 3b19484f293293dc9802049c5bc63add1b37fe27 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 20 Nov 2024 12:50:58 +0700 Subject: [PATCH 66/94] fix(SPG-1030): merge queue when user is exits --- .../queue/data/services/ticket.service.ts | 17 ++++++++++++++ .../handlers/transaction.handler.ts | 22 +++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index c8275ef..f2bae21 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -31,6 +31,10 @@ export class TicketDataService extends BaseDataService { return await this.order.save(order); } + async updateQueueTicket(ticket: QueueTicket): Promise { + return await this.repo.save(ticket); + } + async loginQueue(id: string): Promise { return this.order.findOneOrFail({ relations: ['tickets'], @@ -52,6 +56,19 @@ export class TicketDataService extends BaseDataService { }); } + async ticketByUser(user: string, phone: string): Promise { + const start = moment().startOf('day').valueOf(); + const end = moment().endOf('day').valueOf(); + + return this.repo.findOne({ + where: { + customer: user, + phone: phone, + date: Between(start, end), + }, + }); + } + async loginPhone(user: string, phone: string): Promise { const start = moment().startOf('day').valueOf(); const end = moment().endOf('day').valueOf(); diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index 2f1ab55..865767f 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -64,15 +64,29 @@ export class QueueTransactionHandler }; }); - const ticket: QueueTicket = { ...customerOrder, items }; - const order: QueueOrder = { ...customerOrder, tickets: [ticket] }; + const existTicket = await this.ticketService.ticketByUser( + customer_name, + customer_phone, + ); + + const insertTickets = []; + if (existTicket) { + existTicket.items = items; + insertTickets.push(existTicket); + await this.ticketService.updateQueueTicket(existTicket); + } else { + const ticket: QueueTicket = { ...customerOrder, items }; + const order: QueueOrder = { ...customerOrder, tickets: [ticket] }; + + const queueOrder = await this.ticketService.createQueueOrder(order); + insertTickets.push(...queueOrder.tickets); + } - const queueOrder = await this.ticketService.createQueueOrder(order); if ( transaction.customer_category?.has_vip_pass || transaction.customer_type === TransactionUserType.VIP ) { - queueOrder.tickets.forEach((ticket) => { + insertTickets.forEach((ticket) => { const ticket_id = ticket.id; ticket.items.forEach((item) => { const item_id = item.item_id; From 3b7d7ea80b36e4a8c259f67d54d992995c83b0e4 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 20 Nov 2024 14:44:19 +0700 Subject: [PATCH 67/94] fix: don't merge queue when customer name is empty --- .../queue/infrastructure/handlers/transaction.handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index 865767f..a6c3797 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -70,7 +70,7 @@ export class QueueTransactionHandler ); const insertTickets = []; - if (existTicket) { + if (customer_name && customer_phone && existTicket) { existTicket.items = items; insertTickets.push(existTicket); await this.ticketService.updateQueueTicket(existTicket); From 1148a72481a0e73a5b3c4fecb5dbac02a2943c16 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 20 Nov 2024 17:33:00 +0700 Subject: [PATCH 68/94] fix: total QR header --- src/modules/queue/data/services/ticket.service.ts | 7 ++----- src/modules/queue/domain/queue.orchestrator.ts | 9 +++++++-- .../domain/usecases/formula/queue-condition.formula.ts | 6 ++++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index f2bae21..545f2e9 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; import { InjectRepository } from '@nestjs/typeorm'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; -import { Between, In, IsNull, Not, Repository } from 'typeorm'; +import { Between, In, IsNull, Like, Not, Repository } from 'typeorm'; import { QueueTicket } from '../../domain/entities/ticket.entity'; import { QueueItemModel, @@ -47,11 +47,8 @@ export class TicketDataService extends BaseDataService { async ticketByCode(code: string): Promise { return this.repo.find({ - relations: ['order'], where: { - order: { - code, - }, + code: Like(`${code}%`), }, }); } diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index 944cc98..1e96469 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -37,7 +37,8 @@ export class QueueOrchestrator { async loginCustomer(id: string): Promise { try { const order = await this.dataService.loginQueue(id); - const tickets = await this.dataService.ticketByCode(order.code); + const code = order.tickets[0].code ?? order.code; + const tickets = await this.dataService.ticketByCode(code); order.tickets = tickets; return order; } catch (error) { @@ -52,7 +53,11 @@ export class QueueOrchestrator { const { name, phone } = login; const now = moment().format('DD/MM/YYYY'); try { - return await this.dataService.loginPhone(name, phone); + const order = await this.dataService.loginPhone(name, phone); + const code = order.tickets[0].code ?? order.code; + const tickets = await this.dataService.ticketByCode(code); + order.tickets = tickets; + return order; } catch (error) { throw new UnauthorizedException({ message: `Antrian atas nama ${name} dan nomor telepon ${phone} pada tanggal ${now} tidak ditemukan`, diff --git a/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts b/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts index bf64da0..05f9d28 100644 --- a/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts +++ b/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts @@ -32,10 +32,12 @@ export class QueueCondition { if (queues.length == 0) return 0; const calledQueue = queues.filter((q) => q.status === 'called'); + if (calledQueue.length < 1) return 0; + const times = calledQueue.map((queue) => { - return queue.call_time - queue.time; + return (queue.call_time - queue.time) / 1000; }); - return math.sum(times) / times.length; + return Math.ceil(math.mean(times)); } } From 5988a592acd230b3df6128a04ef3607ccc00453c Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 20 Nov 2024 17:41:29 +0700 Subject: [PATCH 69/94] fix: merge new order queue --- src/modules/queue/data/services/ticket.service.ts | 1 + .../queue/infrastructure/handlers/transaction.handler.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index 545f2e9..cb5cc57 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -58,6 +58,7 @@ export class TicketDataService extends BaseDataService { const end = moment().endOf('day').valueOf(); return this.repo.findOne({ + relations: ['items'], where: { customer: user, phone: phone, diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index a6c3797..c1cecc4 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -71,9 +71,11 @@ export class QueueTransactionHandler const insertTickets = []; if (customer_name && customer_phone && existTicket) { + existTicket.items.push(...items); + await this.ticketService.updateQueueTicket(existTicket); + existTicket.items = items; insertTickets.push(existTicket); - await this.ticketService.updateQueueTicket(existTicket); } else { const ticket: QueueTicket = { ...customerOrder, items }; const order: QueueOrder = { ...customerOrder, tickets: [ticket] }; From c05af5c16b91884c9f7ee0ad419726ac62012f77 Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 20 Nov 2024 17:51:47 +0700 Subject: [PATCH 70/94] fix: admin queue time --- .../queue/data/services/queue.service.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index 7d33202..e41ba60 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -35,6 +35,24 @@ export class QueueDataService extends BaseReadService { item_queue_id, status: 'waiting', }, + order: { + time: 'ASC', + }, + }); + } + + async doneQueue(item_queue_id: string) { + const start = moment().startOf('day').valueOf(); + const end = moment().endOf('day').valueOf(); + return this.repo.find({ + where: { + time: Between(start, end), + item_queue_id, + status: In(['done', 'called']), + }, + order: { + time: 'ASC', + }, }); } From 5e4401a974f34d5d41fdad0c7c9d66bea1d38bfb Mon Sep 17 00:00:00 2001 From: shancheas Date: Wed, 20 Nov 2024 18:06:28 +0700 Subject: [PATCH 71/94] fix: queue time string to number --- src/modules/queue/domain/usecases/formula/queue-time.formula.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/queue/domain/usecases/formula/queue-time.formula.ts b/src/modules/queue/domain/usecases/formula/queue-time.formula.ts index 9831e34..a56dd40 100644 --- a/src/modules/queue/domain/usecases/formula/queue-time.formula.ts +++ b/src/modules/queue/domain/usecases/formula/queue-time.formula.ts @@ -35,7 +35,7 @@ export class QueueTimeFormula { const timeNow = moment().valueOf(); const lastCall = calledQueue?.call_time ?? timeNow - average; - const callTime = lastCall + average; + const callTime = +lastCall + average; const currentQueueCallTime = timeNow > callTime ? timeNow : callTime; queueTimes[queues[0].id] = currentQueueCallTime; From ea25e0cae147d309855ef355fc6c6c648f8d81f0 Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 21 Nov 2024 10:39:20 +0700 Subject: [PATCH 72/94] fix(SPG-1047): merge item queue with same item --- .../queue/data/services/ticket.service.ts | 8 ++++-- .../handlers/transaction.handler.ts | 26 ++++++++++++------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index cb5cc57..1fe03ea 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -118,8 +118,12 @@ export class TicketDataService extends BaseDataService { } async deleteQueue(transaction_id: string): Promise { - const transactions = await this.transactions(transaction_id); - await this.order.remove(transactions); + try { + const transactions = await this.transactions(transaction_id); + await this.order.remove(transactions); + } catch (error) { + console.log('transaction not found'); + } } async orderIds(order_id: string): Promise { diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index c1cecc4..57d42ca 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -59,6 +59,7 @@ export class QueueTransactionHandler .filter((item) => item.item.use_queue) .map((item) => { return { + item_queue_id: item.item.item_queue_id, item_id: item.item_id, qty: item.qty, }; @@ -69,7 +70,7 @@ export class QueueTransactionHandler customer_phone, ); - const insertTickets = []; + const insertTickets: QueueTicket[] = []; if (customer_name && customer_phone && existTicket) { existTicket.items.push(...items); await this.ticketService.updateQueueTicket(existTicket); @@ -90,18 +91,25 @@ export class QueueTransactionHandler ) { insertTickets.forEach((ticket) => { const ticket_id = ticket.id; + const items = {}; ticket.items.forEach((item) => { - const item_id = item.item_id; + const item_id = item['item_queue_id']; + const currentItem = items[item_id]; - // for (let i = 0; i < item.qty; i++) { - const payload = { - item_id, - ticket_id, - qty: item.qty, - }; + if (currentItem) { + currentItem.qty += item.qty; + } + items[item_id] = currentItem + ? currentItem + : { + item_id, + ticket_id, + qty: item.qty, + }; + }); + Object.values(items).forEach((payload: any) => { this.create(payload); - // } }); }); } From cb452cf5f399cf4edf78f1b739576ff9f7a96eb6 Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 21 Nov 2024 11:30:31 +0700 Subject: [PATCH 73/94] fix(SPG-1045): send rest qty instead all qty --- .../queue/data/services/ticket.service.ts | 17 +++++++++++++++++ src/modules/queue/domain/queue.orchestrator.ts | 9 +++++++++ .../controllers/queue.controller.ts | 5 +++++ 3 files changed, 31 insertions(+) diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index 1fe03ea..db6bdd9 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -182,6 +182,23 @@ export class TicketDataService extends BaseDataService { }); } + async queueUniqueTickets(order_id: string): Promise { + const start = moment().startOf('day').valueOf(); + const end = moment().endOf('day').valueOf(); + return this.repo.find({ + relations: [ + 'items', + 'items.queue', + 'items.item', + 'items.item.item_queue', + ], + where: { + order_id: order_id, + date: Between(start, end), + }, + }); + } + async queueTicketItems( order_id: string, ticket_id: string, diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index 1e96469..2f0d6b3 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -145,6 +145,15 @@ export class QueueOrchestrator { return manager.data; } + async queueUniqueItems(order_id: string): Promise { + const tickets = await this.dataService.queueUniqueTickets(order_id); + const queueItemIds = this.getQueueItemFromTickets(tickets); + const queues = await this.queueService.queues(queueItemIds); + const manager = new CustomerQueueItemListManager(tickets); + manager.currentQueues(queues); + return manager.data; + } + async queueOrderItems(order_id: string): Promise { const tickets = await this.dataService.queueTickets(order_id); const manager = new CustomerQueueListManager(tickets); diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index 65572de..ad6e3a5 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -71,6 +71,11 @@ export class QueueController { return await this.orchestrator.queueItems(id); } + @Get(':id/unique-items') + async queueUniqueItems(@Param('id') id: string): Promise { + return await this.orchestrator.queueUniqueItems(id); + } + @Get(':id/pos-items') async queuePosItems(@Param('id') id: string): Promise { return await this.orchestrator.queuePOSTickets(id); From 4eedca12e77ade84d2ef6a85f07f78cc37f997a2 Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 21 Nov 2024 11:31:29 +0700 Subject: [PATCH 74/94] fix(SPG-1044): validate split QR when phone and username exist --- .../domain/usecases/split-queue.manager.ts | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/modules/queue/domain/usecases/split-queue.manager.ts b/src/modules/queue/domain/usecases/split-queue.manager.ts index c8a3c2c..6b67bd9 100644 --- a/src/modules/queue/domain/usecases/split-queue.manager.ts +++ b/src/modules/queue/domain/usecases/split-queue.manager.ts @@ -1,4 +1,8 @@ -import { Injectable } from '@nestjs/common'; +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; import { EventTopics, columnUniques, @@ -9,11 +13,15 @@ import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base import { QueueOrderModel } from '../../data/models/queue.model'; import { SplitQueueDto } from '../../infrastructure/controllers/dto/split-queue.dto'; import { QueueService } from '../../data/services/queue.service'; +import { TicketDataService } from '../../data/services/ticket.service'; @Injectable() export class SplitQueueManager extends BaseCreateManager { private dto: SplitQueueDto; - constructor(private readonly queueService: QueueService) { + constructor( + private readonly queueService: QueueService, + private readonly ticketService: TicketDataService, + ) { super(); } @@ -21,7 +29,25 @@ export class SplitQueueManager extends BaseCreateManager { this.dto = dto; } - prepareData(): Promise { + async validateProcess(): Promise { + await super.validateProcess(); + const existTickets = await this.ticketService.ticketByUser( + this.dto.name, + this.dto.phone, + ); + if (existTickets) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + code: 20002, + message: + 'Antrian dengan nama dan nomor telepon sudah terdaftar, silahkan gunakan nomor telepon atau nama lain', + error: 'QUEUE_ALREADY_EXIST', + }); + } + } + + async prepareData(): Promise { + const existTickets = await this.ticketService.ticketByCode(this.data.code); const { tickets, ...order } = this.data; const ticket = tickets[0]; const items = tickets @@ -29,6 +55,7 @@ export class SplitQueueManager extends BaseCreateManager { return ticket.items; }) .flat(); + const postfix = existTickets.length; this.data = { code: order.code, customer: this.dto.name, @@ -37,7 +64,7 @@ export class SplitQueueManager extends BaseCreateManager { updated_at: new Date().getTime(), tickets: [ { - code: `${order.code}-1`, + code: `${order.code}-${postfix}`, customer: this.dto.name, phone: this.dto.phone, date: ticket.date, From 60b5bcf6388dce65ae40907b4099561afbc764de Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 21 Nov 2024 17:51:45 +0700 Subject: [PATCH 75/94] fix: time calculation customer --- src/modules/queue/data/models/queue.model.ts | 2 + .../queue/data/services/queue-bucket.ts | 11 +--- .../queue/data/services/queue.service.ts | 30 ++++++++++- .../formula/queue-condition.formula.ts | 52 ++++++++++++++++--- .../usecases/formula/queue-time.formula.ts | 12 ++++- .../usecases/queue/customer-queue.manager.ts | 2 +- .../domain/usecases/register-queue.manager.ts | 21 ++++++-- .../handlers/transaction.handler.ts | 7 ++- 8 files changed, 114 insertions(+), 23 deletions(-) diff --git a/src/modules/queue/data/models/queue.model.ts b/src/modules/queue/data/models/queue.model.ts index ec2083f..57c2b2f 100644 --- a/src/modules/queue/data/models/queue.model.ts +++ b/src/modules/queue/data/models/queue.model.ts @@ -136,4 +136,6 @@ export class QueueModel extends BaseModel implements Queue { @Column('int') qty: number; + + average = 0; } diff --git a/src/modules/queue/data/services/queue-bucket.ts b/src/modules/queue/data/services/queue-bucket.ts index 0042c4a..4e5bacb 100644 --- a/src/modules/queue/data/services/queue-bucket.ts +++ b/src/modules/queue/data/services/queue-bucket.ts @@ -24,16 +24,9 @@ export class QueueBucketReadService extends BaseReadService { const start = moment().startOf('day').valueOf(); const end = moment().endOf('day').valueOf(); - const queueItem = await this.item.findOne({ - relations: ['item'], - where: { - id: item_id, - }, - }); - const queue = await this.repo.findOne({ where: { - queue_item_id: queueItem.item.item_queue_id ?? queueItem.item.id, + queue_item_id: item_id, date: Between(start, end), }, }); @@ -42,7 +35,7 @@ export class QueueBucketReadService extends BaseReadService { const regularNumber = vip ? 0 : 1; const vipNumber = vip ? 1 : 0; await this.repo.save({ - queue_item_id: queueItem.item.item_queue_id ?? queueItem.item.id, + queue_item_id: item_id, date: start, regular: regularNumber, vip: vipNumber, diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index e41ba60..4f7ab15 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -13,6 +13,7 @@ import { BaseDataService } from 'src/core/modules/data/service/base-data.service import { ItemQueueModel } from 'src/modules/item-related/item-queue/data/models/item-queue.model'; import * as moment from 'moment'; import * as math from 'mathjs'; +import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; @Injectable() export class QueueDataService extends BaseReadService { @@ -136,6 +137,9 @@ export class QueueService extends BaseDataService { @InjectRepository(QueueItemModel, CONNECTION_NAME.DEFAULT) private item: Repository, + @InjectRepository(ItemModel, CONNECTION_NAME.DEFAULT) + private itemMaster: Repository, + @InjectDataSource(CONNECTION_NAME.DEFAULT) private dataSource: DataSource, ) { @@ -145,7 +149,13 @@ export class QueueService extends BaseDataService { async queues(ids: string[]) { const start = moment().startOf('day').valueOf(); const end = moment().endOf('day').valueOf(); - return this.repo.find({ + const playEstimations = {}; + + for (const id of ids) { + playEstimations[id] = await this.itemAverageTimeEstimation(id); + } + + const queues = await this.repo.find({ where: { item_queue_id: In(ids), time: Between(start, end), @@ -154,6 +164,24 @@ export class QueueService extends BaseDataService { time: 'ASC', }, }); + + queues.forEach((queue) => { + queue.average = playEstimations[queue.item_queue_id]; + }); + + return queues; + } + + async itemAverageTimeEstimation(item_queue_id: string) { + const items = await this.itemMaster.find({ + where: { + item_queue_id, + }, + }); + + const times = items.map((item) => item.play_estimation ?? 0); + const average = math.mean(times) * 60 * 1000; // change average minute to milliseconds + return average; } async getTicketItems(ticket_id: string, item_id: string) { diff --git a/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts b/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts index 05f9d28..ce3d387 100644 --- a/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts +++ b/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts @@ -1,6 +1,8 @@ import { QueueModel } from 'src/modules/queue/data/models/queue.model'; import { toTime } from '../../helpers/time.helper'; import * as math from 'mathjs'; +import { QueueTimeFormula } from './queue-time.formula'; +import * as moment from 'moment'; export class QueueCondition { private ticketItems = {}; @@ -14,23 +16,59 @@ export class QueueCondition { condition(item_id: string) { const queues: QueueModel[] = this.ticketItems[item_id] ?? []; - const time = queues[0]; - const nearest = time ? toTime(time.time) : 0; + const playEstimation = queues[0]?.average ?? 0; + const [time, last] = this.queueTime(queues, playEstimation); + const nearest = time ? toTime(time) : 0; + const lastTime = last ? toTime(last + playEstimation) : 0; + + const queuePeople = this.queuePeople(queues); - const last = [...queues].pop(); - const lastTime = last ? toTime(last.time) : 0; return { - available: queues.length == 0, + available: queuePeople == 0, average: this.averageTime(queues), nearest: nearest, - crowded_level: queues.length, + crowded_level: queuePeople, available_time: lastTime, }; } + queuePeople(queues: QueueModel[]): number { + const queue = this.activeQueue(queues); + const queuePeople = queue.reduce((acc, q) => { + return acc + q.qty; + }, 0); + return queuePeople; + } + + activeQueue(queues: QueueModel[]) { + return queues.filter((q) => q.status == 'waiting'); + } + + queueTime(queues: QueueModel[], average = 0): number[] { + const calledQueue = queues.filter((q) => + ['called', 'done'].includes(q.status), + ); + const lastCalledQueue = calledQueue[calledQueue.length - 1]; + const activeQueues = this.activeQueue(queues); + + const queueTimes = QueueTimeFormula.queueTime( + activeQueues, + lastCalledQueue, + average, + ); + + const queueEstimation = Object.values(queueTimes); + + const first = queueEstimation[0] ?? moment().valueOf(); + const last = queueEstimation[queueEstimation.length - 1] ?? 0; + return [first, last]; + } + averageTime(queues: QueueModel[]) { if (queues.length == 0) return 0; - const calledQueue = queues.filter((q) => q.status === 'called'); + const calledQueue = queues.filter((q) => + ['called', 'done'].includes(q.status), + ); if (calledQueue.length < 1) return 0; diff --git a/src/modules/queue/domain/usecases/formula/queue-time.formula.ts b/src/modules/queue/domain/usecases/formula/queue-time.formula.ts index a56dd40..20e1e95 100644 --- a/src/modules/queue/domain/usecases/formula/queue-time.formula.ts +++ b/src/modules/queue/domain/usecases/formula/queue-time.formula.ts @@ -5,6 +5,7 @@ import { QueueDataService } from 'src/modules/queue/data/services/queue.service' import { Repository } from 'typeorm'; import * as moment from 'moment'; import * as math from 'mathjs'; +import { QueueModel } from 'src/modules/queue/data/models/queue.model'; export class QueueTimeFormula { constructor( @@ -31,9 +32,18 @@ export class QueueTimeFormula { const calledQueue = await this.queueDataService.lastQueue(item_queue_id); + return QueueTimeFormula.queueTime(queues, calledQueue, average); + } + + static queueTime( + queues: QueueModel[], + lastQueue: QueueModel, + average: number, + ) { const queueTimes = {}; + if (queues.length < 1) return queueTimes; const timeNow = moment().valueOf(); - const lastCall = calledQueue?.call_time ?? timeNow - average; + const lastCall = lastQueue?.call_time ?? timeNow - average; const callTime = +lastCall + average; const currentQueueCallTime = timeNow > callTime ? timeNow : callTime; diff --git a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts index 915e850..8c5ab2b 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts @@ -106,7 +106,7 @@ export class CustomerQueueManager { const queueItem = item.item.item_queue ?? item.item; return { id: item.item_id, - queue_item_id: item.id, + queue_item_id: queueItem.id, title: queueItem.name, image_url: item.item.image_url, qty: item_qty, diff --git a/src/modules/queue/domain/usecases/register-queue.manager.ts b/src/modules/queue/domain/usecases/register-queue.manager.ts index 10176bd..3b5679b 100644 --- a/src/modules/queue/domain/usecases/register-queue.manager.ts +++ b/src/modules/queue/domain/usecases/register-queue.manager.ts @@ -10,10 +10,14 @@ import { QueueItemModel, QueueModel } from '../../data/models/queue.model'; import { padCode } from 'src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper'; import { QueueBucketReadService } from '../../data/services/queue-bucket'; import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; +import { QueueTimeFormula } from './formula/queue-time.formula'; @Injectable() export class RegisterQueueManager extends BaseCreateManager { - constructor(private readonly bucketService: QueueBucketReadService) { + constructor( + private readonly bucketService: QueueBucketReadService, + private readonly queueTimeFormula: QueueTimeFormula, + ) { super(); } @@ -22,11 +26,22 @@ export class RegisterQueueManager extends BaseCreateManager { return item.play_estimation; } + async queueTime(queue_id: string): Promise { + const queueTimes = await this.queueTimeFormula.items(queue_id); + const queues = Object.values(queueTimes); + + const first = queues[0]; + const last = queues[queues.length - 1]; + return [first, last]; + } + async beforeProcess(): Promise { const vip = this.data.vip ?? false; const item = await this.getItemMaster(); + const [, end] = await this.queueTime(item.item_queue_id); + const queueNumber = await this.bucketService.getQueue( - this.data.item_id, + item.item_queue_id, vip, ); const prefix = vip ? 'B' : 'A'; @@ -34,7 +49,7 @@ export class RegisterQueueManager extends BaseCreateManager { Object.assign(this.data, { status: STATUS.WAITING, - time: new Date().getTime(), + time: end, item_queue_id: item.item_queue_id, vip, code, diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index 57d42ca..d6413e9 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -15,6 +15,7 @@ import { RegisterQueueDto } from '../controllers/dto/register-queue.dto'; import { QueueService } from '../../data/services/queue.service'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { QueueBucketReadService } from '../../data/services/queue-bucket'; +import { QueueTimeFormula } from '../../domain/usecases/formula/queue-time.formula'; @EventsHandler(TransactionChangeStatusEvent, TransactionCreateQueueEvent) export class QueueTransactionHandler @@ -25,6 +26,7 @@ export class QueueTransactionHandler private readonly ticketService: TicketDataService, private readonly queueService: QueueService, private readonly bucketService: QueueBucketReadService, + private readonly queueTimeFormula: QueueTimeFormula, ) {} async handle(event: TransactionChangeStatusEvent) { @@ -125,7 +127,10 @@ export class QueueTransactionHandler item_id: queue.id, vip: true, }; - const registerQueueManager = new RegisterQueueManager(this.bucketService); + const registerQueueManager = new RegisterQueueManager( + this.bucketService, + this.queueTimeFormula, + ); registerQueueManager.setData(queueRequest); registerQueueManager.setService(this.queueService, TABLE_NAME.QUEUE); await registerQueueManager.execute(); From 61045a39abd757944ca6c3312e85e4b3126ac3e2 Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 21 Nov 2024 18:28:25 +0700 Subject: [PATCH 76/94] fix(SPG-1027): total ticket not match --- .../queue/customer-queue-item-list.manager.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts index b385e68..9c03ab3 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts @@ -12,31 +12,51 @@ export class CustomerQueueItemListManager extends CustomerQueueManager { } get data() { const tickets = this.tickets; + const ticketCount = {}; const queueCondition = new QueueCondition(this.queues); const ticketItems = {}; tickets.forEach((ticket) => { + const ticketItems = []; ticket.items.forEach((item) => { const item_id = item.item.item_queue?.id ?? item.item.id; const currentItem = ticketItems[item_id]; ticketItems[item_id] = currentItem ? [...currentItem, item] : [item]; + ticketItems.push(item_id); + }); + + const uniqueTicket = Array.from(new Set(ticketItems)); + + uniqueTicket.forEach((item_id) => { + ticketCount[item_id] = ticketCount[item_id] + ? ticketCount[item_id] + 1 + : 1; }); }); return Object.values(ticketItems).map((items) => { const item = items[0]; const item_qty = items.reduce((acc, item) => acc + item.qty, 0); + const queue_qty = this.queueItemQty(items); const queueItem = item.item.item_queue ?? item.item; return { id: queueItem.id, queue_item_id: item.id, title: queueItem.name, image_url: item.item.image_url, - qty: item_qty, + qty: item_qty - queue_qty, + ticket: ticketCount[queueItem.id], queue_condition: queueCondition.condition(queueItem.id), ...queueCondition.condition(queueItem.id), }; }); } + + private queueItemQty(queues: QueueItemModel[]) { + return queues.reduce( + (acc, item) => acc + item.queue.reduce((acc, q) => acc + q.qty, 0), + 0, + ); + } } From e09190df42944672b39349e808ee62a347e4cbac Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 21 Nov 2024 18:48:49 +0700 Subject: [PATCH 77/94] fix: time empty when no queue --- .../usecases/queue/customer-queue-item-list.manager.ts | 6 +++--- src/modules/queue/domain/usecases/register-queue.manager.ts | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts index 9c03ab3..56c6a39 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts @@ -17,15 +17,15 @@ export class CustomerQueueItemListManager extends CustomerQueueManager { const ticketItems = {}; tickets.forEach((ticket) => { - const ticketItems = []; + const itemsInTicket = []; ticket.items.forEach((item) => { const item_id = item.item.item_queue?.id ?? item.item.id; const currentItem = ticketItems[item_id]; ticketItems[item_id] = currentItem ? [...currentItem, item] : [item]; - ticketItems.push(item_id); + itemsInTicket.push(item_id); }); - const uniqueTicket = Array.from(new Set(ticketItems)); + const uniqueTicket = Array.from(new Set(itemsInTicket)); uniqueTicket.forEach((item_id) => { ticketCount[item_id] = ticketCount[item_id] diff --git a/src/modules/queue/domain/usecases/register-queue.manager.ts b/src/modules/queue/domain/usecases/register-queue.manager.ts index 3b5679b..19e8204 100644 --- a/src/modules/queue/domain/usecases/register-queue.manager.ts +++ b/src/modules/queue/domain/usecases/register-queue.manager.ts @@ -11,6 +11,7 @@ import { padCode } from 'src/modules/transaction/vip-code/domain/usecases/manage import { QueueBucketReadService } from '../../data/services/queue-bucket'; import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; import { QueueTimeFormula } from './formula/queue-time.formula'; +import * as moment from 'moment'; @Injectable() export class RegisterQueueManager extends BaseCreateManager { @@ -31,7 +32,7 @@ export class RegisterQueueManager extends BaseCreateManager { const queues = Object.values(queueTimes); const first = queues[0]; - const last = queues[queues.length - 1]; + const last = queues[queues.length - 1] ?? moment().valueOf(); return [first, last]; } From 83d53847d7da8a26327cb3df9f207ecfd1d52da6 Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 22 Nov 2024 10:54:51 +0700 Subject: [PATCH 78/94] feat: add estimation time --- src/modules/queue/domain/usecases/index-queue.manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/queue/domain/usecases/index-queue.manager.ts b/src/modules/queue/domain/usecases/index-queue.manager.ts index 0db811f..a606a2b 100644 --- a/src/modules/queue/domain/usecases/index-queue.manager.ts +++ b/src/modules/queue/domain/usecases/index-queue.manager.ts @@ -48,7 +48,7 @@ export class IndexQueueManager extends BaseIndexManager { data: result.data.map((queue) => { return { ...queue, - time: this.queueTimes[queue.id], + estimation: this.queueTimes[queue.id], }; }), }; From b4bc31463ed5a059df4d253ad12e0abfaa98b6e2 Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 22 Nov 2024 14:49:42 +0700 Subject: [PATCH 79/94] fix(SPG-1049): add order to queue item --- .../controllers/dto/queue.filter.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/modules/queue/infrastructure/controllers/dto/queue.filter.ts b/src/modules/queue/infrastructure/controllers/dto/queue.filter.ts index c48bb27..6f33aac 100644 --- a/src/modules/queue/infrastructure/controllers/dto/queue.filter.ts +++ b/src/modules/queue/infrastructure/controllers/dto/queue.filter.ts @@ -1,7 +1,8 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsBoolean, ValidateIf } from 'class-validator'; +import { IsBoolean, IsEnum, ValidateIf } from 'class-validator'; import { Transform } from 'class-transformer'; import { FilterQueueEntity } from 'src/modules/queue/domain/entities/filter.entity'; +import { ORDER_TYPE } from 'src/core/strings/constants/base.constants'; export class QueueDto implements FilterQueueEntity { @ApiProperty({ @@ -21,4 +22,20 @@ export class QueueDto implements FilterQueueEntity { @IsBoolean() @ValidateIf((body) => body.vip) vip: boolean; + + @ApiProperty({ type: String, required: false }) + order_by: string; + + @ApiProperty({ + type: 'string', + required: false, + description: `Select ("${ORDER_TYPE.ASC}", "${ORDER_TYPE.DESC}")`, + }) + @ValidateIf((body) => body.order_type) + @IsEnum(ORDER_TYPE, { + message: `order_type must be a valid enum ${JSON.stringify( + Object.values(ORDER_TYPE), + )}`, + }) + order_type: ORDER_TYPE; } From 7305dfd068cff60f23b516c61e203db5e1cfc554 Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 22 Nov 2024 17:27:05 +0700 Subject: [PATCH 80/94] fix(SPG-1060): order queue by time --- .../queue/domain/usecases/queue/customer-queue-list.manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts index 2173a35..88e36c5 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-list.manager.ts @@ -14,7 +14,7 @@ export class CustomerQueueListManager extends CustomerQueueManager { return queue.flat(); }); - return ticket.flat(); + return ticket.flat().sort((a, b) => b.time - a.time); } get data() { From f7e3d5399c08032e8eef763c77935d4541300162 Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 22 Nov 2024 18:51:22 +0700 Subject: [PATCH 81/94] fix(SPG-1055): update QR when customer and phone exist --- .../queue/domain/queue.orchestrator.ts | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index 2f0d6b3..6cd07e1 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -21,7 +21,7 @@ import * as moment from 'moment'; import { CustomerQueueTicketSummaryManager } from './usecases/queue/customer-queue-ticket.manager'; import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; import { CustomerQueuePosItemManager } from './usecases/queue/customer-queue-pos-item.manager'; -import { QueueTicketModel } from '../data/models/queue.model'; +import { QueueOrderModel, QueueTicketModel } from '../data/models/queue.model'; @Injectable() export class QueueOrchestrator { @@ -85,6 +85,28 @@ export class QueueOrchestrator { const queueIds = data.items.map((i) => i.queue_item_id); const queue = await this.dataService.orderItems(data.order_id, queueIds); + const existTicket = await this.dataService.ticketByUser( + data.name, + data.phone, + ); + + if (existTicket) { + const itemTickets = this.itemsFromOrder(queue); + + const items = data.items.map((item) => { + const item_id = itemTickets[item.queue_item_id]; + return { + item_queue_id: item_id, + item_id: item_id, + qty: item.qty, + }; + }); + existTicket.items.push(...items); + await this.dataService.updateQueueTicket(existTicket); + + return queue; + } + this.splitQueueManager.setRequestData(data); this.splitQueueManager.setData(queue); this.splitQueueManager.setService( @@ -95,6 +117,17 @@ export class QueueOrchestrator { return this.splitQueueManager.getResult(); } + itemsFromOrder(order: QueueOrderModel) { + const itemTickets = {}; + order.tickets.forEach((ticket) => { + ticket.items.forEach((item) => { + itemTickets[item.id] = item.item_id; + }); + }); + + return itemTickets; + } + async queuePOSTickets(order_id: string): Promise { const tickets = await this.dataService.queuePosTickets(order_id); const manager = new CustomerQueuePosItemManager(tickets); From 9db5c4b3268aec64989b2ad143cc8abb4f542a79 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 26 Nov 2024 10:31:06 +0700 Subject: [PATCH 82/94] feat: add queue recommendations --- .../queue/data/services/queue.service.ts | 15 +++++++++++-- .../queue/domain/queue.orchestrator.ts | 14 +++++++++++++ .../queue/customer-queue-item-list.manager.ts | 4 ++-- .../queue/customer-queue-recommend.manager.ts | 21 +++++++++++++++++++ .../controllers/queue.controller.ts | 5 +++++ 5 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 src/modules/queue/domain/usecases/queue/customer-queue-recommend.manager.ts diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index 4f7ab15..c040f38 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; -import { Between, DataSource, In, Repository } from 'typeorm'; +import { Between, DataSource, In, Not, Repository } from 'typeorm'; import { QueueItemModel, QueueModel, @@ -57,6 +57,17 @@ export class QueueDataService extends BaseReadService { }); } + async exclude(item_queue_id: string[]) { + const queues = await this.itemQueueRepo.find({ + relations: ['items'], + where: { + id: Not(In(item_queue_id)), + }, + }); + + return queues.filter((q) => q.items.length > 0); + } + async lastQueue(item_queue_id: string) { const start = moment().startOf('day').valueOf(); const end = moment().endOf('day').valueOf(); @@ -180,7 +191,7 @@ export class QueueService extends BaseDataService { }); const times = items.map((item) => item.play_estimation ?? 0); - const average = math.mean(times) * 60 * 1000; // change average minute to milliseconds + const average = times.length > 0 ? math.mean(times) * 60 * 1000 : 0; // change average minute to milliseconds return average; } diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index 6cd07e1..db15418 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -3,6 +3,7 @@ import { TicketDataService } from '../data/services/ticket.service'; import { QueueOrder } from './entities/order.entity'; import { Queue } from './entities/queue.entity'; import { + QueueDataService, QueueOrderService, QueueService, } from '../data/services/queue.service'; @@ -22,6 +23,7 @@ import { CustomerQueueTicketSummaryManager } from './usecases/queue/customer-que import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; import { CustomerQueuePosItemManager } from './usecases/queue/customer-queue-pos-item.manager'; import { QueueOrderModel, QueueTicketModel } from '../data/models/queue.model'; +import { CustomerQueueRecommendManager } from './usecases/queue/customer-queue-recommend.manager'; @Injectable() export class QueueOrchestrator { @@ -32,6 +34,7 @@ export class QueueOrchestrator { private readonly queueOrderService: QueueOrderService, private readonly registerQueueManager: RegisterQueueManager, private readonly splitQueueManager: SplitQueueManager, + private readonly queueDataService: QueueDataService, ) {} async loginCustomer(id: string): Promise { @@ -169,6 +172,17 @@ export class QueueOrchestrator { return manager.data; } + async queueItemRecommendation(order_id: string): Promise { + const tickets = await this.dataService.queueTickets(order_id); + const queueItemIds = this.getQueueItemFromTickets(tickets); + const recommendItems = await this.queueDataService.exclude(queueItemIds); + const recommendIds = recommendItems.map((item) => item.id); + const queues = await this.queueService.queues(recommendIds); + const manager = new CustomerQueueRecommendManager(tickets); + manager.currentQueues(queues); + return manager.recommend(recommendItems); + } + async queueItems(order_id: string): Promise { const tickets = await this.dataService.queueTickets(order_id); const queueItemIds = this.getQueueItemFromTickets(tickets); diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts index 56c6a39..23431a6 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-item-list.manager.ts @@ -6,7 +6,7 @@ import { CustomerQueueManager } from './customer-queue.manager'; import { QueueCondition } from '../formula/queue-condition.formula'; export class CustomerQueueItemListManager extends CustomerQueueManager { - private queues: QueueModel[] = []; + protected queues: QueueModel[] = []; currentQueues(queues: QueueModel[]) { this.queues = queues; } @@ -53,7 +53,7 @@ export class CustomerQueueItemListManager extends CustomerQueueManager { }); } - private queueItemQty(queues: QueueItemModel[]) { + protected queueItemQty(queues: QueueItemModel[]) { return queues.reduce( (acc, item) => acc + item.queue.reduce((acc, q) => acc + q.qty, 0), 0, diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-recommend.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-recommend.manager.ts new file mode 100644 index 0000000..f1273a9 --- /dev/null +++ b/src/modules/queue/domain/usecases/queue/customer-queue-recommend.manager.ts @@ -0,0 +1,21 @@ +import { QueueCondition } from '../formula/queue-condition.formula'; +import { CustomerQueueItemListManager } from './customer-queue-item-list.manager'; +import { ItemQueueModel } from 'src/modules/item-related/item-queue/data/models/item-queue.model'; + +export class CustomerQueueRecommendManager extends CustomerQueueItemListManager { + recommend(items: ItemQueueModel[]) { + const queueCondition = new QueueCondition(this.queues); + + return items.map((queueItem) => { + const item = queueItem.items[0]; + return { + id: item.id, + queue_item_id: queueItem.id, + title: queueItem.name, + image_url: item.image_url, + + queue_condition: queueCondition.condition(queueItem.id), + }; + }); + } +} diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index ad6e3a5..5294eb5 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -66,6 +66,11 @@ export class QueueController { return await this.orchestrator.queueTicketSummary(id, ticket_id); } + @Get(':id/recommendations') + async queueItemRecommends(@Param('id') id: string): Promise { + return await this.orchestrator.queueItemRecommendation(id); + } + @Get(':id/items') async queueItems(@Param('id') id: string): Promise { return await this.orchestrator.queueItems(id); From b4f141e628020bef59dd032f55f30a278e6ba11e Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 26 Nov 2024 12:40:02 +0700 Subject: [PATCH 83/94] feat(SPG-1061): add bundling to queue --- .../handlers/transaction.handler.ts | 58 +++++++++++++++---- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/src/modules/queue/infrastructure/handlers/transaction.handler.ts b/src/modules/queue/infrastructure/handlers/transaction.handler.ts index d6413e9..b3e5b42 100644 --- a/src/modules/queue/infrastructure/handlers/transaction.handler.ts +++ b/src/modules/queue/infrastructure/handlers/transaction.handler.ts @@ -16,6 +16,7 @@ import { QueueService } from '../../data/services/queue.service'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { QueueBucketReadService } from '../../data/services/queue-bucket'; import { QueueTimeFormula } from '../../domain/usecases/formula/queue-time.formula'; +import { TransactionItemModel } from 'src/modules/transaction/transaction/data/models/transaction-item.model'; @EventsHandler(TransactionChangeStatusEvent, TransactionCreateQueueEvent) export class QueueTransactionHandler @@ -41,7 +42,12 @@ export class QueueTransactionHandler where: { id: event.data.id, }, - relations: ['customer_category', 'items', 'items.item'], + relations: [ + 'customer_category', + 'items', + 'items.item', + 'items.item.bundling_items', + ], }); const date = transaction.booking_date ?? transaction.invoice_date; @@ -57,15 +63,7 @@ export class QueueTransactionHandler transaction_id: id, }; - const items = transaction.items - .filter((item) => item.item.use_queue) - .map((item) => { - return { - item_queue_id: item.item.item_queue_id, - item_id: item.item_id, - qty: item.qty, - }; - }); + const items = this.generateItems(transaction.items); const existTicket = await this.ticketService.ticketByUser( customer_name, @@ -117,6 +115,46 @@ export class QueueTransactionHandler } } + generateItems(items: TransactionItemModel[]): QueueItem[] { + const transactionItems = []; + + items.forEach((item) => { + if (item.item.use_queue) { + transactionItems.push({ + item_queue_id: item.item.item_queue_id, + item_id: item.item_id, + qty: item.qty, + }); + } + + if (item.item.bundling_items.length > 0) { + item.item.bundling_items.forEach((bundling_item) => { + if (bundling_item.use_queue) { + transactionItems.push({ + item_queue_id: bundling_item.item_queue_id, + item_id: bundling_item.id, + qty: item.qty, + }); + } + }); + } + }); + + return this.mergeQueueItems(transactionItems); + } + + mergeQueueItems(arr) { + const result = {}; + arr.forEach((item) => { + if (result[item.item_id]) { + result[item.item_id].qty += item.qty; + } else { + result[item.item_id] = { ...item }; + } + }); + return Object.values(result); + } + async create(data: RegisterQueueDto): Promise { const queue = await this.queueService.getTicketItems( data.ticket_id, From 4a3e77043e0d29ba2f6137d05f5194dcff34edff Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 28 Nov 2024 11:24:31 +0700 Subject: [PATCH 84/94] fix: parent qty not reduce when split QR --- src/modules/queue/domain/queue.orchestrator.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index db15418..bbb25a0 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -107,6 +107,10 @@ export class QueueOrchestrator { existTicket.items.push(...items); await this.dataService.updateQueueTicket(existTicket); + data.items.forEach((item) => { + this.queueService.updateItemQty(item.queue_item_id, item.qty); + }); + return queue; } From f661276c5851209f58da578acab8b0eac4e23b15 Mon Sep 17 00:00:00 2001 From: shancheas Date: Thu, 28 Nov 2024 17:18:33 +0700 Subject: [PATCH 85/94] fix: calculate average time when no queue --- src/modules/queue/domain/usecases/formula/queue-time.formula.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/queue/domain/usecases/formula/queue-time.formula.ts b/src/modules/queue/domain/usecases/formula/queue-time.formula.ts index 20e1e95..9151cae 100644 --- a/src/modules/queue/domain/usecases/formula/queue-time.formula.ts +++ b/src/modules/queue/domain/usecases/formula/queue-time.formula.ts @@ -28,7 +28,7 @@ export class QueueTimeFormula { }); const times = itemQueue.items.map((item) => item.play_estimation ?? 0); - const average = math.mean(times) * 60 * 1000; // change average minute to milliseconds + const average = times.length > 0 ? math.mean(times) * 60 * 1000 : 0; // change average minute to milliseconds const calledQueue = await this.queueDataService.lastQueue(item_queue_id); From d71c2096b8fa01af5da9baedfc332a440d446859 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 3 Dec 2024 11:05:03 +0700 Subject: [PATCH 86/94] fix: done queue not update call time --- .../migrations/1729151429165-queue-table.ts | 2 +- .../queue/domain/queue-admin.orchestrator.ts | 14 +++++++++----- .../queue/domain/usecases/call-queue.manager.ts | 7 +++++++ src/modules/queue/queue.module.ts | 6 +++++- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/database/migrations/1729151429165-queue-table.ts b/src/database/migrations/1729151429165-queue-table.ts index 27f1dd7..cb3b8e8 100644 --- a/src/database/migrations/1729151429165-queue-table.ts +++ b/src/database/migrations/1729151429165-queue-table.ts @@ -15,7 +15,7 @@ export class QueueTable1729151429165 implements MigrationInterface { ); await queryRunner.query( - `ALTER TABLE "queue_tickets" ADD CONSTRAINT "FK_0e9823b8b7ca9523b3be73878e5" FOREIGN KEY ("order_id") REFERENCES "queue_orders"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + `ALTER TABLE "queue_tickets" ADD CONSTRAINT "FK_0e9823b8b7ca9523b3be73878e5" FOREIGN KEY ("order_id") REFERENCES "queue_orders"("id") ON DELETE SET NULL ON UPDATE CASCADE`, ); await queryRunner.query( `ALTER TABLE "queue_items" ADD CONSTRAINT "FK_25352739034765f6917757df74b" FOREIGN KEY ("ticket_id") REFERENCES "queue_tickets"("id") ON DELETE CASCADE ON UPDATE CASCADE`, diff --git a/src/modules/queue/domain/queue-admin.orchestrator.ts b/src/modules/queue/domain/queue-admin.orchestrator.ts index 36be553..30d6b4d 100644 --- a/src/modules/queue/domain/queue-admin.orchestrator.ts +++ b/src/modules/queue/domain/queue-admin.orchestrator.ts @@ -4,7 +4,10 @@ import { IndexQueueManager } from './usecases/index-queue.manager'; import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; import { Queue } from './entities/queue.entity'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; -import { CallQueueManager } from './usecases/call-queue.manager'; +import { + CallQueueManager, + DoneQueueManager, +} from './usecases/call-queue.manager'; import { ORDER_TYPE, QUEUE_STATUS, @@ -17,6 +20,7 @@ export class QueueAdminOrchestrator { private readonly service: QueueService, private indexManager: IndexQueueManager, private callManager: CallQueueManager, + private doneManager: DoneQueueManager, ) {} async index(params): Promise> { @@ -34,9 +38,9 @@ export class QueueAdminOrchestrator { } async done(dataId): Promise { - this.callManager.setData(dataId, QUEUE_STATUS.DONE); - this.callManager.setService(this.service, TABLE_NAME.QUEUE); - await this.callManager.execute(); - return this.callManager.getResult(); + this.doneManager.setData(dataId, QUEUE_STATUS.DONE); + this.doneManager.setService(this.service, TABLE_NAME.QUEUE); + await this.doneManager.execute(); + return this.doneManager.getResult(); } } diff --git a/src/modules/queue/domain/usecases/call-queue.manager.ts b/src/modules/queue/domain/usecases/call-queue.manager.ts index a49ca11..edce656 100644 --- a/src/modules/queue/domain/usecases/call-queue.manager.ts +++ b/src/modules/queue/domain/usecases/call-queue.manager.ts @@ -38,3 +38,10 @@ export class CallQueueManager extends BaseUpdateStatusManager { return []; } } + +@Injectable() +export class DoneQueueManager extends CallQueueManager { + async beforeProcess(): Promise { + return; + } +} diff --git a/src/modules/queue/queue.module.ts b/src/modules/queue/queue.module.ts index 19b2637..a117956 100644 --- a/src/modules/queue/queue.module.ts +++ b/src/modules/queue/queue.module.ts @@ -26,7 +26,10 @@ import { import { QueueAdminController } from './infrastructure/controllers/queue-admin.controller'; import { QueueAdminOrchestrator } from './domain/queue-admin.orchestrator'; import { IndexQueueManager } from './domain/usecases/index-queue.manager'; -import { CallQueueManager } from './domain/usecases/call-queue.manager'; +import { + CallQueueManager, + DoneQueueManager, +} from './domain/usecases/call-queue.manager'; import { RegisterQueueManager } from './domain/usecases/register-queue.manager'; import { QueueBucketModel } from './data/models/queue-bucket.model'; import { QueueBucketReadService } from './data/services/queue-bucket'; @@ -70,6 +73,7 @@ import { QueueTimeFormula } from './domain/usecases/formula/queue-time.formula'; IndexQueueManager, CallQueueManager, + DoneQueueManager, RegisterQueueManager, SplitQueueManager, From 5f6214eeb6d15b473b6038d776964e065ca74c2a Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 3 Dec 2024 11:21:44 +0700 Subject: [PATCH 87/94] feat: add max peak level and call prepare call time to item queue --- ...0134-item-queue-add-time-and-peak-level.ts | 25 +++++++++++++++++++ .../data/models/item-queue.model.ts | 6 +++++ .../domain/entities/item-queue.entity.ts | 2 ++ .../managers/detail-item-queue.manager.ts | 2 ++ .../managers/index-item-queue.manager.ts | 2 ++ .../infrastructure/dto/item-queue.dto.ts | 20 ++++++++++++++- 6 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 src/database/migrations/1733199330134-item-queue-add-time-and-peak-level.ts diff --git a/src/database/migrations/1733199330134-item-queue-add-time-and-peak-level.ts b/src/database/migrations/1733199330134-item-queue-add-time-and-peak-level.ts new file mode 100644 index 0000000..6a21374 --- /dev/null +++ b/src/database/migrations/1733199330134-item-queue-add-time-and-peak-level.ts @@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ItemQueueAddTimeAndPeakLevel1733199330134 + implements MigrationInterface +{ + name = 'ItemQueueAddTimeAndPeakLevel1733199330134'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "item_queues" ADD "max_peak_level" integer NOT NULL DEFAULT '100'`, + ); + await queryRunner.query( + `ALTER TABLE "item_queues" ADD "call_preparation" integer NOT NULL DEFAULT '5'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "item_queues" DROP COLUMN "call_preparation"`, + ); + await queryRunner.query( + `ALTER TABLE "item_queues" DROP COLUMN "max_peak_level"`, + ); + } +} diff --git a/src/modules/item-related/item-queue/data/models/item-queue.model.ts b/src/modules/item-related/item-queue/data/models/item-queue.model.ts index 58733b8..7f0be80 100644 --- a/src/modules/item-related/item-queue/data/models/item-queue.model.ts +++ b/src/modules/item-related/item-queue/data/models/item-queue.model.ts @@ -10,6 +10,12 @@ export class ItemQueueModel extends BaseStatusModel implements ItemQueueEntity { + @Column('int', { default: 100 }) + max_peak_level: number; + + @Column('int', { default: 5 }) + call_preparation: number; + @Column('varchar', { name: 'name' }) name: string; diff --git a/src/modules/item-related/item-queue/domain/entities/item-queue.entity.ts b/src/modules/item-related/item-queue/domain/entities/item-queue.entity.ts index 2d706a7..5b89cfe 100644 --- a/src/modules/item-related/item-queue/domain/entities/item-queue.entity.ts +++ b/src/modules/item-related/item-queue/domain/entities/item-queue.entity.ts @@ -6,5 +6,7 @@ export interface ItemQueueEntity extends BaseStatusEntity { name: string; item_type: ItemType; information?: string; + max_peak_level: number; + call_preparation: number; items: ItemEntity[]; } diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/detail-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/detail-item-queue.manager.ts index 6b815e1..67d7861 100644 --- a/src/modules/item-related/item-queue/domain/usecases/managers/detail-item-queue.manager.ts +++ b/src/modules/item-related/item-queue/domain/usecases/managers/detail-item-queue.manager.ts @@ -36,6 +36,8 @@ export class DetailItemQueueManager extends BaseDetailManager { `${this.tableName}.creator_name`, `${this.tableName}.updated_at`, `${this.tableName}.editor_name`, + `${this.tableName}.max_peak_level`, + `${this.tableName}.call_preparation`, `items.id`, `items.created_at`, diff --git a/src/modules/item-related/item-queue/domain/usecases/managers/index-item-queue.manager.ts b/src/modules/item-related/item-queue/domain/usecases/managers/index-item-queue.manager.ts index b1256d3..93c6eb8 100644 --- a/src/modules/item-related/item-queue/domain/usecases/managers/index-item-queue.manager.ts +++ b/src/modules/item-related/item-queue/domain/usecases/managers/index-item-queue.manager.ts @@ -39,6 +39,8 @@ export class IndexItemQueueManager extends BaseIndexManager { `${this.tableName}.creator_name`, `${this.tableName}.updated_at`, `${this.tableName}.editor_name`, + `${this.tableName}.max_peak_level`, + `${this.tableName}.call_preparation`, `items.id`, `items.created_at`, diff --git a/src/modules/item-related/item-queue/infrastructure/dto/item-queue.dto.ts b/src/modules/item-related/item-queue/infrastructure/dto/item-queue.dto.ts index fbbceb3..43cb677 100644 --- a/src/modules/item-related/item-queue/infrastructure/dto/item-queue.dto.ts +++ b/src/modules/item-related/item-queue/infrastructure/dto/item-queue.dto.ts @@ -1,12 +1,30 @@ import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.dto'; import { ItemQueueEntity } from '../../domain/entities/item-queue.entity'; -import { IsArray, IsString } from 'class-validator'; +import { IsArray, IsNumber, IsString } from 'class-validator'; import { ItemType } from '../../constants'; import { ApiProperty } from '@nestjs/swagger'; import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity'; import { Exclude, Transform } from 'class-transformer'; export class ItemQueueDto extends BaseStatusDto implements ItemQueueEntity { + @ApiProperty({ + name: 'max_peak_level', + required: false, + example: 'Menentukan level peak maksimal penuhnya wahana', + default: 100, + }) + @IsNumber() + max_peak_level: number; + + @ApiProperty({ + name: 'call_preparation', + required: false, + example: 'Waktu persiapan untuk memanggil antrian dalam menit', + default: 5, + }) + @IsNumber() + call_preparation: number; + @Exclude() items: ItemEntity[]; From 55e89426d9da75a56dd5897573e182c510a94072 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 3 Dec 2024 14:27:12 +0700 Subject: [PATCH 88/94] feat(SPG-1078): summary all item queue --- src/modules/queue/data/services/queue.service.ts | 8 ++++++++ src/modules/queue/domain/queue.orchestrator.ts | 9 +++++++++ .../queue/infrastructure/controllers/queue.controller.ts | 5 +++++ 3 files changed, 22 insertions(+) diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index c040f38..a67f0c6 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -68,6 +68,14 @@ export class QueueDataService extends BaseReadService { return queues.filter((q) => q.items.length > 0); } + async allQueue() { + const queues = await this.itemQueueRepo.find({ + relations: ['items'], + }); + + return queues.filter((q) => q.items.length > 0); + } + async lastQueue(item_queue_id: string) { const start = moment().startOf('day').valueOf(); const end = moment().endOf('day').valueOf(); diff --git a/src/modules/queue/domain/queue.orchestrator.ts b/src/modules/queue/domain/queue.orchestrator.ts index bbb25a0..112af5b 100644 --- a/src/modules/queue/domain/queue.orchestrator.ts +++ b/src/modules/queue/domain/queue.orchestrator.ts @@ -187,6 +187,15 @@ export class QueueOrchestrator { return manager.recommend(recommendItems); } + async queueItemSummary(): Promise { + const recommendItems = await this.queueDataService.allQueue(); + const recommendIds = recommendItems.map((item) => item.id); + const queues = await this.queueService.queues(recommendIds); + const manager = new CustomerQueueRecommendManager(null); + manager.currentQueues(queues); + return manager.recommend(recommendItems); + } + async queueItems(order_id: string): Promise { const tickets = await this.dataService.queueTickets(order_id); const queueItemIds = this.getQueueItemFromTickets(tickets); diff --git a/src/modules/queue/infrastructure/controllers/queue.controller.ts b/src/modules/queue/infrastructure/controllers/queue.controller.ts index 5294eb5..e7a12b7 100644 --- a/src/modules/queue/infrastructure/controllers/queue.controller.ts +++ b/src/modules/queue/infrastructure/controllers/queue.controller.ts @@ -71,6 +71,11 @@ export class QueueController { return await this.orchestrator.queueItemRecommendation(id); } + @Get(':id/queue-summary') + async queueItemSummary(): Promise { + return await this.orchestrator.queueItemSummary(); + } + @Get(':id/items') async queueItems(@Param('id') id: string): Promise { return await this.orchestrator.queueItems(id); From 8cfc0032613c9f2b6d4ce53ad4070cd60f3b457b Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 10 Dec 2024 11:57:33 +0700 Subject: [PATCH 89/94] feat: add query name filter to items --- src/core/modules/infrastructure/dto/base-filter.dto.ts | 1 + .../item/domain/usecases/managers/index-item.manager.ts | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/core/modules/infrastructure/dto/base-filter.dto.ts b/src/core/modules/infrastructure/dto/base-filter.dto.ts index b149663..a33dec5 100644 --- a/src/core/modules/infrastructure/dto/base-filter.dto.ts +++ b/src/core/modules/infrastructure/dto/base-filter.dto.ts @@ -23,6 +23,7 @@ export class BaseFilterDto implements BaseFilterEntity { @IsNumber() limit = 10; + @ApiProperty({ type: String, required: false }) q: string; @ApiProperty({ type: ['string'], required: false }) diff --git a/src/modules/item-related/item/domain/usecases/managers/index-item.manager.ts b/src/modules/item-related/item/domain/usecases/managers/index-item.manager.ts index 4da5d1a..3c51840 100644 --- a/src/modules/item-related/item/domain/usecases/managers/index-item.manager.ts +++ b/src/modules/item-related/item/domain/usecases/managers/index-item.manager.ts @@ -84,6 +84,12 @@ export class IndexItemManager extends BaseIndexManager { setQueryFilter( queryBuilder: SelectQueryBuilder, ): SelectQueryBuilder { + if (this.filterParam.q) { + queryBuilder.andWhere( + `${this.tableName}.name ILIKE '%${this.filterParam.q}%'`, + ); + } + if (this.filterParam.tenant_ids?.length) { queryBuilder.andWhere(`${this.tableName}.tenant_id In (:...tenantIds)`, { tenantIds: this.filterParam.tenant_ids, From b38c4897777320401c1a8e859e2cf5e15e15e836 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 10 Dec 2024 12:27:34 +0700 Subject: [PATCH 90/94] fix: last item queue time --- .../queue/domain/usecases/formula/queue-time.formula.ts | 3 +++ src/modules/queue/domain/usecases/register-queue.manager.ts | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/queue/domain/usecases/formula/queue-time.formula.ts b/src/modules/queue/domain/usecases/formula/queue-time.formula.ts index 9151cae..3cb72b3 100644 --- a/src/modules/queue/domain/usecases/formula/queue-time.formula.ts +++ b/src/modules/queue/domain/usecases/formula/queue-time.formula.ts @@ -14,6 +14,8 @@ export class QueueTimeFormula { private itemQueueRepo: Repository, ) {} + public average = 0; + async items(item_queue_id: string) { const queues = await this.queueDataService.waitingQueue(item_queue_id); if (queues.length == 0) { @@ -29,6 +31,7 @@ export class QueueTimeFormula { const times = itemQueue.items.map((item) => item.play_estimation ?? 0); const average = times.length > 0 ? math.mean(times) * 60 * 1000 : 0; // change average minute to milliseconds + this.average = average; const calledQueue = await this.queueDataService.lastQueue(item_queue_id); diff --git a/src/modules/queue/domain/usecases/register-queue.manager.ts b/src/modules/queue/domain/usecases/register-queue.manager.ts index 19e8204..90ec7d6 100644 --- a/src/modules/queue/domain/usecases/register-queue.manager.ts +++ b/src/modules/queue/domain/usecases/register-queue.manager.ts @@ -33,7 +33,8 @@ export class RegisterQueueManager extends BaseCreateManager { const first = queues[0]; const last = queues[queues.length - 1] ?? moment().valueOf(); - return [first, last]; + const average = this.queueTimeFormula.average; + return [first, last + average]; } async beforeProcess(): Promise { From d73752252abf1efb9ff3885201fdebcb8f960efe Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 10 Dec 2024 13:51:28 +0700 Subject: [PATCH 91/94] feat: peak level base on configuration --- src/modules/queue/data/services/queue.service.ts | 10 ++++++++-- .../usecases/formula/queue-condition.formula.ts | 11 +++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/modules/queue/data/services/queue.service.ts b/src/modules/queue/data/services/queue.service.ts index a67f0c6..bb03a82 100644 --- a/src/modules/queue/data/services/queue.service.ts +++ b/src/modules/queue/data/services/queue.service.ts @@ -169,9 +169,12 @@ export class QueueService extends BaseDataService { const start = moment().startOf('day').valueOf(); const end = moment().endOf('day').valueOf(); const playEstimations = {}; + const queuePeakLevel = {}; for (const id of ids) { - playEstimations[id] = await this.itemAverageTimeEstimation(id); + const est = await this.itemAverageTimeEstimation(id); + playEstimations[id] = est.average; + queuePeakLevel[id] = est.peakLevel; } const queues = await this.repo.find({ @@ -186,6 +189,7 @@ export class QueueService extends BaseDataService { queues.forEach((queue) => { queue.average = playEstimations[queue.item_queue_id]; + queue.peak_level = queuePeakLevel[queue.item_queue_id]; }); return queues; @@ -193,6 +197,7 @@ export class QueueService extends BaseDataService { async itemAverageTimeEstimation(item_queue_id: string) { const items = await this.itemMaster.find({ + relations: ['item_queue'], where: { item_queue_id, }, @@ -200,7 +205,8 @@ export class QueueService extends BaseDataService { const times = items.map((item) => item.play_estimation ?? 0); const average = times.length > 0 ? math.mean(times) * 60 * 1000 : 0; // change average minute to milliseconds - return average; + const peakLevel = items[0]?.item_queue?.max_peak_level ?? 100; + return { average, peakLevel }; } async getTicketItems(ticket_id: string, item_id: string) { diff --git a/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts b/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts index ce3d387..c3599c6 100644 --- a/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts +++ b/src/modules/queue/domain/usecases/formula/queue-condition.formula.ts @@ -17,11 +17,12 @@ export class QueueCondition { condition(item_id: string) { const queues: QueueModel[] = this.ticketItems[item_id] ?? []; const playEstimation = queues[0]?.average ?? 0; + const peakLevel = queues[0]?.peak_level ?? 100; const [time, last] = this.queueTime(queues, playEstimation); const nearest = time ? toTime(time) : 0; const lastTime = last ? toTime(last + playEstimation) : 0; - const queuePeople = this.queuePeople(queues); + const queuePeople = this.queuePeople(queues, peakLevel); return { available: queuePeople == 0, @@ -32,12 +33,13 @@ export class QueueCondition { }; } - queuePeople(queues: QueueModel[]): number { + queuePeople(queues: QueueModel[], peakLevel): number { const queue = this.activeQueue(queues); + const level = peakLevel / 100; const queuePeople = queue.reduce((acc, q) => { return acc + q.qty; }, 0); - return queuePeople; + return queuePeople * level; } activeQueue(queues: QueueModel[]) { @@ -76,6 +78,7 @@ export class QueueCondition { return (queue.call_time - queue.time) / 1000; }); - return Math.ceil(math.mean(times)); + const avg = Math.ceil(math.mean(times)); + return Math.max(0, avg); } } From e3db958e0d79a4cd490c3365d87f3162c0b887e7 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 10 Dec 2024 14:00:38 +0700 Subject: [PATCH 92/94] fix: error ticket not found when split queue --- .../queue/data/services/ticket.service.ts | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/modules/queue/data/services/ticket.service.ts b/src/modules/queue/data/services/ticket.service.ts index db6bdd9..ed78fc9 100644 --- a/src/modules/queue/data/services/ticket.service.ts +++ b/src/modules/queue/data/services/ticket.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, UnprocessableEntityException } from '@nestjs/common'; import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; import { InjectRepository } from '@nestjs/typeorm'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; @@ -137,17 +137,23 @@ export class TicketDataService extends BaseDataService { item_ids: string[], ): Promise { const order = await this.orderIds(order_id); - return this.order.findOneOrFail({ - relations: ['tickets', 'tickets.items'], - where: { - tickets: { - order_id: In(order), - items: { - id: In(item_ids), + try { + const ticket = await this.order.findOneOrFail({ + relations: ['tickets', 'tickets.items'], + where: { + tickets: { + order_id: In(order), + items: { + id: In(item_ids), + }, }, }, - }, - }); + }); + + return ticket; + } catch (error) { + throw new UnprocessableEntityException('Ticket tidak dapat ditemukan'); + } } async queuePosTickets(order_id: string): Promise { From 6dc65794501acd14bef696ecce17a16ad6293700 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 10 Dec 2024 14:19:24 +0700 Subject: [PATCH 93/94] feat: add item id --- src/modules/queue/domain/entities/queue-item.entity.ts | 1 + .../domain/usecases/queue/customer-queue-pos-item.manager.ts | 2 +- .../queue/domain/usecases/queue/customer-queue.manager.ts | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/queue/domain/entities/queue-item.entity.ts b/src/modules/queue/domain/entities/queue-item.entity.ts index 7cbf761..36b434d 100644 --- a/src/modules/queue/domain/entities/queue-item.entity.ts +++ b/src/modules/queue/domain/entities/queue-item.entity.ts @@ -13,6 +13,7 @@ export interface QueueItem extends BaseCoreEntity { export interface MergedItemQueue extends BaseCoreEntity { id: string; queue_item_id: string; + item_id: string; title: string; image_url: string; qty: number; diff --git a/src/modules/queue/domain/usecases/queue/customer-queue-pos-item.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue-pos-item.manager.ts index 9629493..663f68d 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue-pos-item.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue-pos-item.manager.ts @@ -14,7 +14,7 @@ export class CustomerQueuePosItemManager extends CustomerQueueManager { items: queueItems.map((item) => { return { id: item.id, - item_queue_id: item.queue_item_id, + item_queue_id: item.item_id, title: item.title, image_url: item.image_url, summary: { diff --git a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts index 8c5ab2b..0219db7 100644 --- a/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts +++ b/src/modules/queue/domain/usecases/queue/customer-queue.manager.ts @@ -107,6 +107,7 @@ export class CustomerQueueManager { return { id: item.item_id, queue_item_id: queueItem.id, + item_id: item.id, title: queueItem.name, image_url: item.item.image_url, qty: item_qty, From 661dbb8cf2a9161c0a4e46c8e9a934f51f52faf6 Mon Sep 17 00:00:00 2001 From: shancheas Date: Tue, 10 Dec 2024 15:55:27 +0700 Subject: [PATCH 94/94] fix: add peak level --- src/modules/queue/data/models/queue.model.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/queue/data/models/queue.model.ts b/src/modules/queue/data/models/queue.model.ts index 57c2b2f..eff707f 100644 --- a/src/modules/queue/data/models/queue.model.ts +++ b/src/modules/queue/data/models/queue.model.ts @@ -138,4 +138,5 @@ export class QueueModel extends BaseModel implements Queue { qty: number; average = 0; + peak_level = 100; }