import type { Actor } from "@/lib/actor" import { allow, deny } from "@/lib/decision" import type { ApiDecision } from "@/lib/errors" import { generateId } from "@/lib/id" import type { Dispute } from "@/lib/types" import { useAuthStore } from "@/store/auth" import { useNotificationStore } from "@/store/notifications" import { useOrderStore } from "@/store/orders" import { create } from "zustand" type DisputeTimelineType = "created" | "response" | "reviewing" | "resolved" | "appealed" interface DisputeTimelineItem { id: string type: DisputeTimelineType content: string createdAt: string } export interface DisputeRecord extends Dispute { respondentReason?: string respondentEvidence: string[] appealReason?: string appealedAt?: string timeline: DisputeTimelineItem[] } interface SubmitDisputeInput { orderId: string reason: string evidence: string[] } interface DisputeMutationResult { decision: ApiDecision dispute?: DisputeRecord } interface DisputeState { disputes: DisputeRecord[] getDisputeByOrderId: (orderId: string) => DisputeRecord | undefined submitDispute: (input: SubmitDisputeInput) => DisputeMutationResult submitResponse: ( disputeId: string, actorId: string, reason: string, evidence: string[], ) => ApiDecision submitAppeal: (disputeId: string, actorId: string, reason: string) => ApiDecision } function resolveParticipantActor(orderId: string, userId: string): Actor | null { const order = useOrderStore.getState().orders.find((item) => item.id === orderId) if (!order) return null if (order.consumerId === userId) { return { userId, role: "consumer", shopId: order.shopId, } } if (order.playerId === userId) { return { userId, role: "player", shopId: order.shopId, } } return null } function notifyDispute(orderId: string, title: string, content: string) { if (!useAuthStore.getState().notificationPrefs.order) { return } useNotificationStore.getState().addNotification({ type: "order", title, content, link: `/dispute/${orderId}`, }) } export const useDisputeStore = create((set, get) => { return { disputes: [], getDisputeByOrderId: (orderId) => get().disputes.find((dispute) => dispute.orderId === orderId), submitDispute: (input) => { const order = useOrderStore.getState().orders.find((item) => item.id === input.orderId) if (!order) { return { decision: deny(404, "订单不存在") } } if (order.status !== "in_progress" && order.status !== "pending_close") { return { decision: deny(400, "当前阶段不可发起争议") } } if (!input.reason.trim()) { return { decision: deny(400, "请填写争议原因") } } const actor = resolveParticipantActor(input.orderId, useAuthStore.getState().user?.id ?? "") if (!actor) { return { decision: deny(403, "仅订单参与方可发起争议") } } const markResult = useOrderStore.getState().markDisputed(input.orderId, actor) if (!markResult.decision.ok) { return { decision: markResult.decision } } const createdAt = new Date().toISOString() const dispute: DisputeRecord = { id: generateId("disp"), orderId: input.orderId, reason: input.reason.trim(), evidence: input.evidence, status: "open", createdAt, respondentEvidence: [], timeline: [ { id: generateId("timeline"), type: "created", content: "争议已提交", createdAt, }, ], } set((state) => ({ disputes: [dispute, ...state.disputes] })) return { decision: allow(), dispute } }, submitResponse: (disputeId, actorId, reason, evidence) => { const dispute = get().disputes.find((item) => item.id === disputeId) if (!dispute) { return deny(404, "争议不存在") } const order = useOrderStore.getState().orders.find((item) => item.id === dispute.orderId) if (!order) { return deny(404, "关联订单不存在") } const actor = resolveParticipantActor(dispute.orderId, actorId) if (!actor) { return deny(403, "仅订单参与方可提交回应") } if (dispute.respondentReason) { return deny(400, "回应已提交") } if (dispute.status !== "open" && dispute.status !== "reviewing") { return deny(400, "当前状态不可提交回应") } if (!reason.trim()) { return deny(400, "请填写回应理由") } set((state) => ({ disputes: state.disputes.map((item) => { if (item.id !== disputeId) return item return { ...item, respondentReason: reason.trim(), respondentEvidence: evidence, timeline: [ ...item.timeline, { id: generateId("timeline"), type: "response", content: "对方已提交回应材料", createdAt: new Date().toISOString(), }, ], } }), })) notifyDispute(dispute.orderId, "争议收到回应", "对方已提交争议回应材料") return allow() }, submitAppeal: (disputeId, actorId, reason) => { const dispute = get().disputes.find((item) => item.id === disputeId) if (!dispute) { return deny(404, "争议不存在") } const actor = resolveParticipantActor(dispute.orderId, actorId) if (!actor) { return deny(403, "仅订单参与方可提交申诉") } if (dispute.status !== "resolved") { return deny(400, "当前状态不可申诉") } if (dispute.appealedAt) { return deny(400, "该争议已申诉过") } if (!reason.trim()) { return deny(400, "请填写申诉理由") } set((state) => ({ disputes: state.disputes.map((item) => { if (item.id !== disputeId) return item return { ...item, status: "appealed", appealReason: reason.trim(), appealedAt: new Date().toISOString(), timeline: [ ...item.timeline, { id: generateId("timeline"), type: "appealed", content: "已提交申诉,平台将复核", createdAt: new Date().toISOString(), }, ], } }), })) notifyDispute(dispute.orderId, "争议已申诉", "申诉已提交,平台将继续复核") return allow() }, } })