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) : {}; 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> { if (!Array.isArray(rawParts)) return []; const selectedNorm = normalizeDamageSelectedParts(selectedParts, carType); return rawParts.map((raw) => { const stored = raw && typeof raw === "object" ? (raw as Record) : {}; const sp = resolveResendPartRow(raw, carType, selectedNorm); const catalogKey = sp.catalogKey?.trim() || catalogLikeKeyFromPart(sp); const row: Record = { 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)); }