diff --git a/docs/external-api-curls.md b/docs/external-api-curls.md index 7141ed7..1c8f0a6 100644 --- a/docs/external-api-curls.md +++ b/docs/external-api-curls.md @@ -433,6 +433,23 @@ curl -X POST "$ESG_URL/inquiry/policyByPlate" \ }' ``` +### Car By Chassis (Two-Factor VIN Inquiry) + +Use the two-factor chassis route when the backend must match both the VIN and +the resolved third-party policyholder. Do not send `nationalCode` to the +one-factor `policyByChassis` route. + +```sh +curl -X POST "$ESG_URL/inquiry/carByChassis" \ + -H "Authorization: Bearer $ESG_ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + --data '{ + "nationalCode": "0012345678", + "chassis": "NAAR03HFFRDE07024" + }' +``` + ### Person Inquiry ESG expects Jalali birth date, normalized as `YYYY-MM-DD`. diff --git a/docs/external-integrations-reference.fa.html b/docs/external-integrations-reference.fa.html index 6a07fda..4abbbf4 100644 --- a/docs/external-integrations-reference.fa.html +++ b/docs/external-integrations-reference.fa.html @@ -161,7 +161,7 @@ @@ -208,7 +208,7 @@

پروتکل ارسال خسارت (۴ مرحله)

-
۱
خسارت پایه (GEN.03) — POST /car/third-party-car-financial-claims. داده‌های مالک، راننده، بیمه، وسیله نقلیه و تصادف را ارسال می‌کند. یک claimId و claimNo فناوران برمی‌گرداند. پیامک با هر دو شناسه برای مالک ارسال می‌شود.
+
۱
خسارت پایه (GEN.03) — POST /car/third-party-car-financial-claims. داده‌های مالک، راننده، بیمه، وسیله نقلیه و تصادف را ارسال می‌کند. یک claimId و claimNo فناوران برمی‌گرداند که برای نمایش در پنل ذخیره می‌شوند.
۲
موارد خسارت (GEN.05) — POST /car/third-party-car-financial-claims/{claimId}/dmg-cases. یک ورودی به ازای هر قطعه آسیب‌دیده با شناسه کامپوننت، شدت و قیمت. سقف: کل ≤ ۵۳،۰۰۰،۰۰۰ تومان.
۳
پیوست‌ها (GEN.07) — POST /car/third-party-car-financial-claims/{claimId}/files. اسناد، تصاویر car-capture و ویدیوها که با شناسه فایل ارجاع داده شده‌اند.
۴
کارشناسی (GEN.08) — POST /car/third-party-car-financial-claims/{claimId}/expertise. متادیتای ارزیابی کارشناس (نقش کارشناس، تاریخ، نتیجه). ارسال را نهایی می‌کند.
@@ -359,7 +359,7 @@ - +
متدمسیرتوضیح
POST/inquiry/policyByPlateجستجوی بیمه‌نامه مبتنی بر پلاک (THIRD_PARTY). بدنه: nationalCode، plk1–plk4. پاسخ قبل از ذخیره به فرمت قدیمی تجارت نگاشت می‌شود.
POST/inquiry/policyByChassisجایگزین مبتنی بر VIN/شاسی برای پرس‌وجوی پلاک. توسط اندپوینت‌های run-inquiries-vin فراخوانی می‌شود. از جستجوی شاسی ESG استفاده می‌کند (نه مسیر SandHub). بدنه: nationalCode، chassis.
POST/inquiry/carByChassisجایگزین دوعاملی VIN/شاسی برای استعلام پلاک. توسط اندپوینت‌های run-inquiries-vin فراخوانی می‌شود. بدنه: nationalCode، chassis. مسیر تک‌عاملی policyByChassis استفاده نمی‌شود، چون فیلد nationalCode را نمی‌پذیرد.
POST/inquiry/personبررسی هویت شخصی. بدنه: nationalCode، birthDate (جلالی، نه میلادی).
POST/inquiry/shebaاعتبارسنجی شبا / حساب بانکی.
@@ -415,7 +415,7 @@ token = "تصادف"/"خسارت"، token2 = publicId، token3 = نام خانوادگی کارشناسکارشناس یک فایل تقصیر یا خسارت را قفل می‌کندyara-expert-lock token = نوع فایل، token2 = publicId، token3 = لینککارشناس درخواست ارسال مجدد اسناد می‌دهدyara-resend-documents token = نوع فایل، token2 = publicId، token3 = نام خانوادگی کارشناس، token10 = لینکطرف مطلع می‌شود که ارزیابی خسارت کارشناس را امضا کندyara-signature - token = publicId، token2 = claimId فناوران، token3 = claimNo فناورانارسال فناوران تأیید شد — با شماره و شناسه خسارت فناوران برای مالک خسارت ارسال می‌شودyara-fanavaran-claim + token = publicId، token2 = claimId فناوران، token3 = claimNo فناورانقالب قدیمی نگه‌داری شده است؛ ارسال خودکار پس از آخرین مرحله فناوران غیرفعال استyara-fanavaran-claim

تمام فراخوانی‌های پیامک fire-and-forget هستند — هرگز throw نمی‌کنند. شکست‌ها log می‌شوند اما جریان اصلی را مسدود نمی‌کنند. diff --git a/docs/external-integrations-reference.html b/docs/external-integrations-reference.html index 8c7eefd..8bfd209 100644 --- a/docs/external-integrations-reference.html +++ b/docs/external-integrations-reference.html @@ -156,7 +156,7 @@

@@ -204,7 +204,7 @@

Claim submission protocol (4 steps)

-
1
Base claim (GEN.03) — POST /car/third-party-car-financial-claims. Sends owner, driver, insurance, vehicle, and accident data. Returns a Fanavaran claimId and claimNo. SMS is sent to the owner with both identifiers.
+
1
Base claim (GEN.03) — POST /car/third-party-car-financial-claims. Sends owner, driver, insurance, vehicle, and accident data. Returns a Fanavaran claimId and claimNo, which are persisted for panel display.
2
Damage cases (GEN.05) — POST /car/third-party-car-financial-claims/{claimId}/dmg-cases. One entry per damaged part with component ID, severity, and price. Cap: total ≤ 53 000 000 Toman.
3
Attachments (GEN.07) — POST /car/third-party-car-financial-claims/{claimId}/files. Documents, car-capture images, and videos referenced by file ID.
4
Expertise (GEN.08) — POST /car/third-party-car-financial-claims/{claimId}/expertise. Expert assessment metadata (expert role, date, result). Finalises the submission.
@@ -356,7 +356,7 @@ - +
MethodPathWhat it does
POST/inquiry/policyByPlatePlate-based policy lookup (THIRD_PARTY). Body: nationalCode, plk1–plk4. Response is mapped to the old Tejarat format before being stored.
POST/inquiry/policyByChassisVIN/chassis-based alternative to plate inquiry. Called by run-inquiries-vin endpoints. Uses ESG chassis lookup (not the SandHub path). Body: nationalCode, chassis.
POST/inquiry/carByChassisTwo-factor VIN/chassis alternative to the plate inquiry. Called by run-inquiries-vin endpoints. Body: nationalCode, chassis. The one-factor policyByChassis route is not used because it rejects nationalCode.
POST/inquiry/personPersonal identity check. Body: nationalCode, birthDate (Jalali, NOT Gregorian).
POST/inquiry/shebaSheba / bank account validation.
@@ -412,7 +412,7 @@ yara-expert-lockExpert locks a blame or claim filetoken = "تصادف"/"خسارت", token2 = publicId, token3 = expert surname yara-resend-documentsExpert requests document resendtoken = file kind, token2 = publicId, token3 = link yara-signatureParty notified to sign the expert's damage assessmenttoken = file kind, token2 = publicId, token3 = expert surname, token10 = link - yara-fanavaran-claimFanavaran submission confirmed — sent to claim owner with Fanavaran claim number and IDtoken = publicId, token2 = Fanavaran claimId, token3 = Fanavaran claimNo + yara-fanavaran-claimRetained legacy template; automatic dispatch after the final Fanavaran stage is disabledtoken = publicId, token2 = Fanavaran claimId, token3 = Fanavaran claimNo

All SMS calls are fire-and-forget — they never throw. Failures are logged but do not block the main flow. diff --git a/docs/fanavaran/01-write-apis.md b/docs/fanavaran/01-write-apis.md index 5e90042..41cf34b 100644 --- a/docs/fanavaran/01-write-apis.md +++ b/docs/fanavaran/01-write-apis.md @@ -308,7 +308,7 @@ Requires `claimId` + `dmgCaseId` (soft-ensures earlier stages). | `Id` | `claimCases.expertiseId` | History: `FANAVARAN_EXPERTISE_AUTO_SUBMIT_SUCCEEDED`. -SMS owner via `SmsOrchestrationService` (deduped `fanavaranSync.expertise.smsNotifiedAt`). +No owner SMS is sent after expertise; the returned identifier is persisted only. Proven Parsian: `expertiseId=403144`, `ClaimExpertId=29`. Manual: `POST /v2/fanavaran/{client}/claim-cases/{id}/expertise/submit`. diff --git a/docs/fanavaran/05-claim-flow.md b/docs/fanavaran/05-claim-flow.md index ffb35b8..17770ae 100644 --- a/docs/fanavaran/05-claim-flow.md +++ b/docs/fanavaran/05-claim-flow.md @@ -21,7 +21,7 @@ Start local claim → Upload docs/images locally → GEN.07 attachments (per file, best-effort) → Expert pricing ready - → GEN.08 expertise → store expertiseId → SMS owner + → GEN.08 expertise → store expertiseId (no SMS) → End (local completion independent of Fanavaran success) ``` @@ -76,7 +76,7 @@ sequenceDiagram YARA->>FV: POST .../expertise (GEN.08) FV-->>YARA: ExpertiseId YARA->>DB: expertiseId, history SUCCESS - YARA->>YARA: SMS owner (deduped) + Note over YARA: Post-expertise owner SMS is disabled ``` ## Flow diagram (stages + soft-ensure) diff --git a/docs/fanavaran/06-yara-integration.md b/docs/fanavaran/06-yara-integration.md index 560d8f0..827c92d 100644 --- a/docs/fanavaran/06-yara-integration.md +++ b/docs/fanavaran/06-yara-integration.md @@ -17,7 +17,7 @@ source: fanavaran-module-docs | Tenant config | boot + System Settings | `fanavaran-client-config.service.ts`, `system-settings.*` | | Orchestration | `ClaimRequestManagementModule` | `claim-request-management.service.ts` | | Policy select | same | `fanavaran-policy-selection.ts` | -| SMS after expertise | `SmsOrchestrationModule` | `sms-orchestration.service.ts` | +| SMS after expertise | Disabled by product decision | No message is dispatched | --- @@ -49,7 +49,7 @@ source: fanavaran-module-docs | Send | GEN.03 JSON + business headers | | Receive | `Id`, `ClaimNo` | | Store | `claimId`, `claimNo`, `fanavaranSync.baseClaim.*`, history | -| Consumers | Damage/expertise soft-ensure; UI claim detail; SMS | +| Consumers | Damage/expertise soft-ensure; UI claim detail | ### C. Damage case diff --git a/docs/fanavaran/09-error-handling.md b/docs/fanavaran/09-error-handling.md index f07ecd9..252618e 100644 --- a/docs/fanavaran/09-error-handling.md +++ b/docs/fanavaran/09-error-handling.md @@ -69,4 +69,4 @@ Per HTTP step: ## SMS -After successful expertise (last Fanavaran stage), owner SMS may be sent once (`fanavaranSync.expertise.smsNotifiedAt`). Failure to SMS should not roll back Fanavaran expertise submit; treat as separate notification concern. Claims already notified at base claim (`fanavaranSync.baseClaim.smsNotifiedAt`) are not re-notified. +The post-expertise owner SMS is disabled. A successful final Fanavaran response only persists the returned `expertiseId` and synchronization state; it does not dispatch a notification. diff --git a/src/claim-request-management/claim-request-management.module.ts b/src/claim-request-management/claim-request-management.module.ts index 57a9f6f..552c58f 100644 --- a/src/claim-request-management/claim-request-management.module.ts +++ b/src/claim-request-management/claim-request-management.module.ts @@ -58,7 +58,6 @@ import { MediaPolicyModule } from "src/media-policy/media-policy.module"; import { FanavaranAuditModule } from "src/fanavaran/fanavaran-audit.module"; import { FanavaranLookupModule } from "src/fanavaran/fanavaran-lookup.module"; import { PlateNormalizerModule } from "src/utils/plate-normalizer/plate-normalizer.module"; -import { SmsOrchestrationModule } from "src/sms-orchestration/sms-orchestration.module"; @Module({ imports: [ @@ -77,7 +76,6 @@ import { SmsOrchestrationModule } from "src/sms-orchestration/sms-orchestration. SandHubModule, ClientModule, MediaPolicyModule, - SmsOrchestrationModule, JwtModule.register({}), MongooseModule.forFeature([ { name: ClaimCase.name, schema: ClaimCaseSchema }, diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index 330b9eb..286e122 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -249,7 +249,6 @@ import { FanavaranAuditStatus, FanavaranAuditStep, } from "src/fanavaran/schema/fanavaran-audit-log.schema"; -import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service"; import { selectLatestActiveFanavaranPolicy } from "./fanavaran-policy-selection"; import { selectAccidentVehicleUsedId, @@ -487,7 +486,6 @@ export class ClaimRequestManagementService { private readonly fanavaranAuditService: FanavaranAuditService, private readonly fanavaranAuthService: FanavaranAuthService, private readonly fanavaranLookupService: FanavaranLookupService, - private readonly smsOrchestrationService: SmsOrchestrationService, private readonly fileMakerDbService: FileMakerDbService, private readonly fileReviewerDbService: FileReviewerDbService, private readonly fieldExpertDbService: FieldExpertDbService, @@ -7395,13 +7393,7 @@ export class ClaimRequestManagementService { this.logger.log( `[executeFanavaranExpertiseSubmit] Already have expertiseId=${claimCase.expertiseId}; skipping Fanavaran POST`, ); - // Catch-up / deduped: SMS may still be pending if expertise predated this notify. - await this.notifyClaimOwnerFanavaranClaimRegistered({ - claimCaseId, - claimId: claimCase.claimId, - claimNo: claimCase.claimNo, - logPrefix: `[Fanavaran ${clientKey} V2 Expertise]`, - }); + // Product decision: completing the final Fanavaran stage must not send SMS. return ( (claimCase as any)?.fanavaranSync?.expertise?.response ?? { Id: claimCase.expertiseId, @@ -7477,13 +7469,7 @@ export class ClaimRequestManagementService { }, }); - // Best-effort: SMS after last Fanavaran stage (expertise) succeeds - await this.notifyClaimOwnerFanavaranClaimRegistered({ - claimCaseId, - claimId: claimCase.claimId, - claimNo: claimCase.claimNo, - logPrefix: `[Fanavaran ${clientKey} V2 Expertise]`, - }); + // Product decision: completing the final Fanavaran stage must not send SMS. return response.data; } catch (error) { @@ -7521,12 +7507,7 @@ export class ClaimRequestManagementService { }; } if (claimCase.expertiseId != null) { - await this.notifyClaimOwnerFanavaranClaimRegistered({ - claimCaseId, - claimId: claimCase.claimId, - claimNo: claimCase.claimNo, - logPrefix, - }); + // Product decision: an already-completed Fanavaran stage must not send SMS. return { attempted: false, submitted: false, @@ -8695,111 +8676,6 @@ export class ClaimRequestManagementService { return true; } - /** - * Phone for the claim owner / damaged party (same recipient as other claim SMS). - */ - private async resolveFanavaranClaimOwnerPhone( - claimCase: any, - ): Promise { - const notifyUserId = - claimCase?.damagedPartyUserId ?? claimCase?.owner?.userId; - if (!notifyUserId) return undefined; - const ownerUserId = String(notifyUserId); - - if (claimCase.blameRequestId) { - const blame = await this.blameRequestDbService.findById( - String(claimCase.blameRequestId), - ); - const ownerParty = (blame?.parties || []).find( - (p: any) => - p?.person?.userId && String(p.person.userId) === ownerUserId, - ); - const fromParty = ownerParty?.person?.phoneNumber; - if (typeof fromParty === "string" && fromParty.trim()) { - return fromParty.trim(); - } - } - - const user = await this.userDbService.findOne({ - _id: new Types.ObjectId(ownerUserId), - }); - const mobile = user?.mobile; - if (typeof mobile === "string" && mobile.trim()) return mobile.trim(); - return undefined; - } - - /** - * After Fanavaran expertise (last stage) succeeds, SMS the claim owner with - * publicId + claimId + ClaimNo. Uses SmsOrchestrationService → same provider - * as login for this deployment (`SMS` / `SMS_PROVIDER`). Never throws. - */ - private async notifyClaimOwnerFanavaranClaimRegistered(input: { - claimCaseId: string; - claimId?: number | string | null; - claimNo?: number | string | null; - logPrefix: string; - }): Promise { - try { - if (input.claimId == null && input.claimNo == null) { - this.logger.warn( - `${input.logPrefix} Skip Fanavaran SMS: no claimId/ClaimNo on response`, - ); - return; - } - - const claimCase = await this.claimCaseDbService.findById( - input.claimCaseId, - ); - if (!claimCase) return; - - const sync = (claimCase as any)?.fanavaranSync; - // Dedup: expertise stage, or older base-claim notify from before the move. - if (sync?.expertise?.smsNotifiedAt || sync?.baseClaim?.smsNotifiedAt) { - this.logger.log( - `${input.logPrefix} Fanavaran SMS already sent; skipping duplicate`, - ); - return; - } - - const phone = await this.resolveFanavaranClaimOwnerPhone(claimCase); - if (!phone) { - this.logger.warn( - `${input.logPrefix} Skip Fanavaran SMS: no phone for claim owner`, - ); - return; - } - - const publicId = - typeof claimCase.publicId === "string" && claimCase.publicId.trim() - ? claimCase.publicId.trim() - : String(input.claimCaseId); - - const sent = - await this.smsOrchestrationService.sendFanavaranClaimRegisteredNotice({ - receptor: phone, - publicId, - claimNo: input.claimNo ?? claimCase.claimNo, - claimId: input.claimId ?? claimCase.claimId, - }); - - if (sent) { - await this.claimCaseDbService.findByIdAndUpdate(input.claimCaseId, { - $set: { - "fanavaranSync.expertise.smsNotifiedAt": new Date(), - }, - }); - this.logger.log( - `${input.logPrefix} Fanavaran claim SMS sent to claim owner phone=${phone} publicId=${publicId} claimNo=${input.claimNo ?? claimCase.claimNo ?? "-"} claimId=${input.claimId ?? claimCase.claimId ?? "-"}`, - ); - } - } catch (error) { - this.logger.error( - `${input.logPrefix} Fanavaran claim SMS failed (non-fatal)`, - error, - ); - } - } - private async executeFanavaranV2Submit( claimCaseId: string, clientKey: FanavaranClientKey, diff --git a/src/claim-request-management/entites/schema/claim-cases.schema.ts b/src/claim-request-management/entites/schema/claim-cases.schema.ts index 3f733e0..87e43f8 100644 --- a/src/claim-request-management/entites/schema/claim-cases.schema.ts +++ b/src/claim-request-management/entites/schema/claim-cases.schema.ts @@ -142,7 +142,7 @@ export class FanavaranSyncStage { @Prop({ type: Date }) lastPayloadBuiltAt?: Date; - /** When we SMS'd the claim owner about Fanavaran claimId/ClaimNo (after expertise). */ + /** Legacy marker from the retired post-expertise Fanavaran SMS flow. */ @Prop({ type: Date }) smsNotifiedAt?: Date; diff --git a/src/client/dto/client-external-inquiries.dto.ts b/src/client/dto/client-external-inquiries.dto.ts index 01abad2..1c7a50c 100644 --- a/src/client/dto/client-external-inquiries.dto.ts +++ b/src/client/dto/client-external-inquiries.dto.ts @@ -52,7 +52,7 @@ export class ExternalInquiryFlagsDto implements ExternalInquiryFlags { @ApiProperty({ description: - "ESG VIN/chassis-number inquiry (`/inquiry/policyByChassis`). Required for the VIN initial-form path.", + "ESG two-factor VIN/chassis inquiry (`/inquiry/carByChassis`). Required for the VIN initial-form path.", example: false, }) @IsBoolean() diff --git a/src/expert-insurer/expert-insurer.file-summary.spec.ts b/src/expert-insurer/expert-insurer.file-summary.spec.ts index 1ac54c7..f46fe50 100644 --- a/src/expert-insurer/expert-insurer.file-summary.spec.ts +++ b/src/expert-insurer/expert-insurer.file-summary.spec.ts @@ -1,4 +1,5 @@ import { ExpertInsurerService } from "./expert-insurer.service"; +import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum"; describe("insurer expert file summaries", () => { const service = Object.create( @@ -49,4 +50,25 @@ describe("insurer expert file summaries", () => { expect(summary.reviewDurationMinutes).toBe(0); }); + + it("exposes the four Fanavaran claim references in insurer claim detail", async () => { + const detail = await (service as any).buildInsurerClaimDetail( + { + _id: "6aa50ce70545fc1906433c86", + status: ClaimCaseStatus.COMPLETED, + claimId: 101, + claimNo: 102, + dmgCaseId: 103, + expertiseId: 104, + }, + null, + ); + + expect(detail.fanavaran).toEqual({ + claimId: 101, + claimNo: 102, + dmgCaseId: 103, + expertiseId: 104, + }); + }); }); diff --git a/src/expert-insurer/expert-insurer.service.ts b/src/expert-insurer/expert-insurer.service.ts index d2318f5..591e585 100644 --- a/src/expert-insurer/expert-insurer.service.ts +++ b/src/expert-insurer/expert-insurer.service.ts @@ -86,6 +86,7 @@ import { } from "./helper/timeline-fa-labels"; import { buildEnrichedDamagedParts } from "src/expert-claim/dto/claim-damaged-part.enricher"; import { serializeDamagedPartSelectionHistory } from "src/helpers/claim-damaged-part-audit"; +import { fanavaranClaimReferences } from "src/claim-request-management/fanavaran-claim-references"; @Injectable() export class ExpertInsurerService { @@ -786,6 +787,7 @@ export class ExpertInsurerService { publicId: claim.publicId, status: claim.status, claimStatus: claim.claimStatus, + fanavaran: fanavaranClaimReferences(claim), blameDocumentResendPending: claim.blameDocumentResendPending, workflow: claim.workflow, owner: ownerOut, diff --git a/src/request-management/call-center-blame-v6.controller.ts b/src/request-management/call-center-blame-v6.controller.ts index 72e0f5e..bfd6c1c 100644 --- a/src/request-management/call-center-blame-v6.controller.ts +++ b/src/request-management/call-center-blame-v6.controller.ts @@ -114,7 +114,7 @@ export class CallCenterBlameV6Controller { description: "VIN alternative to `run-inquiry`. " + "The agent supplies the chassis number and personal data collected from the caller. " + - "ESG chassis lookup (`policyByChassis`) is executed and the result is stored on the " + + "ESG two-factor chassis lookup (`carByChassis`) is executed and the result is stored on the " + "blame document under `vehicle.vin` (plateId is left empty). " + "Identical eligibility guards and insurer-company validation as the plate variant. " + "After this call, proceed to `send-link` exactly as in the plate flow.", diff --git a/src/request-management/request-management.policyholder-inquiry.spec.ts b/src/request-management/request-management.policyholder-inquiry.spec.ts index 3682edc..ccdfdf9 100644 --- a/src/request-management/request-management.policyholder-inquiry.spec.ts +++ b/src/request-management/request-management.policyholder-inquiry.spec.ts @@ -1,4 +1,5 @@ import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum"; +import { PartyRole } from "./entities/schema/partyRole.enum"; import { RequestManagementService } from "./request-management.service"; describe("RequestManagementService policyholder inquiry routing", () => { @@ -138,7 +139,7 @@ describe("RequestManagementService policyholder inquiry routing", () => { it("queries VIN with the third-party policyholder", async () => { const service = Object.create(RequestManagementService.prototype) as any; service.sandHubService = { - getPolicyByChassisInquiry: jest.fn().mockResolvedValue({ + getCarByChassisInquiry: jest.fn().mockResolvedValue({ raw: {}, mapped: { CompanyName: "پارسیان" }, }), @@ -151,10 +152,10 @@ describe("RequestManagementService policyholder inquiry routing", () => { await service.getThirdPartyVinInquiry(submission); expect( - service.sandHubService.getPolicyByChassisInquiry, + service.sandHubService.getCarByChassisInquiry, ).toHaveBeenCalledTimes(1); expect( - service.sandHubService.getPolicyByChassisInquiry, + service.sandHubService.getCarByChassisInquiry, ).toHaveBeenCalledWith( { nationalCode: "0022222222", @@ -163,4 +164,62 @@ describe("RequestManagementService policyholder inquiry routing", () => { undefined, ); }); + + it("runs both third-party and car-body inquiries for a car-body VIN", async () => { + const service = Object.create(RequestManagementService.prototype) as any; + service.getThirdPartyVinInquiry = jest.fn().mockResolvedValue({ + raw: { success: true }, + mapped: { + CompanyCode: "8", + CompanyName: "بیمه پارسیان", + PrntPlcyCmpDocNo: "TP-1", + }, + }); + service.sandHubService = { + getCarBodyInquiry: jest.fn().mockResolvedValue({ + source: "ESG_CAR_BODY_VIN_INQUIRY", + raw: { policyId: 123 }, + mapped: { + policyNumber: "BODY-1", + companyId: "8", + CompanyName: "بیمه پارسیان", + VinNumberField: vehicle.vin, + }, + }), + }; + service.clientService = { + findOrCreateClientByCompanyCode: jest + .fn() + .mockResolvedValue({ _id: "client-third-party" }), + }; + service.resolveCarBodyPolicyClientId = jest + .fn() + .mockResolvedValue("client-car-body"); + service.runParticipantPersonalInquiries = jest.fn().mockResolvedValue(undefined); + service.runVehicleOwnershipInquiry = jest.fn().mockResolvedValue(undefined); + + const req: any = { + _id: "car-body-vin-request", + type: BlameRequestType.CAR_BODY, + inquiries: {}, + }; + const party: any = { person: {}, vehicle: {}, insurance: {} }; + + await service.runPartyInquiriesVinV3Internal( + req, + participantInput(BlameRequestType.CAR_BODY), + PartyRole.FIRST, + party, + ); + + expect(service.getThirdPartyVinInquiry).toHaveBeenCalledTimes(1); + expect(service.sandHubService.getCarBodyInquiry).toHaveBeenCalledWith({ + nationalCodeOfInsurer: "0033333333", + plate: vehicle.vin, + }); + expect(req.inquiries.thirdParty.has).toBe(true); + expect(req.inquiries.carBody.has).toBe(true); + expect(party.insurance.policyNumber).toBe("TP-1"); + expect(party.insurance.carBodyInsurance.policyNumber).toBe("BODY-1"); + }); }); diff --git a/src/request-management/request-management.service.ts b/src/request-management/request-management.service.ts index e856449..8f520d6 100644 --- a/src/request-management/request-management.service.ts +++ b/src/request-management/request-management.service.ts @@ -465,7 +465,7 @@ export class RequestManagementService { ); } const subjects = resolveInquirySubjects(submission); - const result = await this.sandHubService.getPolicyByChassisInquiry( + const result = await this.sandHubService.getCarByChassisInquiry( { nationalCode: subjects.thirdPartyPolicyNationalCode, chassis, @@ -2142,7 +2142,7 @@ export class RequestManagementService { * V2 initial-form submitted with a VIN/chassis number instead of a plate. * * Performs: - * 1. ESG `/inquiry/policyByChassis` (or its mock when `vinChassis` is off) + * 1. ESG two-factor `/inquiry/carByChassis` (or its mock when `vinChassis` is off) * 2. Personal identity inquiry (same as the plate path) * * The inquiry result is mapped through the same `mapEsgPolicyByPlateToOldFormat` @@ -2236,7 +2236,7 @@ export class RequestManagementService { } } - // ---- External inquiry: ESG policyByChassis ---- + // ---- External inquiry: ESG two-factor carByChassis ---- const inquiryClientId = party.person?.clientId ? String(party.person.clientId) : undefined; @@ -2256,10 +2256,10 @@ export class RequestManagementService { inquiryRaw = inquiry.raw; inquiryMapped = inquiry.mapped; this.logger.log( - `[ESG] policyByChassis raw for request=${req._id}: ${JSON.stringify(inquiryRaw)}`, + `[ESG] carByChassis raw for request=${req._id}: ${JSON.stringify(inquiryRaw)}`, ); this.logger.log( - `[ESG] policyByChassis mapped for request=${req._id}: ${JSON.stringify(inquiryMapped)}`, + `[ESG] carByChassis mapped for request=${req._id}: ${JSON.stringify(inquiryMapped)}`, ); this.recordPartyCaseInquiryStatus( req, @@ -2274,11 +2274,11 @@ export class RequestManagementService { ); } catch (err: any) { this.logger.error( - `[ESG] policyByChassis failed for request=${req._id}: ${err?.message || err}`, + `[ESG] carByChassis failed for request=${req._id}: ${err?.message || err}`, ); if (err?.response) { this.logger.error( - `[ESG] policyByChassis response for request=${req._id}: status=${ + `[ESG] carByChassis response for request=${req._id}: status=${ err.response.status }, data=${JSON.stringify(err.response.data)}`, ); @@ -2300,7 +2300,7 @@ export class RequestManagementService { if (inquiryMapped?.Error) { this.logger.warn( - `[ESG] policyByChassis error for request=${req._id}: ${JSON.stringify(inquiryMapped.Error)}`, + `[ESG] carByChassis error for request=${req._id}: ${JSON.stringify(inquiryMapped.Error)}`, ); this.recordPartyCaseInquiryStatus(req, "thirdParty", role, false, { source: "ESG_VIN_INQUIRY", @@ -10645,7 +10645,7 @@ export class RequestManagementService { /** * VIN variant of `runInquiriesV3`. - * Identical flow, but calls `getPolicyByChassisInquiry` instead of + * Identical flow, but calls the two-factor `getCarByChassisInquiry` instead of * `getTejaratBlockInquiry` for the primary policy lookup. */ async runInquiriesVinV3( @@ -10788,7 +10788,7 @@ export class RequestManagementService { /** * VIN variant of `runPartyInquiriesV3Internal`. - * Calls `getPolicyByChassisInquiry` (ESG chassis lookup) instead of + * Calls `getCarByChassisInquiry` (ESG two-factor chassis lookup) instead of * `getTejaratBlockInquiry` (plate-based). All other inquiries (personal, * driving licence, car-body for CAR_BODY) are unchanged. */ diff --git a/src/sand-hub/sand-hub.service.spec.ts b/src/sand-hub/sand-hub.service.spec.ts index 1ad9b20..beec09a 100644 --- a/src/sand-hub/sand-hub.service.spec.ts +++ b/src/sand-hub/sand-hub.service.spec.ts @@ -189,7 +189,7 @@ describe("SandHubService inquiry mocks", () => { }, }); - const result = await service.getPolicyByChassisInquiry({ + const result = await service.getCarByChassisInquiry({ nationalCode: "0012345678", chassis: "NAAR03HFFRDE07024", }); @@ -239,7 +239,7 @@ describe("SandHubService inquiry mocks", () => { }); await expect( - service.getPolicyByChassisInquiry( + service.getCarByChassisInquiry( { nationalCode: "1234567890", chassis: "NAAR03HFFRDE07024", @@ -256,13 +256,13 @@ describe("SandHubService inquiry mocks", () => { data: { CmpCod: "8", CmpNam: "بیمه پارسیان" }, }); - await service.getPolicyByChassisInquiry({ + await service.getCarByChassisInquiry({ nationalCode: "0012345678", chassis: "NAAR03HFFRDE07024", }); expect(esg).toHaveBeenCalledWith( - expect.stringContaining("/inquiry/policyByChassis"), + expect.stringContaining("/inquiry/carByChassis"), { nationalCode: "0012345678", chassis: "NAAR03HFFRDE07024", diff --git a/src/sand-hub/sand-hub.service.ts b/src/sand-hub/sand-hub.service.ts index de2f862..9b35049 100644 --- a/src/sand-hub/sand-hub.service.ts +++ b/src/sand-hub/sand-hub.service.ts @@ -1187,7 +1187,7 @@ export class SandHubService { } /** - * ESG VIN/chassis-number inquiry (`/inquiry/policyByChassis`). + * ESG two-factor VIN/chassis inquiry (`/inquiry/carByChassis`). * * When `vinChassis` inquiry is disabled (mock mode) the response shape mirrors * `buildMockPlateInquiryRaw` so the downstream mapper (`mapEsgPolicyByPlateToOldFormat`) @@ -1196,12 +1196,12 @@ export class SandHubService { * @param identity - policyholder national code and 17-character VIN/chassis * @param options - optional per-tenant client scope */ - async getPolicyByChassisInquiry( + async getCarByChassisInquiry( identity: { nationalCode: string; chassis: string }, options?: SandHubInquiryOptions, ): Promise<{ raw: any; mapped: any }> { const baseUrl = process.env.ESG_URL ?? "http://192.168.20.22:8085"; - const requestUrl = `${baseUrl}/inquiry/policyByChassis`; + const requestUrl = `${baseUrl}/inquiry/carByChassis`; const requestPayload = { nationalCode: String(identity.nationalCode), chassis: String(identity.chassis), @@ -1213,7 +1213,7 @@ export class SandHubService { const ctx = await this.mockCompanyContext(options); const raw = this.buildMockPlateInquiryRaw(ctx); this.logger.debug( - `[MOCK] getPolicyByChassisInquiry nationalCode=${identity.nationalCode} chassis=${identity.chassis}`, + `[MOCK] getCarByChassisInquiry nationalCode=${identity.nationalCode} chassis=${identity.chassis}`, ); const mapped = this.mapEsgPolicyByPlateToOldFormat(raw, "thirdPartyVin"); if (!mapped?.Error) {