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-19 12:08:00 +00:00
parent 4977a213a0
commit d339c17dc0
4 changed files with 220 additions and 128 deletions

View file

@ -1,95 +1,118 @@
import { Knex } from 'knex';
import { Dispute, DisputeEvent, createDisputeTables } from './dispute-flow.model';
import { Dispute, DisputeEvent } from './dispute-flow.model';
import { db } from '../db';
export class DisputeFlowService {
constructor(private knex: Knex) {}
async init(): Promise<void> {
await createDisputeTables(this.knex);
}
async createDispute(disputeData: Omit<Dispute, 'id' | 'created_at' | 'updated_at'>): Promise<Dispute> {
const [dispute] = await this.knex('disputes').insert(disputeData).returning('*');
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 *
`;
// Log the creation event
await this.logDisputeEvent(dispute.id, 'dispute_created', disputeData.opened_by_user_id, {
...disputeData,
disputeId: dispute.id
});
return dispute;
const values = [
disputeData.dealId,
disputeData.openedByUserId,
disputeData.status || 'open',
disputeData.reasonCode,
disputeData.summary,
disputeData.requestedOutcome
];
const result = await db.query(query, values);
return result.rows[0];
}
async getDispute(id: number): Promise<Dispute | null> {
return await this.knex('disputes').where({ id }).first();
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 (?, ?, ?, ?)
`;
const values = [
disputeId,
'evidence',
actorUserId,
JSON.stringify(evidenceData)
];
await db.query(query, values);
}
async updateDisputeStatus(disputeId: number, status: Dispute['status'], updatedByUserId: number): Promise<void> {
const dispute = await this.getDispute(disputeId);
if (!dispute) {
throw new Error(`Dispute with id ${disputeId} not found`);
}
await this.knex('disputes')
.where({ id: disputeId })
.update({ status, updated_at: this.knex.fn.now() });
// Log the status change event
await this.logDisputeEvent(disputeId, 'status_changed', updatedByUserId, {
oldStatus: dispute.status,
newStatus: status
});
async updateDisputeStatus(disputeId: number, newStatus: string, actorUserId: number): Promise<void> {
// Update the dispute status
const query = `
UPDATE disputes
SET status = ?
WHERE id = ?
`;
await db.query(query, [newStatus, disputeId]);
// Log the status change as an event
const eventQuery = `
INSERT INTO dispute_events
(dispute_id, event_type, actor_user_id, payload_json)
VALUES (?, ?, ?, ?)
`;
const values = [
disputeId,
'status_change',
actorUserId,
JSON.stringify({ newStatus })
];
await db.query(eventQuery, values);
}
async addEvidence(disputeId: number, evidenceData: any, uploadedByUserId: number): Promise<void> {
const dispute = await this.getDispute(disputeId);
if (!dispute) {
throw new Error(`Dispute with id ${disputeId} not found`);
}
// Log the evidence upload event
await this.logDisputeEvent(disputeId, 'evidence_added', uploadedByUserId, {
...evidenceData,
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 = ?
`;
await db.query(query, [
decisionData.decision,
decisionData.decisionReason,
actorUserId,
disputeId
});
]);
// Log the resolution as an event
const eventQuery = `
INSERT INTO dispute_events
(dispute_id, event_type, actor_user_id, payload_json)
VALUES (?, ?, ?, ?)
`;
const values = [
disputeId,
'resolved',
actorUserId,
JSON.stringify(decisionData)
];
await db.query(eventQuery, values);
}
async resolveDispute(disputeId: number, decision: string, reason: string, resolvedByUserId: number): Promise<void> {
const dispute = await this.getDispute(disputeId);
if (!dispute) {
throw new Error(`Dispute with id ${disputeId} not found`);
}
await this.knex('disputes')
.where({ id: disputeId })
.update({
status: 'resolved',
final_decision: decision,
final_reason: reason,
decided_by_user_id: resolvedByUserId,
decided_at: this.knex.fn.now(),
updated_at: this.knex.fn.now()
});
// Log the resolution event
await this.logDisputeEvent(disputeId, 'dispute_resolved', resolvedByUserId, {
decision,
reason,
disputeId
});
}
async logDisputeEvent(disputeId: number, eventType: string, actorUserId: number, payload: any): Promise<void> {
await this.knex('dispute_events').insert({
dispute_id: disputeId,
event_type: eventType,
actor_user_id: actorUserId,
payload_json: payload,
created_at: this.knex.fn.now()
});
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;
}
async getDisputeEvents(disputeId: number): Promise<DisputeEvent[]> {
return await this.knex('dispute_events').where({ dispute_id: disputeId }).orderBy('created_at', 'asc');
const query = 'SELECT * FROM dispute_events WHERE dispute_id = ? ORDER BY created_at ASC';
const result = await db.query(query, [disputeId]);
return result.rows;
}
}