update status
This commit is contained in:
@@ -10,13 +10,11 @@ export const AUTH__REFRESH_THROTTLE_LIMIT = 10;
|
|||||||
export const CONSOLE_JWT_STRATEGY_NAME = 'console_jwt_strategy';
|
export const CONSOLE_JWT_STRATEGY_NAME = 'console_jwt_strategy';
|
||||||
export const LOCAL_JWT_STRATEGY_NAME = 'local_jwt_strategy';
|
export const LOCAL_JWT_STRATEGY_NAME = 'local_jwt_strategy';
|
||||||
|
|
||||||
export const DANAK_IMAPS_SERVER = 'imaps.danakcorp.com';
|
export const API_HEADER_SLUG = {
|
||||||
export const DANAK_SMTPS_SERVER = 'smtps.danakcorp.com';
|
name: 'X-Slug',
|
||||||
export const DANAK_POP3_SERVER = 'pop3.danakcorp.com';
|
required: true,
|
||||||
|
schema: {
|
||||||
export const DANAK_IMAPS_PORT = 993;
|
type: 'string',
|
||||||
export const DANAK_POP3_PORT = 995;
|
default: 'zhivan',
|
||||||
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';
|
|
||||||
|
|||||||
@@ -13,15 +13,7 @@ import { AuthGuard } from '../../auth/guards/auth.guard';
|
|||||||
import { UserId } from 'src/common/decorators/user-id.decorator';
|
import { UserId } from 'src/common/decorators/user-id.decorator';
|
||||||
import { RestId } from 'src/common/decorators/rest-id.decorator';
|
import { RestId } from 'src/common/decorators/rest-id.decorator';
|
||||||
import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto';
|
import { SetAllCartParmsDto } from '../dto/set-all-cart-params.dto';
|
||||||
|
import { API_HEADER_SLUG } from 'src/common/constants/index';
|
||||||
const API_HEADER_SLUG = {
|
|
||||||
name: 'X-Slug',
|
|
||||||
required: true,
|
|
||||||
schema: {
|
|
||||||
type: 'string',
|
|
||||||
default: 'zhivan',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@@ -166,11 +158,7 @@ export class CartController {
|
|||||||
@ApiOperation({ summary: 'Set all cart params' })
|
@ApiOperation({ summary: 'Set all cart params' })
|
||||||
@ApiHeader(API_HEADER_SLUG)
|
@ApiHeader(API_HEADER_SLUG)
|
||||||
@ApiBody({ type: SetAllCartParmsDto })
|
@ApiBody({ type: SetAllCartParmsDto })
|
||||||
setAllCartParams(
|
setAllCartParams(@UserId() userId: string, @RestId() restaurantId: string, @Body() dto: SetAllCartParmsDto) {
|
||||||
@UserId() userId: string,
|
|
||||||
@RestId() restaurantId: string,
|
|
||||||
@Body() dto: SetAllCartParmsDto,
|
|
||||||
) {
|
|
||||||
return this.cartService.setAllCartParams(userId, restaurantId, dto);
|
return this.cartService.setAllCartParams(userId, restaurantId, dto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,11 @@ import { RestId } from 'src/common/decorators/rest-id.decorator';
|
|||||||
import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard';
|
import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard';
|
||||||
import { FindOrdersDto } from '../dto/find-orders.dto';
|
import { FindOrdersDto } from '../dto/find-orders.dto';
|
||||||
import { OrderStatus } from '../interface/order.interface';
|
import { OrderStatus } from '../interface/order.interface';
|
||||||
|
import { API_HEADER_SLUG } from 'src/common/constants/index';
|
||||||
|
|
||||||
@ApiTags('orders')
|
@ApiTags('orders')
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ApiHeader({
|
@ApiHeader(API_HEADER_SLUG)
|
||||||
name: 'X-Slug',
|
|
||||||
required: true,
|
|
||||||
schema: {
|
|
||||||
type: 'string',
|
|
||||||
default: 'zhivan',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class OrdersController {
|
export class OrdersController {
|
||||||
constructor(private readonly ordersService: OrdersService) {}
|
constructor(private readonly ordersService: OrdersService) {}
|
||||||
@@ -45,11 +39,16 @@ export class OrdersController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
@Patch('public/orders/:id/cancel')
|
@Patch('public/orders/:id/:status')
|
||||||
@ApiOperation({ summary: 'Cancel an order By User' })
|
@ApiParam({
|
||||||
|
name: 'status',
|
||||||
|
description: 'Order status',
|
||||||
|
enum: OrderStatus,
|
||||||
|
})
|
||||||
|
@ApiOperation({ summary: 'Update status of an order By User' })
|
||||||
@ApiParam({ name: 'id', description: 'Order ID' })
|
@ApiParam({ name: 'id', description: 'Order ID' })
|
||||||
cancelOrder(@Param('id') id: string, @RestId() restId: string) {
|
cancelOrder(@Param('id') id: string, @Param('status') status: OrderStatus, @RestId() restId: string) {
|
||||||
return this.ordersService.cancelOrderAsUser(id, restId);
|
return this.ordersService.changeOrderStatus(id, restId, status, 'user');
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@@ -68,28 +67,41 @@ export class OrdersController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
@UseGuards(AdminAuthGuard)
|
||||||
@Patch('admin/orders/:orderId/confirm')
|
@Patch('admin/orders/:orderId/:status')
|
||||||
@ApiOperation({ summary: 'Accept an order' })
|
@ApiOperation({ summary: 'Update an order status' })
|
||||||
@ApiParam({ name: 'orderId', description: 'Order ID' })
|
@ApiParam({ name: 'orderId', description: 'Order ID' })
|
||||||
acceptOrder(@Param('orderId') orderId: string, @RestId() restId: string) {
|
@ApiParam({
|
||||||
return this.ordersService.confirmOrder(orderId, restId);
|
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)
|
// @UseGuards(AdminAuthGuard)
|
||||||
@Patch('admin/orders/:id/prepare')
|
// @Patch('admin/orders/:orderId/confirm')
|
||||||
@ApiOperation({ summary: 'Prepare an order By Admin' })
|
// @ApiOperation({ summary: 'Accept an order' })
|
||||||
@ApiParam({ name: 'id', description: 'Order ID' })
|
// @ApiParam({ name: 'orderId', description: 'Order ID' })
|
||||||
prepareOrder(@Param('id') id: string, @RestId() restId: string) {
|
// acceptOrder(@Param('orderId') orderId: string, @RestId() restId: string) {
|
||||||
return this.ordersService.prepareOrder(id, restId);
|
// return this.ordersService.confirmOrder(orderId, restId);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@UseGuards(AdminAuthGuard)
|
// @UseGuards(AdminAuthGuard)
|
||||||
@Patch('admin/orders/:orderId/reject')
|
// @Patch('admin/orders/:id/prepare')
|
||||||
@ApiOperation({ summary: 'Reject an order' })
|
// @ApiOperation({ summary: 'Prepare an order By Admin' })
|
||||||
@ApiParam({ name: 'orderId', description: 'Order ID' })
|
// @ApiParam({ name: 'id', description: 'Order ID' })
|
||||||
rejectOrder(@Param('orderId') orderId: string, @RestId() restId: string) {
|
// prepareOrder(@Param('id') id: string, @RestId() restId: string) {
|
||||||
return this.ordersService.rejectOrder(orderId, restId);
|
// 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)
|
// @UseGuards(AdminAuthGuard)
|
||||||
// @ApiBearerAuth()
|
// @ApiBearerAuth()
|
||||||
@@ -108,17 +120,4 @@ export class OrdersController {
|
|||||||
// markAsDelivered(@Param('orderId') orderId: string, @RestId() restId: string) {
|
// markAsDelivered(@Param('orderId') orderId: string, @RestId() restId: string) {
|
||||||
// return this.ordersService.markAsDelivered(orderId, restId);
|
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,8 +90,8 @@ export class Order extends BaseEntity {
|
|||||||
@Enum(() => OrderStatus)
|
@Enum(() => OrderStatus)
|
||||||
status!: OrderStatus;
|
status!: OrderStatus;
|
||||||
|
|
||||||
// @Enum(() => PaymentStatusEnum)
|
@Property({ type: 'json', nullable: true })
|
||||||
// paymentStatus!: PaymentStatusEnum;
|
history: Array<{ status: OrderStatus; changedAt: Date; desc: string | null }> = [];
|
||||||
|
|
||||||
@BeforeCreate()
|
@BeforeCreate()
|
||||||
async generateOrderNumber(args: EventArgs<Order>) {
|
async generateOrderNumber(args: EventArgs<Order>) {
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ export enum OrderStatus {
|
|||||||
COMPLETED = 'completed',
|
COMPLETED = 'completed',
|
||||||
|
|
||||||
CANCELED = 'canceled',
|
CANCELED = 'canceled',
|
||||||
FAILED = 'failed',
|
|
||||||
REFUNDED = 'refunded',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderCouponDetail {
|
export interface OrderCouponDetail {
|
||||||
|
|||||||
@@ -32,21 +32,21 @@ type ValidatedCartForOrder = {
|
|||||||
orderItemsData: OrderItemData[];
|
orderItemsData: OrderItemData[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type StatusTransitionRef = 'user' | 'admin';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OrdersService {
|
export class OrdersService {
|
||||||
private readonly logger = new Logger(OrdersService.name);
|
private readonly logger = new Logger(OrdersService.name);
|
||||||
|
|
||||||
private static readonly STATUS_TRANSITIONS: Record<OrderStatus, readonly OrderStatus[]> = {
|
private static readonly STATUS_TRANSITIONS: Record<OrderStatus, readonly OrderStatus[]> = {
|
||||||
[OrderStatus.PENDING_PAYMENT]: [OrderStatus.PAID, OrderStatus.FAILED, OrderStatus.CANCELED, OrderStatus.CONFIRMED],
|
[OrderStatus.PENDING_PAYMENT]: [OrderStatus.PAID, OrderStatus.CANCELED, OrderStatus.CONFIRMED],
|
||||||
[OrderStatus.PAID]: [OrderStatus.CONFIRMED, OrderStatus.REFUNDED],
|
[OrderStatus.PAID]: [OrderStatus.CONFIRMED, OrderStatus.CANCELED],
|
||||||
[OrderStatus.CONFIRMED]: [OrderStatus.PREPARING, OrderStatus.CANCELED],
|
[OrderStatus.CONFIRMED]: [OrderStatus.PREPARING, OrderStatus.CANCELED],
|
||||||
[OrderStatus.PREPARING]: [OrderStatus.READY, OrderStatus.SHIPPED, OrderStatus.CANCELED],
|
[OrderStatus.PREPARING]: [OrderStatus.READY, OrderStatus.SHIPPED, OrderStatus.CANCELED],
|
||||||
[OrderStatus.READY]: [OrderStatus.COMPLETED, OrderStatus.FAILED],
|
[OrderStatus.READY]: [OrderStatus.COMPLETED, OrderStatus.CANCELED],
|
||||||
[OrderStatus.SHIPPED]: [OrderStatus.COMPLETED, OrderStatus.FAILED],
|
[OrderStatus.SHIPPED]: [OrderStatus.COMPLETED, OrderStatus.CANCELED],
|
||||||
[OrderStatus.COMPLETED]: [],
|
[OrderStatus.COMPLETED]: [OrderStatus.CANCELED],
|
||||||
[OrderStatus.CANCELED]: [],
|
[OrderStatus.CANCELED]: [],
|
||||||
[OrderStatus.FAILED]: [],
|
|
||||||
[OrderStatus.REFUNDED]: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -80,6 +80,7 @@ export class OrdersService {
|
|||||||
description: cart.description,
|
description: cart.description,
|
||||||
tableNumber: cart.tableNumber,
|
tableNumber: cart.tableNumber,
|
||||||
status: OrderStatus.PENDING_PAYMENT,
|
status: OrderStatus.PENDING_PAYMENT,
|
||||||
|
history: [{ status: OrderStatus.PENDING_PAYMENT, changedAt: new Date() }],
|
||||||
});
|
});
|
||||||
|
|
||||||
em.persist(order);
|
em.persist(order);
|
||||||
@@ -220,69 +221,84 @@ export class OrdersService {
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirmOrder(orderId: string, restId: string) {
|
// async confirmOrder(orderId: string, restId: string) {
|
||||||
return this.changeOrderStatus(orderId, restId, OrderStatus.CONFIRMED);
|
// return this.changeOrderStatus(orderId, restId, OrderStatus.CONFIRMED);
|
||||||
}
|
// }
|
||||||
|
|
||||||
async prepareOrder(orderId: string, restId: string) {
|
// async prepareOrder(orderId: string, restId: string) {
|
||||||
return this.changeOrderStatus(orderId, restId, OrderStatus.PREPARING);
|
// return this.changeOrderStatus(orderId, restId, OrderStatus.PREPARING);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// just admin can reject the order any time
|
// just admin can reject the order any time
|
||||||
async rejectOrder(orderId: string, restId: string) {
|
// async rejectOrder(orderId: string, restId: string) {
|
||||||
return this.changeOrderStatus(orderId, restId, OrderStatus.CANCELED, { skipTransitionValidation: true });
|
// return this.changeOrderStatus(orderId, restId, OrderStatus.CANCELED, { skipTransitionValidation: true });
|
||||||
}
|
// }
|
||||||
|
|
||||||
async readyForDelivery(orderId: string, restId: string) {
|
// async readyForDelivery(orderId: string, restId: string) {
|
||||||
return this.changeOrderStatus(orderId, restId, OrderStatus.READY);
|
// return this.changeOrderStatus(orderId, restId, OrderStatus.READY);
|
||||||
}
|
// }
|
||||||
|
|
||||||
async cancelOrderAsUser(orderId: string, restId: string) {
|
// async cancelOrderAsUser(orderId: string, restId: string) {
|
||||||
return this.changeOrderStatus(orderId, restId, OrderStatus.CANCELED);
|
// return this.changeOrderStatus(orderId, restId, OrderStatus.CANCELED);
|
||||||
}
|
// }
|
||||||
|
|
||||||
async markAsDelivered(orderId: string, restId: string) {
|
// async markAsDelivered(orderId: string, restId: string) {
|
||||||
return this.changeOrderStatus(orderId, restId, OrderStatus.COMPLETED);
|
// return this.changeOrderStatus(orderId, restId, OrderStatus.COMPLETED);
|
||||||
}
|
// }
|
||||||
|
|
||||||
async updateStatus(orderId: string, status: OrderStatus, restId: string) {
|
// async updateStatus(orderId: string, restId: string, status: OrderStatus, ref: StatusTransitionRef) {
|
||||||
return this.changeOrderStatus(orderId, restId, status);
|
// return this.changeOrderStatus(orderId, restId, status, ref);
|
||||||
}
|
// }
|
||||||
|
|
||||||
private async changeOrderStatus(
|
async changeOrderStatus(
|
||||||
orderId: string,
|
orderId: string,
|
||||||
restId: string,
|
restId: string,
|
||||||
toStatus: OrderStatus,
|
toStatus: OrderStatus,
|
||||||
options?: { skipTransitionValidation?: boolean },
|
ref: StatusTransitionRef,
|
||||||
): Promise<Order> {
|
): Promise<Order> {
|
||||||
const order = await this.getOrderOrFail(orderId, restId);
|
const order = await this.getOrderOrFail(orderId, restId);
|
||||||
|
|
||||||
if (!options?.skipTransitionValidation) {
|
this.assertStatusTransitionAllowed(order, toStatus, ref);
|
||||||
this.assertStatusTransitionAllowed(order, toStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
order.status = toStatus;
|
order.status = toStatus;
|
||||||
await this.em.persistAndFlush(order);
|
await this.em.persistAndFlush(order);
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
private assertStatusTransitionAllowed(order: Order, to: OrderStatus) {
|
private assertStatusTransitionAllowed(order: Order, to: OrderStatus, ref: StatusTransitionRef) {
|
||||||
const paymentMethod = order.paymentMethod?.method;
|
const paymentMethod = order.paymentMethod?.method;
|
||||||
if (!paymentMethod) {
|
if (!paymentMethod) {
|
||||||
throw new BadRequestException('Order payment method is missing');
|
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}`);
|
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 (!OrdersService.STATUS_TRANSITIONS[from]?.includes(to)) return false;
|
||||||
|
|
||||||
if (paymentMethod === PaymentMethodEnum.Cash) {
|
if (to === OrderStatus.CANCELED) {
|
||||||
if ([OrderStatus.PENDING_PAYMENT, OrderStatus.PAID].includes(to)) return false;
|
// 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 (paymentMethod === PaymentMethodEnum.Online) {
|
||||||
if (to === OrderStatus.CONFIRMED && from !== OrderStatus.PAID) return false;
|
if (to === OrderStatus.CONFIRMED && from !== OrderStatus.PAID) return false;
|
||||||
|
|||||||
@@ -37,4 +37,7 @@ export class Payment extends BaseEntity {
|
|||||||
|
|
||||||
@Property({ nullable: true })
|
@Property({ nullable: true })
|
||||||
failedAt?: Date | null = null;
|
failedAt?: Date | null = null;
|
||||||
|
|
||||||
|
@Property({ nullable: true })
|
||||||
|
description?: string | null = null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user