update status

This commit is contained in:
2025-12-19 18:18:10 +03:30
parent add2127998
commit 47e69554d4
7 changed files with 112 additions and 110 deletions
+54 -38
View File
@@ -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, readonly OrderStatus[]> = {
[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<Order> {
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;