1
0
forked from Yara724/api

Fixed damagedParts unified structure and resend problems

This commit is contained in:
SepehrYahyaee
2026-06-03 11:34:21 +03:30
parent 456135ad08
commit 077bae429e
6 changed files with 154 additions and 95 deletions

View File

@@ -62,7 +62,9 @@ export function findCatalogItemByPersianLabel(
/** Non-catalog lines (expert-added interior / free-text); never use catalog numeric ids. */
export function isInternalDamageSide(side: string): boolean {
const s = String(side ?? "").trim().toLowerCase();
const s = String(side ?? "")
.trim()
.toLowerCase();
if (!s || s === "internal" || s === "other") return true;
return !SIDE_SET.has(s);
}
@@ -120,7 +122,9 @@ const SIDE_LABEL_FA: Record<string, string> = {
[OuterPartSideV2.TOP]: "بالا",
};
function outerCatalogListForItem(item: OuterPartCatalogItem): OuterPartCatalogItem[] {
function outerCatalogListForItem(
item: OuterPartCatalogItem,
): OuterPartCatalogItem[] {
for (const list of Object.values(OUTER_PARTS_BY_CAR_TYPE)) {
if (list.some((x) => x.key === item.key || x.id === item.id)) {
return list;
@@ -165,9 +169,10 @@ function findCatalogItemByKey(key: string): OuterPartCatalogItem | undefined {
}
/** Strip leading `left_|right_|…` when present so `name` is side-agnostic for clients. */
export function splitCatalogKeyToNameAndSide(
fullKey: string,
): { name: string; side: string } {
export function splitCatalogKeyToNameAndSide(fullKey: string): {
name: string;
side: string;
} {
const t = String(fullKey ?? "").trim();
const i = t.indexOf("_");
if (i <= 0) return { name: t, side: "" };
@@ -329,7 +334,8 @@ export function normalizeDamageSelectedParts(
let catItem: OuterPartCatalogItem | undefined;
if (catalog && byCatalogKey) {
if (ck) catItem = byCatalogKey.get(ck);
if (!catItem && id != null) catItem = catalog.find((c) => c.id === id);
if (!catItem && id != null)
catItem = catalog.find((c) => c.id === id);
if (!catItem && side && name) {
catItem = byCatalogKey.get(catalogLikeKeyFromPart({ side, name }));
}
@@ -377,7 +383,9 @@ export function normalizeDamageSelectedParts(
const cat =
byCatalogKey?.get(k) ??
findCatalogItemByKey(k) ??
(toNum(o.id) != null ? CATALOG_ITEM_BY_ID.get(toNum(o.id)!) : undefined);
(toNum(o.id) != null
? CATALOG_ITEM_BY_ID.get(toNum(o.id)!)
: undefined);
if (cat) {
const list = catalog ?? outerCatalogListForItem(cat);
out.push(catalogItemToSelectedPart(cat, list));
@@ -581,7 +589,9 @@ export function catalogPartIdFromCarPartDamage(
export const partIdentityKeyFromCarPartDamage = catalogPartIdFromCarPartDamage;
function normPartSegment(s: string): string {
return String(s ?? "").replace(/[^a-z0-9]/gi, "").toLowerCase();
return String(s ?? "")
.replace(/[^a-z0-9]/gi, "")
.toLowerCase();
}
function looseCatalogNameMatch(
@@ -601,9 +611,13 @@ export function findCatalogItemFromLegacyExpertPart(
side: string,
carType?: ClaimVehicleTypeV2,
): OuterPartCatalogItem | undefined {
const s = String(side ?? "").toLowerCase().trim();
const s = String(side ?? "")
.toLowerCase()
.trim();
const lists = carType
? [OUTER_PARTS_BY_CAR_TYPE[carType]].filter(Boolean) as OuterPartCatalogItem[][]
? ([OUTER_PARTS_BY_CAR_TYPE[carType]].filter(
Boolean,
) as OuterPartCatalogItem[][])
: (Object.values(OUTER_PARTS_BY_CAR_TYPE) as OuterPartCatalogItem[][]);
for (const catalog of lists) {
if (!catalog?.length) continue;
@@ -615,7 +629,9 @@ export function findCatalogItemFromLegacyExpertPart(
return undefined;
}
function selectedPartToStoredRecord(p: DamageSelectedPartV2): Record<string, unknown> {
function selectedPartToStoredRecord(
p: DamageSelectedPartV2,
): Record<string, unknown> {
const r: Record<string, unknown> = {
name: p.name,
side: String(p.side || "").toLowerCase(),
@@ -780,9 +796,7 @@ export function isDamagedPartsMediaArray(data: unknown): boolean {
return Array.isArray(data);
}
function getLegacyMapRecord(
data: unknown,
): Record<string, unknown> | null {
function getLegacyMapRecord(data: unknown): Record<string, unknown> | null {
if (data == null) return null;
if (data instanceof Map) {
return Object.fromEntries((data as Map<string, unknown>).entries());
@@ -819,11 +833,9 @@ export function migrateLegacyDamagedPartsMapToArray(
selected: DamageSelectedPartV2[],
): Record<string, unknown>[] {
return selected.map((sp) => {
const keysTry = [
sp.catalogKey,
catalogLikeKeyFromPart(sp),
sp.name,
].filter(Boolean) as string[];
const keysTry = [sp.catalogKey, catalogLikeKeyFromPart(sp), sp.name].filter(
Boolean,
) as string[];
let blob: unknown;
for (const k of keysTry) {
blob = getCaptureBlobLoose(legacyMap, k);
@@ -887,12 +899,14 @@ export function resolvePartCaptureIndex(
if (!raw) return -1;
if (selected.length) {
// Pass 1: exact matches on catalogKey and catalogLikeKey
for (let i = 0; i < selected.length; i++) {
const sp = selected[i];
if (sp.catalogKey === raw) return i;
if (catalogLikeKeyFromPart(sp) === raw) return i;
}
// Pass 2: numeric id or array index
if (/^\d+$/.test(raw)) {
const n = Number(raw);
const byId = selected.findIndex((p) => p.id === n);
@@ -901,31 +915,35 @@ export function resolvePartCaptureIndex(
if (!hasIdCollision && n >= 0 && n < selected.length) return n;
}
// Pass 3: loose normalized match (handles spaces, underscores, case)
const loose = raw.toLowerCase().replace(/[\s_-]/g, "");
for (let i = 0; i < selected.length; i++) {
const sp = selected[i];
const cand = [
const candidates = [
sp.catalogKey,
catalogLikeKeyFromPart(sp),
sp.name,
].filter(Boolean) as string[];
for (const c of cand) {
for (const c of candidates) {
if (c.toLowerCase().replace(/[\s_-]/g, "") === loose) return i;
}
}
}
if (damagedPartsArray && damagedPartsArray.length) {
for (let i = 0; i < damagedPartsArray.length; i++) {
const row = damagedPartsArray[i];
if (!row || typeof row !== "object") continue;
const id = toNum((row as { id?: unknown }).id);
if (id != null && String(id) === raw) return i;
const ck = String((row as { catalogKey?: unknown }).catalogKey || "");
if (ck && ck === raw) return i;
const name = String((row as { name?: unknown }).name || "");
const side = String((row as { side?: unknown }).side || "");
if (side && name && `${side}_${name}` === raw) return i;
// Pass 4: suffix match — handles car-type-prefixed catalog keys
// e.g. "sedan_front_hood" should match sp.catalogKey="front_hood"
// or normalizeResendPartKeys output "sedan_front_hood" vs stored "front_hood"
for (let i = 0; i < selected.length; i++) {
const sp = selected[i];
const candidates = [
sp.catalogKey,
catalogLikeKeyFromPart(sp),
sp.name,
].filter(Boolean) as string[];
for (const c of candidates) {
const cLoose = c.toLowerCase().replace(/[\s_-]/g, "");
// raw ends with the candidate (prefix stripped)
if (loose.endsWith(cLoose) || cLoose.endsWith(loose)) return i;
}
}
}
@@ -939,7 +957,11 @@ export function mediaRowOrLegacyHasCapture(
): boolean {
if (damagedPartsData == null) return false;
if (Array.isArray(damagedPartsData)) {
const idx = resolvePartCaptureIndex(partKey, selected, damagedPartsData as any[]);
const idx = resolvePartCaptureIndex(
partKey,
selected,
damagedPartsData as any[],
);
if (idx < 0) return false;
return mediaRowHasCapture((damagedPartsData as unknown[])[idx]);
}