fix(order): remove status bypass and wire pending_review lifecycle
This commit is contained in:
+47
-39
@@ -1,5 +1,9 @@
|
||||
import { create } from "zustand"
|
||||
import { ORDER_ACCEPT_TIMEOUT_MS, ORDER_CLOSE_TIMEOUT_MS } from "@/lib/config/demo-timers"
|
||||
import {
|
||||
ORDER_ACCEPT_TIMEOUT_MS,
|
||||
ORDER_CLOSE_TIMEOUT_MS,
|
||||
ORDER_REVIEW_TIMEOUT_MS,
|
||||
} from "@/lib/config/demo-timers"
|
||||
import { evaluateOrderTransition, type OrderAction } from "@/lib/domain/order-machine"
|
||||
import { generateId } from "@/lib/id"
|
||||
import { allow, deny } from "@/lib/policy/assert"
|
||||
@@ -11,6 +15,7 @@ import { useAuthStore } from "@/store/auth"
|
||||
import { useChatStore } from "@/store/chat"
|
||||
import { useNotificationStore } from "@/store/notifications"
|
||||
import { useWalletStore } from "@/store/wallet"
|
||||
import { useShopStore } from "@/store/shops"
|
||||
|
||||
interface CreateOrderInput {
|
||||
consumerId: string
|
||||
@@ -46,10 +51,12 @@ interface OrderState {
|
||||
markDisputed: (orderId: string, actor: Actor) => OrderMutationResult
|
||||
autoTimeoutPendingAccept: (orderId: string) => OrderMutationResult
|
||||
autoTimeoutPendingClose: (orderId: string) => OrderMutationResult
|
||||
updateOrderStatus: (orderId: string, status: OrderStatus) => void
|
||||
autoTimeoutPendingReview: (orderId: string) => OrderMutationResult
|
||||
resolveDispute: (orderId: string) => OrderMutationResult
|
||||
}
|
||||
|
||||
const orderTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
const pendingCreations = new Set<string>()
|
||||
|
||||
function clearOrderTimeout(orderId: string) {
|
||||
const timer = orderTimeouts.get(orderId)
|
||||
@@ -67,7 +74,7 @@ function isOrderOwnerActor(order: Order, actor: Actor) {
|
||||
}
|
||||
|
||||
function validateActorForAction(order: Order, action: OrderAction, actor?: Actor): PolicyDecision {
|
||||
if (action.startsWith("AUTO_TIMEOUT_")) {
|
||||
if (action.startsWith("AUTO_TIMEOUT_") || (action === "RESOLVE_DISPUTE" && !actor?.userId)) {
|
||||
return allow()
|
||||
}
|
||||
|
||||
@@ -165,11 +172,16 @@ function syncChatSession(order: Order, previousStatus: OrderStatus) {
|
||||
function scheduleOrderTimeout(orderId: string, status: OrderStatus) {
|
||||
clearOrderTimeout(orderId)
|
||||
|
||||
if (status !== "pending_accept" && status !== "pending_close") {
|
||||
if (status !== "pending_accept" && status !== "pending_close" && status !== "pending_review") {
|
||||
return
|
||||
}
|
||||
|
||||
const timeoutMs = status === "pending_accept" ? ORDER_ACCEPT_TIMEOUT_MS : ORDER_CLOSE_TIMEOUT_MS
|
||||
const timeoutMap: Partial<Record<OrderStatus, number>> = {
|
||||
pending_accept: ORDER_ACCEPT_TIMEOUT_MS,
|
||||
pending_close: ORDER_CLOSE_TIMEOUT_MS,
|
||||
pending_review: ORDER_REVIEW_TIMEOUT_MS,
|
||||
}
|
||||
const timeoutMs = timeoutMap[status]!
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
const state = useOrderStore.getState()
|
||||
@@ -181,10 +193,10 @@ function scheduleOrderTimeout(orderId: string, status: OrderStatus) {
|
||||
|
||||
if (status === "pending_accept") {
|
||||
state.autoTimeoutPendingAccept(orderId)
|
||||
}
|
||||
|
||||
if (status === "pending_close") {
|
||||
} else if (status === "pending_close") {
|
||||
state.autoTimeoutPendingClose(orderId)
|
||||
} else if (status === "pending_review") {
|
||||
state.autoTimeoutPendingReview(orderId)
|
||||
}
|
||||
|
||||
orderTimeouts.delete(orderId)
|
||||
@@ -330,9 +342,15 @@ export const useOrderStore = create<OrderState>((set, get) => {
|
||||
return { decision: deny("ROLE_FORBIDDEN", "仅客户可下单支付") }
|
||||
}
|
||||
|
||||
const dedupeKey = `${input.consumerId}-${input.service.id}`
|
||||
if (pendingCreations.has(dedupeKey)) {
|
||||
return { decision: deny("DUPLICATE_REQUEST", "订单正在创建中,请勿重复提交") }
|
||||
}
|
||||
pendingCreations.add(dedupeKey)
|
||||
const orderId = generateId("ord")
|
||||
const paid = useWalletStore.getState().deductBalance(orderId, input.totalPrice)
|
||||
if (!paid) {
|
||||
pendingCreations.delete(dedupeKey)
|
||||
return { decision: deny("PAYMENT_FAILED", "余额不足或订单已支付") }
|
||||
}
|
||||
|
||||
@@ -357,9 +375,16 @@ export const useOrderStore = create<OrderState>((set, get) => {
|
||||
|
||||
useChatStore.getState().ensureOrderSession(order)
|
||||
notifyOrderStatus(order)
|
||||
setTimeout(() => pendingCreations.delete(dedupeKey), 2000)
|
||||
return { decision: allow(), order }
|
||||
},
|
||||
payOrder: (orderId, actor) => applyTransition(orderId, "PAY", actor),
|
||||
payOrder: (orderId, actor) => {
|
||||
const order = get().orders.find((item) => item.id === orderId)
|
||||
if (!order) return { decision: deny("NOT_FOUND", "订单不存在") }
|
||||
const paid = useWalletStore.getState().deductBalance(orderId, order.totalPrice)
|
||||
if (!paid) return { decision: deny("PAYMENT_FAILED", "余额不足或订单已支付") }
|
||||
return applyTransition(orderId, "PAY", actor)
|
||||
},
|
||||
acceptOrder: (orderId, actor) => applyTransition(orderId, "ACCEPT", actor),
|
||||
requestClose: (orderId, actor) => applyTransition(orderId, "REQUEST_CLOSE", actor),
|
||||
confirmClose: (orderId, actor) => applyTransition(orderId, "CONFIRM_CLOSE", actor),
|
||||
@@ -367,35 +392,8 @@ export const useOrderStore = create<OrderState>((set, get) => {
|
||||
markDisputed: (orderId, actor) => applyTransition(orderId, "OPEN_DISPUTE", actor),
|
||||
autoTimeoutPendingAccept: (orderId) => applyTransition(orderId, "AUTO_TIMEOUT_PENDING_ACCEPT"),
|
||||
autoTimeoutPendingClose: (orderId) => applyTransition(orderId, "AUTO_TIMEOUT_PENDING_CLOSE"),
|
||||
updateOrderStatus: (orderId, status) => {
|
||||
set((state) => {
|
||||
let previousOrder: Order | undefined
|
||||
let updatedOrder: Order | undefined
|
||||
|
||||
const orders = state.orders.map((order) => {
|
||||
if (order.id !== orderId) return order
|
||||
previousOrder = order
|
||||
updatedOrder = applyStatus(order, status)
|
||||
return updatedOrder
|
||||
})
|
||||
|
||||
if (previousOrder && updatedOrder) {
|
||||
if (previousOrder.status !== "completed" && updatedOrder.status === "completed") {
|
||||
useWalletStore
|
||||
.getState()
|
||||
.addIncome(updatedOrder.id, updatedOrder.totalPrice, updatedOrder.shopId)
|
||||
}
|
||||
|
||||
if (previousOrder.status === "pending_accept" && updatedOrder.status === "cancelled") {
|
||||
useWalletStore.getState().refundPayment(updatedOrder.id, updatedOrder.totalPrice)
|
||||
}
|
||||
|
||||
syncChatSession(updatedOrder, previousOrder.status)
|
||||
}
|
||||
|
||||
return { orders }
|
||||
})
|
||||
},
|
||||
autoTimeoutPendingReview: (orderId) => applyTransition(orderId, "AUTO_TIMEOUT_PENDING_REVIEW"),
|
||||
resolveDispute: (orderId) => applyTransition(orderId, "RESOLVE_DISPUTE"),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -404,9 +402,19 @@ useOrderStore.subscribe((state, prevState) => {
|
||||
const prevOrder = prevState.orders.find((item) => item.id === order.id)
|
||||
if (!prevOrder || prevOrder.status !== order.status) {
|
||||
scheduleOrderTimeout(order.id, order.status)
|
||||
if (order.status === "pending_accept" && order.shopId) {
|
||||
const shop = useShopStore.getState().shops.find((s) => s.id === order.shopId)
|
||||
if (shop?.dispatchMode === "auto") {
|
||||
setTimeout(() => {
|
||||
const current = useOrderStore.getState().orders.find((o) => o.id === order.id)
|
||||
if (!current || current.status !== "pending_accept") return
|
||||
const actor: Actor = { userId: order.playerId, role: "player", shopId: order.shopId }
|
||||
useOrderStore.getState().acceptOrder(order.id, actor)
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
prevState.orders.forEach((order) => {
|
||||
if (!state.orders.some((item) => item.id === order.id)) {
|
||||
clearOrderTimeout(order.id)
|
||||
|
||||
@@ -105,6 +105,7 @@ export const useReviewStore = create<ReviewState>((set, get) => ({
|
||||
item.orderId === input.orderId ? { ...item, sealed: false } : item,
|
||||
),
|
||||
}))
|
||||
useOrderStore.getState().autoTimeoutPendingReview(input.orderId)
|
||||
}
|
||||
|
||||
return allow()
|
||||
|
||||
Reference in New Issue
Block a user