diff --git a/src/common/constants/index.ts b/src/common/constants/index.ts index b06a16c..d2b97d8 100755 --- a/src/common/constants/index.ts +++ b/src/common/constants/index.ts @@ -10,13 +10,11 @@ export const AUTH__REFRESH_THROTTLE_LIMIT = 10; export const CONSOLE_JWT_STRATEGY_NAME = 'console_jwt_strategy'; export const LOCAL_JWT_STRATEGY_NAME = 'local_jwt_strategy'; -export const DANAK_IMAPS_SERVER = 'imaps.danakcorp.com'; -export const DANAK_SMTPS_SERVER = 'smtps.danakcorp.com'; -export const DANAK_POP3_SERVER = 'pop3.danakcorp.com'; - -export const DANAK_IMAPS_PORT = 993; -export const DANAK_POP3_PORT = 995; -export const DANAK_SMTPS_PORT = 465; -export const DANAK_IMAPS_ENCRYPTION = 'TLS /SSL'; -export const DANAK_SMTPS_ENCRYPTION = 'TLS /SSL'; -export const DANAK_POP3_ENCRYPTION = 'TLS /SSL'; +export const API_HEADER_SLUG = { + name: 'X-Slug', + required: true, + schema: { + type: 'string', + default: 'zhivan', + }, +}; diff --git a/src/modules/cart/controllers/cart.controller.ts b/src/modules/cart/controllers/cart.controller.ts index d42864e..db113b7 100644 --- a/src/modules/cart/controllers/cart.controller.ts +++ b/src/modules/cart/controllers/cart.controller.ts @@ -13,15 +13,7 @@ import { AuthGuard } from '../../auth/guards/auth.guard'; import { UserId } from 'src/common/decorators/user-id.decorator'; import { RestId } from 'src/common/decorators/rest-id.decorator'; import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto'; - -const API_HEADER_SLUG = { - name: 'X-Slug', - required: true, - schema: { - type: 'string', - default: 'zhivan', - }, -}; +import { API_HEADER_SLUG } from 'src/common/constants/index'; @UseGuards(AuthGuard) @ApiBearerAuth() @@ -166,11 +158,7 @@ export class CartController { @ApiOperation({ summary: 'Set all cart params' }) @ApiHeader(API_HEADER_SLUG) @ApiBody({ type: SetAllCartParmsDto }) - setAllCartParams( - @UserId() userId: string, - @RestId() restaurantId: string, - @Body() dto: SetAllCartParmsDto, - ) { + setAllCartParams(@UserId() userId: string, @RestId() restaurantId: string, @Body() dto: SetAllCartParmsDto) { return this.cartService.setAllCartParams(userId, restaurantId, dto); } } diff --git a/src/modules/orders/controllers/orders.controller.ts b/src/modules/orders/controllers/orders.controller.ts index d619517..80e1d6c 100644 --- a/src/modules/orders/controllers/orders.controller.ts +++ b/src/modules/orders/controllers/orders.controller.ts @@ -7,20 +7,14 @@ import { RestId } from 'src/common/decorators/rest-id.decorator'; import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard'; import { FindOrdersDto } from '../dto/find-orders.dto'; import { OrderStatus } from '../interface/order.interface'; +import { API_HEADER_SLUG } from 'src/common/constants/index'; @ApiTags('orders') @ApiBearerAuth() -@ApiHeader({ - name: 'X-Slug', - required: true, - schema: { - type: 'string', - default: 'zhivan', - }, -}) +@ApiHeader(API_HEADER_SLUG) @Controller() export class OrdersController { - constructor(private readonly ordersService: OrdersService) { } + constructor(private readonly ordersService: OrdersService) {} @UseGuards(AuthGuard) @Post('public/checkout') @@ -45,11 +39,16 @@ export class OrdersController { } @UseGuards(AuthGuard) - @Patch('public/orders/:id/cancel') - @ApiOperation({ summary: 'Cancel an order By User' }) + @Patch('public/orders/:id/:status') + @ApiParam({ + name: 'status', + description: 'Order status', + enum: OrderStatus, + }) + @ApiOperation({ summary: 'Update status of an order By User' }) @ApiParam({ name: 'id', description: 'Order ID' }) - cancelOrder(@Param('id') id: string, @RestId() restId: string) { - return this.ordersService.cancelOrderAsUser(id, restId); + cancelOrder(@Param('id') id: string, @Param('status') status: OrderStatus, @RestId() restId: string) { + return this.ordersService.changeOrderStatus(id, restId, status, 'user'); } @UseGuards(AdminAuthGuard) @@ -68,28 +67,41 @@ export class OrdersController { } @UseGuards(AdminAuthGuard) - @Patch('admin/orders/:orderId/confirm') - @ApiOperation({ summary: 'Accept an order' }) + @Patch('admin/orders/:orderId/:status') + @ApiOperation({ summary: 'Update an order status' }) @ApiParam({ name: 'orderId', description: 'Order ID' }) - acceptOrder(@Param('orderId') orderId: string, @RestId() restId: string) { - return this.ordersService.confirmOrder(orderId, restId); + @ApiParam({ + name: 'status', + description: 'Order status', + enum: OrderStatus, + }) + updateStatus(@Param('orderId') orderId: string, @Param('status') status: OrderStatus, @RestId() restId: string) { + return this.ordersService.changeOrderStatus(orderId, restId, status, 'admin'); } - @UseGuards(AdminAuthGuard) - @Patch('admin/orders/:id/prepare') - @ApiOperation({ summary: 'Prepare an order By Admin' }) - @ApiParam({ name: 'id', description: 'Order ID' }) - prepareOrder(@Param('id') id: string, @RestId() restId: string) { - return this.ordersService.prepareOrder(id, restId); - } + // @UseGuards(AdminAuthGuard) + // @Patch('admin/orders/:orderId/confirm') + // @ApiOperation({ summary: 'Accept an order' }) + // @ApiParam({ name: 'orderId', description: 'Order ID' }) + // acceptOrder(@Param('orderId') orderId: string, @RestId() restId: string) { + // return this.ordersService.confirmOrder(orderId, restId); + // } - @UseGuards(AdminAuthGuard) - @Patch('admin/orders/:orderId/reject') - @ApiOperation({ summary: 'Reject an order' }) - @ApiParam({ name: 'orderId', description: 'Order ID' }) - rejectOrder(@Param('orderId') orderId: string, @RestId() restId: string) { - return this.ordersService.rejectOrder(orderId, restId); - } + // @UseGuards(AdminAuthGuard) + // @Patch('admin/orders/:id/prepare') + // @ApiOperation({ summary: 'Prepare an order By Admin' }) + // @ApiParam({ name: 'id', description: 'Order ID' }) + // prepareOrder(@Param('id') id: string, @RestId() restId: string) { + // return this.ordersService.prepareOrder(id, restId); + // } + + // @UseGuards(AdminAuthGuard) + // @Patch('admin/orders/:orderId/reject') + // @ApiOperation({ summary: 'Reject an order' }) + // @ApiParam({ name: 'orderId', description: 'Order ID' }) + // rejectOrder(@Param('orderId') orderId: string, @RestId() restId: string) { + // return this.ordersService.rejectOrder(orderId, restId); + // } // @UseGuards(AdminAuthGuard) // @ApiBearerAuth() @@ -108,17 +120,4 @@ export class OrdersController { // markAsDelivered(@Param('orderId') orderId: string, @RestId() restId: string) { // return this.ordersService.markAsDelivered(orderId, restId); // } - - @UseGuards(AdminAuthGuard) - @Patch('admin/orders/:orderId/:status') - @ApiOperation({ summary: 'Update an order status' }) - @ApiParam({ name: 'orderId', description: 'Order ID' }) - @ApiParam({ - name: 'status', - description: 'Order status', - enum: OrderStatus, - }) - updateStatus(@Param('orderId') orderId: string, @Param('status') status: OrderStatus, @RestId() restId: string) { - return this.ordersService.updateStatus(orderId, status, restId); - } } diff --git a/src/modules/orders/entities/order.entity.ts b/src/modules/orders/entities/order.entity.ts index aa37811..ed48e14 100644 --- a/src/modules/orders/entities/order.entity.ts +++ b/src/modules/orders/entities/order.entity.ts @@ -90,8 +90,8 @@ export class Order extends BaseEntity { @Enum(() => OrderStatus) status!: OrderStatus; - // @Enum(() => PaymentStatusEnum) - // paymentStatus!: PaymentStatusEnum; + @Property({ type: 'json', nullable: true }) + history: Array<{ status: OrderStatus; changedAt: Date; desc: string | null }> = []; @BeforeCreate() async generateOrderNumber(args: EventArgs) { diff --git a/src/modules/orders/interface/order.interface.ts b/src/modules/orders/interface/order.interface.ts index 0af28c8..7d51182 100644 --- a/src/modules/orders/interface/order.interface.ts +++ b/src/modules/orders/interface/order.interface.ts @@ -28,8 +28,6 @@ export enum OrderStatus { COMPLETED = 'completed', CANCELED = 'canceled', - FAILED = 'failed', - REFUNDED = 'refunded', } export interface OrderCouponDetail { diff --git a/src/modules/orders/providers/orders.service.ts b/src/modules/orders/providers/orders.service.ts index 1df3724..bdeb9ec 100644 --- a/src/modules/orders/providers/orders.service.ts +++ b/src/modules/orders/providers/orders.service.ts @@ -32,21 +32,21 @@ type ValidatedCartForOrder = { orderItemsData: OrderItemData[]; }; +export type StatusTransitionRef = 'user' | 'admin'; + @Injectable() export class OrdersService { private readonly logger = new Logger(OrdersService.name); private static readonly STATUS_TRANSITIONS: Record = { - [OrderStatus.PENDING_PAYMENT]: [OrderStatus.PAID, OrderStatus.FAILED, OrderStatus.CANCELED, OrderStatus.CONFIRMED], - [OrderStatus.PAID]: [OrderStatus.CONFIRMED, OrderStatus.REFUNDED], + [OrderStatus.PENDING_PAYMENT]: [OrderStatus.PAID, OrderStatus.CANCELED, OrderStatus.CONFIRMED], + [OrderStatus.PAID]: [OrderStatus.CONFIRMED, OrderStatus.CANCELED], [OrderStatus.CONFIRMED]: [OrderStatus.PREPARING, OrderStatus.CANCELED], [OrderStatus.PREPARING]: [OrderStatus.READY, OrderStatus.SHIPPED, OrderStatus.CANCELED], - [OrderStatus.READY]: [OrderStatus.COMPLETED, OrderStatus.FAILED], - [OrderStatus.SHIPPED]: [OrderStatus.COMPLETED, OrderStatus.FAILED], - [OrderStatus.COMPLETED]: [], + [OrderStatus.READY]: [OrderStatus.COMPLETED, OrderStatus.CANCELED], + [OrderStatus.SHIPPED]: [OrderStatus.COMPLETED, OrderStatus.CANCELED], + [OrderStatus.COMPLETED]: [OrderStatus.CANCELED], [OrderStatus.CANCELED]: [], - [OrderStatus.FAILED]: [], - [OrderStatus.REFUNDED]: [], }; constructor( @@ -80,6 +80,7 @@ export class OrdersService { description: cart.description, tableNumber: cart.tableNumber, status: OrderStatus.PENDING_PAYMENT, + history: [{ status: OrderStatus.PENDING_PAYMENT, changedAt: new Date() }], }); em.persist(order); @@ -220,69 +221,84 @@ export class OrdersService { return order; } - async confirmOrder(orderId: string, restId: string) { - return this.changeOrderStatus(orderId, restId, OrderStatus.CONFIRMED); - } + // async confirmOrder(orderId: string, restId: string) { + // return this.changeOrderStatus(orderId, restId, OrderStatus.CONFIRMED); + // } - async prepareOrder(orderId: string, restId: string) { - return this.changeOrderStatus(orderId, restId, OrderStatus.PREPARING); - } + // async prepareOrder(orderId: string, restId: string) { + // return this.changeOrderStatus(orderId, restId, OrderStatus.PREPARING); + // } // just admin can reject the order any time - async rejectOrder(orderId: string, restId: string) { - return this.changeOrderStatus(orderId, restId, OrderStatus.CANCELED, { skipTransitionValidation: true }); - } + // async rejectOrder(orderId: string, restId: string) { + // return this.changeOrderStatus(orderId, restId, OrderStatus.CANCELED, { skipTransitionValidation: true }); + // } - async readyForDelivery(orderId: string, restId: string) { - return this.changeOrderStatus(orderId, restId, OrderStatus.READY); - } + // async readyForDelivery(orderId: string, restId: string) { + // return this.changeOrderStatus(orderId, restId, OrderStatus.READY); + // } - async cancelOrderAsUser(orderId: string, restId: string) { - return this.changeOrderStatus(orderId, restId, OrderStatus.CANCELED); - } + // async cancelOrderAsUser(orderId: string, restId: string) { + // return this.changeOrderStatus(orderId, restId, OrderStatus.CANCELED); + // } - async markAsDelivered(orderId: string, restId: string) { - return this.changeOrderStatus(orderId, restId, OrderStatus.COMPLETED); - } + // async markAsDelivered(orderId: string, restId: string) { + // return this.changeOrderStatus(orderId, restId, OrderStatus.COMPLETED); + // } - async updateStatus(orderId: string, status: OrderStatus, restId: string) { - return this.changeOrderStatus(orderId, restId, status); - } + // async updateStatus(orderId: string, restId: string, status: OrderStatus, ref: StatusTransitionRef) { + // return this.changeOrderStatus(orderId, restId, status, ref); + // } - private async changeOrderStatus( + async changeOrderStatus( orderId: string, restId: string, toStatus: OrderStatus, - options?: { skipTransitionValidation?: boolean }, + ref: StatusTransitionRef, ): Promise { const order = await this.getOrderOrFail(orderId, restId); - if (!options?.skipTransitionValidation) { - this.assertStatusTransitionAllowed(order, toStatus); - } + this.assertStatusTransitionAllowed(order, toStatus, ref); order.status = toStatus; await this.em.persistAndFlush(order); return order; } - private assertStatusTransitionAllowed(order: Order, to: OrderStatus) { + private assertStatusTransitionAllowed(order: Order, to: OrderStatus, ref: StatusTransitionRef) { const paymentMethod = order.paymentMethod?.method; if (!paymentMethod) { throw new BadRequestException('Order payment method is missing'); } - if (!this.canTransition(order.status, to, paymentMethod)) { + if (!this.canTransition(order.status, to, paymentMethod, ref)) { throw new BadRequestException(`Invalid status transition: ${order.status} -> ${to}`); } } - private canTransition(from: OrderStatus, to: OrderStatus, paymentMethod: PaymentMethodEnum) { + private canTransition(from: OrderStatus, to: OrderStatus, paymentMethod: PaymentMethodEnum, ref: 'user' | 'admin') { if (!OrdersService.STATUS_TRANSITIONS[from]?.includes(to)) return false; - if (paymentMethod === PaymentMethodEnum.Cash) { - if ([OrderStatus.PENDING_PAYMENT, OrderStatus.PAID].includes(to)) return false; + if (to === OrderStatus.CANCELED) { + // only allow orders with status of PENDING_PAYMENT and PAID are allowed to be canceled by user + if (ref === 'user' && ![OrderStatus.PENDING_PAYMENT, OrderStatus.PAID, OrderStatus.CONFIRMED].includes(from)) { + return false; + } else if (ref === 'admin') { + return true; + } } + // only allow orders with status of PENDING_PAYMENT and payment + // method of cash are allowed to move to CONFIRMED directly + if ( + from == OrderStatus.PENDING_PAYMENT && + to == OrderStatus.CONFIRMED && + paymentMethod !== PaymentMethodEnum.Cash + ) { + return false; + } + // if (paymentMethod === PaymentMethodEnum.Cash) { + // if ([OrderStatus.PENDING_PAYMENT, OrderStatus.PAID].includes(to)) return false; + // } if (paymentMethod === PaymentMethodEnum.Online) { if (to === OrderStatus.CONFIRMED && from !== OrderStatus.PAID) return false; diff --git a/src/modules/payments/entities/payment.entity.ts b/src/modules/payments/entities/payment.entity.ts index 546f41b..1c30e9c 100644 --- a/src/modules/payments/entities/payment.entity.ts +++ b/src/modules/payments/entities/payment.entity.ts @@ -37,4 +37,7 @@ export class Payment extends BaseEntity { @Property({ nullable: true }) failedAt?: Date | null = null; + + @Property({ nullable: true }) + description?: string | null = null; }