feat(order): complete 7.1 order full lifecycle state transitions, 15m/30m/48h auto-timeouts, expert reject/apply-complete, and partial refund arbitration

这个提交包含在:
xcbyy
2026-10-14 15:52:56 +08:00
父节点 a14f27d641
当前提交 3bf38db8b6
共有 10 个文件被更改,包括 551 次插入 和 34 次删除

查看文件

@@ -38,13 +38,16 @@ export interface CompanionOrder {
gameDetails?: GameDetails;
offlineDetails?: OfflineDetails;
offlineVerifyCode?: string;
paidAt?: Date;
checkedInAt?: Date;
finishAppliedAt?: Date;
completedAt?: Date;
disputeReason?: string;
disputedAt?: Date;
arbitratedAt?: Date;
arbitratedBy?: number;
arbitrationDecision?: 'REFUND' | 'RELEASE';
arbitrationDecision?: 'REFUND' | 'PARTIAL_REFUND' | 'RELEASE';
refundAmountCents?: number;
arbitrationReason?: string;
createdAt: Date;
}
@@ -93,6 +96,7 @@ export class CompanionOrderService {
throw new Error(`订单状态 [${order.status}] 不允许支付`);
}
order.paidAt = new Date();
order.status = OrderStatus.PENDING_ACCEPT;
return order;
}
@@ -148,6 +152,7 @@ export class CompanionOrderService {
throw new Error(`订单当前状态 [${order.status}] 不可拒单`);
}
order.refundAmountCents = order.totalAmountCents;
order.status = OrderStatus.REFUNDED;
return order;
}
@@ -188,6 +193,7 @@ export class CompanionOrderService {
throw new Error(`订单当前状态 [${order.status}] 无法申请完工结算`);
}
order.finishAppliedAt = new Date();
order.status = OrderStatus.PENDING_CONFIRM;
return order;
}
@@ -226,7 +232,8 @@ export class CompanionOrderService {
arbitrate(params: {
orderId: string;
operatorAdminId: number;
decision: 'REFUND' | 'RELEASE';
decision: 'REFUND' | 'PARTIAL_REFUND' | 'RELEASE';
refundAmountCents?: number;
reason?: string;
roleManager?: AuthRoleManager;
}): CompanionOrder {
@@ -250,20 +257,73 @@ export class CompanionOrderService {
throw new Error(`订单当前状态 [${order.status}] 非争议仲裁中,不可执行仲裁`);
}
if (params.decision === 'PARTIAL_REFUND') {
const partial = Number(params.refundAmountCents || 0);
if (partial <= 0 || partial > order.totalAmountCents) {
throw new Error('部分退款金额必须大于0且不超过订单总金额');
}
order.refundAmountCents = partial;
} else if (params.decision === 'REFUND') {
order.refundAmountCents = order.totalAmountCents;
}
order.arbitratedAt = new Date();
order.arbitratedBy = params.operatorAdminId;
order.arbitrationDecision = params.decision;
order.arbitrationReason = params.reason;
if (params.decision === 'REFUND') {
if (params.decision === 'REFUND' || params.decision === 'PARTIAL_REFUND') {
order.status = OrderStatus.REFUNDED;
} else {
order.completedAt = new Date();
order.status = OrderStatus.COMPLETED;
}
return order;
}
/**
* 扫描并执行订单生命周期超时流转:
* 1. 待付款超过 15 分钟未支付 -> 已取消 (CANCELLED)
* 2. 待接单超过 30 分钟未接单 -> 已退款 (REFUNDED)
* 3. 待确认超过 48 小时未确认 -> 已完成 (COMPLETED)
*/
processOrderTimeouts(now: Date = new Date()): {
cancelledIds: string[];
refundedIds: string[];
autoCompletedIds: string[];
} {
const nowMs = now.getTime();
const cancelledIds: string[] = [];
const refundedIds: string[] = [];
const autoCompletedIds: string[] = [];
for (const order of this.orders.values()) {
if (order.status === OrderStatus.PENDING_PAYMENT) {
if (nowMs - order.createdAt.getTime() >= 15 * 60 * 1000) {
order.status = OrderStatus.CANCELLED;
cancelledIds.push(order.id);
}
} else if (order.status === OrderStatus.PENDING_ACCEPT) {
const baseTime = (order.paidAt || order.createdAt).getTime();
if (nowMs - baseTime >= 30 * 60 * 1000) {
order.status = OrderStatus.REFUNDED;
order.refundAmountCents = order.totalAmountCents;
refundedIds.push(order.id);
}
} else if (order.status === OrderStatus.PENDING_CONFIRM && !order.offlineVerifyCode) {
const baseTime = (order.finishAppliedAt || order.createdAt).getTime();
if (nowMs - baseTime >= 48 * 60 * 60 * 1000) {
order.status = OrderStatus.COMPLETED;
order.completedAt = now;
autoCompletedIds.push(order.id);
}
}
}
return { cancelledIds, refundedIds, autoCompletedIds };
}
getAllOrders(): CompanionOrder[] {
return Array.from(this.orders.values());
}