Files
nick-doc/02 - Data Models/Dispute.md
Siavash Sameni 81625d35d2 docs: AML scope note, human-blocked items, Task #11 pre-flight inventory
- Add AML scope note to Handoff - RN Multichain Probe (sanctions-only vs full KYT)
- Add human-blocked section with 3 precise next steps for owner
- Create Task 11 Pre-flight Inventory: library choice, dev/prod flow, admin UI gaps, backend gaps, risks, acceptance criteria
2026-05-28 20:42:42 +04:00

5.8 KiB

title, tags, aliases
title tags aliases
Dispute
data-model
mongoose
Complaint
IDispute

Dispute

Buyer-raised complaint tied to a PurchaseRequest. Captures the reason, priority, category, an array of evidence uploads, a chronological timeline of actions, an optional resolution, and SLA deadlines. An admin (adminId) is assigned during triage and resolves the dispute with a structured action (refund, replacement, compensation, warning_seller, ban_seller, or no_action).

[!note] Implementation status backend/src/models/Dispute.ts, backend/src/services/dispute/DisputeService.ts, backend/src/routes/disputeRoutes.ts, and release-hold helper routes now exist. The remaining gap is canonical state alignment between the full dispute document and the lighter PurchaseRequest/Payment hold flags used by release gating.

Source: backend/src/models/Dispute.ts — schema definition and model export.

Schema

Field Type Required Default Validation Index Description
purchaseRequestId ObjectId → PurchaseRequest yes yes The disputed request.
buyerId ObjectId → User yes yes Complaining buyer.
sellerId ObjectId → User no yes Implicated seller.
adminId ObjectId → User no yes (single + compound) Admin owning the case.
reason String yes trim, maxlength 200 Short reason.
description String yes trim, maxlength 2000 Detailed description.
priority String no medium enum: low / medium / high / urgent yes Triage priority.
category String yes enum: product_quality / delivery_delay / wrong_item / payment_issue / seller_behavior / other yes Issue type.
status String no pending enum: pending / in_progress / waiting_response / resolved / rejected / closed yes (single + compound) Lifecycle state.
evidence[].type String yes enum: image / document / screenshot / video Evidence kind.
evidence[].url String yes Stored URL.
evidence[].description String no Notes.
evidence[].uploadedBy ObjectId → User yes Uploader.
evidence[].uploadedAt Date no Date.now Upload time.
chatId ObjectId → Chat no Linked support chat.
timeline[].action String yes Action label.
timeline[].performedBy ObjectId → User yes Actor.
timeline[].performedAt Date no Date.now When.
timeline[].details String no Free-form notes.
resolution.action String no enum: refund / replacement / compensation / warning_seller / ban_seller / no_action Outcome.
resolution.amount Number no Monetary amount (refund/compensation).
resolution.currency String no enum: USD / EUR / IRR / USDT Currency.
resolution.notes String no maxlength 1000 Resolution notes.
resolution.resolvedBy ObjectId → User no Admin who resolved.
resolution.resolvedAt Date no When resolved.
deadline Date no Overall SLA deadline.
responseDeadline Date no Response SLA.
tags[] String[] no trim Filter tags.
closedAt Date no When closed.
createdAt Date auto yes (desc) Mongoose timestamp.
updatedAt Date auto Mongoose timestamp.

[!note] messages in the interface The TypeScript interface mentions an optional embedded messages[] array, but the actual Mongoose schema does not declare it — messages live in Chat via chatId.

Virtuals

None defined.

Indexes

Defined at backend/src/models/Dispute.ts:

  • { purchaseRequestId: 1 }
  • { buyerId: 1 }
  • { sellerId: 1 }
  • { adminId: 1 }
  • { status: 1 }
  • { priority: 1 }
  • { category: 1 }
  • { createdAt: -1 }
  • { status: 1, priority: -1 } — admin queue
  • { adminId: 1, status: 1 } — per-admin workload

Pre/Post Hooks

Hook Behaviour
pre('save') (backend/src/models/Dispute.ts) On new documents pushes a dispute_created entry into timeline attributed to buyerId.

Instance Methods

None defined.

Static Methods

None defined.

Relationships

  • References: PurchaseRequest (purchaseRequestId), User (buyerId, sellerId, adminId, evidence and timeline contributors, resolution.resolvedBy), Chat (chatId).
  • Referenced by: none directly.

State Transitions

stateDiagram-v2
    [*] --> pending
    pending --> in_progress : admin assigned
    in_progress --> waiting_response : awaiting party
    waiting_response --> in_progress : response received
    in_progress --> resolved : action applied
    in_progress --> rejected : invalid
    resolved --> closed
    rejected --> closed
    closed --> [*]

Common Queries

// Admin queue
Dispute.find({ status: { $in: ['pending', 'in_progress', 'waiting_response'] } })
       .sort({ priority: -1, createdAt: 1 });

// Buyer's disputes
Dispute.find({ buyerId }).sort({ createdAt: -1 });

// Seller's open disputes
Dispute.find({ sellerId, status: { $nin: ['resolved', 'rejected', 'closed'] } });

// Append timeline entry atomically
Dispute.updateOne(
  { _id },
  { $push: { timeline: { action, performedBy: adminId, performedAt: new Date(), details } } }
);

Related: PurchaseRequest, User, Chat, Payment.