feat(dispute-flow): Implement dispute flow service and API endpoints
Some checks are pending
Docker Test / test (push) Waiting to run

This commit is contained in:
J.A.R.V.I.S. 2026-03-20 01:08:12 +00:00
parent 5a61bf2dbf
commit 25424ccb7e
3 changed files with 140 additions and 150 deletions

View file

@ -1,118 +1,120 @@
import { Dispute, DisputeEvent } from './dispute-flow.model';
import { db } from '../db';
import { db } from '../db/database';
import { Dispute, DisputeEvent, DisputeStatus } from './dispute-flow.model';
export class DisputeFlowService {
async createDispute(disputeData: Partial<Dispute>): Promise<Dispute> {
const query = `
INSERT INTO disputes
(deal_id, opened_by_user_id, status, reason_code, summary, requested_outcome)
VALUES (?, ?, ?, ?, ?, ?)
RETURNING *
`;
static async createDispute(disputeData: Omit<Dispute, 'id' | 'status' | 'created_at' | 'updated_at'>): Promise<Dispute> {
const { dealId, openedByUserId, reasonCode, summary, requestedOutcome } = disputeData;
const values = [
disputeData.dealId,
disputeData.openedByUserId,
disputeData.status || 'open',
disputeData.reasonCode,
disputeData.summary,
disputeData.requestedOutcome
];
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]
);
const result = await db.query(query, values);
return result.rows[0];
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];
}
async addEvidence(disputeId: number, evidenceData: any, actorUserId: number): Promise<void> {
// Insert evidence as a new event
const query = `
INSERT INTO dispute_events
(dispute_id, event_type, actor_user_id, payload_json)
VALUES (?, ?, ?, ?)
`;
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');
}
const values = [
disputeId,
'evidence',
actorUserId,
JSON.stringify(evidenceData)
];
const dispute = disputes[0];
if (dispute.status !== 'evidence') {
throw new Error('Evidence can only be added when dispute status is "evidence"');
}
await db.query(query, values);
await db.query(
`INSERT INTO dispute_events (dispute_id, event_type, actor_user_id, payload_json)
VALUES (?, ?, ?, ?)`,
[disputeId, 'evidence_added', actorUserId, JSON.stringify(evidenceData)]
);
}
async updateDisputeStatus(disputeId: number, newStatus: string, actorUserId: number): Promise<void> {
// Update the dispute status
const query = `
UPDATE disputes
SET status = ?
WHERE id = ?
`;
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');
}
await db.query(query, [newStatus, disputeId]);
const dispute = disputes[0];
// Log the status change as an event
const eventQuery = `
INSERT INTO dispute_events
(dispute_id, event_type, actor_user_id, payload_json)
VALUES (?, ?, ?, ?)
`;
// Validate status transition
const validTransitions: Record<DisputeStatus, DisputeStatus[]> = {
open: ['evidence'],
evidence: ['mediation', 'resolved', 'cancelled'],
mediation: ['resolved', 'cancelled'],
resolved: [],
cancelled: []
};
const values = [
disputeId,
'status_change',
actorUserId,
JSON.stringify({ newStatus })
];
if (!validTransitions[dispute.status].includes(newStatus)) {
throw new Error(`Invalid status transition from ${dispute.status} to ${newStatus}`);
}
await db.query(eventQuery, values);
// 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 })]
);
}
async resolveDispute(disputeId: number, decisionData: any, actorUserId: number): Promise<void> {
// Update the dispute with final decision
const query = `
UPDATE disputes
SET status = 'resolved',
final_decision = ?,
final_reason = ?,
decided_by_user_id = ?,
decided_at = NOW()
WHERE id = ?
`;
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');
}
await db.query(query, [
decisionData.decision,
decisionData.decisionReason,
actorUserId,
disputeId
]);
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"');
}
// Log the resolution as an event
const eventQuery = `
INSERT INTO dispute_events
(dispute_id, event_type, actor_user_id, payload_json)
VALUES (?, ?, ?, ?)
`;
// 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]
);
const values = [
disputeId,
'resolved',
actorUserId,
JSON.stringify(decisionData)
];
await db.query(eventQuery, values);
// 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)]
);
}
async getDisputeById(disputeId: number): Promise<Dispute | null> {
const query = 'SELECT * FROM disputes WHERE id = ?';
const result = await db.query(query, [disputeId]);
return result.rows[0] || null;
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;
}
async getDisputeEvents(disputeId: number): Promise<DisputeEvent[]> {
const query = 'SELECT * FROM dispute_events WHERE dispute_id = ? ORDER BY created_at ASC';
const result = await db.query(query, [disputeId]);
return result.rows;
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;
}
}