Compare commits

...

6 Commits

20 changed files with 150 additions and 177 deletions

1
.gitignore vendored
View File

@@ -186,4 +186,3 @@ dist
*.sh *.sh
!scripts/fanavaran-flow-test.sh !scripts/fanavaran-flow-test.sh
!scripts/fanavaran-auth.sh !scripts/fanavaran-auth.sh
*.json

View File

@@ -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",
"chassisNo": "NAAR03HFFRDE07024"
}'
```
### Person Inquiry ### Person Inquiry
ESG expects Jalali birth date, normalized as `YYYY-MM-DD`. ESG expects Jalali birth date, normalized as `YYYY-MM-DD`.

View File

@@ -161,7 +161,7 @@
<ul> <ul>
<li>درخواست همیشه با پلاک فعلی ارسال‌شده و بیمه‌گذار نهایی همان نوع بیمه انجام می‌شود. متادیتای انتقال اخیر هیچ استعلامی برای پلاک یا بیمه‌گذار قبلی ایجاد نمی‌کند.</li> <li>درخواست همیشه با پلاک فعلی ارسال‌شده و بیمه‌گذار نهایی همان نوع بیمه انجام می‌شود. متادیتای انتقال اخیر هیچ استعلامی برای پلاک یا بیمه‌گذار قبلی ایجاد نمی‌کند.</li>
<li>۱. بررسی داده‌های آفلاین (MongoDB) — اگر داده مطابق یافت شد، آن را برگردانده و تمام HTTP را رد کن.</li> <li>۱. بررسی داده‌های آفلاین (MongoDB) — اگر داده مطابق یافت شد، آن را برگردانده و تمام HTTP را رد کن.</li>
<li>۲. اگر <code>CLIENT_ID=8</code> (تنانت پارسیان/ESG) → مسیریابی به <strong>ESG</strong> <code>/inquiry/policyByPlate</code> یا <code>/inquiry/policyByChassis</code>.</li> <li>۲. اگر <code>CLIENT_ID=8</code> (تنانت پارسیان/ESG) → برای پلاک به <strong>ESG</strong> <code>/inquiry/policyByPlate</code> و برای VIN/شاسی به مسیر دوعاملی <code>/inquiry/carByChassis</code> مسیریابی می‌شود.</li>
<li>۳. در غیر این صورت → مسیریابی به <strong>پرس‌وجوی تجارت</strong> <code>/block-inquiry-tejarat</code> (THIRD_PARTY) یا <code>/block-inquiry-tejarat/badane</code> (CAR_BODY).</li> <li>۳. در غیر این صورت → مسیریابی به <strong>پرس‌وجوی تجارت</strong> <code>/block-inquiry-tejarat</code> (THIRD_PARTY) یا <code>/block-inquiry-tejarat/badane</code> (CAR_BODY).</li>
<li>۴. اگر <code>system_settings.externalApis.sandHubUseLiveApi = false</code> (پیش‌فرض) → پاسخ mock برگردانده شود به جای انجام فراخوانی‌های HTTP.</li> <li>۴. اگر <code>system_settings.externalApis.sandHubUseLiveApi = false</code> (پیش‌فرض) → پاسخ mock برگردانده شود به جای انجام فراخوانی‌های HTTP.</li>
</ul> </ul>
@@ -208,7 +208,7 @@
<div class="card card-blue"> <div class="card card-blue">
<h3>پروتکل ارسال خسارت (۴ مرحله)</h3> <h3>پروتکل ارسال خسارت (۴ مرحله)</h3>
<div class="flow-box"> <div class="flow-box">
<div class="flow-step"><div class="flow-num">۱</div><div class="flow-text"><strong>خسارت پایه (GEN.03)</strong> — <code>POST /car/third-party-car-financial-claims</code>. داده‌های مالک، راننده، بیمه، وسیله نقلیه و تصادف را ارسال می‌کند. یک <code>claimId</code> و <code>claimNo</code> فناوران برمی‌گرداند. پیامک با هر دو شناسه برای مالک ارسال می‌شود.</div></div> <div class="flow-step"><div class="flow-num">۱</div><div class="flow-text"><strong>خسارت پایه (GEN.03)</strong> — <code>POST /car/third-party-car-financial-claims</code>. داده‌های مالک، راننده، بیمه، وسیله نقلیه و تصادف را ارسال می‌کند. یک <code>claimId</code> و <code>claimNo</code> فناوران برمی‌گرداند که برای نمایش در پنل ذخیره می‌شوند.</div></div>
<div class="flow-step"><div class="flow-num">۲</div><div class="flow-text"><strong>موارد خسارت (GEN.05)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/dmg-cases</code>. یک ورودی به ازای هر قطعه آسیب‌دیده با شناسه کامپوننت، شدت و قیمت. سقف: کل ≤ ۵۳،۰۰۰،۰۰۰ تومان.</div></div> <div class="flow-step"><div class="flow-num">۲</div><div class="flow-text"><strong>موارد خسارت (GEN.05)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/dmg-cases</code>. یک ورودی به ازای هر قطعه آسیب‌دیده با شناسه کامپوننت، شدت و قیمت. سقف: کل ≤ ۵۳،۰۰۰،۰۰۰ تومان.</div></div>
<div class="flow-step"><div class="flow-num">۳</div><div class="flow-text"><strong>پیوست‌ها (GEN.07)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/files</code>. اسناد، تصاویر car-capture و ویدیوها که با شناسه فایل ارجاع داده شده‌اند.</div></div> <div class="flow-step"><div class="flow-num">۳</div><div class="flow-text"><strong>پیوست‌ها (GEN.07)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/files</code>. اسناد، تصاویر car-capture و ویدیوها که با شناسه فایل ارجاع داده شده‌اند.</div></div>
<div class="flow-step"><div class="flow-num">۴</div><div class="flow-text"><strong>کارشناسی (GEN.08)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/expertise</code>. متادیتای ارزیابی کارشناس (نقش کارشناس، تاریخ، نتیجه). ارسال را نهایی می‌کند.</div></div> <div class="flow-step"><div class="flow-num">۴</div><div class="flow-text"><strong>کارشناسی (GEN.08)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/expertise</code>. متادیتای ارزیابی کارشناس (نقش کارشناس، تاریخ، نتیجه). ارسال را نهایی می‌کند.</div></div>
@@ -359,7 +359,7 @@
<table> <table>
<tr><th style="width:70px">متد</th><th>مسیر</th><th>توضیح</th></tr> <tr><th style="width:70px">متد</th><th>مسیر</th><th>توضیح</th></tr>
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/policyByPlate</code></td><td>جستجوی بیمه‌نامه مبتنی بر پلاک (THIRD_PARTY). بدنه: <code>nationalCode</code>، <code>plk1–plk4</code>. پاسخ قبل از ذخیره به فرمت قدیمی تجارت نگاشت می‌شود.</td></tr> <tr><td><span class="method post">POST</span></td><td><code>/inquiry/policyByPlate</code></td><td>جستجوی بیمه‌نامه مبتنی بر پلاک (THIRD_PARTY). بدنه: <code>nationalCode</code>، <code>plk1–plk4</code>. پاسخ قبل از ذخیره به فرمت قدیمی تجارت نگاشت می‌شود.</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/policyByChassis</code></td><td>جایگزین مبتنی بر VIN/شاسی برای پرس‌وجوی پلاک. توسط اندپوینت‌های <code>run-inquiries-vin</code> فراخوانی می‌شود. از جستجوی شاسی ESG استفاده می‌کند (نه مسیر SandHub). بدنه: <code>nationalCode</code>، <code>chassis</code>.</td></tr> <tr><td><span class="method post">POST</span></td><td><code>/inquiry/carByChassis</code></td><td>جایگزین دوعاملی VIN/شاسی برای استعلام پلاک. توسط اندپوینت‌های <code>run-inquiries-vin</code> فراخوانی می‌شود. بدنه: <code>nationalCode</code>، <code>chassisNo</code>. مسیر تک‌عاملی <code>policyByChassis</code> استفاده نمی‌شود، چون فیلد <code>nationalCode</code> را نمی‌پذیرد.</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/person</code></td><td>بررسی هویت شخصی. بدنه: <code>nationalCode</code>، <code>birthDate</code> (جلالی، نه میلادی).</td></tr> <tr><td><span class="method post">POST</span></td><td><code>/inquiry/person</code></td><td>بررسی هویت شخصی. بدنه: <code>nationalCode</code>، <code>birthDate</code> (جلالی، نه میلادی).</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/sheba</code></td><td>اعتبارسنجی شبا / حساب بانکی.</td></tr> <tr><td><span class="method post">POST</span></td><td><code>/inquiry/sheba</code></td><td>اعتبارسنجی شبا / حساب بانکی.</td></tr>
</table> </table>
@@ -415,7 +415,7 @@
<tr><td><code>token</code> = "تصادف"/"خسارت"، <code>token2</code> = publicId، <code>token3</code> = نام خانوادگی کارشناس</td><td>کارشناس یک فایل تقصیر یا خسارت را قفل می‌کند</td><td><code>yara-expert-lock</code></td></tr> <tr><td><code>token</code> = "تصادف"/"خسارت"، <code>token2</code> = publicId، <code>token3</code> = نام خانوادگی کارشناس</td><td>کارشناس یک فایل تقصیر یا خسارت را قفل می‌کند</td><td><code>yara-expert-lock</code></td></tr>
<tr><td><code>token</code> = نوع فایل، <code>token2</code> = publicId، <code>token3</code> = لینک</td><td>کارشناس درخواست ارسال مجدد اسناد می‌دهد</td><td><code>yara-resend-documents</code></td></tr> <tr><td><code>token</code> = نوع فایل، <code>token2</code> = publicId، <code>token3</code> = لینک</td><td>کارشناس درخواست ارسال مجدد اسناد می‌دهد</td><td><code>yara-resend-documents</code></td></tr>
<tr><td><code>token</code> = نوع فایل، <code>token2</code> = publicId، <code>token3</code> = نام خانوادگی کارشناس، <code>token10</code> = لینک</td><td>طرف مطلع می‌شود که ارزیابی خسارت کارشناس را امضا کند</td><td><code>yara-signature</code></td></tr> <tr><td><code>token</code> = نوع فایل، <code>token2</code> = publicId، <code>token3</code> = نام خانوادگی کارشناس، <code>token10</code> = لینک</td><td>طرف مطلع می‌شود که ارزیابی خسارت کارشناس را امضا کند</td><td><code>yara-signature</code></td></tr>
<tr><td><code>token</code> = publicId، <code>token2</code> = claimId فناوران، <code>token3</code> = claimNo فناوران</td><td>ارسال فناوران تأیید شد — با شماره و شناسه خسارت فناوران برای مالک خسارت ارسال می‌شود</td><td><code>yara-fanavaran-claim</code></td></tr> <tr><td><code>token</code> = publicId، <code>token2</code> = claimId فناوران، <code>token3</code> = claimNo فناوران</td><td>قالب قدیمی نگه‌داری شده است؛ ارسال خودکار پس از آخرین مرحله فناوران غیرفعال است</td><td><code>yara-fanavaran-claim</code></td></tr>
</table> </table>
<p class="note" style="margin-top:8px;"> <p class="note" style="margin-top:8px;">
تمام فراخوانی‌های پیامک fire-and-forget هستند — هرگز throw نمی‌کنند. شکست‌ها log می‌شوند اما جریان اصلی را مسدود نمی‌کنند. تمام فراخوانی‌های پیامک fire-and-forget هستند — هرگز throw نمی‌کنند. شکست‌ها log می‌شوند اما جریان اصلی را مسدود نمی‌کنند.

View File

@@ -156,7 +156,7 @@
<ul> <ul>
<li>The request always uses the submitted current plate and the resolved policyholder for that policy type. Recent-transfer metadata never triggers a previous-plate or previous-policyholder lookup.</li> <li>The request always uses the submitted current plate and the resolved policyholder for that policy type. Recent-transfer metadata never triggers a previous-plate or previous-policyholder lookup.</li>
<li>1. Check offline-inquiry seeds (MongoDB) — if a matching seed exists, return it and skip all HTTP.</li> <li>1. Check offline-inquiry seeds (MongoDB) — if a matching seed exists, return it and skip all HTTP.</li>
<li>2. If <code>CLIENT_ID=8</code> (Parsian/ESG tenant) → route to <strong>ESG</strong> <code>/inquiry/policyByPlate</code> or <code>/inquiry/policyByChassis</code>.</li> <li>2. If <code>CLIENT_ID=8</code> (Parsian/ESG tenant) → route to <strong>ESG</strong> <code>/inquiry/policyByPlate</code> for plates or the two-factor <code>/inquiry/carByChassis</code> for VIN/chassis inquiries.</li>
<li>3. Otherwise → route to <strong>Tejarat inquiry</strong> <code>/block-inquiry-tejarat</code> (THIRD_PARTY) or <code>/block-inquiry-tejarat/badane</code> (CAR_BODY).</li> <li>3. Otherwise → route to <strong>Tejarat inquiry</strong> <code>/block-inquiry-tejarat</code> (THIRD_PARTY) or <code>/block-inquiry-tejarat/badane</code> (CAR_BODY).</li>
<li>4. If <code>system_settings.externalApis.sandHubUseLiveApi = false</code> (default) → return mock response instead of making HTTP calls.</li> <li>4. If <code>system_settings.externalApis.sandHubUseLiveApi = false</code> (default) → return mock response instead of making HTTP calls.</li>
</ul> </ul>
@@ -204,7 +204,7 @@
<div class="card card-blue"> <div class="card card-blue">
<h3>Claim submission protocol (4 steps)</h3> <h3>Claim submission protocol (4 steps)</h3>
<div class="flow-box"> <div class="flow-box">
<div class="flow-step"><div class="flow-num">1</div><div class="flow-text"><strong>Base claim (GEN.03)</strong> — <code>POST /car/third-party-car-financial-claims</code>. Sends owner, driver, insurance, vehicle, and accident data. Returns a Fanavaran <code>claimId</code> and <code>claimNo</code>. SMS is sent to the owner with both identifiers.</div></div> <div class="flow-step"><div class="flow-num">1</div><div class="flow-text"><strong>Base claim (GEN.03)</strong> — <code>POST /car/third-party-car-financial-claims</code>. Sends owner, driver, insurance, vehicle, and accident data. Returns a Fanavaran <code>claimId</code> and <code>claimNo</code>, which are persisted for panel display.</div></div>
<div class="flow-step"><div class="flow-num">2</div><div class="flow-text"><strong>Damage cases (GEN.05)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/dmg-cases</code>. One entry per damaged part with component ID, severity, and price. Cap: total ≤ 53 000 000 Toman.</div></div> <div class="flow-step"><div class="flow-num">2</div><div class="flow-text"><strong>Damage cases (GEN.05)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/dmg-cases</code>. One entry per damaged part with component ID, severity, and price. Cap: total ≤ 53 000 000 Toman.</div></div>
<div class="flow-step"><div class="flow-num">3</div><div class="flow-text"><strong>Attachments (GEN.07)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/files</code>. Documents, car-capture images, and videos referenced by file ID.</div></div> <div class="flow-step"><div class="flow-num">3</div><div class="flow-text"><strong>Attachments (GEN.07)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/files</code>. Documents, car-capture images, and videos referenced by file ID.</div></div>
<div class="flow-step"><div class="flow-num">4</div><div class="flow-text"><strong>Expertise (GEN.08)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/expertise</code>. Expert assessment metadata (expert role, date, result). Finalises the submission.</div></div> <div class="flow-step"><div class="flow-num">4</div><div class="flow-text"><strong>Expertise (GEN.08)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/expertise</code>. Expert assessment metadata (expert role, date, result). Finalises the submission.</div></div>
@@ -356,7 +356,7 @@
<table> <table>
<tr><th style="width:70px">Method</th><th>Path</th><th>What it does</th></tr> <tr><th style="width:70px">Method</th><th>Path</th><th>What it does</th></tr>
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/policyByPlate</code></td><td>Plate-based policy lookup (THIRD_PARTY). Body: <code>nationalCode</code>, <code>plk1–plk4</code>. Response is mapped to the old Tejarat format before being stored.</td></tr> <tr><td><span class="method post">POST</span></td><td><code>/inquiry/policyByPlate</code></td><td>Plate-based policy lookup (THIRD_PARTY). Body: <code>nationalCode</code>, <code>plk1–plk4</code>. Response is mapped to the old Tejarat format before being stored.</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/policyByChassis</code></td><td>VIN/chassis-based alternative to plate inquiry. Called by <code>run-inquiries-vin</code> endpoints. Uses ESG chassis lookup (not the SandHub path). Body: <code>nationalCode</code>, <code>chassis</code>.</td></tr> <tr><td><span class="method post">POST</span></td><td><code>/inquiry/carByChassis</code></td><td>Two-factor VIN/chassis alternative to the plate inquiry. Called by <code>run-inquiries-vin</code> endpoints. Body: <code>nationalCode</code>, <code>chassisNo</code>. The one-factor <code>policyByChassis</code> route is not used because it rejects <code>nationalCode</code>.</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/person</code></td><td>Personal identity check. Body: <code>nationalCode</code>, <code>birthDate</code> (Jalali, NOT Gregorian).</td></tr> <tr><td><span class="method post">POST</span></td><td><code>/inquiry/person</code></td><td>Personal identity check. Body: <code>nationalCode</code>, <code>birthDate</code> (Jalali, NOT Gregorian).</td></tr>
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/sheba</code></td><td>Sheba / bank account validation.</td></tr> <tr><td><span class="method post">POST</span></td><td><code>/inquiry/sheba</code></td><td>Sheba / bank account validation.</td></tr>
</table> </table>
@@ -412,7 +412,7 @@
<tr><td><code>yara-expert-lock</code></td><td>Expert locks a blame or claim file</td><td><code>token</code> = "تصادف"/"خسارت", <code>token2</code> = publicId, <code>token3</code> = expert surname</td></tr> <tr><td><code>yara-expert-lock</code></td><td>Expert locks a blame or claim file</td><td><code>token</code> = "تصادف"/"خسارت", <code>token2</code> = publicId, <code>token3</code> = expert surname</td></tr>
<tr><td><code>yara-resend-documents</code></td><td>Expert requests document resend</td><td><code>token</code> = file kind, <code>token2</code> = publicId, <code>token3</code> = link</td></tr> <tr><td><code>yara-resend-documents</code></td><td>Expert requests document resend</td><td><code>token</code> = file kind, <code>token2</code> = publicId, <code>token3</code> = link</td></tr>
<tr><td><code>yara-signature</code></td><td>Party notified to sign the expert's damage assessment</td><td><code>token</code> = file kind, <code>token2</code> = publicId, <code>token3</code> = expert surname, <code>token10</code> = link</td></tr> <tr><td><code>yara-signature</code></td><td>Party notified to sign the expert's damage assessment</td><td><code>token</code> = file kind, <code>token2</code> = publicId, <code>token3</code> = expert surname, <code>token10</code> = link</td></tr>
<tr><td><code>yara-fanavaran-claim</code></td><td>Fanavaran submission confirmed — sent to claim owner with Fanavaran claim number and ID</td><td><code>token</code> = publicId, <code>token2</code> = Fanavaran claimId, <code>token3</code> = Fanavaran claimNo</td></tr> <tr><td><code>yara-fanavaran-claim</code></td><td>Retained legacy template; automatic dispatch after the final Fanavaran stage is disabled</td><td><code>token</code> = publicId, <code>token2</code> = Fanavaran claimId, <code>token3</code> = Fanavaran claimNo</td></tr>
</table> </table>
<p class="note" style="margin-top:8px;"> <p class="note" style="margin-top:8px;">
All SMS calls are fire-and-forget — they never throw. Failures are logged but do not block the main flow. All SMS calls are fire-and-forget — they never throw. Failures are logged but do not block the main flow.

View File

@@ -308,7 +308,7 @@ Requires `claimId` + `dmgCaseId` (soft-ensures earlier stages).
| `Id` | `claimCases.expertiseId` | | `Id` | `claimCases.expertiseId` |
History: `FANAVARAN_EXPERTISE_AUTO_SUBMIT_SUCCEEDED`. 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`. Proven Parsian: `expertiseId=403144`, `ClaimExpertId=29`.
Manual: `POST /v2/fanavaran/{client}/claim-cases/{id}/expertise/submit`. Manual: `POST /v2/fanavaran/{client}/claim-cases/{id}/expertise/submit`.

View File

@@ -21,7 +21,7 @@ Start local claim
→ Upload docs/images locally → Upload docs/images locally
→ GEN.07 attachments (per file, best-effort) → GEN.07 attachments (per file, best-effort)
→ Expert pricing ready → Expert pricing ready
→ GEN.08 expertise → store expertiseId → SMS owner → GEN.08 expertise → store expertiseId (no SMS)
→ End (local completion independent of Fanavaran success) → End (local completion independent of Fanavaran success)
``` ```
@@ -76,7 +76,7 @@ sequenceDiagram
YARA->>FV: POST .../expertise (GEN.08) YARA->>FV: POST .../expertise (GEN.08)
FV-->>YARA: ExpertiseId FV-->>YARA: ExpertiseId
YARA->>DB: expertiseId, history SUCCESS YARA->>DB: expertiseId, history SUCCESS
YARA->>YARA: SMS owner (deduped) Note over YARA: Post-expertise owner SMS is disabled
``` ```
## Flow diagram (stages + soft-ensure) ## Flow diagram (stages + soft-ensure)

View File

@@ -17,7 +17,7 @@ source: fanavaran-module-docs
| Tenant config | boot + System Settings | `fanavaran-client-config.service.ts`, `system-settings.*` | | Tenant config | boot + System Settings | `fanavaran-client-config.service.ts`, `system-settings.*` |
| Orchestration | `ClaimRequestManagementModule` | `claim-request-management.service.ts` | | Orchestration | `ClaimRequestManagementModule` | `claim-request-management.service.ts` |
| Policy select | same | `fanavaran-policy-selection.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 | | Send | GEN.03 JSON + business headers |
| Receive | `Id`, `ClaimNo` | | Receive | `Id`, `ClaimNo` |
| Store | `claimId`, `claimNo`, `fanavaranSync.baseClaim.*`, history | | 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 ### C. Damage case

View File

@@ -69,4 +69,4 @@ Per HTTP step:
## SMS ## 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.

View File

@@ -58,7 +58,6 @@ import { MediaPolicyModule } from "src/media-policy/media-policy.module";
import { FanavaranAuditModule } from "src/fanavaran/fanavaran-audit.module"; import { FanavaranAuditModule } from "src/fanavaran/fanavaran-audit.module";
import { FanavaranLookupModule } from "src/fanavaran/fanavaran-lookup.module"; import { FanavaranLookupModule } from "src/fanavaran/fanavaran-lookup.module";
import { PlateNormalizerModule } from "src/utils/plate-normalizer/plate-normalizer.module"; import { PlateNormalizerModule } from "src/utils/plate-normalizer/plate-normalizer.module";
import { SmsOrchestrationModule } from "src/sms-orchestration/sms-orchestration.module";
@Module({ @Module({
imports: [ imports: [
@@ -77,7 +76,6 @@ import { SmsOrchestrationModule } from "src/sms-orchestration/sms-orchestration.
SandHubModule, SandHubModule,
ClientModule, ClientModule,
MediaPolicyModule, MediaPolicyModule,
SmsOrchestrationModule,
JwtModule.register({}), JwtModule.register({}),
MongooseModule.forFeature([ MongooseModule.forFeature([
{ name: ClaimCase.name, schema: ClaimCaseSchema }, { name: ClaimCase.name, schema: ClaimCaseSchema },

View File

@@ -249,7 +249,6 @@ import {
FanavaranAuditStatus, FanavaranAuditStatus,
FanavaranAuditStep, FanavaranAuditStep,
} from "src/fanavaran/schema/fanavaran-audit-log.schema"; } from "src/fanavaran/schema/fanavaran-audit-log.schema";
import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service";
import { selectLatestActiveFanavaranPolicy } from "./fanavaran-policy-selection"; import { selectLatestActiveFanavaranPolicy } from "./fanavaran-policy-selection";
import { import {
selectAccidentVehicleUsedId, selectAccidentVehicleUsedId,
@@ -487,7 +486,6 @@ export class ClaimRequestManagementService {
private readonly fanavaranAuditService: FanavaranAuditService, private readonly fanavaranAuditService: FanavaranAuditService,
private readonly fanavaranAuthService: FanavaranAuthService, private readonly fanavaranAuthService: FanavaranAuthService,
private readonly fanavaranLookupService: FanavaranLookupService, private readonly fanavaranLookupService: FanavaranLookupService,
private readonly smsOrchestrationService: SmsOrchestrationService,
private readonly fileMakerDbService: FileMakerDbService, private readonly fileMakerDbService: FileMakerDbService,
private readonly fileReviewerDbService: FileReviewerDbService, private readonly fileReviewerDbService: FileReviewerDbService,
private readonly fieldExpertDbService: FieldExpertDbService, private readonly fieldExpertDbService: FieldExpertDbService,
@@ -7395,13 +7393,7 @@ export class ClaimRequestManagementService {
this.logger.log( this.logger.log(
`[executeFanavaranExpertiseSubmit] Already have expertiseId=${claimCase.expertiseId}; skipping Fanavaran POST`, `[executeFanavaranExpertiseSubmit] Already have expertiseId=${claimCase.expertiseId}; skipping Fanavaran POST`,
); );
// Catch-up / deduped: SMS may still be pending if expertise predated this notify. // Product decision: completing the final Fanavaran stage must not send SMS.
await this.notifyClaimOwnerFanavaranClaimRegistered({
claimCaseId,
claimId: claimCase.claimId,
claimNo: claimCase.claimNo,
logPrefix: `[Fanavaran ${clientKey} V2 Expertise]`,
});
return ( return (
(claimCase as any)?.fanavaranSync?.expertise?.response ?? { (claimCase as any)?.fanavaranSync?.expertise?.response ?? {
Id: claimCase.expertiseId, Id: claimCase.expertiseId,
@@ -7477,13 +7469,7 @@ export class ClaimRequestManagementService {
}, },
}); });
// Best-effort: SMS after last Fanavaran stage (expertise) succeeds // Product decision: completing the final Fanavaran stage must not send SMS.
await this.notifyClaimOwnerFanavaranClaimRegistered({
claimCaseId,
claimId: claimCase.claimId,
claimNo: claimCase.claimNo,
logPrefix: `[Fanavaran ${clientKey} V2 Expertise]`,
});
return response.data; return response.data;
} catch (error) { } catch (error) {
@@ -7521,12 +7507,7 @@ export class ClaimRequestManagementService {
}; };
} }
if (claimCase.expertiseId != null) { if (claimCase.expertiseId != null) {
await this.notifyClaimOwnerFanavaranClaimRegistered({ // Product decision: an already-completed Fanavaran stage must not send SMS.
claimCaseId,
claimId: claimCase.claimId,
claimNo: claimCase.claimNo,
logPrefix,
});
return { return {
attempted: false, attempted: false,
submitted: false, submitted: false,
@@ -8695,111 +8676,6 @@ export class ClaimRequestManagementService {
return true; return true;
} }
/**
* Phone for the claim owner / damaged party (same recipient as other claim SMS).
*/
private async resolveFanavaranClaimOwnerPhone(
claimCase: any,
): Promise<string | undefined> {
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<void> {
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( private async executeFanavaranV2Submit(
claimCaseId: string, claimCaseId: string,
clientKey: FanavaranClientKey, clientKey: FanavaranClientKey,

View File

@@ -142,7 +142,7 @@ export class FanavaranSyncStage {
@Prop({ type: Date }) @Prop({ type: Date })
lastPayloadBuiltAt?: 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 }) @Prop({ type: Date })
smsNotifiedAt?: Date; smsNotifiedAt?: Date;

View File

@@ -76,7 +76,7 @@ describe("fanavaran hull base claim", () => {
expect(hull.IsLicenseReplaced).toBeNull(); expect(hull.IsLicenseReplaced).toBeNull();
expect(hull.CostSeparationToDmgSections).toBe(0); expect(hull.CostSeparationToDmgSections).toBe(0);
expect(hull.IsOwnerChanged).toBe(0); expect(hull.IsOwnerChanged).toBe(0);
expect(hull.CulpritTypeId).toBe(301); expect(hull.CulpritTypeId).toBe(300);
expect(hull.DamagedCount).toBeUndefined(); expect(hull.DamagedCount).toBeUndefined();
expect(hull.HasOtherCulprit).toBeUndefined(); expect(hull.HasOtherCulprit).toBeUndefined();
expect(hull.CompensationReferenceId).toBeUndefined(); expect(hull.CompensationReferenceId).toBeUndefined();

View File

@@ -52,7 +52,7 @@ export class ExternalInquiryFlagsDto implements ExternalInquiryFlags {
@ApiProperty({ @ApiProperty({
description: 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, example: false,
}) })
@IsBoolean() @IsBoolean()

View File

@@ -1,4 +1,5 @@
import { ExpertInsurerService } from "./expert-insurer.service"; import { ExpertInsurerService } from "./expert-insurer.service";
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
describe("insurer expert file summaries", () => { describe("insurer expert file summaries", () => {
const service = Object.create( const service = Object.create(
@@ -49,4 +50,25 @@ describe("insurer expert file summaries", () => {
expect(summary.reviewDurationMinutes).toBe(0); 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,
});
});
}); });

View File

@@ -86,6 +86,7 @@ import {
} from "./helper/timeline-fa-labels"; } from "./helper/timeline-fa-labels";
import { buildEnrichedDamagedParts } from "src/expert-claim/dto/claim-damaged-part.enricher"; import { buildEnrichedDamagedParts } from "src/expert-claim/dto/claim-damaged-part.enricher";
import { serializeDamagedPartSelectionHistory } from "src/helpers/claim-damaged-part-audit"; import { serializeDamagedPartSelectionHistory } from "src/helpers/claim-damaged-part-audit";
import { fanavaranClaimReferences } from "src/claim-request-management/fanavaran-claim-references";
@Injectable() @Injectable()
export class ExpertInsurerService { export class ExpertInsurerService {
@@ -786,6 +787,7 @@ export class ExpertInsurerService {
publicId: claim.publicId, publicId: claim.publicId,
status: claim.status, status: claim.status,
claimStatus: claim.claimStatus, claimStatus: claim.claimStatus,
fanavaran: fanavaranClaimReferences(claim),
blameDocumentResendPending: claim.blameDocumentResendPending, blameDocumentResendPending: claim.blameDocumentResendPending,
workflow: claim.workflow, workflow: claim.workflow,
owner: ownerOut, owner: ownerOut,

View File

@@ -114,7 +114,7 @@ export class CallCenterBlameV6Controller {
description: description:
"VIN alternative to `run-inquiry`. " + "VIN alternative to `run-inquiry`. " +
"The agent supplies the chassis number and personal data collected from the caller. " + "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). " + "blame document under `vehicle.vin` (plateId is left empty). " +
"Identical eligibility guards and insurer-company validation as the plate variant. " + "Identical eligibility guards and insurer-company validation as the plate variant. " +
"After this call, proceed to `send-link` exactly as in the plate flow.", "After this call, proceed to `send-link` exactly as in the plate flow.",

View File

@@ -1,4 +1,5 @@
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum"; import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
import { PartyRole } from "./entities/schema/partyRole.enum";
import { RequestManagementService } from "./request-management.service"; import { RequestManagementService } from "./request-management.service";
describe("RequestManagementService policyholder inquiry routing", () => { describe("RequestManagementService policyholder inquiry routing", () => {
@@ -138,7 +139,7 @@ describe("RequestManagementService policyholder inquiry routing", () => {
it("queries VIN with the third-party policyholder", async () => { it("queries VIN with the third-party policyholder", async () => {
const service = Object.create(RequestManagementService.prototype) as any; const service = Object.create(RequestManagementService.prototype) as any;
service.sandHubService = { service.sandHubService = {
getPolicyByChassisInquiry: jest.fn().mockResolvedValue({ getCarByChassisInquiry: jest.fn().mockResolvedValue({
raw: {}, raw: {},
mapped: { CompanyName: "پارسیان" }, mapped: { CompanyName: "پارسیان" },
}), }),
@@ -151,16 +152,74 @@ describe("RequestManagementService policyholder inquiry routing", () => {
await service.getThirdPartyVinInquiry(submission); await service.getThirdPartyVinInquiry(submission);
expect( expect(
service.sandHubService.getPolicyByChassisInquiry, service.sandHubService.getCarByChassisInquiry,
).toHaveBeenCalledTimes(1); ).toHaveBeenCalledTimes(1);
expect( expect(
service.sandHubService.getPolicyByChassisInquiry, service.sandHubService.getCarByChassisInquiry,
).toHaveBeenCalledWith( ).toHaveBeenCalledWith(
{ {
nationalCode: "0022222222", nationalCode: "0022222222",
chassis: vehicle.vin, chassisNo: vehicle.vin,
}, },
undefined, 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");
});
}); });

View File

@@ -465,10 +465,10 @@ export class RequestManagementService {
); );
} }
const subjects = resolveInquirySubjects(submission); const subjects = resolveInquirySubjects(submission);
const result = await this.sandHubService.getPolicyByChassisInquiry( const result = await this.sandHubService.getCarByChassisInquiry(
{ {
nationalCode: subjects.thirdPartyPolicyNationalCode, nationalCode: subjects.thirdPartyPolicyNationalCode,
chassis, chassisNo: chassis,
}, },
options, options,
); );
@@ -2142,7 +2142,7 @@ export class RequestManagementService {
* V2 initial-form submitted with a VIN/chassis number instead of a plate. * V2 initial-form submitted with a VIN/chassis number instead of a plate.
* *
* Performs: * 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) * 2. Personal identity inquiry (same as the plate path)
* *
* The inquiry result is mapped through the same `mapEsgPolicyByPlateToOldFormat` * 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 const inquiryClientId = party.person?.clientId
? String(party.person.clientId) ? String(party.person.clientId)
: undefined; : undefined;
@@ -2256,10 +2256,10 @@ export class RequestManagementService {
inquiryRaw = inquiry.raw; inquiryRaw = inquiry.raw;
inquiryMapped = inquiry.mapped; inquiryMapped = inquiry.mapped;
this.logger.log( 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( 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( this.recordPartyCaseInquiryStatus(
req, req,
@@ -2274,11 +2274,11 @@ export class RequestManagementService {
); );
} catch (err: any) { } catch (err: any) {
this.logger.error( 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) { if (err?.response) {
this.logger.error( this.logger.error(
`[ESG] policyByChassis response for request=${req._id}: status=${ `[ESG] carByChassis response for request=${req._id}: status=${
err.response.status err.response.status
}, data=${JSON.stringify(err.response.data)}`, }, data=${JSON.stringify(err.response.data)}`,
); );
@@ -2300,7 +2300,7 @@ export class RequestManagementService {
if (inquiryMapped?.Error) { if (inquiryMapped?.Error) {
this.logger.warn( 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, { this.recordPartyCaseInquiryStatus(req, "thirdParty", role, false, {
source: "ESG_VIN_INQUIRY", source: "ESG_VIN_INQUIRY",
@@ -10645,7 +10645,7 @@ export class RequestManagementService {
/** /**
* VIN variant of `runInquiriesV3`. * 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. * `getTejaratBlockInquiry` for the primary policy lookup.
*/ */
async runInquiriesVinV3( async runInquiriesVinV3(
@@ -10788,7 +10788,7 @@ export class RequestManagementService {
/** /**
* VIN variant of `runPartyInquiriesV3Internal`. * 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, * `getTejaratBlockInquiry` (plate-based). All other inquiries (personal,
* driving licence, car-body for CAR_BODY) are unchanged. * driving licence, car-body for CAR_BODY) are unchanged.
*/ */

View File

@@ -189,9 +189,9 @@ describe("SandHubService inquiry mocks", () => {
}, },
}); });
const result = await service.getPolicyByChassisInquiry({ const result = await service.getCarByChassisInquiry({
nationalCode: "0012345678", nationalCode: "0012345678",
chassis: "NAAR03HFFRDE07024", chassisNo: "NAAR03HFFRDE07024",
}); });
expect(result.mapped.Error.Message).toBe( expect(result.mapped.Error.Message).toBe(
@@ -239,10 +239,10 @@ describe("SandHubService inquiry mocks", () => {
}); });
await expect( await expect(
service.getPolicyByChassisInquiry( service.getCarByChassisInquiry(
{ {
nationalCode: "1234567890", nationalCode: "1234567890",
chassis: "NAAR03HFFRDE07024", chassisNo: "NAAR03HFFRDE07024",
}, },
{ enforceDeploymentClientMatch: true }, { enforceDeploymentClientMatch: true },
), ),
@@ -256,16 +256,16 @@ describe("SandHubService inquiry mocks", () => {
data: { CmpCod: "8", CmpNam: "بیمه پارسیان" }, data: { CmpCod: "8", CmpNam: "بیمه پارسیان" },
}); });
await service.getPolicyByChassisInquiry({ await service.getCarByChassisInquiry({
nationalCode: "0012345678", nationalCode: "0012345678",
chassis: "NAAR03HFFRDE07024", chassisNo: "NAAR03HFFRDE07024",
}); });
expect(esg).toHaveBeenCalledWith( expect(esg).toHaveBeenCalledWith(
expect.stringContaining("/inquiry/policyByChassis"), expect.stringContaining("/inquiry/carByChassis"),
{ {
nationalCode: "0012345678", nationalCode: "0012345678",
chassis: "NAAR03HFFRDE07024", chassisNo: "NAAR03HFFRDE07024",
}, },
"vinChassis", "vinChassis",
undefined, undefined,

View File

@@ -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 * When `vinChassis` inquiry is disabled (mock mode) the response shape mirrors
* `buildMockPlateInquiryRaw` so the downstream mapper (`mapEsgPolicyByPlateToOldFormat`) * `buildMockPlateInquiryRaw` so the downstream mapper (`mapEsgPolicyByPlateToOldFormat`)
@@ -1196,15 +1196,15 @@ export class SandHubService {
* @param identity - policyholder national code and 17-character VIN/chassis * @param identity - policyholder national code and 17-character VIN/chassis
* @param options - optional per-tenant client scope * @param options - optional per-tenant client scope
*/ */
async getPolicyByChassisInquiry( async getCarByChassisInquiry(
identity: { nationalCode: string; chassis: string }, identity: { nationalCode: string; chassisNo: string },
options?: SandHubInquiryOptions, options?: SandHubInquiryOptions,
): Promise<{ raw: any; mapped: any }> { ): Promise<{ raw: any; mapped: any }> {
const baseUrl = process.env.ESG_URL ?? "http://192.168.20.22:8085"; const baseUrl = process.env.ESG_URL ?? "http://192.168.20.22:8085";
const requestUrl = `${baseUrl}/inquiry/policyByChassis`; const requestUrl = `${baseUrl}/inquiry/carByChassis`;
const requestPayload = { const requestPayload = {
nationalCode: String(identity.nationalCode), nationalCode: String(identity.nationalCode),
chassis: String(identity.chassis), chassisNo: String(identity.chassisNo),
}; };
const live = await this.isInquiryLive("vinChassis", options); const live = await this.isInquiryLive("vinChassis", options);
@@ -1213,7 +1213,7 @@ export class SandHubService {
const ctx = await this.mockCompanyContext(options); const ctx = await this.mockCompanyContext(options);
const raw = this.buildMockPlateInquiryRaw(ctx); const raw = this.buildMockPlateInquiryRaw(ctx);
this.logger.debug( this.logger.debug(
`[MOCK] getPolicyByChassisInquiry nationalCode=${identity.nationalCode} chassis=${identity.chassis}`, `[MOCK] getCarByChassisInquiry nationalCode=${identity.nationalCode} chassisNo=${identity.chassisNo}`,
); );
const mapped = this.mapEsgPolicyByPlateToOldFormat(raw, "thirdPartyVin"); const mapped = this.mapEsgPolicyByPlateToOldFormat(raw, "thirdPartyVin");
if (!mapped?.Error) { if (!mapped?.Error) {