From 972b4b871972fbd4cd87212738a1483b3d7516b7 Mon Sep 17 00:00:00 2001 From: Soheil Hajizadeh Date: Mon, 4 May 2026 21:19:11 +0330 Subject: [PATCH] status modfied --- .../claim-case-status.enum.ts | 16 +++++ .../claim-request-management.service.ts | 19 ++--- .../claim-request-management.v2.controller.ts | 8 +-- .../dto/claim-details-v2.dto.ts | 6 +- .../dto/my-claims-v2.dto.ts | 6 +- .../schema/claim-case.evaluation.schema.ts | 2 +- src/expert-claim/dto/claim-list-v2.dto.ts | 8 ++- src/expert-claim/expert-claim.service.ts | 48 +++++++------ .../expert-claim.v2.controller.ts | 17 ++--- .../claim-details-v2-owner-guidance.ts | 6 +- src/helpers/claim-v2-expert-reply-workflow.ts | 72 +++++++++++++++++-- 11 files changed, 153 insertions(+), 55 deletions(-) diff --git a/src/Types&Enums/claim-request-management/claim-case-status.enum.ts b/src/Types&Enums/claim-request-management/claim-case-status.enum.ts index 10711c6..2476c94 100644 --- a/src/Types&Enums/claim-request-management/claim-case-status.enum.ts +++ b/src/Types&Enums/claim-request-management/claim-case-status.enum.ts @@ -17,7 +17,23 @@ export enum ClaimCaseStatus { // Expert flow WAITING_FOR_DAMAGE_EXPERT = "WAITING_FOR_DAMAGE_EXPERT", EXPERT_REVIEWING = "EXPERT_REVIEWING", + /** + * @deprecated Prefer specific post-expert statuses below. Kept for existing DB rows and reads. + * Historically used for all owner/insurer steps after expert pricing. + */ WAITING_FOR_INSURER_APPROVAL = "WAITING_FOR_INSURER_APPROVAL", + + /** Expert reply has only priced lines (`factorNeeded=false` everywhere). Owner final accept/reject at `INSURER_REVIEW`. Also set after expert finishes repair-factor validation when the case returns to final owner sign-off. */ + INSURER_REVIEW_AWAITING_OWNER_SIGN = "INSURER_REVIEW_AWAITING_OWNER_SIGN", + + /** Expert reply mixes priced lines and factor-needed lines: owner signs priced lines, then uploads factors (status stays through both sub-steps). */ + INSURER_REVIEW_MIXED_FACTORS_PENDING = "INSURER_REVIEW_MIXED_FACTORS_PENDING", + + /** Expert reply requires a repair-factor file for every line before cost validation. */ + OWNER_REPAIR_FACTOR_UPLOAD_PENDING = "OWNER_REPAIR_FACTOR_UPLOAD_PENDING", + + /** All required factor files are uploaded; damage expert validates factors (`UNDER_REVIEW` @ `EXPERT_COST_EVALUATION`). */ + EXPERT_VALIDATING_REPAIR_FACTORS = "EXPERT_VALIDATING_REPAIR_FACTORS", // Final states COMPLETED = "COMPLETED", diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index 418d346..4d7afc1 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -85,6 +85,8 @@ import { partKeyAllowedForExpertResend, } from "src/helpers/claim-expert-resend"; import { + claimCaseStatusAllowsOwnerFactorUploadDuringRevision, + claimCaseStatusAllowsOwnerInsurerSignStep, classifyV2ExpertPricingParts, getActiveV2ExpertReply, objectionDisallowedDueToOutstandingFactorWorkflow, @@ -2369,9 +2371,9 @@ export class ClaimRequestManagementService { ); } - if (claim.status !== ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL) { + if (!claimCaseStatusAllowsOwnerFactorUploadDuringRevision(claim.status)) { throw new BadRequestException( - "Factors can only be uploaded while the insurer-approval phase is pending.", + "Factors can only be uploaded while the claim is in a repair-factor upload phase (see ClaimCaseStatus).", ); } if (claim.claimStatus !== ClaimStatus.NEEDS_REVISION) { @@ -2487,6 +2489,7 @@ export class ClaimRequestManagementService { await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, { $set: { + status: ClaimCaseStatus.EXPERT_VALIDATING_REPAIR_FACTORS, claimStatus: ClaimStatus.UNDER_REVIEW, "workflow.currentStep": ClaimWorkflowStep.EXPERT_COST_EVALUATION, "workflow.nextStep": ClaimWorkflowStep.INSURER_REVIEW, @@ -5064,7 +5067,7 @@ export class ClaimRequestManagementService { /** * V2 API: User objection (ClaimCase + evaluation.objection payload). * - * Priced-flow: WAITING_FOR_INSURER_APPROVAL @ INSURER_REVIEW — either NEEDS_REVISION (mixed-priced gate before factor upload) + * Priced-flow: insurer-review post-expert statuses (`INSURER_REVIEW_AWAITING_OWNER_SIGN`, `INSURER_REVIEW_MIXED_FACTORS_PENDING`, or legacy `WAITING_FOR_INSURER_APPROVAL`) @ `INSURER_REVIEW` — either NEEDS_REVISION (mixed-priced gate before factor upload) * or APPROVED (final totals after pricing / factor validation); never while uploading factors or while expert validates. * Legacy: objection during unpaid expert resend (`WAITING_FOR_USER_RESEND` @ `USER_EXPERT_RESEND`). * Post: resets owner signatures when applicable and returns claim to WAITING_FOR_DAMAGE_EXPERT. @@ -5121,7 +5124,7 @@ export class ClaimRequestManagementService { let pricingObjectionEligible = false; if ( - claimCase.status === ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL && + claimCaseStatusAllowsOwnerInsurerSignStep(claimCase.status) && claimCase.workflow?.currentStep === ClaimWorkflowStep.INSURER_REVIEW && !claimCase.evaluation?.ownerInsurerApproval?.signedAt ) { @@ -5321,11 +5324,11 @@ export class ClaimRequestManagementService { /** * V2: Claim owner signature on expert pricing. * - * Phase 1 (mixed priced + factor-needed lines): WAITING_FOR_INSURER_APPROVAL, NEEDS_REVISION, INSURER_REVIEW, + * Phase 1 (mixed priced + factor-needed lines): `INSURER_REVIEW_MIXED_FACTORS_PENDING` (or legacy `WAITING_FOR_INSURER_APPROVAL`), NEEDS_REVISION, INSURER_REVIEW, * owner has not yet accepted priced lines — agreeing records `evaluation.ownerPricedPartsApproval` and moves the * workflow to OWNER_UPLOAD_FACTOR_DOCUMENTS. Rejecting rejects the whole case. * - * Phase 2 (final): WAITING_FOR_INSURER_APPROVAL, APPROVED, INSURER_REVIEW — full accept/reject completes the case. + * Phase 2 (final): INSURER_REVIEW_AWAITING_OWNER_SIGN (or legacy WAITING_FOR_INSURER_APPROVAL), APPROVED, INSURER_REVIEW — full accept/reject completes the case. */ async submitOwnerInsurerApprovalSignV2( claimRequestId: string, @@ -5366,7 +5369,7 @@ export class ClaimRequestManagementService { ); } - if (claimCase.status !== ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL) { + if (!claimCaseStatusAllowsOwnerInsurerSignStep(claimCase.status)) { throw new BadRequestException( `Claim is not waiting for your insurer-approval signature. Current status: ${String(claimCase.status)}`, ); @@ -5515,7 +5518,7 @@ export class ClaimRequestManagementService { message: "Priced repair lines accepted. Upload repair factor files for each factor-needed part; the claim will return to the damage expert for cost validation.", claimRequestId, - status: ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL, + status: claimCase.status, claimStatus: ClaimStatus.NEEDS_REVISION, currentStep: ClaimWorkflowStep.OWNER_UPLOAD_FACTOR_DOCUMENTS, accepted: true, diff --git a/src/claim-request-management/claim-request-management.v2.controller.ts b/src/claim-request-management/claim-request-management.v2.controller.ts index adbd17b..b01a232 100644 --- a/src/claim-request-management/claim-request-management.v2.controller.ts +++ b/src/claim-request-management/claim-request-management.v2.controller.ts @@ -156,7 +156,7 @@ export class ClaimRequestManagementV2Controller { @ApiOperation({ summary: "Submit user objection (V2)", description: - "**Windows:** (1) **Insurer-review:** `WAITING_FOR_INSURER_APPROVAL` + `workflow.currentStep=INSURER_REVIEW` without a recorded **final** `evaluation.ownerInsurerApproval` signature — including **mixed** priced+factor gate (`NEEDS_REVISION` before factor upload signature) or **final** totals (`claimStatus=APPROVED`). Not allowed while `OWNER_UPLOAD_FACTOR_DOCUMENTS` or `EXPERT_COST_EVALUATION` (finish factor uploads / validation first).\n" + + "**Windows:** (1) **Insurer-review:** `ClaimCaseStatus` in **`INSURER_REVIEW_AWAITING_OWNER_SIGN`**, **`INSURER_REVIEW_MIXED_FACTORS_PENDING`**, or legacy **`WAITING_FOR_INSURER_APPROVAL`**, with `workflow.currentStep=INSURER_REVIEW` and no recorded **final** `evaluation.ownerInsurerApproval` — including **mixed** priced+factor gate (`NEEDS_REVISION` before priced-line signature for factors) or **final** totals (`claimStatus=APPROVED`). Not allowed while uploading factors (`OWNER_UPLOAD_FACTOR_DOCUMENTS`) or expert validation (`EXPERT_COST_EVALUATION`/`EXPERT_VALIDATING_REPAIR_FACTORS`).\n" + "(2) **Legacy resend:** active expert resend (`WAITING_FOR_USER_RESEND` @ `USER_EXPERT_RESEND`).\n\n" + "`objectionParts` may only reference **priced** repair lines (`factorNeeded=false`). Factor-only lines cannot be disputed until they have expert pricing.\n\n" + "After **`damageExpertReplyFinal`** exists (final reply following a prior objection), **no second objection** — owner uses **owner-insurer-approval/sign** to accept/reject and close the case.\n\n" + @@ -247,7 +247,7 @@ export class ClaimRequestManagementV2Controller { @ApiOperation({ summary: "Sign priced lines or final claim pricing (owner)", description: - "Multipart: `sign`, `agree`, `branchId`. Always requires `status=WAITING_FOR_INSURER_APPROVAL` and `workflow.currentStep=INSURER_REVIEW` (never during `EXPERT_COST_EVALUATION`).\n\n" + + "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.", @@ -895,9 +895,9 @@ Returns status of each item (uploaded/captured or not). summary: "Upload repair factor file for a factor-needed part (V2)", description: "Part must have `factorNeeded: true` on the active reply (`evaluation.damageExpertReply` or `evaluation.damageExpertReplyFinal`). One file per part.\n\n" + - "**Requires:** `status=WAITING_FOR_INSURER_APPROVAL`, `claimStatus=NEEDS_REVISION`, `workflow.currentStep=OWNER_UPLOAD_FACTOR_DOCUMENTS`.\n\n" + + "**Requires:** `ClaimCaseStatus` **`OWNER_REPAIR_FACTOR_UPLOAD_PENDING`** or **`INSURER_REVIEW_MIXED_FACTORS_PENDING`** (or legacy **`WAITING_FOR_INSURER_APPROVAL`**), `claimStatus=NEEDS_REVISION`, `workflow.currentStep=OWNER_UPLOAD_FACTOR_DOCUMENTS`.\n\n" + "**Mixed priced+factor replies:** owner must complete **owner-insurer-approval/sign** (priced-line phase) first so `evaluation.ownerPricedPartsApproval` exists.\n\n" + - "When every `factorNeeded` line has `factorLink`, the case moves to `claimStatus=UNDER_REVIEW`, `workflow.currentStep=EXPERT_COST_EVALUATION` for damage expert validation.", + "When every `factorNeeded` line has `factorLink`, the case moves to **`EXPERT_VALIDATING_REPAIR_FACTORS`**, `claimStatus=UNDER_REVIEW`, `workflow.currentStep=EXPERT_COST_EVALUATION` for damage expert validation.", }) @ApiParam({ name: "claimRequestId", description: "Claim case ID" }) @ApiParam({ name: "partId", description: "Part id from expert reply" }) diff --git a/src/claim-request-management/dto/claim-details-v2.dto.ts b/src/claim-request-management/dto/claim-details-v2.dto.ts index b7a4c6d..51c7676 100644 --- a/src/claim-request-management/dto/claim-details-v2.dto.ts +++ b/src/claim-request-management/dto/claim-details-v2.dto.ts @@ -83,7 +83,11 @@ export class ClaimDetailsV2ResponseDto { @ApiProperty({ description: 'Request number' }) requestNo: string; - @ApiProperty({ description: 'Overall case status' }) + @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.", + example: "OWNER_REPAIR_FACTOR_UPLOAD_PENDING", + }) status: string; @ApiProperty({ description: 'Claim damage status' }) diff --git a/src/claim-request-management/dto/my-claims-v2.dto.ts b/src/claim-request-management/dto/my-claims-v2.dto.ts index e593c0b..a6572a1 100644 --- a/src/claim-request-management/dto/my-claims-v2.dto.ts +++ b/src/claim-request-management/dto/my-claims-v2.dto.ts @@ -10,7 +10,11 @@ export class ClaimListItemV2Dto { @ApiProperty({ description: 'Claim request number', example: 'CL12345' }) requestNo: string; - @ApiProperty({ description: 'Overall case status', example: 'WAITING_FOR_DAMAGE_EXPERT' }) + @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", + }) status: string; @ApiProperty({ description: 'Claim damage determination status', example: 'PENDING' }) diff --git a/src/claim-request-management/entites/schema/claim-case.evaluation.schema.ts b/src/claim-request-management/entites/schema/claim-case.evaluation.schema.ts index ba861d1..a757237 100644 --- a/src/claim-request-management/entites/schema/claim-case.evaluation.schema.ts +++ b/src/claim-request-management/entites/schema/claim-case.evaluation.schema.ts @@ -76,7 +76,7 @@ export class ClaimUserComment { export const ClaimUserCommentSchema = SchemaFactory.createForClass(ClaimUserComment); -/** Owner acceptance + signature after expert pricing (WAITING_FOR_INSURER_APPROVAL @ INSURER_REVIEW). */ +/** Owner acceptance + signature after expert pricing (post-expert insurer `INSURER_REVIEW` — `INSURER_REVIEW_AWAITING_OWNER_SIGN` / `INSURER_REVIEW_MIXED_FACTORS_PENDING` / legacy `WAITING_FOR_INSURER_APPROVAL`). */ @Schema({ _id: false }) export class ClaimOwnerInsurerApproval { @Prop({ type: Boolean, required: true }) diff --git a/src/expert-claim/dto/claim-list-v2.dto.ts b/src/expert-claim/dto/claim-list-v2.dto.ts index ac44c47..58ff9ca 100644 --- a/src/expert-claim/dto/claim-list-v2.dto.ts +++ b/src/expert-claim/dto/claim-list-v2.dto.ts @@ -8,7 +8,11 @@ export class ClaimListItemV2Dto { @ApiProperty({ description: 'Public ID shared across blame+claim', example: 'A14235' }) publicId: string; - @ApiProperty({ description: 'Overall case status', example: 'WAITING_FOR_DAMAGE_EXPERT' }) + @ApiProperty({ + description: + "ClaimCaseStatus (expert list may normalize EXPERT_REVIEWING → WAITING_FOR_DAMAGE_EXPERT). Post-expert owner phase: INSURER_REVIEW_* / OWNER_REPAIR_FACTOR_UPLOAD_PENDING / EXPERT_VALIDATING_REPAIR_FACTORS; see expert reply & validate-factors docs.", + example: "WAITING_FOR_DAMAGE_EXPERT", + }) status: string; @ApiProperty({ description: 'Workflow step', example: 'USER_SUBMISSION_COMPLETE' }) @@ -51,7 +55,7 @@ export class ClaimListItemV2Dto { @ApiPropertyOptional({ description: - 'True when the claim is in the expert factor validation queue (all factors uploaded).', + "True in the expert repair-factor validation queue: `status=EXPERT_VALIDATING_REPAIR_FACTORS` (or legacy `WAITING_FOR_INSURER_APPROVAL`) with `claimStatus=UNDER_REVIEW` and `currentStep=EXPERT_COST_EVALUATION`.", }) awaitingFactorValidation?: boolean; } diff --git a/src/expert-claim/expert-claim.service.ts b/src/expert-claim/expert-claim.service.ts index 97f2f01..369fec2 100644 --- a/src/expert-claim/expert-claim.service.ts +++ b/src/expert-claim/expert-claim.service.ts @@ -66,7 +66,9 @@ import { } from "src/helpers/blame-party-agreement-decision"; import { resendRequestHasPayload } from "src/helpers/claim-expert-resend"; import { + claimCaseStatusAfterExpertReplyV2, classifyV2ExpertPricingParts, + claimIsAwaitingExpertFactorValidationV2, } from "src/helpers/claim-v2-expert-reply-workflow"; import { getClaimCarAngleCaptureBlob, @@ -1911,7 +1913,7 @@ export class ExpertClaimService { * V2 (ClaimCase): validate user-uploaded repair factors on the active expert reply. * Preconditions: all `factorNeeded` parts have `factorLink`; case is UNDER_REVIEW at EXPERT_COST_EVALUATION. * — All approved → claimStatus APPROVED, INSURER_REVIEW: owner accepts/rejects to close. - * — Any rejected (expert repriced) → WAITING_FOR_INSURER_APPROVAL, APPROVED, INSURER_REVIEW until owner accepts/rejects. + * — Any rejected (expert repriced) → INSURER_REVIEW_AWAITING_OWNER_SIGN, APPROVED, INSURER_REVIEW until owner accepts/rejects. */ async validateClaimFactorsV2( claimRequestId: string, @@ -1927,11 +1929,7 @@ export class ExpertClaimService { const factorValidationSnapshot = await this.snapshotDamageExpert(actor.sub); - if ( - claim.status !== ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL || - claim.claimStatus !== ClaimStatus.UNDER_REVIEW || - claim.workflow?.currentStep !== ClaimWorkflowStep.EXPERT_COST_EVALUATION - ) { + if (!claimIsAwaitingExpertFactorValidationV2(claim)) { throw new BadRequestException( "Claim is not awaiting expert factor validation (UNDER_REVIEW at EXPERT_COST_EVALUATION).", ); @@ -2075,7 +2073,7 @@ export class ExpertClaimService { if (anyRejected) { await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, { $set: { - status: ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL, + status: ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN, claimStatus: ClaimStatus.APPROVED, "workflow.currentStep": ClaimWorkflowStep.INSURER_REVIEW, "workflow.nextStep": ClaimWorkflowStep.CLAIM_COMPLETED, @@ -2108,7 +2106,7 @@ export class ExpertClaimService { "Factors reviewed with repricing. Owner must accept or reject via owner-insurer-approval/sign.", claimRequestId, claimStatus: ClaimStatus.APPROVED, - caseStatus: ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL, + caseStatus: ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN, outcome: "REJECTED_REPRICED_OWNER_SIGN", }; } @@ -2155,7 +2153,7 @@ export class ExpertClaimService { "All factors approved. The claim proceeds to insurer review (same as non-factor expert reply).", claimRequestId, claimStatus: ClaimStatus.APPROVED, - caseStatus: ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL, + caseStatus: ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN, outcome: "ALL_APPROVED_INSURER_REVIEW", }; } @@ -2482,7 +2480,7 @@ export class ExpertClaimService { * On success: * - Stores reply; clears owner signature fields (`ownerInsurerApproval`, `ownerPricedPartsApproval`) * - Unlocks the workflow - * - Pricing-only (`factorNeeded=false` everywhere): WAITING_FOR_INSURER_APPROVAL, APPROVED, INSURER_REVIEW → owner final sign/reject. + * - Pricing-only (`factorNeeded=false` everywhere): INSURER_REVIEW_AWAITING_OWNER_SIGN, APPROVED, INSURER_REVIEW → owner final sign/reject. * - 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. */ @@ -2576,7 +2574,7 @@ export class ExpertClaimService { let currentStep = ClaimWorkflowStep.INSURER_REVIEW; let nextWorkflowStep = ClaimWorkflowStep.CLAIM_COMPLETED; - const nextCaseStatus = ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL; + const nextCaseStatus = claimCaseStatusAfterExpertReplyV2(processedParts); let nextClaimStatus = ClaimStatus.APPROVED; if (needsFactorUpload) { @@ -2780,7 +2778,7 @@ export class ExpertClaimService { * Returns claims that are (then filtered to the actor's insurer via `claimCaseTouchesClient`): * 1. WAITING_FOR_DAMAGE_EXPERT and not locked (open queue) * 2. Locked by this expert (in-progress) - * 3. WAITING_FOR_INSURER_APPROVAL + UNDER_REVIEW at EXPERT_COST_EVALUATION (factor validation queue) + * 3. EXPERT_VALIDATING_REPAIR_FACTORS (or legacy WAITING_FOR_INSURER_APPROVAL) + UNDER_REVIEW at EXPERT_COST_EVALUATION (factor validation queue) */ async getClaimListV2(actor: any): Promise { requireActorClientKey(actor); @@ -2808,9 +2806,18 @@ export class ExpertClaimService { }, // User uploaded all factors; expert must approve/reject (unlocked queue) { - status: ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL, - claimStatus: ClaimStatus.UNDER_REVIEW, - 'workflow.currentStep': ClaimWorkflowStep.EXPERT_COST_EVALUATION, + $or: [ + { + status: ClaimCaseStatus.EXPERT_VALIDATING_REPAIR_FACTORS, + claimStatus: ClaimStatus.UNDER_REVIEW, + 'workflow.currentStep': ClaimWorkflowStep.EXPERT_COST_EVALUATION, + }, + { + status: ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL, + claimStatus: ClaimStatus.UNDER_REVIEW, + 'workflow.currentStep': ClaimWorkflowStep.EXPERT_COST_EVALUATION, + }, + ], }, ], }); @@ -2867,10 +2874,7 @@ export class ExpertClaimService { ); const list = filtered.map((c) => { - const awaitingFactorValidation = - c.status === ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL && - c.claimStatus === ClaimStatus.UNDER_REVIEW && - c.workflow?.currentStep === ClaimWorkflowStep.EXPERT_COST_EVALUATION; + const awaitingFactorValidation = claimIsAwaitingExpertFactorValidationV2(c); const v = this.vehicleForExpertFromClaimAndBlameMap(c, blameById); const blame = c.blameRequestId ? blameById.get(c.blameRequestId.toString()) @@ -3237,7 +3241,7 @@ export class ExpertClaimService { * Validations: * - Claim must exist and belong to the actor's insurer (`assertClaimCaseForTenant`) * - Allowed when: WAITING_FOR_DAMAGE_EXPERT (queue), or EXPERT_REVIEWING (after lock — same expert must be able to reopen detail), - * or factor-validation queue (WAITING_FOR_INSURER_APPROVAL + UNDER_REVIEW + EXPERT_COST_EVALUATION) + * or factor-validation queue (EXPERT_VALIDATING_REPAIR_FACTORS / legacy WAITING_FOR_INSURER_APPROVAL + UNDER_REVIEW + EXPERT_COST_EVALUATION) * - If an active workflow lock exists (15 min from lockedAt): only the locking expert; after expiry, any expert (tenant) may view */ async getClaimDetailV2( @@ -3259,9 +3263,7 @@ export class ExpertClaimService { claim.status === ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT || claim.status === ClaimCaseStatus.EXPERT_REVIEWING; const isFactorValidationPending = - claim.status === ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL && - claim.claimStatus === ClaimStatus.UNDER_REVIEW && - claim.workflow?.currentStep === ClaimWorkflowStep.EXPERT_COST_EVALUATION; + claimIsAwaitingExpertFactorValidationV2(claim); if (!isDamageExpertPhase && !isFactorValidationPending) { throw new ForbiddenException( diff --git a/src/expert-claim/expert-claim.v2.controller.ts b/src/expert-claim/expert-claim.v2.controller.ts index b34aa22..0570eb7 100644 --- a/src/expert-claim/expert-claim.v2.controller.ts +++ b/src/expert-claim/expert-claim.v2.controller.ts @@ -48,7 +48,7 @@ export class ExpertClaimV2Controller { @ApiOperation({ summary: "List available claim requests for damage expert", description: - "Returns claims that are WAITING_FOR_DAMAGE_EXPERT and not locked by another expert, this expert's locked/in-progress claims, and claims awaiting factor validation (UNDER_REVIEW at EXPERT_COST_EVALUATION).", + "Returns claims that are WAITING_FOR_DAMAGE_EXPERT and not locked by another expert, this expert's locked/in-progress claims, and claims awaiting factor validation (`status=EXPERT_VALIDATING_REPAIR_FACTORS` or legacy `WAITING_FOR_INSURER_APPROVAL` with UNDER_REVIEW at EXPERT_COST_EVALUATION).", }) async getClaimListV2(@CurrentUser() actor) { return await this.expertClaimService.getClaimListV2(actor); @@ -87,10 +87,11 @@ export class ExpertClaimV2Controller { summary: "Submit expert damage assessment reply", description: "**Preconditions:** claim locked by this expert (`EXPERT_REVIEWING`). **Unlocks** the claim. Each part needs `daghi` (V1 rules) and may set `factorNeeded` (repair factor file required from owner) or priced lines only. **Cap:** sum of `totalPayment` ≤ 30,000,000. Clears any prior `evaluation.ownerInsurerApproval` / `ownerPricedPartsApproval`.\n\n" + - "**Frontend routing by outcome:**\n" + - "- **All parts `factorNeeded`:** `status=WAITING_FOR_INSURER_APPROVAL`, `claimStatus=NEEDS_REVISION`, `workflow.currentStep=OWNER_UPLOAD_FACTOR_DOCUMENTS`, `workflow.nextStep=EXPERT_COST_EVALUATION` → owner uploads all factors, then `claimStatus` becomes `UNDER_REVIEW` and `currentStep=EXPERT_COST_EVALUATION` for expert validate-factors.\n" + - "- **Mixed (some priced, some factorNeeded):** same `status`, `claimStatus=NEEDS_REVISION`, `currentStep=INSURER_REVIEW`, `nextStep=OWNER_UPLOAD_FACTOR_DOCUMENTS` → owner must call **owner-insurer-approval/sign** first (priced-line acceptance), then `currentStep` moves to `OWNER_UPLOAD_FACTOR_DOCUMENTS` for factor uploads, then expert validation.\n" + - "- **No factors:** `claimStatus=APPROVED`, `currentStep=INSURER_REVIEW`, `nextStep=CLAIM_COMPLETED` → owner final sign/reject only.\n\n" + + "**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" + + "**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.", }) @ApiParam({ name: "claimRequestId" }) @@ -145,11 +146,11 @@ export class ExpertClaimV2Controller { @ApiOperation({ summary: "Validate uploaded repair factors (V2 ClaimCase)", description: - "**Preconditions:** `status=WAITING_FOR_INSURER_APPROVAL`, `claimStatus=UNDER_REVIEW`, `workflow.currentStep=EXPERT_COST_EVALUATION`, every `factorNeeded` line has `factorLink`.\n\n" + + "**Preconditions:** `status=EXPERT_VALIDATING_REPAIR_FACTORS` (or legacy `WAITING_FOR_INSURER_APPROVAL`), `claimStatus=UNDER_REVIEW`, `workflow.currentStep=EXPERT_COST_EVALUATION`, every `factorNeeded` line has `factorLink`.\n\n" + "**Decisions:** each factor line gets `APPROVED` or `REJECTED`; rejected lines require expert `totalPayment` (or both `price` and `salary`).\n\n" + "**Outcomes:**\n" + - "- **All approved:** `claimStatus=APPROVED`, `currentStep=INSURER_REVIEW` → owner must **sign or reject** via `owner-insurer-approval/sign`; agree completes the case.\n" + - "- **Any rejected (repriced):** same waiter + `INSURER_REVIEW` → owner still must **accept or reject** the repriced totals (case is not completed until they sign).\n" + + "- **All approved:** `status=INSURER_REVIEW_AWAITING_OWNER_SIGN`, `claimStatus=APPROVED`, `currentStep=INSURER_REVIEW` → owner must **sign or reject** via `owner-insurer-approval/sign`; agree completes the case.\n" + + "- **Any rejected (repriced):** same as above (`INSURER_REVIEW_AWAITING_OWNER_SIGN` + `INSURER_REVIEW`) → owner must **accept or reject** the repriced totals.\n" + "- **Partial batch:** service may return pending until every factor line has a non-pending decision.", }) @ApiParam({ name: "claimRequestId" }) diff --git a/src/helpers/claim-details-v2-owner-guidance.ts b/src/helpers/claim-details-v2-owner-guidance.ts index afb74cf..66c13eb 100644 --- a/src/helpers/claim-details-v2-owner-guidance.ts +++ b/src/helpers/claim-details-v2-owner-guidance.ts @@ -3,6 +3,8 @@ import { ClaimStatus } from "src/Types&Enums/claim-request-management/claimStatu import { ClaimWorkflowStep } from "src/Types&Enums/claim-request-management/claim-workflow-steps.enum"; import { classifyV2ExpertPricingParts, + claimCaseStatusAllowsOwnerInsurerSignStep, + claimCaseStatusIsV2OwnerPostExpertPipeline, getActiveV2ExpertReply, objectionDisallowedDueToOutstandingFactorWorkflow, } from "src/helpers/claim-v2-expert-reply-workflow"; @@ -46,7 +48,7 @@ function objectionWindowForOwner(claim: any): { let pricingEligible = false; if ( - claim.status === ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL && + claimCaseStatusAllowsOwnerInsurerSignStep(claim.status) && claim.workflow?.currentStep === ClaimWorkflowStep.INSURER_REVIEW && !claim.evaluation?.ownerInsurerApproval?.signedAt ) { @@ -191,7 +193,7 @@ export function buildClaimDetailsV2OwnerGuidance( }; } - if (claim.status !== ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL) { + if (!claimCaseStatusIsV2OwnerPostExpertPipeline(claim.status)) { return { phaseKey: "USER_FLOW", headline: "Continue your claim registration", diff --git a/src/helpers/claim-v2-expert-reply-workflow.ts b/src/helpers/claim-v2-expert-reply-workflow.ts index fc7616b..a6f7762 100644 --- a/src/helpers/claim-v2-expert-reply-workflow.ts +++ b/src/helpers/claim-v2-expert-reply-workflow.ts @@ -40,6 +40,72 @@ export function classifyV2ExpertPricingParts(parts: ClaimPricingPartLite[]) { }; } +const OWNER_POST_EXPERT_INSURER_SIGNATURE_STATUSES: ReadonlySet = new Set([ + ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL, + ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN, + ClaimCaseStatus.INSURER_REVIEW_MIXED_FACTORS_PENDING, +]); + +const OWNER_FACTOR_UPLOAD_PHASE_STATUSES: ReadonlySet = new Set([ + ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL, + ClaimCaseStatus.OWNER_REPAIR_FACTOR_UPLOAD_PENDING, + ClaimCaseStatus.INSURER_REVIEW_MIXED_FACTORS_PENDING, +]); + +const V2_POST_EXPERT_OWNER_PIPELINE_STATUSES: ReadonlySet = new Set([ + ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL, + ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN, + ClaimCaseStatus.INSURER_REVIEW_MIXED_FACTORS_PENDING, + ClaimCaseStatus.OWNER_REPAIR_FACTOR_UPLOAD_PENDING, + ClaimCaseStatus.EXPERT_VALIDATING_REPAIR_FACTORS, +]); + +/** Owner multipart sign at `INSURER_REVIEW` (priced-line gate or final acceptance). */ +export function claimCaseStatusAllowsOwnerInsurerSignStep( + status: ClaimCaseStatus | string | undefined, +): boolean { + return OWNER_POST_EXPERT_INSURER_SIGNATURE_STATUSES.has(String(status ?? "")); +} + +/** Repair-factor upload during `NEEDS_REVISION` @ `OWNER_UPLOAD_FACTOR_DOCUMENTS`. */ +export function claimCaseStatusAllowsOwnerFactorUploadDuringRevision( + status: ClaimCaseStatus | string | undefined, +): boolean { + return OWNER_FACTOR_UPLOAD_PHASE_STATUSES.has(String(status ?? "")); +} + +/** Owner-facing post-expert flow: signatures, factor uploads, or waiting on expert factor validation. */ +export function claimCaseStatusIsV2OwnerPostExpertPipeline( + status: ClaimCaseStatus | string | undefined, +): boolean { + return V2_POST_EXPERT_OWNER_PIPELINE_STATUSES.has(String(status ?? "")); +} + +export function claimIsAwaitingExpertFactorValidationV2(claim: { + status?: ClaimCaseStatus | string; + claimStatus?: ClaimStatus; + workflow?: { currentStep?: string }; +}): boolean { + return ( + claim.claimStatus === ClaimStatus.UNDER_REVIEW && + claim.workflow?.currentStep === ClaimWorkflowStep.EXPERT_COST_EVALUATION && + (claim.status === ClaimCaseStatus.EXPERT_VALIDATING_REPAIR_FACTORS || + claim.status === ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL) + ); +} + +export function claimCaseStatusAfterExpertReplyV2(parts: ClaimPricingPartLite[]): ClaimCaseStatus { + const { mixedFactorAndPrice } = classifyV2ExpertPricingParts(parts); + const needsFactorUpload = parts.some((p) => p.factorNeeded === true); + if (!needsFactorUpload) { + return ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN; + } + if (mixedFactorAndPrice) { + return ClaimCaseStatus.INSURER_REVIEW_MIXED_FACTORS_PENDING; + } + return ClaimCaseStatus.OWNER_REPAIR_FACTOR_UPLOAD_PENDING; +} + export function claimIsV2ExpertFactorUploadStep(claim: { workflow?: { currentStep?: string } }) { return claim.workflow?.currentStep === ClaimWorkflowStep.OWNER_UPLOAD_FACTOR_DOCUMENTS; } @@ -49,11 +115,7 @@ export function claimIsV2PendingFactorExpertValidation(claim: { claimStatus?: ClaimStatus; workflow?: { currentStep?: string }; }) { - return ( - claim.status === ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL && - claim.claimStatus === ClaimStatus.UNDER_REVIEW && - claim.workflow?.currentStep === ClaimWorkflowStep.EXPERT_COST_EVALUATION - ); + return claimIsAwaitingExpertFactorValidationV2(claim); } export function objectionDisallowedDueToOutstandingFactorWorkflow(claim: {