forked from Yara724/api
Merge pull request 'YARA-1177' (#235) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#235
This commit is contained in:
@@ -38,6 +38,7 @@ import { ClaimCaseDbService } from "./entites/db-service/claim-case.db.service";
|
||||
import { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service";
|
||||
import { CreateClaimFromBlameResponseDto } from "./dto/create-claim-v2.dto";
|
||||
import {
|
||||
OuterPartCatalogItemDto,
|
||||
SelectOuterPartsV2Dto,
|
||||
SelectOuterPartsV2ResponseDto,
|
||||
} from "./dto/select-outer-parts-v2.dto";
|
||||
@@ -140,7 +141,7 @@ import {
|
||||
} from "src/helpers/claim-car-angle-media";
|
||||
import {
|
||||
ClaimVehicleTypeV2,
|
||||
OUTER_PARTS_BY_CAR_TYPE,
|
||||
FANAVARAN_CAR_PARTS_CATALOG,
|
||||
OuterPartCatalogItem,
|
||||
} from "src/static/outer-car-parts-catalog";
|
||||
import {
|
||||
@@ -563,67 +564,53 @@ export class ClaimRequestManagementService {
|
||||
/**
|
||||
* Outer-parts catalog returned to clients (user app + expert panel).
|
||||
*
|
||||
* The shape intentionally mirrors how items are persisted under
|
||||
* `damage.selectedParts` so the front-end can match catalog rows to the
|
||||
* stored selection by `id` / `catalogKey` without renaming fields:
|
||||
*
|
||||
* { id, name, side, label_fa, catalogKey, carType }
|
||||
*
|
||||
* - `name` is side-agnostic (`backWheel`, not `left_backWheel`).
|
||||
* - `label_fa` is disambiguated with the side in parentheses when multiple
|
||||
* catalog rows share the same Farsi title (e.g. `چرخ عقب (چپ)`).
|
||||
* - `catalogKey` keeps the original full key (`left_backWheel`) for clients
|
||||
* that already address parts by it.
|
||||
* Fetches the live `car/base-info/car-components` lookup from Fanavaran so
|
||||
* the list always matches what Fanavaran accepts in DmgSections[].DmgSectionId.
|
||||
* The lookup service handles caching to disk; `carType` is ignored because
|
||||
* Fanavaran uses a single flat catalog across all vehicle types.
|
||||
*/
|
||||
getOuterPartsCatalogV2(
|
||||
carType?: ClaimVehicleTypeV2,
|
||||
): (DamageSelectedPartV2 & { carType: ClaimVehicleTypeV2 })[] {
|
||||
const buildForType = (
|
||||
type: ClaimVehicleTypeV2,
|
||||
items: OuterPartCatalogItem[],
|
||||
): (DamageSelectedPartV2 & { carType: ClaimVehicleTypeV2 })[] =>
|
||||
items.map((p) => ({
|
||||
...catalogItemToSelectedPart(p, items),
|
||||
carType: type,
|
||||
}));
|
||||
|
||||
if (carType) {
|
||||
const items = OUTER_PARTS_BY_CAR_TYPE[carType] || [];
|
||||
return buildForType(carType, items);
|
||||
}
|
||||
|
||||
const out: (DamageSelectedPartV2 & { carType: ClaimVehicleTypeV2 })[] = [];
|
||||
for (const [type, items] of Object.entries(OUTER_PARTS_BY_CAR_TYPE)) {
|
||||
out.push(...buildForType(type as ClaimVehicleTypeV2, items));
|
||||
}
|
||||
return out.sort((a, b) => {
|
||||
if (a.carType === b.carType) return (a.id ?? 0) - (b.id ?? 0);
|
||||
return String(a.carType).localeCompare(String(b.carType));
|
||||
});
|
||||
async getOuterPartsCatalogV2(): Promise<OuterPartCatalogItemDto[]> {
|
||||
const clientKey = resolveFanavaranClientKey();
|
||||
const rows = await this.getFanavaranLookupRows(clientKey, "car-components");
|
||||
return rows
|
||||
.filter(
|
||||
(r): r is { Id: number; Caption: string } =>
|
||||
!!r &&
|
||||
typeof r === "object" &&
|
||||
typeof (r as any).Id === "number" &&
|
||||
typeof (r as any).Caption === "string",
|
||||
)
|
||||
.map((r) => ({ id: r.Id, label_fa: r.Caption }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal raw catalog (kept in the original `OuterPartCatalogItem` shape so
|
||||
* existing internal code that indexes by `key`/`titleFa` keeps working).
|
||||
* Public API consumers should use `getOuterPartsCatalogV2` instead.
|
||||
* Resolve the live Fanavaran car-components catalog as `OuterPartCatalogItem`
|
||||
* rows for internal use (e.g. validating selectedPartIds).
|
||||
* Falls back to the static snapshot when the remote lookup is unavailable.
|
||||
*/
|
||||
private getOuterPartsRawCatalog(
|
||||
carType?: ClaimVehicleTypeV2,
|
||||
): OuterPartCatalogItem[] {
|
||||
if (carType) {
|
||||
return (OUTER_PARTS_BY_CAR_TYPE[carType] || []).map((p) => ({
|
||||
...p,
|
||||
carType,
|
||||
private async getLiveFanavaranCatalogItems(): Promise<OuterPartCatalogItem[]> {
|
||||
const clientKey = resolveFanavaranClientKey();
|
||||
const rows = await this.getFanavaranLookupRows(clientKey, "car-components");
|
||||
if (!rows.length) {
|
||||
this.logger.warn(
|
||||
"getLiveFanavaranCatalogItems: remote lookup returned empty, falling back to snapshot",
|
||||
);
|
||||
return FANAVARAN_CAR_PARTS_CATALOG;
|
||||
}
|
||||
return rows
|
||||
.filter(
|
||||
(r): r is { Id: number; Caption: string } =>
|
||||
!!r &&
|
||||
typeof r === "object" &&
|
||||
typeof (r as any).Id === "number" &&
|
||||
typeof (r as any).Caption === "string",
|
||||
)
|
||||
.map((r): OuterPartCatalogItem => ({
|
||||
id: r.Id,
|
||||
key: String(r.Id),
|
||||
titleFa: r.Caption,
|
||||
side: "",
|
||||
}));
|
||||
}
|
||||
const out: OuterPartCatalogItem[] = [];
|
||||
for (const [type, items] of Object.entries(OUTER_PARTS_BY_CAR_TYPE)) {
|
||||
const t = type as ClaimVehicleTypeV2;
|
||||
for (const p of items) {
|
||||
out.push({ ...p, carType: t });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private userDamageDetail(blRequest: RequestManagementModel) {
|
||||
@@ -7628,22 +7615,14 @@ export class ClaimRequestManagementService {
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Validate by selected car type and resolve selected parts by ids/keys
|
||||
// 5. Fetch the live Fanavaran catalog and resolve selected parts by ids
|
||||
const selectedCarType = (body as any)?.carType as
|
||||
| ClaimVehicleTypeV2
|
||||
| undefined;
|
||||
if (!selectedCarType || !OUTER_PARTS_BY_CAR_TYPE[selectedCarType]) {
|
||||
throw new BadRequestException(
|
||||
"Vehicle type is required before selecting outer parts.",
|
||||
);
|
||||
}
|
||||
|
||||
const catalog = OUTER_PARTS_BY_CAR_TYPE[selectedCarType];
|
||||
const liveCatalog = await this.getLiveFanavaranCatalogItems();
|
||||
const byId = new Map<number, OuterPartCatalogItem>(
|
||||
catalog.map((p) => [p.id, p]),
|
||||
);
|
||||
const byKey = new Map<string, OuterPartCatalogItem>(
|
||||
catalog.map((p) => [p.key, p]),
|
||||
liveCatalog.map((p) => [p.id, p]),
|
||||
);
|
||||
|
||||
const selectedFromIds: OuterPartCatalogItem[] = [];
|
||||
@@ -7655,43 +7634,20 @@ export class ClaimRequestManagementService {
|
||||
const item = byId.get(id);
|
||||
if (!item) {
|
||||
throw new BadRequestException(
|
||||
`Invalid outer part id for ${selectedCarType}: ${id}`,
|
||||
`Invalid outer part id: ${id}. Use GET outer-parts-catalog to see valid IDs.`,
|
||||
);
|
||||
}
|
||||
selectedFromIds.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Backward compatibility with selectedParts + legacy carPartDamage
|
||||
let selectedParts = Array.isArray((body as any)?.selectedParts)
|
||||
? ((body as any).selectedParts as string[])
|
||||
: [];
|
||||
if (selectedParts.length === 0 && (body as any)?.carPartDamage) {
|
||||
this.assertCarPartDamageAtMostTwoOfFourSides(
|
||||
(body as any).carPartDamage as CarDamagePartDto,
|
||||
if (selectedFromIds.length === 0) {
|
||||
throw new BadRequestException(
|
||||
"selectedPartIds is required and must contain at least one valid Fanavaran part ID",
|
||||
);
|
||||
selectedParts = this.carDamagePartDtoToOuterPartSlugs(
|
||||
(body as any).carPartDamage as CarDamagePartDto,
|
||||
);
|
||||
}
|
||||
const selectedFromKeys: OuterPartCatalogItem[] = [];
|
||||
for (const key of selectedParts) {
|
||||
const item = byKey.get(key);
|
||||
if (!item) {
|
||||
throw new BadRequestException(
|
||||
`Invalid outer part key for ${selectedCarType}: ${key}`,
|
||||
);
|
||||
}
|
||||
selectedFromKeys.push(item);
|
||||
}
|
||||
|
||||
const selectedItems =
|
||||
selectedFromIds.length > 0 ? selectedFromIds : selectedFromKeys;
|
||||
if (selectedItems.length === 0) {
|
||||
throw new BadRequestException(
|
||||
"selectedPartIds or selectedParts is required and must contain at least one item",
|
||||
);
|
||||
}
|
||||
const selectedItems = selectedFromIds;
|
||||
|
||||
// DISABLED: At most two non-top sides allowed
|
||||
// const sideSet = new Set(
|
||||
@@ -7706,7 +7662,7 @@ export class ClaimRequestManagementService {
|
||||
// }
|
||||
|
||||
const selectedPartDocs = selectedItems.map((p) =>
|
||||
catalogItemToSelectedPart(p, catalog),
|
||||
catalogItemToSelectedPart(p, liveCatalog),
|
||||
);
|
||||
const damagedPartsInitial = selectedPartDocs.map((p) => ({
|
||||
id: p.id ?? undefined,
|
||||
@@ -8061,18 +8017,12 @@ export class ClaimRequestManagementService {
|
||||
"Only the claim owner can view capture requirements",
|
||||
);
|
||||
|
||||
// Build car-type aware outer-parts lookup (complete source: static catalog).
|
||||
// Uses the raw catalog shape (`{id, key, titleFa, side, ...}`) because the
|
||||
// map below is keyed on the full `key` (e.g. `left_backfender`).
|
||||
const selectedCarType = claimCase.vehicle?.carType as
|
||||
| ClaimVehicleTypeV2
|
||||
| undefined;
|
||||
const catalogForType =
|
||||
selectedCarType && OUTER_PARTS_BY_CAR_TYPE[selectedCarType]
|
||||
? OUTER_PARTS_BY_CAR_TYPE[selectedCarType]
|
||||
: this.getOuterPartsRawCatalog();
|
||||
// Build outer-parts lookup for resolving stored DB parts.
|
||||
// Uses the static snapshot (FANAVARAN_CAR_PARTS_CATALOG) which is keyed
|
||||
// by numeric id (key === String(id)). This is only needed for legacy
|
||||
// migration of parts stored before the Fanavaran catalog change.
|
||||
const catalogByKey = new Map<string, OuterPartCatalogItem>();
|
||||
for (const item of catalogForType) {
|
||||
for (const item of FANAVARAN_CAR_PARTS_CATALOG) {
|
||||
if (!catalogByKey.has(item.key)) catalogByKey.set(item.key, item);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user