forked from Yara724/api
Merge pull request 'feat: complete in-person claims without final sign' (#282) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#282
This commit is contained in:
@@ -1364,14 +1364,15 @@
|
||||
<div class="step-num teal">10</div>
|
||||
<div class="step-body">
|
||||
<div class="step-label">
|
||||
Owner sign on expert pricing (FileReviewer acts on
|
||||
behalf of user)
|
||||
Claim completes after expert work (no final owner
|
||||
signature)
|
||||
</div>
|
||||
<div class="step-route">
|
||||
PUT /claim-sign/:claimId
|
||||
Expert submits to Fanavaran manually when ready
|
||||
</div>
|
||||
<div class="step-note">
|
||||
Submits agree + branchId + signature image
|
||||
Mixed priced/factor claims still require priced-line
|
||||
acceptance before factor uploads
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1395,11 +1396,13 @@
|
||||
FileMaker steps are identical to V4 except
|
||||
<code>requiresFileMakerApproval=true</code> is set at creation. The
|
||||
FileReviewer steps are also identical to V4. The only difference is the
|
||||
tail: after the damage expert completes their review and the owner signs,
|
||||
tail: after the damage expert completes their review (and any required factors
|
||||
are validated),
|
||||
the claim moves to
|
||||
<strong>WAITING_FOR_FILE_MAKER_APPROVAL</strong> instead of proceeding
|
||||
directly to fanavaran submission. The FileMaker then approves (→ fanavaran)
|
||||
or rejects (→ back to WAITING_FOR_DAMAGE_EXPERT, max 2 rejections).
|
||||
directly to completion. The FileMaker then approves (→ COMPLETED; manual
|
||||
Fanavaran submission) or rejects (→ back to WAITING_FOR_DAMAGE_EXPERT,
|
||||
max 2 rejections).
|
||||
</p>
|
||||
<div class="card">
|
||||
<div class="roles-row">
|
||||
@@ -1468,7 +1471,7 @@
|
||||
Claim → WAITING_FOR_FILE_MAKER_APPROVAL
|
||||
</div>
|
||||
<div class="step-note">
|
||||
(instead of direct fanavaran submit)
|
||||
(before final completion; no owner signature)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1496,7 +1499,7 @@
|
||||
<div class="step-num orange">✓</div>
|
||||
<div class="step-body">
|
||||
<div class="step-label">
|
||||
Fanavaran submission (on approve)
|
||||
Claim COMPLETED (on approve)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1845,8 +1848,8 @@
|
||||
<td>DAMAGE_EXPERT</td>
|
||||
<td>DAMAGE_EXPERT</td>
|
||||
<td>DAMAGE_EXPERT</td>
|
||||
<td>DAMAGE_EXPERT → owner sign (FileReviewer)</td>
|
||||
<td>DAMAGE_EXPERT → owner sign → FileMaker approval</td>
|
||||
<td>DAMAGE_EXPERT → COMPLETED (manual Fanavaran submit)</td>
|
||||
<td>DAMAGE_EXPERT → FileMaker approval → COMPLETED (manual Fanavaran submit)</td>
|
||||
<td>DAMAGE_EXPERT (standard V2)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
<td><code>file_maker</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ارزیابی خسارت V4/V5 (فیلدهای تصادف، قطعات، عکسها، امضای مالک)</td>
|
||||
<td>ارزیابی خسارت V4/V5 (فیلدهای تصادف، قطعات و عکسها؛ تأیید خطوط قیمتگذاریشده در جریان ترکیبیِ فاکتور در صورت نیاز)</td>
|
||||
<td>فایلهای تخصیصیافته</td>
|
||||
<td>پنل بازبین فایل</td>
|
||||
<td><code>file_reviewer</code></td>
|
||||
@@ -451,7 +451,8 @@
|
||||
OTPها، استعلامها، موقعیت/توضیحات/صدا و امضاها برای هر دو طرف.
|
||||
همچنین اسناد اولیه خسارت (گواهینامهها، کارتهای خودرو) را بارگذاری میکند. پس از
|
||||
امضای دوم، فایل برای تحویل به بازبین فایل «مهرومومه» میشود. در V5، فایلساز
|
||||
در انتها بازمیگردد تا خسارت تکمیلشده را قبل از ارسال به فناوران تأیید یا رد کند.
|
||||
در انتها بازمیگردد تا خسارت تکمیلشده را تأیید یا رد کند. پس از تأیید،
|
||||
کارشناس پرونده را بهصورت دستی به فناوران ارسال میکند.
|
||||
</p>
|
||||
|
||||
<div class="card card-purple">
|
||||
@@ -475,10 +476,10 @@
|
||||
|
||||
<div class="card card-purple">
|
||||
<h3>تأیید خسارت V5 — <code>v5/file-maker/claim-approval/</code></h3>
|
||||
<p class="note">فقط در V5 استفاده میشود. پس از بررسی کارشناس خسارت و امضای مالک، خسارت وارد <code>WAITING_FOR_FILE_MAKER_APPROVAL</code> میشود.</p>
|
||||
<p class="note">فقط در V5 استفاده میشود. پس از بررسی کارشناس خسارت و اعتبارسنجی فاکتورهای لازم، خسارت وارد <code>WAITING_FOR_FILE_MAKER_APPROVAL</code> میشود؛ امضای نهایی مالک لازم نیست.</p>
|
||||
<table>
|
||||
<tr><th style="width:70px">متد</th><th>مسیر</th><th>توضیح</th></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><code>approve/:claimId</code></td><td>تأیید خسارت تکمیلشده ← ارسال SMS امضای مالک را فعال میکند ← پس از امضای مالک به فناوران ارسال میشود.</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><code>approve/:claimId</code></td><td>تأیید خسارت تکمیلشده ← وضعیت خسارت <code>COMPLETED</code> میشود. کارشناس در زمان مناسب آن را بهصورت دستی به فناوران ارسال میکند.</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><code>reject/:claimId</code></td><td>رد به بازبین فایل ← خسارت به WAITING_FOR_DAMAGE_EXPERT برمیگردد. محدودیت: حداکثر ۲ رد در هر خسارت؛ تلاش سوم ۴۲۲ با کد <code>FILE_MAKER_REJECTION_LIMIT_EXCEEDED</code> برمیگرداند.</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -489,7 +490,7 @@
|
||||
دومین اکتور در تقسیم V4/V5. بازبین فایل فایلهای مهرومومهشده (پس از اتمام کار
|
||||
فایلساز) را تحویل میگیرد و مرحله کامل ارزیابی خسارت را انجام میدهد: فیلدهای
|
||||
تصادف، دریافت الزامات عکسبرداری، بارگذاری اسناد (شاسی/موتور)، انتخاب قطعات،
|
||||
عکسهای قطعات، ویدیوی دور زدن خودرو، امضای مالک. تقصیر با car-capture به
|
||||
عکسهای قطعات و ویدیوی دور زدن خودرو. تقصیر با car-capture به
|
||||
COMPLETED علامتگذاری میشود. بازبین فایل همچنین دسترسی خواندن به پنل expert-claim
|
||||
برای خسارتهایی که بررسی میکند دارد.
|
||||
</p>
|
||||
@@ -499,8 +500,8 @@
|
||||
<p class="note">اندپوینتهای V4 و V5 یکسان هستند — فقط پیشوند تغییر میکند.</p>
|
||||
<table>
|
||||
<tr><th style="width:70px">متد</th><th>مسیر</th><th>توضیح</th></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><code>my-files</code></td><td>فهرست تمام فایلهای تخصیصیافته به این بازبین فایل.</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><code>my-files/:requestId</code></td><td>جزئیات کامل یک فایل (طرفین، گردش کار، فیلدهای کارشناس، شناسه خسارت مرتبط).</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><code>my-files</code></td><td>فهرست فایلهای مهرومومشدهٔ فایلساز که در شرکت بیمهٔ این بازبین برای دریافت آمادهاند، بهعلاوهٔ فایلهای تخصیصیافته به خود او.</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><code>my-files/:requestId</code></td><td>جزئیات کامل یک فایل آماده برای دریافت یا تخصیصیافته، در شرکت بیمهٔ همین بازبین (طرفین، گردش کار، فیلدهای کارشناس، شناسه خسارت مرتبط).</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><code>claim-id/:requestId</code></td><td>دریافت شناسه خسارت ایجادشده بهصورت خودکار (از استعلام طرف مقصر فایلساز).</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><code>accident-fields/:requestId</code></td><td>مرحله ۱ (بازبین): ذخیره فیلدهای تصادف (accidentWay، accidentReason، accidentType).</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><code>capture-requirements/:claimId</code></td><td>الزامات عکسبرداری آگاه از مرحله (فاز اسناد پیش از عکسبرداری در مقابل فاز عکسبرداری قطعات).</td></tr>
|
||||
@@ -509,7 +510,7 @@
|
||||
<tr><td><span class="method patch">PATCH</span></td><td><code>select-other-parts/:claimId</code></td><td>انتخاب سایر قطعات آسیبدیده (غیر بدنه).</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><code>capture-part/:claimId</code></td><td>عکسبرداری از قطعات + زوایا برای هر قطعه آسیبدیده انتخابشده.</td></tr>
|
||||
<tr><td><span class="method patch">PATCH</span></td><td><code>car-capture/:claimId</code></td><td>ویدیوی دور زدن خودرو (آخرین مرحله عکسبرداری بازبین). خسارت ← <code>WAITING_FOR_DAMAGE_EXPERT</code>، تقصیر ← <code>COMPLETED</code>.</td></tr>
|
||||
<tr><td><span class="method put">PUT</span></td><td><code>claim-sign/:claimId</code></td><td>ثبت امضای مالک روی قیمتگذاری کارشناس به نمایندگی از طرف زیاندیده (موافقت + branchId + تصویر امضا).</td></tr>
|
||||
<tr><td><span class="method put">PUT</span></td><td><code>claim-sign/:claimId</code></td><td>فقط برای خسارتهای ترکیبیِ قیمت/فاکتور: ثبت موافقت با خطوط قیمتگذاریشده پیش از بارگذاری فاکتور. امضای نهایی مالک دیگر لازم نیست.</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><code>upload-video/:requestId</code></td><td class="dep">در V4/V5 بیاثر است — تقصیر از قبل توسط car-capture COMPLETED شده. موفقیت idempotent برمیگرداند.</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
<td><code>file_reviewer</code></td>
|
||||
<td>FileReviewer panel</td>
|
||||
<td>Assigned files</td>
|
||||
<td>V4/V5 damage assessment (accident fields, parts, captures, owner sign)</td>
|
||||
<td>V4/V5 damage assessment (accident fields, parts, captures; mixed-factor priced-line acceptance when needed)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>registrar</code></td>
|
||||
@@ -367,7 +367,7 @@
|
||||
<tr><td><span class="method post">POST</span></td><td><code>v2/expert-claim/assign/:claimRequestId</code></td><td>Lock claim to this expert. Returns <code>assigned</code>, <code>already_assigned_to_you</code>, or 409.</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><code>v2/expert-claim/request/:claimRequestId/price-drop</code></td><td>Price-drop context: severity labels, coefficient catalog, damaged parts + mapping, suggested car year from blame inquiry.</td></tr>
|
||||
<tr><td><span class="method put">PUT</span></td><td><code>v2/expert-claim/request/:claimRequestId/price-drop</code></td><td>Calculate and persist price-drop: carPrice × yearCoeff × sumOfCoeffs ÷ 400.</td></tr>
|
||||
<tr><td><span class="method put">PUT</span></td><td><code>v2/expert-claim/reply/submit/:claimRequestId</code></td><td>Submit damage assessment reply (priced parts list, daghi, branchId). Cap: total ≤ 53 000 000 Toman. Depending on factorNeeded flags moves claim to owner-sign, mixed-factors-pending, or factor-validation queue.</td></tr>
|
||||
<tr><td><span class="method put">PUT</span></td><td><code>v2/expert-claim/reply/submit/:claimRequestId</code></td><td>Submit damage assessment reply (priced parts list, daghi, branchId). Cap: total ≤ 53 000 000 Toman. A priced-only claim completes immediately; factor claims continue through factor collection/validation. No final owner signature or automatic Fanavaran submission.</td></tr>
|
||||
<tr><td><span class="method put">PUT</span></td><td><code>v2/expert-claim/reply/resend/:claimRequestId</code></td><td>Request user to resend documents/photos. One resend per claim lifecycle; returns 422 if already fulfilled.</td></tr>
|
||||
<tr><td><span class="method patch">PATCH</span></td><td><code>v2/expert-claim/:claimRequestId/visit</code></td><td>Ask user to come in person. Unlocks claim, sets claimStatus to NEEDS_REVISION.</td></tr>
|
||||
<tr><td><span class="method patch">PATCH</span></td><td><code>v2/expert-claim/validate-factors/:claimRequestId</code></td><td>Validate uploaded repair factor invoices. Approve or reject each factor line with totalPayment. Cap applies across all lines (≤ 53 000 000 Toman). Auto-completes when all lines are decided.</td></tr>
|
||||
@@ -444,7 +444,8 @@
|
||||
OTPs, inquiries, location/description/voice, and signatures for both parties.
|
||||
They also upload the initial claim documents (licences, car cards). After the second
|
||||
signature the file is "sealed" for FileReviewer pickup. In V5, FileMaker comes
|
||||
back at the end to approve or reject the completed claim before fanavaran submission.
|
||||
back at the end to approve or reject the completed claim. After approval, an
|
||||
expert submits the case to Fanavaran manually.
|
||||
</p>
|
||||
|
||||
<div class="card card-purple">
|
||||
@@ -468,10 +469,10 @@
|
||||
|
||||
<div class="card card-purple">
|
||||
<h3>V5 claim approval — <code>v5/file-maker/claim-approval/</code></h3>
|
||||
<p class="note">Used only in V5. After damage expert review and owner sign, claim enters <code>WAITING_FOR_FILE_MAKER_APPROVAL</code>.</p>
|
||||
<p class="note">Used only in V5. After damage expert review and any required factor validation, claim enters <code>WAITING_FOR_FILE_MAKER_APPROVAL</code>; no final owner signature is needed.</p>
|
||||
<table>
|
||||
<tr><th style="width:70px">Method</th><th>Route</th><th>What it does</th></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><code>approve/:claimId</code></td><td>Approve the completed claim → triggers owner sign SMS → fanavaran submission after owner signs.</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><code>approve/:claimId</code></td><td>Approve the completed claim → claim becomes <code>COMPLETED</code>. An expert submits to Fanavaran manually when ready.</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><code>reject/:claimId</code></td><td>Reject back to FileReviewer → claim returns to WAITING_FOR_DAMAGE_EXPERT. Limit: max 2 rejections per claim; 3rd attempt returns 422 <code>FILE_MAKER_REJECTION_LIMIT_EXCEEDED</code>.</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -482,7 +483,7 @@
|
||||
The second actor in the V4/V5 split. FileReviewer picks up sealed files (after
|
||||
FileMaker is done) and performs the full damage assessment pass: accident fields,
|
||||
capture requirements lookup, document upload (chassis/engine), part selection,
|
||||
part photos, walk-around video, owner signature. The blame is marked COMPLETED
|
||||
part photos and walk-around video. The blame is marked COMPLETED
|
||||
by car-capture. FileReviewer also has read access to the expert-claim panel for
|
||||
claims they are reviewing.
|
||||
</p>
|
||||
@@ -492,8 +493,8 @@
|
||||
<p class="note">V4 and V5 endpoints are identical — only the prefix changes.</p>
|
||||
<table>
|
||||
<tr><th style="width:70px">Method</th><th>Route</th><th>What it does</th></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><code>my-files</code></td><td>List all files assigned to this FileReviewer.</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><code>my-files/:requestId</code></td><td>Full detail for one file (parties, workflow, expert fields, linked claim ID).</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><code>my-files</code></td><td>List FileMaker-sealed files available to claim in this reviewer’s insurer, plus files already assigned to this FileReviewer.</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><code>my-files/:requestId</code></td><td>Full detail for one available or assigned file in this reviewer’s insurer (parties, workflow, expert fields, linked claim ID).</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><code>claim-id/:requestId</code></td><td>Get the auto-created claim ID (from FileMaker's guilty-party inquiry).</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><code>accident-fields/:requestId</code></td><td>Step 1 (FileReviewer): save accident fields (accidentWay, accidentReason, accidentType).</td></tr>
|
||||
<tr><td><span class="method get">GET</span></td><td><code>capture-requirements/:claimId</code></td><td>Step-aware capture requirements (pre-capture docs phase vs capture-parts phase).</td></tr>
|
||||
@@ -502,7 +503,7 @@
|
||||
<tr><td><span class="method patch">PATCH</span></td><td><code>select-other-parts/:claimId</code></td><td>Select other (non-body) damaged parts.</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><code>capture-part/:claimId</code></td><td>Capture part photos + angles for each selected damaged part.</td></tr>
|
||||
<tr><td><span class="method patch">PATCH</span></td><td><code>car-capture/:claimId</code></td><td>Walk-around video (final FileReviewer capture step). Claim → <code>WAITING_FOR_DAMAGE_EXPERT</code>, blame → <code>COMPLETED</code>.</td></tr>
|
||||
<tr><td><span class="method put">PUT</span></td><td><code>claim-sign/:claimId</code></td><td>Submit owner signature on expert pricing on behalf of the damaged party (agree + branchId + signature image).</td></tr>
|
||||
<tr><td><span class="method put">PUT</span></td><td><code>claim-sign/:claimId</code></td><td>For mixed priced/factor claims only: record acceptance of priced lines before factor uploads. A final owner signature is no longer required.</td></tr>
|
||||
<tr><td><span class="method post">POST</span></td><td><code>upload-video/:requestId</code></td><td class="dep">No-op in V4/V5 — blame already COMPLETED by car-capture. Returns idempotent success.</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -322,7 +322,8 @@ export class ClaimRequestManagementV2Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* V2: Owner signature — priced-line gate (mixed factors) or final accept/reject.
|
||||
* V2–V5: owner signature used only as the priced-line gate for mixed-factor claims.
|
||||
* The final accept/reject phase is retained for legacy rows only.
|
||||
*/
|
||||
@Put("request/:claimRequestId/owner-insurer-approval/sign")
|
||||
@ApiParam({
|
||||
@@ -332,12 +333,12 @@ export class ClaimRequestManagementV2Controller {
|
||||
})
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiOperation({
|
||||
summary: "Sign priced lines or final claim pricing (owner)",
|
||||
summary: "Sign priced lines before factor uploads (owner; final phase is legacy only)",
|
||||
description:
|
||||
"Multipart: `sign`, `agree`, `branchId`. Requires `ClaimCaseStatus` **`INSURER_REVIEW_AWAITING_OWNER_SIGN`**, **`INSURER_REVIEW_MIXED_FACTORS_PENDING`**, or legacy **`WAITING_FOR_INSURER_APPROVAL`**, and `workflow.currentStep=INSURER_REVIEW` (not during owner factor upload or `EXPERT_COST_EVALUATION`).\n\n" +
|
||||
"**Phase A — Mixed reply, priced lines only:** `claimStatus=NEEDS_REVISION`, no `evaluation.ownerPricedPartsApproval` yet. `agree=true` records that signature and moves to `OWNER_UPLOAD_FACTOR_DOCUMENTS` for factor uploads; `agree=false` rejects the whole case (`REJECTED`).\n\n" +
|
||||
"**Phase B — Final:** `claimStatus=APPROVED`, no `evaluation.ownerInsurerApproval` yet. `agree=true` → `COMPLETED`; `agree=false` → `REJECTED`.\n\n" +
|
||||
"Response may include `phase`: `PRICED_PARTS_FOR_FACTORS` or `FINAL_APPROVAL` for UI state.",
|
||||
"**Phase B — Legacy final phase only:** pre-existing rows with `claimStatus=APPROVED` may still be accepted or rejected through this endpoint. New V2–V5 claims complete after expert work (and V5 FileMaker approval) without a final owner signature.\n\n" +
|
||||
"Response may include `phase`: `PRICED_PARTS_FOR_FACTORS` or legacy `FINAL_APPROVAL` for UI state.",
|
||||
})
|
||||
@ApiBody({
|
||||
description: "Signature file, agreement, and branch",
|
||||
|
||||
@@ -102,7 +102,7 @@ export class ClaimDetailsV2ResponseDto {
|
||||
|
||||
@ApiProperty({
|
||||
description:
|
||||
"ClaimCaseStatus; see also `ownerGuidance` for UX. Post-expert: INSURER_REVIEW_AWAITING_OWNER_SIGN | INSURER_REVIEW_MIXED_FACTORS_PENDING | OWNER_REPAIR_FACTOR_UPLOAD_PENDING | EXPERT_VALIDATING_REPAIR_FACTORS; legacy WAITING_FOR_INSURER_APPROVAL may still appear.",
|
||||
"ClaimCaseStatus; see also `ownerGuidance` for UX. New V2–V5 priced-only claims are COMPLETED after expert work; factor claims use INSURER_REVIEW_MIXED_FACTORS_PENDING | OWNER_REPAIR_FACTOR_UPLOAD_PENDING | EXPERT_VALIDATING_REPAIR_FACTORS. V5 then uses WAITING_FOR_FILE_MAKER_APPROVAL. Legacy WAITING_FOR_INSURER_APPROVAL or INSURER_REVIEW_AWAITING_OWNER_SIGN may still appear.",
|
||||
example: "OWNER_REPAIR_FACTOR_UPLOAD_PENDING",
|
||||
})
|
||||
status: string;
|
||||
|
||||
@@ -12,8 +12,8 @@ export class ClaimListItemV2Dto {
|
||||
|
||||
@ApiProperty({
|
||||
description:
|
||||
"ClaimCaseStatus. Post-expert owner phase includes: INSURER_REVIEW_AWAITING_OWNER_SIGN (priced lines only → final owner sign); INSURER_REVIEW_MIXED_FACTORS_PENDING (priced + factor lines); OWNER_REPAIR_FACTOR_UPLOAD_PENDING (all lines factor-needed); EXPERT_VALIDATING_REPAIR_FACTORS (all factors uploaded, expert validating). Legacy DB rows may still use WAITING_FOR_INSURER_APPROVAL for those flows.",
|
||||
example: "INSURER_REVIEW_AWAITING_OWNER_SIGN",
|
||||
"ClaimCaseStatus. New V2–V5 priced-only claims become COMPLETED after expert work. Factor claims use INSURER_REVIEW_MIXED_FACTORS_PENDING (priced-line acceptance before factor uploads), OWNER_REPAIR_FACTOR_UPLOAD_PENDING, and EXPERT_VALIDATING_REPAIR_FACTORS. V5 then waits at WAITING_FOR_FILE_MAKER_APPROVAL. Legacy DB rows may still use WAITING_FOR_INSURER_APPROVAL or INSURER_REVIEW_AWAITING_OWNER_SIGN.",
|
||||
example: "COMPLETED",
|
||||
})
|
||||
status: string;
|
||||
|
||||
|
||||
@@ -527,7 +527,7 @@ Returns status of each item (uploaded/captured or not).
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Owner signature on expert pricing ───────────────────────────────────────
|
||||
// ─── Mixed-factor priced-line signature ──────────────────────────────────────
|
||||
|
||||
@Put("claim-sign/:claimRequestId")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
@@ -553,11 +553,12 @@ Returns status of each item (uploaded/captured or not).
|
||||
})
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Owner signature on expert pricing (Flow 3 — expert acts on behalf of user)",
|
||||
"Priced-line acceptance before factor uploads (Flow 3 — expert acts on behalf of user)",
|
||||
description:
|
||||
"Field expert submits the damaged party's signature during the final approval stage. " +
|
||||
"Delegates to the same service method as the user sign endpoint; the expert's " +
|
||||
"identity is resolved to the claim owner via `resolveClaimEffectiveUserId`.",
|
||||
"For mixed priced/factor claims only, the field expert records the damaged party's " +
|
||||
"acceptance of priced lines before factor uploads. V2–V5 no longer require a final " +
|
||||
"owner signature: they complete after expert work. The expert's identity is resolved " +
|
||||
"to the claim owner via `resolveClaimEffectiveUserId`.",
|
||||
})
|
||||
@UseInterceptors(
|
||||
FileInterceptor("sign", {
|
||||
|
||||
@@ -2223,10 +2223,16 @@ export class ExpertClaimService {
|
||||
actorType: string;
|
||||
},
|
||||
metadata: Record<string, unknown>,
|
||||
): Promise<void> {
|
||||
): Promise<ClaimCaseStatus> {
|
||||
// V5 retains the FileMaker approval gate, but no longer waits for a
|
||||
// damaged-party final signature after that approval.
|
||||
const completionStatus = (claimForTenant as any).requiresFileMakerApproval
|
||||
? ClaimCaseStatus.WAITING_FOR_FILE_MAKER_APPROVAL
|
||||
: ClaimCaseStatus.COMPLETED;
|
||||
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||
$set: {
|
||||
status: ClaimCaseStatus.COMPLETED,
|
||||
status: completionStatus,
|
||||
claimStatus: ClaimStatus.APPROVED,
|
||||
"workflow.currentStep": ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
"workflow.nextStep": ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
@@ -2248,6 +2254,8 @@ export class ExpertClaimService {
|
||||
eventType: ExpertFileActivityType.HANDLED,
|
||||
idempotencyKey: `claim:${claimRequestId}:factor_validation:${historyType}:${actor.sub}`,
|
||||
});
|
||||
|
||||
return completionStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2420,7 +2428,7 @@ export class ExpertClaimService {
|
||||
};
|
||||
|
||||
if (anyRejected) {
|
||||
await this.completeClaimCaseAfterFactorValidationV2(
|
||||
const completionStatus = await this.completeClaimCaseAfterFactorValidationV2(
|
||||
claimRequestId,
|
||||
claim,
|
||||
actor,
|
||||
@@ -2428,28 +2436,19 @@ export class ExpertClaimService {
|
||||
historyActor,
|
||||
{ replyField },
|
||||
);
|
||||
const fanavaran =
|
||||
await this.claimRequestManagementService.autoSubmitToFanavaranV2OnClaimCompleted(
|
||||
claimRequestId,
|
||||
);
|
||||
// Expertise (stage 4) is intentionally NOT submitted here.
|
||||
// It is only triggered after the owner's final approval signature
|
||||
// (submitOwnerInsurerApprovalSignV2), because until that point the
|
||||
// expert review is not considered final — the user can still object.
|
||||
return {
|
||||
message: this.appendFanavaranAutoSubmitToMessage(
|
||||
"Factors were reviewed with expert repricing on rejected lines. The claim is completed without an owner signature (temporary policy; may require owner acceptance later).",
|
||||
fanavaran,
|
||||
),
|
||||
message:
|
||||
completionStatus === ClaimCaseStatus.WAITING_FOR_FILE_MAKER_APPROVAL
|
||||
? "Factors were reviewed with expert repricing. The claim is awaiting FileMaker approval."
|
||||
: "Factors were reviewed with expert repricing. The claim is completed without a final owner signature.",
|
||||
claimRequestId,
|
||||
claimStatus: ClaimStatus.APPROVED,
|
||||
caseStatus: ClaimCaseStatus.COMPLETED,
|
||||
caseStatus: completionStatus,
|
||||
outcome: "REJECTED_REPRICED_AUTO_COMPLETED",
|
||||
fanavaran,
|
||||
};
|
||||
}
|
||||
|
||||
await this.completeClaimCaseAfterFactorValidationV2(
|
||||
const completionStatus = await this.completeClaimCaseAfterFactorValidationV2(
|
||||
claimRequestId,
|
||||
claim,
|
||||
actor,
|
||||
@@ -2458,25 +2457,15 @@ export class ExpertClaimService {
|
||||
{ replyField },
|
||||
);
|
||||
|
||||
const fanavaran =
|
||||
await this.claimRequestManagementService.autoSubmitToFanavaranV2OnClaimCompleted(
|
||||
claimRequestId,
|
||||
);
|
||||
// Expertise (stage 4) is intentionally NOT submitted here.
|
||||
// It is only triggered after the owner's final approval signature
|
||||
// (submitOwnerInsurerApprovalSignV2), because until that point the
|
||||
// expert review is not considered final — the user can still object.
|
||||
|
||||
return {
|
||||
message: this.appendFanavaranAutoSubmitToMessage(
|
||||
"All factors were approved by the expert. The claim is completed without an additional owner signature.",
|
||||
fanavaran,
|
||||
),
|
||||
message:
|
||||
completionStatus === ClaimCaseStatus.WAITING_FOR_FILE_MAKER_APPROVAL
|
||||
? "All factors were approved. The claim is awaiting FileMaker approval."
|
||||
: "All factors were approved by the expert. The claim is completed without a final owner signature.",
|
||||
claimRequestId,
|
||||
claimStatus: ClaimStatus.APPROVED,
|
||||
caseStatus: ClaimCaseStatus.COMPLETED,
|
||||
caseStatus: completionStatus,
|
||||
outcome: "ALL_APPROVED_AUTO_COMPLETED",
|
||||
fanavaran,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3230,9 +3219,9 @@ export class ExpertClaimService {
|
||||
* On success:
|
||||
* - Stores reply; clears owner signature fields (`ownerInsurerApproval`, `ownerPricedPartsApproval`)
|
||||
* - Unlocks the workflow
|
||||
* - Pricing-only (`factorNeeded=false` everywhere): INSURER_REVIEW_AWAITING_OWNER_SIGN, APPROVED, INSURER_REVIEW → owner final sign/reject.
|
||||
* - Pricing-only (`factorNeeded=false` everywhere): COMPLETED, APPROVED, CLAIM_COMPLETED — no final owner signature.
|
||||
* - All lines `factorNeeded`: NEEDS_REVISION, OWNER_UPLOAD_FACTOR_DOCUMENTS → uploads → UNDER_REVIEW, EXPERT_COST_EVALUATION (validate factors).
|
||||
* - Mixed priced + factor: NEEDS_REVISION, INSURER_REVIEW with next OWNER_UPLOAD_FACTOR_DOCUMENTS → owner signs priced lines first → upload factors → expert validates → final sign.
|
||||
* - Mixed priced + factor: NEEDS_REVISION, INSURER_REVIEW with next OWNER_UPLOAD_FACTOR_DOCUMENTS → owner signs priced lines first → upload factors → expert validates → complete.
|
||||
*/
|
||||
async submitExpertReplyV2(
|
||||
claimRequestId: string,
|
||||
@@ -3398,14 +3387,23 @@ export class ExpertClaimService {
|
||||
let currentStep = ClaimWorkflowStep.INSURER_REVIEW;
|
||||
let nextWorkflowStep = ClaimWorkflowStep.CLAIM_COMPLETED;
|
||||
|
||||
const nextCaseStatus = claimCaseStatusAfterExpertReplyV2(processedParts);
|
||||
const blameForFlow = claim.blameRequestId
|
||||
? await this.blameRequestDbService.findById(String(claim.blameRequestId))
|
||||
: undefined;
|
||||
const skipsFinalOwnerSignature =
|
||||
!!(blameForFlow as any)?.expertInitiated &&
|
||||
(blameForFlow as any)?.creationMethod === "IN_PERSON";
|
||||
const nextCaseStatus = claimCaseStatusAfterExpertReplyV2(
|
||||
processedParts,
|
||||
skipsFinalOwnerSignature,
|
||||
);
|
||||
let nextClaimStatus = ClaimStatus.APPROVED;
|
||||
|
||||
// V5 flow: FileMaker must approve before the owner is asked to sign.
|
||||
// When no factor upload is needed, skip the owner-sign step entirely here
|
||||
// and park the claim at WAITING_FOR_FILE_MAKER_APPROVAL. The owner SMS
|
||||
// and INSURER_REVIEW status are emitted by fileMakerApproveV5 instead.
|
||||
// V5 retains a FileMaker approval gate after expert work. Expert-initiated
|
||||
// in-person V2–V5 flows do not request a final damaged-party signature.
|
||||
const isV5Claim = !!(claim as any).requiresFileMakerApproval;
|
||||
const completesWithoutFactors =
|
||||
!needsFactorUpload && !isV5Claim && skipsFinalOwnerSignature;
|
||||
|
||||
if (needsFactorUpload) {
|
||||
nextClaimStatus = ClaimStatus.NEEDS_REVISION;
|
||||
@@ -3418,7 +3416,10 @@ export class ExpertClaimService {
|
||||
}
|
||||
} else if (isV5Claim) {
|
||||
// V5, no factors: hold at WAITING_FOR_FILE_MAKER_APPROVAL
|
||||
currentStep = ClaimWorkflowStep.INSURER_REVIEW;
|
||||
currentStep = ClaimWorkflowStep.CLAIM_COMPLETED;
|
||||
nextWorkflowStep = ClaimWorkflowStep.CLAIM_COMPLETED;
|
||||
} else if (completesWithoutFactors) {
|
||||
currentStep = ClaimWorkflowStep.CLAIM_COMPLETED;
|
||||
nextWorkflowStep = ClaimWorkflowStep.CLAIM_COMPLETED;
|
||||
}
|
||||
|
||||
@@ -3436,10 +3437,11 @@ export class ExpertClaimService {
|
||||
}
|
||||
}
|
||||
|
||||
const updatePayload: Record<string, unknown> = {
|
||||
status: isV5Claim && !needsFactorUpload
|
||||
const persistedCaseStatus = isV5Claim && !needsFactorUpload
|
||||
? ClaimCaseStatus.WAITING_FOR_FILE_MAKER_APPROVAL
|
||||
: nextCaseStatus,
|
||||
: nextCaseStatus;
|
||||
const updatePayload: Record<string, unknown> = {
|
||||
status: persistedCaseStatus,
|
||||
claimStatus: nextClaimStatus,
|
||||
...(expertAddedParts.length > 0
|
||||
? { "damage.selectedParts": mergedSelectedParts }
|
||||
@@ -3454,9 +3456,7 @@ export class ExpertClaimService {
|
||||
"evaluation.ownerPricedPartsApproval": "",
|
||||
},
|
||||
"workflow.currentStep": currentStep,
|
||||
"workflow.nextStep": needsFactorUpload
|
||||
? nextWorkflowStep
|
||||
: ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
"workflow.nextStep": nextWorkflowStep,
|
||||
[`evaluation.${replyField}`]: replyPayload,
|
||||
$push: {
|
||||
"workflow.completedSteps": completedStep,
|
||||
@@ -3495,30 +3495,36 @@ export class ExpertClaimService {
|
||||
idempotencyKey: `claim:${claimRequestId}:handled:${actor.sub}`,
|
||||
});
|
||||
|
||||
// V5: owner SMS is sent by fileMakerApproveV5 after FileMaker approval.
|
||||
const ownerPhoneNotify = await this.resolveClaimOwnerPhone(claim);
|
||||
if (ownerPhoneNotify && !needsFactorUpload && !isV5Claim) {
|
||||
const expertLastName =
|
||||
actor?.fullName?.trim()?.split(/\s+/).pop() || "کارشناس";
|
||||
await this.smsOrchestrationService.sendSignatureReviewNotice({
|
||||
receptor: ownerPhoneNotify,
|
||||
fileKind: "claim",
|
||||
publicId: claim.publicId,
|
||||
expertLastName,
|
||||
link: this.smsOrchestrationService.buildClaimLink(String(claim._id), "v2"),
|
||||
});
|
||||
// V1 retains its existing final owner-signature notification. V2–V5 do not
|
||||
// send this notification because their completion policy is manual Fanavaran
|
||||
// submission after expert work (and FileMaker approval for V5).
|
||||
if (!needsFactorUpload && !isV5Claim && !skipsFinalOwnerSignature) {
|
||||
const ownerPhoneNotify = await this.resolveClaimOwnerPhone(claim);
|
||||
if (ownerPhoneNotify) {
|
||||
const expertLastName =
|
||||
actor?.fullName?.trim()?.split(/\s+/).pop() || "کارشناس";
|
||||
await this.smsOrchestrationService.sendSignatureReviewNotice({
|
||||
receptor: ownerPhoneNotify,
|
||||
fileKind: "claim",
|
||||
publicId: claim.publicId,
|
||||
expertLastName,
|
||||
link: this.smsOrchestrationService.buildClaimLink(
|
||||
String(claim._id),
|
||||
"v2",
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Fanavaran expertise is now triggered on owner final sign, not here.
|
||||
// V2–V5 completion and FileMaker approval do not trigger a Fanavaran
|
||||
// submission; the expert submits manually when ready.
|
||||
|
||||
return {
|
||||
claimRequestId,
|
||||
status: nextCaseStatus,
|
||||
status: persistedCaseStatus,
|
||||
claimStatus: nextClaimStatus,
|
||||
currentStep,
|
||||
workflowNextStep: needsFactorUpload
|
||||
? nextWorkflowStep
|
||||
: ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
workflowNextStep: nextWorkflowStep,
|
||||
factorNeeded: needsFactorUpload,
|
||||
mixedPricingAndFactors: mixedFactorAndPrice,
|
||||
allPartsFactorNeeded: !!needsFactorUpload && !!allFactorLines,
|
||||
|
||||
@@ -270,7 +270,7 @@ export class ExpertClaimV2Controller {
|
||||
"**Frontend routing by `ClaimCaseStatus` (`status`):**\n" +
|
||||
"- **All parts `factorNeeded`:** `OWNER_REPAIR_FACTOR_UPLOAD_PENDING`, `claimStatus=NEEDS_REVISION`, `workflow.currentStep=OWNER_UPLOAD_FACTOR_DOCUMENTS`, `workflow.nextStep=EXPERT_COST_EVALUATION` → owner uploads all factors; then `status` becomes **`EXPERT_VALIDATING_REPAIR_FACTORS`**, `claimStatus=UNDER_REVIEW`, `currentStep=EXPERT_COST_EVALUATION` for expert **validate-factors**.\n" +
|
||||
"- **Mixed (some priced, some factorNeeded):** `INSURER_REVIEW_MIXED_FACTORS_PENDING`, `claimStatus=NEEDS_REVISION`, `currentStep=INSURER_REVIEW`, `nextStep=OWNER_UPLOAD_FACTOR_DOCUMENTS` → owner must call **owner-insurer-approval/sign** first (priced-line acceptance); `currentStep` then moves to `OWNER_UPLOAD_FACTOR_DOCUMENTS` (same case `status` until factors are done).\n" +
|
||||
"- **No factors:** **`INSURER_REVIEW_AWAITING_OWNER_SIGN`**, `claimStatus=APPROVED`, `currentStep=INSURER_REVIEW`, `nextStep=CLAIM_COMPLETED` → owner final sign/reject only.\n\n" +
|
||||
"- **No factors (V2–V5):** **`COMPLETED`**, `claimStatus=APPROVED`, `workflow.currentStep=CLAIM_COMPLETED` → no final owner signature. The expert can submit to Fanavaran manually when ready. For V5, the case instead waits for FileMaker approval before becoming `COMPLETED`.\n\n" +
|
||||
"**Legacy rows** may still use `WAITING_FOR_INSURER_APPROVAL` instead of the specific values above.\n\n" +
|
||||
"**After the owner fulfilled an expert resend** (`damageExpertResend.fulfilledAt`), this expert **cannot** initiate **another resend**—use **reply/submit**, in-person visit, or **validate-factors** as appropriate.",
|
||||
})
|
||||
@@ -352,8 +352,8 @@ export class ExpertClaimV2Controller {
|
||||
"**Decisions:** each factor line gets `APPROVED` or `REJECTED`. **Every** decided line must include expert-entered `totalPayment` **or** both `price` and `salary` (factor photos are not read for amounts).\n\n" +
|
||||
"**Cap (when every factor line is decided):** sum of **all** reply lines (priced parts + factor lines) must be ≤ **53,000,000 Toman**; otherwise `PRICE_CAP_ERROR` with message that the maximum acceptable total was exceeded.\n\n" +
|
||||
"**Outcomes:**\n" +
|
||||
"- **All approved:** `caseStatus=COMPLETED`, `claimStatus=APPROVED`, workflow `CLAIM_COMPLETED` — no owner signature.\n" +
|
||||
"- **Any rejected (repriced):** same auto-complete for now (owner acceptance may be added later).\n" +
|
||||
"- **All approved:** `caseStatus=COMPLETED`, `claimStatus=APPROVED`, workflow `CLAIM_COMPLETED` — no owner signature. V5 instead waits for FileMaker approval.\n" +
|
||||
"- **Any rejected (repriced):** same completion behavior for now (V5 waits for FileMaker approval).\n" +
|
||||
"- **Partial batch:** returns pending until every factor line has a non-pending decision (no cap error until the batch is complete).",
|
||||
})
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
|
||||
37
src/helpers/claim-v2-expert-reply-workflow.spec.ts
Normal file
37
src/helpers/claim-v2-expert-reply-workflow.spec.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
||||
import { claimCaseStatusAfterExpertReplyV2 } from "./claim-v2-expert-reply-workflow";
|
||||
|
||||
describe("claimCaseStatusAfterExpertReplyV2", () => {
|
||||
it("keeps V1 priced-only claims in their final owner-signature state", () => {
|
||||
expect(
|
||||
claimCaseStatusAfterExpertReplyV2([
|
||||
{ factorNeeded: false },
|
||||
{ factorNeeded: false },
|
||||
]),
|
||||
).toBe(ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN);
|
||||
});
|
||||
|
||||
it("completes a V2–V5 priced-only claim without a final owner signature", () => {
|
||||
expect(
|
||||
claimCaseStatusAfterExpertReplyV2(
|
||||
[{ factorNeeded: false }, { factorNeeded: false }],
|
||||
true,
|
||||
),
|
||||
).toBe(ClaimCaseStatus.COMPLETED);
|
||||
});
|
||||
|
||||
it("keeps factor-required claims in their factor collection workflow", () => {
|
||||
expect(
|
||||
claimCaseStatusAfterExpertReplyV2([{ factorNeeded: true }]),
|
||||
).toBe(ClaimCaseStatus.OWNER_REPAIR_FACTOR_UPLOAD_PENDING);
|
||||
});
|
||||
|
||||
it("keeps the priced-line acceptance gate for mixed factor claims", () => {
|
||||
expect(
|
||||
claimCaseStatusAfterExpertReplyV2([
|
||||
{ factorNeeded: false },
|
||||
{ factorNeeded: true },
|
||||
]),
|
||||
).toBe(ClaimCaseStatus.INSURER_REVIEW_MIXED_FACTORS_PENDING);
|
||||
});
|
||||
});
|
||||
@@ -95,11 +95,16 @@ export function claimIsAwaitingExpertFactorValidationV2(claim: {
|
||||
);
|
||||
}
|
||||
|
||||
export function claimCaseStatusAfterExpertReplyV2(parts: ClaimPricingPartLite[]): ClaimCaseStatus {
|
||||
export function claimCaseStatusAfterExpertReplyV2(
|
||||
parts: ClaimPricingPartLite[],
|
||||
completeWithoutFinalOwnerSignature = false,
|
||||
): ClaimCaseStatus {
|
||||
const { mixedFactorAndPrice } = classifyV2ExpertPricingParts(parts);
|
||||
const needsFactorUpload = parts.some((p) => p.factorNeeded === true);
|
||||
if (!needsFactorUpload) {
|
||||
return ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN;
|
||||
return completeWithoutFinalOwnerSignature
|
||||
? ClaimCaseStatus.COMPLETED
|
||||
: ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN;
|
||||
}
|
||||
if (mixedFactorAndPrice) {
|
||||
return ClaimCaseStatus.INSURER_REVIEW_MIXED_FACTORS_PENDING;
|
||||
|
||||
@@ -37,12 +37,13 @@ class FileMakerRejectDto {
|
||||
* V5 FileMaker approval panel.
|
||||
*
|
||||
* After the full claim flow completes (FileReviewer does damage assessment via
|
||||
* expert-claim APIs, user signs), the claim lands in WAITING_FOR_FILE_MAKER_APPROVAL.
|
||||
* expert-claim APIs and any required factors are validated), the claim lands in
|
||||
* WAITING_FOR_FILE_MAKER_APPROVAL.
|
||||
* The FileMaker who created the blame file can then:
|
||||
*
|
||||
* approve → triggers fanavaran submission (claim → COMPLETED)
|
||||
* approve → claim → COMPLETED; an expert submits to Fanavaran manually
|
||||
* reject → sends claim back to WAITING_FOR_DAMAGE_EXPERT so the FileReviewer
|
||||
* can re-lock, adjust pricing, and redo the user interaction
|
||||
* can re-lock, adjust pricing, and redo the claim work
|
||||
*
|
||||
* All endpoints operate on `claimRequestId` (the claim case ID, not the blame ID).
|
||||
* Use `GET v5/file-maker/blame-request-management/claim-id/:requestId` to obtain
|
||||
@@ -65,9 +66,8 @@ export class FileMakerClaimApprovalV5Controller {
|
||||
summary: "Approve the completed claim (FileMaker V5)",
|
||||
description:
|
||||
"Approves a claim that is in `WAITING_FOR_FILE_MAKER_APPROVAL` status. " +
|
||||
"Moves the claim to `INSURER_REVIEW_AWAITING_OWNER_SIGN` and sends the owner an SMS " +
|
||||
"with a signature link. Fanavaran submission is triggered automatically after the " +
|
||||
"owner signs.",
|
||||
"Moves the claim to `COMPLETED`. No final owner signature is requested and " +
|
||||
"Fanavaran submission remains a manual expert action.",
|
||||
})
|
||||
async approve(
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
|
||||
56
src/request-management/file-maker-claim-approval-v5.spec.ts
Normal file
56
src/request-management/file-maker-claim-approval-v5.spec.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Types } from "mongoose";
|
||||
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { RequestManagementService } from "./request-management.service";
|
||||
|
||||
describe("RequestManagementService V5 FileMaker approval", () => {
|
||||
it("completes the claim without notifying the owner for a final signature", async () => {
|
||||
const fileMakerId = new Types.ObjectId();
|
||||
const claimId = new Types.ObjectId();
|
||||
const claimCaseDbService = {
|
||||
findById: jest.fn().mockResolvedValue({
|
||||
_id: claimId,
|
||||
publicId: "CLM-V5",
|
||||
status: ClaimCaseStatus.WAITING_FOR_FILE_MAKER_APPROVAL,
|
||||
requiresFileMakerApproval: true,
|
||||
fileMakerApprovalActorId: fileMakerId,
|
||||
}),
|
||||
findByIdAndUpdate: jest.fn().mockResolvedValue({}),
|
||||
};
|
||||
const service = new (RequestManagementService as any)(
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
claimCaseDbService,
|
||||
) as RequestManagementService;
|
||||
|
||||
const result = await service.fileMakerApproveV5(
|
||||
{ sub: String(fileMakerId), role: RoleEnum.FILE_MAKER },
|
||||
String(claimId),
|
||||
);
|
||||
|
||||
expect(result.status).toBe(ClaimCaseStatus.COMPLETED);
|
||||
expect(result.message).toContain("Submit it to Fanavaran manually");
|
||||
expect(claimCaseDbService.findByIdAndUpdate).toHaveBeenCalledWith(
|
||||
String(claimId),
|
||||
expect.objectContaining({
|
||||
$set: expect.objectContaining({
|
||||
status: ClaimCaseStatus.COMPLETED,
|
||||
requiresFileMakerApproval: false,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -344,7 +344,7 @@ export class FileReviewerBlameV4Controller {
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Owner signature on expert pricing ───────────────────────────────────────
|
||||
// ─── Mixed-factor priced-line signature ──────────────────────────────────────
|
||||
|
||||
@Put("claim-sign/:claimRequestId")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
@@ -362,11 +362,12 @@ export class FileReviewerBlameV4Controller {
|
||||
},
|
||||
})
|
||||
@ApiOperation({
|
||||
summary: "Owner signature on expert pricing (V4 — FileReviewer acts on behalf of user)",
|
||||
summary: "Priced-line acceptance before factor uploads (V4 — FileReviewer acts for user)",
|
||||
description:
|
||||
"FileReviewer submits the damaged party's signature during the final approval stage. " +
|
||||
"Delegates to the same service method as the user sign endpoint; the FileReviewer's " +
|
||||
"identity is resolved to the claim owner via `resolveClaimEffectiveUserId`.",
|
||||
"For mixed priced/factor claims only, FileReviewer records the damaged party's " +
|
||||
"acceptance of priced lines before factor uploads. V2–V5 no longer require a final " +
|
||||
"owner signature: they complete after expert work. The FileReviewer's identity is " +
|
||||
"resolved to the claim owner via `resolveClaimEffectiveUserId`.",
|
||||
})
|
||||
@UseInterceptors(
|
||||
FileInterceptor("sign", {
|
||||
|
||||
@@ -342,7 +342,7 @@ export class FileReviewerBlameV5Controller {
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Owner signature on expert pricing ───────────────────────────────────────
|
||||
// ─── Mixed-factor priced-line signature ──────────────────────────────────────
|
||||
|
||||
@Put("claim-sign/:claimRequestId")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
@@ -360,11 +360,12 @@ export class FileReviewerBlameV5Controller {
|
||||
},
|
||||
})
|
||||
@ApiOperation({
|
||||
summary: "Owner signature on expert pricing (V5 — FileReviewer acts on behalf of user)",
|
||||
summary: "Priced-line acceptance before factor uploads (V5 — FileReviewer acts for user)",
|
||||
description:
|
||||
"FileReviewer submits the damaged party's signature during the final approval stage. " +
|
||||
"Delegates to the same service method as the user sign endpoint; the FileReviewer's " +
|
||||
"identity is resolved to the claim owner via `resolveClaimEffectiveUserId`.",
|
||||
"For mixed priced/factor claims only, FileReviewer records the damaged party's " +
|
||||
"acceptance of priced lines before factor uploads. V2–V5 no longer require a final " +
|
||||
"owner signature: they complete after expert work. The FileReviewer's identity is " +
|
||||
"resolved to the claim owner via `resolveClaimEffectiveUserId`.",
|
||||
})
|
||||
@UseInterceptors(
|
||||
FileInterceptor("sign", {
|
||||
|
||||
@@ -10462,9 +10462,9 @@ export class RequestManagementService {
|
||||
/**
|
||||
* V5 variant of expertUploadBlameVideoV3.
|
||||
* Same flow as V3/V4 but additionally marks the linked claim with
|
||||
* `requiresFileMakerApproval: true` so that after the owner signs,
|
||||
* the claim is held at WAITING_FOR_FILE_MAKER_APPROVAL rather than
|
||||
* being auto-submitted to fanavaran.
|
||||
* `requiresFileMakerApproval: true` so that after expert work completes,
|
||||
* the claim is held at WAITING_FOR_FILE_MAKER_APPROVAL. FileMaker approval
|
||||
* completes the claim; Fanavaran submission is manual.
|
||||
*/
|
||||
async expertUploadBlameVideoV5(
|
||||
expert: any,
|
||||
@@ -10612,7 +10612,7 @@ export class RequestManagementService {
|
||||
videoId,
|
||||
status: req.status,
|
||||
message: file
|
||||
? "Blame accident video uploaded. File is now in expert review queue. After the full claim flow completes and the owner signs, the FileMaker must approve before fanavaran submission."
|
||||
? "Blame accident video uploaded. File is now in expert review queue. After the full claim flow completes, the FileMaker must approve before manual Fanavaran submission."
|
||||
: "File completed. Claim is now ready for damage expert review.",
|
||||
};
|
||||
}
|
||||
@@ -10625,7 +10625,7 @@ export class RequestManagementService {
|
||||
* - claim.status === WAITING_FOR_FILE_MAKER_APPROVAL
|
||||
* - actor is FILE_MAKER and is the original creator of the linked blame file
|
||||
*
|
||||
* On approval: triggers fanavaran submission and moves claim to COMPLETED.
|
||||
* On approval: completes the claim. Fanavaran submission is manual.
|
||||
*/
|
||||
async fileMakerApproveV5(
|
||||
fileMaker: any,
|
||||
@@ -10669,16 +10669,14 @@ export class RequestManagementService {
|
||||
metadata: {},
|
||||
};
|
||||
|
||||
// V5 approval: move claim to INSURER_REVIEW_AWAITING_OWNER_SIGN so the
|
||||
// owner can now sign. Clear requiresFileMakerApproval so that when the
|
||||
// owner later signs and autoSubmitToFanavaranV2OnClaimCompleted runs, it
|
||||
// does not re-intercept the claim as a pending V5 gate.
|
||||
// V5 approval is the final V5 gate. The damaged-party final signature is
|
||||
// no longer required, and Fanavaran submission is intentionally manual.
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||
$set: {
|
||||
status: ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN,
|
||||
status: ClaimCaseStatus.COMPLETED,
|
||||
claimStatus: ClaimStatus.APPROVED,
|
||||
requiresFileMakerApproval: false,
|
||||
"workflow.currentStep": ClaimWorkflowStep.INSURER_REVIEW,
|
||||
"workflow.currentStep": ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
"workflow.nextStep": ClaimWorkflowStep.CLAIM_COMPLETED,
|
||||
},
|
||||
$push: {
|
||||
@@ -10686,36 +10684,12 @@ export class RequestManagementService {
|
||||
},
|
||||
});
|
||||
|
||||
// Notify the owner that the claim is ready for their signature.
|
||||
const notifyUserId = (claim as any).damagedPartyUserId ?? (claim as any).owner?.userId;
|
||||
if (notifyUserId && claim.blameRequestId) {
|
||||
const blame = await this.blameRequestDbService.findById(
|
||||
String(claim.blameRequestId),
|
||||
);
|
||||
const ownerParty = (blame?.parties || []).find(
|
||||
(p: any) =>
|
||||
p?.person?.userId && String(p.person.userId) === String(notifyUserId),
|
||||
);
|
||||
const ownerPhone = ownerParty?.person?.phoneNumber
|
||||
?? (await this.userDbService.findOne({ _id: new Types.ObjectId(String(notifyUserId)) }))?.mobile;
|
||||
if (ownerPhone && typeof ownerPhone === "string") {
|
||||
await this.smsOrchestrationService.sendSignatureReviewNotice({
|
||||
receptor: ownerPhone,
|
||||
fileKind: "claim",
|
||||
publicId: claim.publicId,
|
||||
expertLastName: actorName.split(/\s+/).pop() || "کارشناس",
|
||||
link: this.smsOrchestrationService.buildClaimLink(claimRequestId, "v2"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
claimRequestId,
|
||||
publicId: claim.publicId,
|
||||
status: ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN,
|
||||
status: ClaimCaseStatus.COMPLETED,
|
||||
message:
|
||||
"Claim approved by FileMaker. Owner has been notified to sign. " +
|
||||
"Once the owner signs, the claim will be completed and submitted to Fanavaran.",
|
||||
"Claim approved by FileMaker and completed. Submit it to Fanavaran manually when ready.",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user