2026-03-20 01:08:12 +00:00
|
|
|
import { db } from '../db/database';
|
|
|
|
|
import { Dispute, DisputeEvent, DisputeStatus } from './dispute-flow.model';
|
2026-03-19 07:08:09 +00:00
|
|
|
|
|
|
|
|
export class DisputeFlowService {
|
2026-03-20 01:08:12 +00:00
|
|
|
static async createDispute(disputeData: Omit<Dispute, 'id' | 'status' | 'created_at' | 'updated_at'>): Promise<Dispute> {
|
|
|
|
|
const { dealId, openedByUserId, reasonCode, summary, requestedOutcome } = disputeData;
|
2026-03-19 07:08:09 +00:00
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
const result = await db.query(
|
|
|
|
|
`INSERT INTO disputes (deal_id, opened_by_user_id, reason_code, summary, requested_outcome)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?)`,
|
|
|
|
|
[dealId, openedByUserId, reasonCode, summary, requestedOutcome]
|
|
|
|
|
);
|
2026-03-19 12:08:00 +00:00
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
const disputeId = result.insertId;
|
|
|
|
|
|
|
|
|
|
// Create initial event
|
|
|
|
|
await db.query(
|
|
|
|
|
`INSERT INTO dispute_events (dispute_id, event_type, actor_user_id, payload_json)
|
|
|
|
|
VALUES (?, ?, ?, ?)`,
|
|
|
|
|
[disputeId, 'dispute_opened', openedByUserId, JSON.stringify({ reasonCode, summary })]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Fetch and return the created dispute
|
|
|
|
|
const [disputes] = await db.query<Dispute[]>('SELECT * FROM disputes WHERE id = ?', [disputeId]);
|
|
|
|
|
return disputes[0];
|
2026-03-19 07:08:09 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
static async addEvidence(disputeId: number, actorUserId: number, evidenceData: any): Promise<void> {
|
|
|
|
|
// Check if dispute exists and is in correct status
|
|
|
|
|
const [disputes] = await db.query<Dispute[]>('SELECT status FROM disputes WHERE id = ?', [disputeId]);
|
|
|
|
|
if (disputes.length === 0) {
|
|
|
|
|
throw new Error('Dispute not found');
|
|
|
|
|
}
|
2026-03-19 12:08:00 +00:00
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
const dispute = disputes[0];
|
|
|
|
|
if (dispute.status !== 'evidence') {
|
|
|
|
|
throw new Error('Evidence can only be added when dispute status is "evidence"');
|
|
|
|
|
}
|
2026-03-19 12:08:00 +00:00
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
await db.query(
|
|
|
|
|
`INSERT INTO dispute_events (dispute_id, event_type, actor_user_id, payload_json)
|
|
|
|
|
VALUES (?, ?, ?, ?)`,
|
|
|
|
|
[disputeId, 'evidence_added', actorUserId, JSON.stringify(evidenceData)]
|
|
|
|
|
);
|
2026-03-19 07:08:09 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
static async updateDisputeStatus(disputeId: number, actorUserId: number, newStatus: DisputeStatus): Promise<void> {
|
|
|
|
|
// Check if dispute exists
|
|
|
|
|
const [disputes] = await db.query<Dispute[]>('SELECT status FROM disputes WHERE id = ?', [disputeId]);
|
|
|
|
|
if (disputes.length === 0) {
|
|
|
|
|
throw new Error('Dispute not found');
|
|
|
|
|
}
|
2026-03-19 12:08:00 +00:00
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
const dispute = disputes[0];
|
2026-03-19 12:08:00 +00:00
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
// Validate status transition
|
|
|
|
|
const validTransitions: Record<DisputeStatus, DisputeStatus[]> = {
|
|
|
|
|
open: ['evidence'],
|
|
|
|
|
evidence: ['mediation', 'resolved', 'cancelled'],
|
|
|
|
|
mediation: ['resolved', 'cancelled'],
|
|
|
|
|
resolved: [],
|
|
|
|
|
cancelled: []
|
|
|
|
|
};
|
2026-03-19 12:08:00 +00:00
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
if (!validTransitions[dispute.status].includes(newStatus)) {
|
|
|
|
|
throw new Error(`Invalid status transition from ${dispute.status} to ${newStatus}`);
|
|
|
|
|
}
|
2026-03-19 12:08:00 +00:00
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
// Update dispute status
|
|
|
|
|
await db.query('UPDATE disputes SET status = ? WHERE id = ?', [newStatus, disputeId]);
|
|
|
|
|
|
|
|
|
|
// Create event
|
|
|
|
|
await db.query(
|
|
|
|
|
`INSERT INTO dispute_events (dispute_id, event_type, actor_user_id, payload_json)
|
|
|
|
|
VALUES (?, ?, ?, ?)`,
|
|
|
|
|
[disputeId, 'status_changed', actorUserId, JSON.stringify({ oldStatus: dispute.status, newStatus })]
|
|
|
|
|
);
|
2026-03-19 07:08:09 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
static async resolveDispute(disputeId: number, actorUserId: number, decisionData: {
|
|
|
|
|
decision: string;
|
|
|
|
|
decisionReason: string;
|
|
|
|
|
}): Promise<void> {
|
|
|
|
|
// Check if dispute exists and is in mediation or evidence status
|
|
|
|
|
const [disputes] = await db.query<Dispute[]>('SELECT status FROM disputes WHERE id = ?', [disputeId]);
|
|
|
|
|
if (disputes.length === 0) {
|
|
|
|
|
throw new Error('Dispute not found');
|
|
|
|
|
}
|
2026-03-19 12:08:00 +00:00
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
const dispute = disputes[0];
|
|
|
|
|
if (dispute.status !== 'mediation' && dispute.status !== 'evidence') {
|
|
|
|
|
throw new Error('Dispute can only be resolved when status is "mediation" or "evidence"');
|
|
|
|
|
}
|
2026-03-19 12:08:00 +00:00
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
// Update dispute with final decision
|
|
|
|
|
await db.query(
|
|
|
|
|
`UPDATE disputes
|
|
|
|
|
SET status = 'resolved', final_decision = ?, final_reason = ?, decided_by_user_id = ?, decided_at = NOW()
|
|
|
|
|
WHERE id = ?`,
|
|
|
|
|
[decisionData.decision, decisionData.decisionReason, actorUserId, disputeId]
|
|
|
|
|
);
|
2026-03-19 12:08:00 +00:00
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
// Create event
|
|
|
|
|
await db.query(
|
|
|
|
|
`INSERT INTO dispute_events (dispute_id, event_type, actor_user_id, payload_json)
|
|
|
|
|
VALUES (?, ?, ?, ?)`,
|
|
|
|
|
[disputeId, 'dispute_resolved', actorUserId, JSON.stringify(decisionData)]
|
|
|
|
|
);
|
2026-03-19 07:08:09 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
static async getDispute(disputeId: number): Promise<Dispute | null> {
|
|
|
|
|
const [disputes] = await db.query<Dispute[]>('SELECT * FROM disputes WHERE id = ?', [disputeId]);
|
|
|
|
|
return disputes.length > 0 ? disputes[0] : null;
|
2026-03-19 07:08:09 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 01:08:12 +00:00
|
|
|
static async getDisputeEvents(disputeId: number): Promise<DisputeEvent[]> {
|
|
|
|
|
const [events] = await db.query<DisputeEvent[]>('SELECT * FROM dispute_events WHERE dispute_id = ? ORDER BY created_at ASC', [disputeId]);
|
|
|
|
|
return events;
|
2026-03-19 07:08:09 +00:00
|
|
|
}
|
|
|
|
|
}
|