feat: split QR customer

pull/115/head
shancheas 2024-11-01 13:33:25 +07:00
parent 492a4ca2ba
commit 50e7f66bb7
9 changed files with 285 additions and 11 deletions

View File

@ -1,9 +1,13 @@
import { Injectable } from '@nestjs/common'; 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 { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { Repository } from 'typeorm'; import { DataSource, Repository } from 'typeorm';
import { QueueItemModel, QueueModel } from '../models/queue.model'; import {
QueueItemModel,
QueueModel,
QueueOrderModel,
} from '../models/queue.model';
import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; import { BaseReadService } from 'src/core/modules/data/service/base-read.service';
import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; import { BaseDataService } from 'src/core/modules/data/service/base-data.service';
@ -25,6 +29,9 @@ export class QueueService extends BaseDataService<QueueModel> {
@InjectRepository(QueueItemModel, CONNECTION_NAME.DEFAULT) @InjectRepository(QueueItemModel, CONNECTION_NAME.DEFAULT)
private item: Repository<QueueItemModel>, private item: Repository<QueueItemModel>,
@InjectDataSource(CONNECTION_NAME.DEFAULT)
private dataSource: DataSource,
) { ) {
super(repo); super(repo);
} }
@ -37,4 +44,19 @@ export class QueueService extends BaseDataService<QueueModel> {
}, },
}); });
} }
async updateItemQty(item_id: string, qty: number): Promise<void> {
const query = `UPDATE queue_items SET qty = qty - ${qty} WHERE id = '${item_id}'`;
this.dataSource.query(query);
}
}
@Injectable()
export class QueueOrderService extends BaseDataService<QueueOrderModel> {
constructor(
@InjectRepository(QueueOrderModel, CONNECTION_NAME.DEFAULT)
private repo: Repository<QueueOrderModel>,
) {
super(repo);
}
} }

View File

@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; import { BaseDataService } from 'src/core/modules/data/service/base-data.service';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; 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 { QueueTicket } from '../../domain/entities/ticket.entity';
import { import {
QueueItemModel, QueueItemModel,
@ -39,7 +39,50 @@ export class TicketDataService extends BaseDataService<QueueTicket> {
}); });
} }
async orders(order_id: string): Promise<QueueOrderModel[]> {
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<string[]> {
const orders = await this.orders(order_id);
return orders.map((order) => order.id);
}
async orderItems(
order_id: string,
item_ids: string[],
): Promise<QueueOrderModel> {
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<QueueTicketModel[]> { async queueTickets(order_id: string): Promise<QueueTicketModel[]> {
const order = await this.orderIds(order_id);
return this.repo.find({ return this.repo.find({
relations: [ relations: [
'items', 'items',
@ -48,7 +91,7 @@ export class TicketDataService extends BaseDataService<QueueTicket> {
'items.item.item_queue', 'items.item.item_queue',
], ],
where: { where: {
order_id, order_id: In(order),
}, },
}); });
} }
@ -57,6 +100,7 @@ export class TicketDataService extends BaseDataService<QueueTicket> {
order_id: string, order_id: string,
ticket_id: string, ticket_id: string,
): Promise<QueueTicketModel[]> { ): Promise<QueueTicketModel[]> {
const order = await this.orderIds(order_id);
return this.repo.find({ return this.repo.find({
relations: [ relations: [
'items', 'items',
@ -65,7 +109,7 @@ export class TicketDataService extends BaseDataService<QueueTicket> {
'items.item.item_queue', 'items.item.item_queue',
], ],
where: { where: {
order_id, order_id: In(order),
id: ticket_id, id: ticket_id,
}, },
}); });
@ -75,6 +119,7 @@ export class TicketDataService extends BaseDataService<QueueTicket> {
order_id: string, order_id: string,
item_id: string, item_id: string,
): Promise<QueueTicketModel[]> { ): Promise<QueueTicketModel[]> {
const order = await this.orderIds(order_id);
return this.repo.find({ return this.repo.find({
relations: [ relations: [
'items', 'items',
@ -83,18 +128,19 @@ export class TicketDataService extends BaseDataService<QueueTicket> {
'items.item.item_queue', 'items.item.item_queue',
], ],
where: { where: {
order_id, order_id: In(order),
items: [{ item_id }, { item: { item_queue: { id: item_id } } }], items: [{ item_id }, { item: { item_queue: { id: item_id } } }],
}, },
}); });
} }
async queueItems(order_id: string): Promise<QueueItemModel[]> { async queueItems(order_id: string): Promise<QueueItemModel[]> {
const order = await this.orderIds(order_id);
return this.item.find({ return this.item.find({
relations: ['queue', 'ticket'], relations: ['queue', 'ticket'],
where: { where: {
ticket: { ticket: {
order_id, order_id: In(order),
}, },
}, },
}); });

View File

@ -0,0 +1,7 @@
export interface QueueBucket {
id: string;
queue_item_id: string;
date: number;
regular: number;
vip: number;
}

View File

@ -2,7 +2,10 @@ import { Injectable, UnauthorizedException } from '@nestjs/common';
import { TicketDataService } from '../data/services/ticket.service'; import { TicketDataService } from '../data/services/ticket.service';
import { QueueOrder } from './entities/order.entity'; import { QueueOrder } from './entities/order.entity';
import { Queue } from './entities/queue.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 { RegisterQueueManager } from './usecases/register-queue.manager';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { RegisterQueueDto } from '../infrastructure/controllers/dto/register-queue.dto'; 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 { CustomerQueueItemManager } from './usecases/queue/customer-queue-item.manager';
import { CustomerQueueItemListManager } from './usecases/queue/customer-queue-item-list.manager'; import { CustomerQueueItemListManager } from './usecases/queue/customer-queue-item-list.manager';
import { CustomerQueueListManager } from './usecases/queue/customer-queue-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() @Injectable()
export class QueueOrchestrator { export class QueueOrchestrator {
constructor( constructor(
private readonly dataService: TicketDataService, private readonly dataService: TicketDataService,
private readonly queueService: QueueService, private readonly queueService: QueueService,
private readonly queueOrderService: QueueOrderService,
private readonly registerQueueManager: RegisterQueueManager, private readonly registerQueueManager: RegisterQueueManager,
private readonly splitQueueManager: SplitQueueManager,
) {} ) {}
async loginCustomer(id: string): Promise<QueueOrder> { async loginCustomer(id: string): Promise<QueueOrder> {
@ -46,6 +53,30 @@ export class QueueOrchestrator {
return this.registerQueueManager.getResult(); return this.registerQueueManager.getResult();
} }
async split(data: SplitQueueDto): Promise<QueueOrder> {
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<any> { async queueTickets(order_id: string): Promise<any> {
const tickets = await this.dataService.queueTickets(order_id); const tickets = await this.dataService.queueTickets(order_id);
const manager = new CustomerQueueSummaryManager(tickets); const manager = new CustomerQueueSummaryManager(tickets);

View File

@ -19,6 +19,7 @@ export class CustomerQueueItemListManager extends CustomerQueueManager {
const item_qty = items.reduce((acc, item) => acc + item.qty, 0); const item_qty = items.reduce((acc, item) => acc + item.qty, 0);
return { return {
id: item.item_id, id: item.item_id,
queue_item_id: item.id,
title: item.item.item_queue?.name ?? item.item.name, title: item.item.item_queue?.name ?? item.item.name,
image_url: item.item.image_url, image_url: item.item.image_url,
qty: item_qty, qty: item_qty,

View File

@ -5,23 +5,41 @@ import {
validateRelations, validateRelations,
} from 'src/core/strings/constants/interface.constants'; } from 'src/core/strings/constants/interface.constants';
import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; 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 { STATUS } from 'src/core/strings/constants/base.constants';
import { QueueModel } from '../../data/models/queue.model'; import { QueueModel } from '../../data/models/queue.model';
import { generateRandom } from 'src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper'; import { generateRandom } from 'src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper';
import { QueueBucketReadService } from '../../data/services/queue-bucket';
@Injectable() @Injectable()
export class RegisterQueueManager extends BaseCreateManager<QueueModel> { export class RegisterQueueManager extends BaseCreateManager<QueueModel> {
constructor(private readonly queueService: QueueBucketReadService) {
super();
}
async averageTime(): Promise<number> {
const item = await this.getItem();
return item.item.item.play_estimation;
}
async beforeProcess(): Promise<void> { async beforeProcess(): Promise<void> {
Object.assign(this.data, { Object.assign(this.data, {
status: STATUS.WAITING, status: STATUS.WAITING,
time: new Date().getTime(), time: new Date().getTime(),
vip: false, vip: false,
code: `Q${generateRandom(4, true)}`, code: `A${generateRandom(4, true)}`,
}); });
return; return;
} }
async getItem(): Promise<QueueModel> {
return this.dataService.repo.findOne({
relations: ['item', 'item.item'],
where: {
item_id: this.data.item_id,
},
});
}
async afterProcess(): Promise<void> { async afterProcess(): Promise<void> {
return; return;
} }

View File

@ -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<QueueOrderModel> {
private dto: SplitQueueDto;
constructor(private readonly queueService: QueueService) {
super();
}
setRequestData(dto: SplitQueueDto): void {
this.dto = dto;
}
prepareData(): Promise<void> {
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<void> {
return;
}
async afterProcess(): Promise<void> {
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;
}
}

View File

@ -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[];
}

View File

@ -8,6 +8,7 @@ import { QueueOrchestrator } from '../../domain/queue.orchestrator';
import { QueueOrder } from '../../domain/entities/order.entity'; import { QueueOrder } from '../../domain/entities/order.entity';
import { Queue } from '../../domain/entities/queue.entity'; import { Queue } from '../../domain/entities/queue.entity';
import { RegisterQueueDto } from './dto/register-queue.dto'; import { RegisterQueueDto } from './dto/register-queue.dto';
import { SplitQueueDto } from './dto/split-queue.dto';
@ApiTags(`Queue`) @ApiTags(`Queue`)
@Controller(`v1/${MODULE_NAME.QUEUE}`) @Controller(`v1/${MODULE_NAME.QUEUE}`)
@ -21,6 +22,11 @@ export class QueueController {
return await this.orchestrator.create(data); return await this.orchestrator.create(data);
} }
@Post('split')
async splitQueue(@Body() data: SplitQueueDto): Promise<QueueOrder> {
return await this.orchestrator.split(data);
}
@Get('login/:id') @Get('login/:id')
async loginCustomer(@Param('id') id: string): Promise<QueueOrder> { async loginCustomer(@Param('id') id: string): Promise<QueueOrder> {
return await this.orchestrator.loginCustomer(id); return await this.orchestrator.loginCustomer(id);