forked from Yara724/api
fix: harden claim review and inquiry workflows
Preserve damage history and current vehicle price, restore depreciation mapping, normalize inquiry/report output, and support resumable expert review with paginated case retrieval.
This commit is contained in:
170
src/helpers/claim-damaged-part-audit.ts
Normal file
170
src/helpers/claim-damaged-part-audit.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { resolveStoredFileUrl } from "src/helpers/urlCreator";
|
||||
import {
|
||||
coerceDamagedPartsMediaToArray,
|
||||
partLookupKey,
|
||||
type DamageSelectedPartV2,
|
||||
} from "src/helpers/outer-damage-parts";
|
||||
|
||||
export interface DamagedPartAuditActor {
|
||||
actorId: string;
|
||||
actorName?: string;
|
||||
actorType: string;
|
||||
}
|
||||
|
||||
export interface StoredDamagedPartAuditRow extends DamageSelectedPartV2 {
|
||||
capture?: {
|
||||
path?: string;
|
||||
fileName?: string;
|
||||
url?: string;
|
||||
capturedAt?: Date | string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StoredDamagedPartSelectionRevision {
|
||||
revisionId: string;
|
||||
changedAt: Date;
|
||||
changedBy: DamagedPartAuditActor;
|
||||
expertProfileSnapshot?: unknown;
|
||||
previousSelectedParts: DamageSelectedPartV2[];
|
||||
selectedParts: DamageSelectedPartV2[];
|
||||
removedParts: StoredDamagedPartAuditRow[];
|
||||
addedParts: StoredDamagedPartAuditRow[];
|
||||
}
|
||||
|
||||
export interface DamagedPartSelectionHistoryRow {
|
||||
revisionId: string;
|
||||
changedAt: Date | string;
|
||||
changedBy: DamagedPartAuditActor;
|
||||
expertProfileSnapshot?: unknown;
|
||||
removedParts: Array<
|
||||
DamageSelectedPartV2 & {
|
||||
partId: number | null;
|
||||
captured: boolean;
|
||||
url?: string;
|
||||
fileName?: string;
|
||||
capturedAt?: Date | string;
|
||||
currentlySelected: boolean;
|
||||
}
|
||||
>;
|
||||
addedParts: Array<
|
||||
DamageSelectedPartV2 & {
|
||||
partId: number | null;
|
||||
currentlySelected: boolean;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
function plainCapture(row: unknown): StoredDamagedPartAuditRow["capture"] {
|
||||
if (!row || typeof row !== "object") return undefined;
|
||||
const source = row as Record<string, unknown>;
|
||||
const capture = {
|
||||
...(source.path ? { path: String(source.path) } : {}),
|
||||
...(source.fileName ? { fileName: String(source.fileName) } : {}),
|
||||
...(source.url ? { url: String(source.url) } : {}),
|
||||
...(source.capturedAt
|
||||
? { capturedAt: source.capturedAt as Date | string }
|
||||
: {}),
|
||||
};
|
||||
return Object.keys(capture).length > 0 ? capture : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds one immutable audit revision before the live selected-parts/media arrays
|
||||
* are replaced. Returns null for a semantic no-op (including order-only changes).
|
||||
*/
|
||||
export function buildDamagedPartSelectionRevision(params: {
|
||||
revisionId: string;
|
||||
changedAt: Date;
|
||||
changedBy: DamagedPartAuditActor;
|
||||
expertProfileSnapshot?: unknown;
|
||||
previousParts: DamageSelectedPartV2[];
|
||||
selectedParts: DamageSelectedPartV2[];
|
||||
previousMedia: unknown;
|
||||
}): StoredDamagedPartSelectionRevision | null {
|
||||
const previousKeys = new Set(params.previousParts.map(partLookupKey));
|
||||
const selectedKeys = new Set(params.selectedParts.map(partLookupKey));
|
||||
const mediaRows = coerceDamagedPartsMediaToArray(
|
||||
params.previousMedia,
|
||||
params.previousParts,
|
||||
);
|
||||
|
||||
const removedParts = params.previousParts.flatMap((part, index) => {
|
||||
if (selectedKeys.has(partLookupKey(part))) return [];
|
||||
const capture = plainCapture(mediaRows[index]);
|
||||
return [{ ...part, ...(capture ? { capture } : {}) }];
|
||||
});
|
||||
const addedParts = params.selectedParts
|
||||
.filter((part) => !previousKeys.has(partLookupKey(part)))
|
||||
.map((part) => ({ ...part }));
|
||||
|
||||
if (removedParts.length === 0 && addedParts.length === 0) return null;
|
||||
|
||||
return {
|
||||
revisionId: params.revisionId,
|
||||
changedAt: params.changedAt,
|
||||
changedBy: params.changedBy,
|
||||
...(params.expertProfileSnapshot
|
||||
? { expertProfileSnapshot: params.expertProfileSnapshot }
|
||||
: {}),
|
||||
previousSelectedParts: params.previousParts.map((part) => ({ ...part })),
|
||||
selectedParts: params.selectedParts.map((part) => ({ ...part })),
|
||||
removedParts,
|
||||
addedParts,
|
||||
};
|
||||
}
|
||||
|
||||
/** Converts stored revisions into a stable, URL-enriched panel contract. */
|
||||
export function serializeDamagedPartSelectionHistory(params: {
|
||||
history: unknown;
|
||||
currentSelectedParts: DamageSelectedPartV2[];
|
||||
}): DamagedPartSelectionHistoryRow[] {
|
||||
if (!Array.isArray(params.history)) return [];
|
||||
const currentKeys = new Set(params.currentSelectedParts.map(partLookupKey));
|
||||
|
||||
return params.history
|
||||
.filter((revision) => revision && typeof revision === "object")
|
||||
.map((revision: any) => ({
|
||||
revisionId: String(revision.revisionId ?? ""),
|
||||
changedAt: revision.changedAt,
|
||||
changedBy: {
|
||||
actorId: String(revision.changedBy?.actorId ?? ""),
|
||||
actorName: revision.changedBy?.actorName,
|
||||
actorType: String(revision.changedBy?.actorType ?? "damage_expert"),
|
||||
},
|
||||
...(revision.expertProfileSnapshot
|
||||
? { expertProfileSnapshot: revision.expertProfileSnapshot }
|
||||
: {}),
|
||||
removedParts: (Array.isArray(revision.removedParts)
|
||||
? revision.removedParts
|
||||
: []
|
||||
).map((part: StoredDamagedPartAuditRow) => ({
|
||||
id: part.id ?? null,
|
||||
partId: part.id ?? null,
|
||||
name: part.name,
|
||||
side: part.side,
|
||||
label_fa: part.label_fa,
|
||||
...(part.catalogKey ? { catalogKey: part.catalogKey } : {}),
|
||||
captured: !!part.capture,
|
||||
...(part.capture?.fileName ? { fileName: part.capture.fileName } : {}),
|
||||
...(part.capture?.capturedAt
|
||||
? { capturedAt: part.capture.capturedAt }
|
||||
: {}),
|
||||
...(resolveStoredFileUrl(part.capture)
|
||||
? { url: resolveStoredFileUrl(part.capture) }
|
||||
: {}),
|
||||
currentlySelected: currentKeys.has(partLookupKey(part)),
|
||||
})),
|
||||
addedParts: (Array.isArray(revision.addedParts)
|
||||
? revision.addedParts
|
||||
: []
|
||||
).map((part: DamageSelectedPartV2) => ({
|
||||
...part,
|
||||
partId: part.id ?? null,
|
||||
currentlySelected: currentKeys.has(partLookupKey(part)),
|
||||
})),
|
||||
}))
|
||||
.filter(
|
||||
(revision) =>
|
||||
revision.removedParts.length > 0 || revision.addedParts.length > 0,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user