Files
nick-doc/02 - Data Models/Dispute.md
Siavash Sameni 9698ec5809 docs: align API reference and data model docs with code reality
API Reference (9 files updated):
- Marketplace API: corrected offer endpoints (scoped under /purchase-requests/:id/offers),
  marked phantom /search /stats /seller/:sellerId /withdraw routes as NOT IMPLEMENTED,
  documented PUT→PATCH mismatches, removed invalid SellerOffer 'active' status
- Dispute API: corrected resolve schema (action enum), categories (no 'fraud'),
  removed 'under_review' status, added security callouts (3 unguarded endpoints),
  route shadowing documented, all socket events marked as TODO stubs
- Notification API: corrected mark-all-read method+path, fixed broken GET /:id,
  added unread-count-update event, 90-day TTL documented
- Payment API: /create→/save, removed 10+ phantom endpoints, fixed release/refund
  paths (no /shkeeper/ segment), added 3 unauthenticated endpoint security warnings,
  stats undercounting documented, export privilege gap documented
- Authentication API: 8-digit→6-digit code, no-complexity warning on reset-with-code,
  rate limiter counts all attempts, passkey stub claims removed, deleteAccount bug noted
- Admin API: PUT→PATCH bug documented, wrong status values documented, hard vs soft
  delete clarified, scanner no-auth security bug, 3 NOT IMPLEMENTED endpoints
- Chat API: file upload wrong endpoint bug, archive PUT→PATCH bug, rate limits added
- Points API: corrected redeem schema, referral triggers on 'completed' only,
  leaderboard period ignored, removed 'refund' PointTransaction type
- Socket Events: removed request-cancelled, notification-read; added unread-count-update;
  dispute events all stubs; referral-signup is auth-domain not points-domain

Data Models (3 files updated):
- SellerOffer: removed 'active' from status enum, withdrawOffer() is dead code
- PurchaseRequest: added pending_payment/active statuses, added 'urgent' urgency,
  corrected description minimum (5 chars), removed finalized/archived
- Dispute: corrected action enum, categories (no fraud), removed under_review,
  security callout on unguarded status/resolve endpoints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 14:57:47 +04:00

6.8 KiB

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

Dispute

Last updated: 2026-05-29 — aligned with code (see Doc vs Code Audit Report)

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.

⚠️ SECURITY — The dispute status update endpoint and the resolve endpoint currently have no role guards. Any authenticated user (not just admins) can modify dispute status or submit a resolution. This is a known gap pending a role-guard audit.

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.

Category enum

Valid values: product_quality · delivery_delay · wrong_item · payment_issue · seller_behavior · other

Note: fraud is not a valid category value. Use seller_behavior or other for fraud-related complaints.

Status enum

Valid values: pending · in_progress · waiting_response · resolved · rejected · closed

Note: under_review does not exist in the schema. The equivalent lifecycle state is in_progress.

Resolution action enum

Valid values: refund · replacement · compensation · warning_seller · ban_seller · no_action

[!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.