From 50e7f66bb7a58735964020fc87b38a425aeebf8d Mon Sep 17 00:00:00 2001 From: shancheas Date: Fri, 1 Nov 2024 13:33:25 +0700 Subject: [PATCH] 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);