mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
75 lines
2.6 KiB
TypeScript
75 lines
2.6 KiB
TypeScript
import type { BookingDocumentReview } from './entities/booking-document-review.entity';
|
|
import type { BookingReviewNote } from './entities/booking-review-note.entity';
|
|
import type { FileRecord } from '../files/entities/file.entity';
|
|
|
|
/** One entry of a clearance document's per-card audit trail, oldest first. */
|
|
export interface ClearanceDocEvent {
|
|
type: 'UPLOADED' | 'RESUBMITTED' | 'QUERIED' | 'APPROVED';
|
|
at: string;
|
|
byName: string | null;
|
|
note: string | null;
|
|
}
|
|
|
|
/**
|
|
* Query review notes are written as `Document "<fileKey>" queried: <note>`
|
|
* (see BookingTransitionService.reviewDocument) — the only place a past query
|
|
* decision survives after the customer re-uploads and the review row resets.
|
|
*/
|
|
const QUERY_NOTE_RE = /^Document "(.+?)" queried: ([\s\S]*)$/;
|
|
|
|
/**
|
|
* Per-document audit trail assembled from data the flow already persists:
|
|
* every stored file version (first = customer upload, later ones = the
|
|
* customer's amendment responses), every query note (who opened it, when,
|
|
* why), and the review row's current approval. Approvals that were later
|
|
* reset by a re-upload are the one thing not kept anywhere — the trail shows
|
|
* the decision that currently stands.
|
|
*/
|
|
export function buildClearanceDocHistory(input: {
|
|
fileKey: string;
|
|
/** All versions of all files on the booking, createdAt ASC, deleted included. */
|
|
allVersions: FileRecord[];
|
|
/** CHANGES_REQUESTED review notes for the booking. */
|
|
queryNotes: BookingReviewNote[];
|
|
review: BookingDocumentReview | null;
|
|
/** staff id → display name. */
|
|
names: Map<string, string>;
|
|
}): ClearanceDocEvent[] {
|
|
const { fileKey, allVersions, queryNotes, review, names } = input;
|
|
const events: ClearanceDocEvent[] = [];
|
|
|
|
const versions = allVersions.filter((v) => v.code === fileKey);
|
|
versions.forEach((v, i) => {
|
|
events.push({
|
|
type: i === 0 ? 'UPLOADED' : 'RESUBMITTED',
|
|
at: v.createdAt.toISOString(),
|
|
byName: v.uploadedByName ?? null,
|
|
note: null,
|
|
});
|
|
});
|
|
|
|
for (const n of queryNotes) {
|
|
const m = QUERY_NOTE_RE.exec(n.note);
|
|
if (!m || m[1] !== fileKey) continue;
|
|
events.push({
|
|
type: 'QUERIED',
|
|
at: n.createdAt.toISOString(),
|
|
byName: n.authorId ? (names.get(n.authorId) ?? null) : null,
|
|
note: m[2] || null,
|
|
});
|
|
}
|
|
|
|
if (review?.status === 'APPROVED' && review.reviewedAt) {
|
|
events.push({
|
|
type: 'APPROVED',
|
|
at: review.reviewedAt.toISOString(),
|
|
byName: review.reviewedByStaffId
|
|
? (names.get(review.reviewedByStaffId) ?? null)
|
|
: null,
|
|
note: null,
|
|
});
|
|
}
|
|
|
|
return events.sort((a, b) => a.at.localeCompare(b.at));
|
|
}
|