forked from Yara724/api
324 lines
10 KiB
TypeScript
324 lines
10 KiB
TypeScript
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
|
import { ClaimWorkflowStep } from "src/Types&Enums/claim-request-management/claim-workflow-steps.enum";
|
|
import { getDamagedPartCaptureBlob } from "./claim-car-angle-media";
|
|
import {
|
|
catalogLikeKeyFromPart,
|
|
DamageSelectedPartV2,
|
|
normalizeDamageSelectedParts,
|
|
partLookupKey,
|
|
} from "./outer-damage-parts";
|
|
import { ClaimVehicleTypeV2 } from "src/static/outer-car-parts-catalog";
|
|
import { canonicalizeResendDocumentKey } from "./claim-resend-document-keys";
|
|
|
|
export type ExpertResendCarPartV2 = {
|
|
id?: number | null;
|
|
name: string;
|
|
side: string;
|
|
label_fa: string;
|
|
label_en?: string;
|
|
catalogKey?: string;
|
|
/** Same as `catalogKey` — kept for clients that use `key` on capture/resend APIs. */
|
|
key?: string;
|
|
captured: boolean;
|
|
url?: string;
|
|
path?: string;
|
|
fileName?: string;
|
|
};
|
|
|
|
export function resendRequestHasPayload(r: {
|
|
resendDescription?: string;
|
|
resendDocuments?: unknown[];
|
|
resendCarParts?: unknown[];
|
|
}): boolean {
|
|
if (!r) return false;
|
|
if (String(r.resendDescription || "").trim() !== "") return true;
|
|
if (Array.isArray(r.resendDocuments) && r.resendDocuments.length > 0)
|
|
return true;
|
|
if (Array.isArray(r.resendCarParts) && r.resendCarParts.length > 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/** Normalize expert-requested document keys (strings or `{ key }`) to canonical enum strings. */
|
|
export function normalizeResendDocumentKeys(
|
|
docs: unknown[] | undefined,
|
|
): string[] {
|
|
if (!Array.isArray(docs)) return [];
|
|
const keys: string[] = [];
|
|
for (const d of docs) {
|
|
if (typeof d === "string" && d.trim()) {
|
|
const c = canonicalizeResendDocumentKey(d.trim());
|
|
if (c) keys.push(c);
|
|
} else if (d && typeof d === "object" && "key" in (d as object)) {
|
|
const k = String((d as { key?: string }).key || "").trim();
|
|
const c = canonicalizeResendDocumentKey(k);
|
|
if (c) keys.push(c);
|
|
}
|
|
}
|
|
return [...new Set(keys)];
|
|
}
|
|
|
|
/** Capture / finalize lookup keys for expert-requested damaged parts. */
|
|
export function normalizeResendPartKeys(
|
|
parts: unknown[] | undefined,
|
|
carType?: ClaimVehicleTypeV2,
|
|
selectedParts?: unknown,
|
|
): string[] {
|
|
if (!Array.isArray(parts)) return [];
|
|
const selectedNorm = normalizeDamageSelectedParts(selectedParts, carType);
|
|
const keys: string[] = [];
|
|
for (const p of parts) {
|
|
const enriched =
|
|
normalizeDamageSelectedParts([p], carType)[0] ||
|
|
matchResendPartFromSelected(p, selectedNorm);
|
|
if (enriched) {
|
|
const ck =
|
|
enriched.catalogKey?.trim() || catalogLikeKeyFromPart(enriched);
|
|
if (ck) keys.push(ck);
|
|
continue;
|
|
}
|
|
if (typeof p === "string" && p.trim()) keys.push(p.trim());
|
|
else if (p && typeof p === "object" && "key" in (p as object)) {
|
|
const k = String((p as { key?: string }).key || "").trim();
|
|
if (k) keys.push(k);
|
|
}
|
|
}
|
|
return [...new Set(keys)];
|
|
}
|
|
|
|
function matchResendPartFromSelected(
|
|
raw: unknown,
|
|
selectedNorm: DamageSelectedPartV2[],
|
|
): DamageSelectedPartV2 | undefined {
|
|
if (!raw || typeof raw !== "object" || !selectedNorm.length) return undefined;
|
|
const o = raw as { id?: number };
|
|
if (typeof o.id === "number" && Number.isFinite(o.id)) {
|
|
return selectedNorm.find((sp) => sp.id === Math.trunc(o.id));
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function resolveResendPartRow(
|
|
raw: unknown,
|
|
carType?: ClaimVehicleTypeV2,
|
|
selectedNorm?: DamageSelectedPartV2[],
|
|
): DamageSelectedPartV2 {
|
|
const fromRaw = normalizeDamageSelectedParts([raw], carType)[0];
|
|
if (fromRaw?.label_fa && fromRaw.name) return fromRaw;
|
|
const fromSelected = matchResendPartFromSelected(raw, selectedNorm ?? []);
|
|
if (fromSelected) return fromSelected;
|
|
if (fromRaw) return fromRaw;
|
|
const o = (raw && typeof raw === "object" ? raw : {}) as Record<
|
|
string,
|
|
unknown
|
|
>;
|
|
const fallbackKey =
|
|
typeof o.key === "string" && o.key.trim()
|
|
? o.key.trim()
|
|
: typeof raw === "string"
|
|
? String(raw).trim()
|
|
: "unknown";
|
|
return {
|
|
id: typeof o.id === "number" ? Math.trunc(o.id) : null,
|
|
name: typeof o.name === "string" ? o.name : fallbackKey,
|
|
side: typeof o.side === "string" ? o.side : "",
|
|
label_fa:
|
|
typeof o.label_fa === "string" && o.label_fa.trim()
|
|
? o.label_fa.trim()
|
|
: fallbackKey,
|
|
catalogKey: typeof o.catalogKey === "string" ? o.catalogKey : undefined,
|
|
};
|
|
}
|
|
|
|
/** Enrich stored resend rows for claim detail / capture UIs (label_fa, catalogKey, capture state). */
|
|
export function mapExpertResendCarPartsForClient(
|
|
rawParts: unknown[] | undefined,
|
|
options: {
|
|
carType?: ClaimVehicleTypeV2;
|
|
selectedParts?: unknown;
|
|
damagedPartsData?: unknown;
|
|
buildUrl?: (path: string) => string;
|
|
} = {},
|
|
): ExpertResendCarPartV2[] {
|
|
if (!Array.isArray(rawParts) || rawParts.length === 0) return [];
|
|
|
|
const selectedNorm = normalizeDamageSelectedParts(
|
|
options.selectedParts,
|
|
options.carType,
|
|
);
|
|
|
|
return rawParts.map((raw) => {
|
|
const stored =
|
|
raw && typeof raw === "object" ? (raw as Record<string, unknown>) : {};
|
|
const sp = resolveResendPartRow(raw, options.carType, selectedNorm);
|
|
const catalogKey = sp.catalogKey?.trim() || catalogLikeKeyFromPart(sp);
|
|
const captureKey = catalogKey || partLookupKey(sp);
|
|
const cap = getDamagedPartCaptureBlob(
|
|
options.damagedPartsData,
|
|
captureKey,
|
|
selectedNorm,
|
|
) as { url?: string; path?: string; fileName?: string } | undefined;
|
|
const capturedFromStore =
|
|
typeof stored.captured === "boolean" ? stored.captured : undefined;
|
|
const url =
|
|
cap?.url ||
|
|
(cap?.path && options.buildUrl ? options.buildUrl(cap.path) : undefined);
|
|
|
|
return {
|
|
id: sp.id,
|
|
name: sp.name,
|
|
side: sp.side,
|
|
label_fa: sp.label_fa,
|
|
...(typeof stored.label_en === "string" && stored.label_en.trim()
|
|
? { label_en: stored.label_en.trim() }
|
|
: {}),
|
|
catalogKey,
|
|
key: catalogKey,
|
|
captured: capturedFromStore ?? !!cap,
|
|
...(url ? { url } : {}),
|
|
...(cap?.path ? { path: cap.path } : {}),
|
|
...(cap?.fileName ? { fileName: cap.fileName } : {}),
|
|
};
|
|
});
|
|
}
|
|
|
|
/** Persistable snapshot for `evaluation.damageExpertResend.resendCarParts`. */
|
|
export function normalizeResendCarPartsForStorage(
|
|
rawParts: unknown[] | undefined,
|
|
carType?: ClaimVehicleTypeV2,
|
|
selectedParts?: unknown,
|
|
): Array<Record<string, unknown>> {
|
|
if (!Array.isArray(rawParts)) return [];
|
|
const selectedNorm = normalizeDamageSelectedParts(selectedParts, carType);
|
|
return rawParts.map((raw) => {
|
|
const stored =
|
|
raw && typeof raw === "object" ? (raw as Record<string, unknown>) : {};
|
|
const sp = resolveResendPartRow(raw, carType, selectedNorm);
|
|
const catalogKey = sp.catalogKey?.trim() || catalogLikeKeyFromPart(sp);
|
|
const row: Record<string, unknown> = {
|
|
id: sp.id,
|
|
name: sp.name,
|
|
side: sp.side,
|
|
label_fa: sp.label_fa,
|
|
catalogKey,
|
|
key: catalogKey,
|
|
captured: false,
|
|
};
|
|
if (typeof stored.label_en === "string" && stored.label_en.trim()) {
|
|
row.label_en = stored.label_en.trim();
|
|
}
|
|
return row;
|
|
});
|
|
}
|
|
|
|
function getRequiredDocEntry(claim: any, documentKey: string): unknown {
|
|
const rd = claim?.requiredDocuments;
|
|
if (!rd) return undefined;
|
|
return rd instanceof Map ? rd.get(documentKey) : rd[documentKey];
|
|
}
|
|
|
|
export function isRequiredDocumentUploaded(
|
|
claim: any,
|
|
documentKey: string,
|
|
): boolean {
|
|
const doc = getRequiredDocEntry(claim, documentKey) as
|
|
| { uploaded?: boolean }
|
|
| undefined;
|
|
return !!doc?.uploaded;
|
|
}
|
|
|
|
export function hasDamagedPartCapture(claim: any, partKey: string): boolean {
|
|
const data = claim?.media?.damagedParts;
|
|
if (!data) return false;
|
|
const carType = claim?.vehicle?.carType as ClaimVehicleTypeV2 | undefined;
|
|
const norm = normalizeDamageSelectedParts(
|
|
claim?.damage?.selectedParts,
|
|
carType,
|
|
claim?.damage?.selectedOuterParts,
|
|
);
|
|
return getDamagedPartCaptureBlob(data, partKey, norm) != null;
|
|
}
|
|
|
|
/**
|
|
* True when the user has satisfied every document and part the damage expert asked for,
|
|
* or when the expert only left instructions (no doc/part list) and there is a description.
|
|
*/
|
|
export function canFinalizeExpertResend(claim: any): boolean {
|
|
if (!claim) return false;
|
|
if (claim.status !== ClaimCaseStatus.WAITING_FOR_USER_RESEND) return false;
|
|
if (claim.workflow?.currentStep !== ClaimWorkflowStep.USER_EXPERT_RESEND)
|
|
return false;
|
|
|
|
const r = claim.evaluation?.damageExpertResend;
|
|
if (!r || r.fulfilledAt) return false;
|
|
|
|
const carType = claim?.vehicle?.carType as ClaimVehicleTypeV2 | undefined;
|
|
const docKeys = normalizeResendDocumentKeys(r.resendDocuments);
|
|
const partKeys = normalizeResendPartKeys(
|
|
r.resendCarParts,
|
|
carType,
|
|
claim?.damage?.selectedParts,
|
|
);
|
|
|
|
if (docKeys.length === 0 && partKeys.length === 0) {
|
|
return String(r.resendDescription || "").trim() !== "";
|
|
}
|
|
|
|
for (const k of docKeys) {
|
|
if (!isRequiredDocumentUploaded(claim, k)) return false;
|
|
}
|
|
for (const k of partKeys) {
|
|
if (!hasDamagedPartCapture(claim, k)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function documentKeyAllowedForExpertResend(
|
|
claim: any,
|
|
documentKey: string,
|
|
): boolean {
|
|
const r = claim?.evaluation?.damageExpertResend;
|
|
if (!r || r.fulfilledAt) return false;
|
|
const allowed = new Set(normalizeResendDocumentKeys(r.resendDocuments));
|
|
const normalized =
|
|
canonicalizeResendDocumentKey(documentKey) ?? documentKey.trim();
|
|
return allowed.has(normalized);
|
|
}
|
|
|
|
export function partKeyAllowedForExpertResend(
|
|
claim: any,
|
|
partKey: string,
|
|
): boolean {
|
|
const r = claim?.evaluation?.damageExpertResend;
|
|
if (!r || r.fulfilledAt) return false;
|
|
|
|
const carType = claim?.vehicle?.carType as ClaimVehicleTypeV2 | undefined;
|
|
const selectedParts = claim?.damage?.selectedParts;
|
|
|
|
// Normalize the stored resend parts to their canonical catalog keys
|
|
const allowedKeys = new Set(
|
|
normalizeResendPartKeys(r.resendCarParts, carType, selectedParts),
|
|
);
|
|
|
|
// Normalize the incoming partKey the same way so the comparison is apples-to-apples
|
|
const resolvedKeys = normalizeResendPartKeys(
|
|
[partKey],
|
|
carType,
|
|
selectedParts,
|
|
);
|
|
const resolvedKey = resolvedKeys[0];
|
|
|
|
// Also try direct catalogKey/name match against the stored objects as fallback
|
|
const directMatch = ((r.resendCarParts as any[]) ?? []).some((p: any) => {
|
|
if (!p || typeof p !== "object") return String(p) === partKey;
|
|
return (
|
|
p.catalogKey === partKey ||
|
|
p.key === partKey ||
|
|
p.name === partKey ||
|
|
(p.id != null && String(p.id) === partKey)
|
|
);
|
|
});
|
|
|
|
return directMatch || (resolvedKey != null && allowedKeys.has(resolvedKey));
|
|
}
|