feat(order): complete 7.1 order full lifecycle state transitions, 15m/30m/48h auto-timeouts, expert reject/apply-complete, and partial refund arbitration
这个提交包含在:
@@ -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());
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户