import { allow, deny, requireAuth } from "@/lib/policy/assert" import type { Actor } from "@/lib/policy/actor" import type { PolicyDecision } from "@/lib/policy/decision" import type { OrderStatus } from "@/lib/types" export type OrderAction = | "PAY" | "ACCEPT" | "REQUEST_CLOSE" | "CONFIRM_CLOSE" | "CANCEL_PRE_ACCEPT" | "OPEN_DISPUTE" | "RESOLVE_DISPUTE" | "SUBMIT_REVIEW" | "AUTO_TIMEOUT_PENDING_ACCEPT" | "AUTO_TIMEOUT_PENDING_CLOSE" | "AUTO_TIMEOUT_PENDING_REVIEW" export type OrderTransitionSideEffectType = | "CLEAR_TIMEOUT" | "SCHEDULE_TIMEOUT" | "SYNC_CHAT_SESSION" | "PAYOUT_INCOME" export interface OrderTransitionSideEffect { type: OrderTransitionSideEffectType status?: OrderStatus } interface TransitionContext { actor?: Actor | null order: { status: OrderStatus } action: OrderAction } export interface OrderTransitionResult { decision: PolicyDecision nextStatus?: OrderStatus sideEffects: OrderTransitionSideEffect[] } export const orderTransitionTable: Record< OrderStatus, Partial> > = { pending_payment: { PAY: "pending_accept", }, pending_accept: { ACCEPT: "in_progress", CANCEL_PRE_ACCEPT: "cancelled", AUTO_TIMEOUT_PENDING_ACCEPT: "cancelled", }, in_progress: { REQUEST_CLOSE: "pending_close", OPEN_DISPUTE: "disputed", }, pending_close: { CONFIRM_CLOSE: "pending_review", OPEN_DISPUTE: "disputed", AUTO_TIMEOUT_PENDING_CLOSE: "pending_review", }, pending_review: { SUBMIT_REVIEW: "completed", AUTO_TIMEOUT_PENDING_REVIEW: "completed", }, disputed: { RESOLVE_DISPUTE: "pending_review", }, completed: {}, cancelled: {}, } function isAutoAction(action: OrderAction): boolean { return action.startsWith("AUTO_TIMEOUT_") } function checkRolePermission(action: OrderAction, actor?: Actor | null): PolicyDecision { if (isAutoAction(action)) { return allow() } const authDecision = requireAuth(actor) if (!authDecision.ok) { return authDecision } if (!actor) { return deny("AUTH_REQUIRED", "请先登录") } if (action === "PAY" || action === "CANCEL_PRE_ACCEPT") { return actor.role === "consumer" ? allow() : deny("ROLE_FORBIDDEN", "仅客户可执行该操作") } if (action === "ACCEPT") { return actor.role === "player" || actor.role === "owner" ? allow() : deny("ROLE_FORBIDDEN", "仅打手或店主可执行该操作") } if (action === "RESOLVE_DISPUTE") { return actor.role === "owner" ? allow() : deny("ROLE_FORBIDDEN", "仅店主可执行该操作") } return actor.role === "consumer" || actor.role === "player" ? allow() : deny("ROLE_FORBIDDEN", "当前身份不可执行该操作") } function buildSideEffects(nextStatus: OrderStatus): OrderTransitionSideEffect[] { const sideEffects: OrderTransitionSideEffect[] = [{ type: "CLEAR_TIMEOUT" }] if ( nextStatus === "pending_accept" || nextStatus === "pending_close" || nextStatus === "pending_review" ) { sideEffects.push({ type: "SCHEDULE_TIMEOUT", status: nextStatus }) } if (nextStatus !== "pending_payment" && nextStatus !== "cancelled") { sideEffects.push({ type: "SYNC_CHAT_SESSION" }) } if (nextStatus === "completed") { sideEffects.push({ type: "PAYOUT_INCOME" }) } return sideEffects } export function evaluateOrderTransition(context: TransitionContext): OrderTransitionResult { const roleDecision = checkRolePermission(context.action, context.actor) if (!roleDecision.ok) { return { decision: roleDecision, sideEffects: [], } } const nextStatus = orderTransitionTable[context.order.status][context.action] if (!nextStatus) { return { decision: deny("INVALID_STATUS", "当前状态不可执行该操作"), sideEffects: [], } } if (nextStatus === context.order.status) { return { decision: deny("IDEMPOTENT_NOOP", "状态未变化"), sideEffects: [], } } return { decision: allow(), nextStatus, sideEffects: buildSideEffects(nextStatus), } }