1
0
forked from Yara724/api

Compare commits

..

2 Commits

Author SHA1 Message Date
SepehrYahyaee
a52b7a0a72 Added catalog endpoints to claim expert panel 2026-05-09 14:00:02 +03:30
SepehrYahyaee
7998649a89 Added external APIs inquiries 2026-05-09 13:48:19 +03:30
4 changed files with 176 additions and 18 deletions

View File

@@ -68,6 +68,7 @@ import { PublicIdService } from "src/utils/public-id/public-id.service";
import { ImageRequiredModel } from "./entites/schema/image-required.schema";
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service";
import { SandHubService } from "src/sand-hub/sand-hub.service";
import {
ExpertFileActivityType,
ExpertFileKind,
@@ -76,6 +77,7 @@ import { FactorStatus } from "src/Types&Enums/claim-request-management/factor-st
import { BranchDbService } from "src/client/entities/db-service/branch.db.service";
import { CreationMethod } from "src/request-management/entities/schema/request-management.schema";
import { RoleEnum } from "src/Types&Enums/role.enum";
import { PartyRole } from "src/request-management/entities/schema/partyRole.enum";
import { UserRatingDto } from "./dto/user-rating.dto";
import {
canFinalizeExpertResend,
@@ -158,8 +160,61 @@ export class ClaimRequestManagementService {
private readonly branchDbService: BranchDbService,
private readonly claimRequiredDocumentDbService: ClaimRequiredDocumentDbService,
private readonly publicIdService: PublicIdService,
private readonly sandHubService: SandHubService,
) {}
private parsePlateFromCompactString(
plateId: string | undefined,
): { leftDigits: number; centerAlphabet: string; centerDigits: number; ir: number } | null {
if (!plateId) return null;
const parts = String(plateId).split("-");
if (parts.length !== 4) return null;
const [irRaw, leftRaw, alphaRaw, centerRaw] = parts;
const ir = Number(irRaw);
const leftDigits = Number(leftRaw);
const centerDigits = Number(centerRaw);
const centerAlphabet = String(alphaRaw || "").trim();
if (
!Number.isFinite(ir) ||
!Number.isFinite(leftDigits) ||
!Number.isFinite(centerDigits) ||
!centerAlphabet
) {
return null;
}
return { leftDigits, centerAlphabet, centerDigits, ir };
}
private resolveOwnershipPlateForClaim(
claimCase: any,
blameRequest?: any,
): { leftDigits: number; centerAlphabet: string; centerDigits: number; ir: number } | null {
const p = claimCase?.vehicle?.plate;
if (
p &&
Number.isFinite(Number(p.leftDigits)) &&
Number.isFinite(Number(p.centerDigits)) &&
Number.isFinite(Number(p.ir)) &&
String(p.centerAlphabet || "").trim()
) {
return {
leftDigits: Number(p.leftDigits),
centerAlphabet: String(p.centerAlphabet).trim(),
centerDigits: Number(p.centerDigits),
ir: Number(p.ir),
};
}
if (!blameRequest || !Array.isArray(blameRequest.parties)) return null;
const ownerUserId = claimCase?.owner?.userId?.toString?.();
const matchedParty =
blameRequest.parties.find(
(party: any) => String(party?.person?.userId || "") === String(ownerUserId || ""),
) || blameRequest.parties.find((party: any) => party?.role === PartyRole.FIRST);
const compactPlateId = matchedParty?.vehicle?.plateId;
return this.parsePlateFromCompactString(compactPlateId);
}
private delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
@@ -4008,17 +4063,17 @@ export class ClaimRequestManagementService {
);
}
// At most two non-top sides allowed
const sideSet = new Set(
selectedItems
.map((p) => p.side)
.filter((s) => s !== "top"),
);
if (sideSet.size > 2) {
throw new BadRequestException(
`At most two of left/right/front/back can be selected. Selected: ${[...sideSet].join(", ")}`,
);
}
// DISABLED: At most two non-top sides allowed
// const sideSet = new Set(
// selectedItems
// .map((p) => p.side)
// .filter((s) => s !== "top"),
// );
// if (sideSet.size > 2) {
// throw new BadRequestException(
// `At most two of left/right/front/back can be selected. Selected: ${[...sideSet].join(", ")}`,
// );
// }
const selectedPartDocs = selectedItems.map((p) =>
catalogItemToSelectedPart(p, catalog),
@@ -4196,6 +4251,22 @@ export class ClaimRequestManagementService {
);
}
const blameRequest = claimCase.blameRequestId
? await this.blameRequestDbService.findById(claimCase.blameRequestId.toString())
: null;
const ownershipPlate = this.resolveOwnershipPlateForClaim(claimCase, blameRequest);
if (!ownershipPlate) {
throw new BadRequestException(
"Could not resolve vehicle plate for ownership validation.",
);
}
// External inquiry checks before moving workflow:
// 1) ownership check for nationalCode + plate
// await this.sandHubService.getCarOwnershipInfo(ownershipPlate, nationalCode);
// 2) sheba check for nationalCode + sheba
await this.sandHubService.getShebaValidation(nationalCode, shebaNumber);
const updatePayload: any = {
'damage.otherParts': otherParts.length > 0 ? otherParts : undefined,
'money.sheba': shebaNumber,

View File

@@ -6,6 +6,7 @@ import {
ApiParam,
ApiPropertyOptional,
ApiQuery,
ApiResponse,
ApiTags,
} from "@nestjs/swagger";
import { IsOptional, IsString } from "class-validator";
@@ -18,6 +19,9 @@ import { ExpertClaimService } from "./expert-claim.service";
import { ClaimSubmitResendV2Dto, SubmitExpertReplyV2Dto } from "./dto/expert-claim-v2.dto";
import { UpdateClaimDamagedPartsV2Dto } from "./dto/update-claim-damaged-parts-v2.dto";
import { FactorValidationV2Dto } from "./dto/factor-validation.dto";
import { ClaimRequestManagementService } from "src/claim-request-management/claim-request-management.service";
import { OuterPartCatalogItemDto } from "src/claim-request-management/dto/select-outer-parts-v2.dto";
import { ClaimVehicleTypeV2 } from "src/static/outer-car-parts-catalog";
class InPersonVisitV2Dto {
@ApiPropertyOptional({ example: 'Paint damage requires physical inspection' })
@@ -32,7 +36,10 @@ class InPersonVisitV2Dto {
@UseGuards(LocalActorAuthGuard, RolesGuard)
@Roles(RoleEnum.DAMAGE_EXPERT)
export class ExpertClaimV2Controller {
constructor(private readonly expertClaimService: ExpertClaimService) { }
constructor(
private readonly expertClaimService: ExpertClaimService,
private readonly claimRequestManagementService: ClaimRequestManagementService,
) {}
@Get("report/status-counts")
@ApiOperation({
@@ -44,6 +51,26 @@ export class ExpertClaimV2Controller {
return await this.expertClaimService.getStatusReportBucketsV2(actor);
}
/**
* Same catalog as `GET v2/claim-request-management/outer-parts-catalog` so factor/resend payloads use identical part ids.
*/
@Get("outer-parts-catalog")
@ApiOperation({
summary: "Get outer parts catalog (V2)",
description:
"Returns outer-damage parts with id/key/side. Optional `carType` filter returns only that type catalog.",
})
@ApiResponse({
status: 200,
description: "Outer parts catalog",
type: [OuterPartCatalogItemDto],
})
async getOuterPartsCatalog(
@Query("carType") carType?: ClaimVehicleTypeV2,
): Promise<OuterPartCatalogItemDto[]> {
return this.claimRequestManagementService.getOuterPartsCatalogV2(carType);
}
@Get("requests")
@ApiOperation({
summary: "List available claim requests for damage expert",

View File

@@ -869,7 +869,68 @@ export class RequestManagementService {
);
}
// NOTE: personal inquiry is not part of Tejarat block inquiry flow.
// ---- External inquiry 2: personal identity check (insurer/driver nationalCode + birthDate) ----
const personalNationalCode =
body.nationalCodeOfInsurer || body.nationalCodeOfDriver;
const birthDateRaw =
body.insurerBirthday ?? body.driverBirthday ?? null;
const birthDateDigits = String(birthDateRaw ?? "")
.replace(/\D/g, "")
.trim();
const personalBirthDate = Number(birthDateDigits);
if (!personalNationalCode || !Number.isFinite(personalBirthDate) || personalBirthDate <= 0) {
throw new BadRequestException(
"Valid nationalCode and birthDate are required for personal inquiry.",
);
}
try {
const personalInquiry = await this.sandHubService.getPersonalInquiry(
personalNationalCode,
personalBirthDate,
);
this.logger.log(
`[SANDHUB] personal inquiry success request=${req._id} nationalCode=${personalNationalCode}: ${JSON.stringify(
personalInquiry,
)}`,
);
} catch (err: any) {
this.logger.error(
`[SANDHUB] personal inquiry failed request=${req._id} nationalCode=${personalNationalCode}: ${err?.message || err}`,
);
throw new HttpException(
"Personal identity inquiry failed",
HttpStatus.BAD_REQUEST,
);
}
// ---- External inquiry 3: driving license check (insurerLicense + nationalCode) ----
// const licenseNationalCode = body.nationalCodeOfInsurer || body.nationalCodeOfDriver;
// const licenseNumber = body.insurerLicense;
// if (!licenseNationalCode || !licenseNumber) {
// throw new BadRequestException(
// "nationalCode and insurerLicense are required for driving license inquiry.",
// );
// }
// try {
// const licenseInquiry = await this.sandHubService.getDrivingLicenseInfo(
// licenseNationalCode,
// licenseNumber,
// );
// this.logger.log(
// `[SANDHUB] license inquiry success request=${req._id} nationalCode=${licenseNationalCode}: ${JSON.stringify(
// licenseInquiry,
// )}`,
// );
// } catch (err: any) {
// this.logger.error(
// `[SANDHUB] license inquiry failed request=${req._id} nationalCode=${licenseNationalCode}: ${err?.message || err}`,
// );
// throw new HttpException(
// "Driving license inquiry failed",
// HttpStatus.BAD_REQUEST,
// );
// }
// Find client by company code
const clientName = inquiryMapped?.CompanyName;

View File

@@ -577,12 +577,11 @@ export class SandHubService {
async getShebaValidation(nationalId: string, shebaId: string) {
try {
const requestUrl = `${process.env.SANDHUB_BASE_URL}/sheba`;
const requestUrl = `${process.env.SANDHUB_BASE_URL}/sheba-tejaratno`;
const requestPayload = {
accountOwnerType: "1",
nationalId: nationalId,
legalId: "",
shebaId: shebaId,
AccountOwnerType: "1",
NationalId: nationalId,
ShebaId: shebaId,
};
this.logger.log(`Validating Sheba ID for national code: ${nationalId}`);