forked from Yara724/api
342 lines
11 KiB
TypeScript
342 lines
11 KiB
TypeScript
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
|
import { ClaimStatus } from "src/Types&Enums/claim-request-management/claimStatus.enum";
|
|
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";
|
|
|
|
const BASE = "v2/claim-request-management";
|
|
|
|
/** One actionable next step for owner UI (`pathTemplate` embeds claim id; replace `{partId}` per line item). */
|
|
export interface ClaimDetailsOwnerNextActionV2 {
|
|
key: string;
|
|
method?: string;
|
|
pathTemplate?: string;
|
|
description: string;
|
|
}
|
|
|
|
/** Derived hints so the client can route without re-implementing server rules (returned only for claim owners, not FIELD_EXPERT). */
|
|
export interface ClaimDetailsOwnerGuidanceV2 {
|
|
phaseKey: string;
|
|
headline: string;
|
|
detail?: string;
|
|
nextActions: ClaimDetailsOwnerNextActionV2[];
|
|
objectionAllowed?: boolean;
|
|
objectionHint?: string;
|
|
}
|
|
|
|
function objectionWindowForOwner(claim: any): {
|
|
pricingEligible: boolean;
|
|
resendEligible: boolean;
|
|
} {
|
|
const r = claim.evaluation?.damageExpertResend;
|
|
const hasExpertResend = !!(
|
|
r &&
|
|
(String(r.resendDescription || "").trim() !== "" ||
|
|
(Array.isArray(r.resendDocuments) && r.resendDocuments.length > 0) ||
|
|
(Array.isArray(r.resendCarParts) && r.resendCarParts.length > 0))
|
|
);
|
|
|
|
const pendingResend =
|
|
hasExpertResend &&
|
|
claim.status === ClaimCaseStatus.WAITING_FOR_USER_RESEND &&
|
|
claim.workflow?.currentStep === ClaimWorkflowStep.USER_EXPERT_RESEND;
|
|
|
|
let pricingEligible = false;
|
|
if (
|
|
claimCaseStatusAllowsOwnerInsurerSignStep(claim.status) &&
|
|
claim.workflow?.currentStep === ClaimWorkflowStep.INSURER_REVIEW &&
|
|
!claim.evaluation?.ownerInsurerApproval?.signedAt
|
|
) {
|
|
if (!objectionDisallowedDueToOutstandingFactorWorkflow(claim)) {
|
|
const active = getActiveV2ExpertReply(
|
|
claim as { evaluation?: Record<string, unknown> },
|
|
);
|
|
const shape = classifyV2ExpertPricingParts(active?.parts ?? []);
|
|
const atMixed =
|
|
claim.claimStatus === ClaimStatus.NEEDS_REVISION &&
|
|
shape.mixedFactorAndPrice &&
|
|
!claim.evaluation?.ownerPricedPartsApproval?.signedAt;
|
|
const atFinal = claim.claimStatus === ClaimStatus.APPROVED;
|
|
pricingEligible = atMixed || atFinal;
|
|
}
|
|
}
|
|
|
|
return { pricingEligible, resendEligible: !!pendingResend };
|
|
}
|
|
|
|
/** Mirrors server rules for PUT .../objection (read-only hint for UI). */
|
|
export function ownerMaySubmitObjectionForClaimDetails(claim: any): boolean {
|
|
if (claim.evaluation?.objection?.submittedAt) return false;
|
|
if (claim.evaluation?.damageExpertReplyFinal) return false;
|
|
const { pricingEligible, resendEligible } = objectionWindowForOwner(claim);
|
|
if (resendEligible) return true;
|
|
return (
|
|
pricingEligible &&
|
|
!claim.evaluation?.ownerInsurerApproval?.signedAt &&
|
|
!objectionDisallowedDueToOutstandingFactorWorkflow(claim)
|
|
);
|
|
}
|
|
|
|
function objectionHintWhen(allowed: boolean): string | undefined {
|
|
return allowed
|
|
? "Optional: PUT v2/claim-request-management/request/{claimRequestId}/objection — only priced repair lines (`factorNeeded=false` in the expert reply)."
|
|
: undefined;
|
|
}
|
|
|
|
/**
|
|
* Build owner-facing guidance for GET claim details V2.
|
|
* Caller should omit this field for FIELD_EXPERT viewers.
|
|
*/
|
|
export function buildClaimDetailsV2OwnerGuidance(
|
|
claim: any,
|
|
claimRequestId: string,
|
|
): ClaimDetailsOwnerGuidanceV2 {
|
|
const id = claimRequestId;
|
|
const act = (key: string, method: string, path: string, description: string) =>
|
|
({ key, method, pathTemplate: path, description });
|
|
|
|
const objectionAllowed = ownerMaySubmitObjectionForClaimDetails(claim);
|
|
const objectionHint = objectionHintWhen(objectionAllowed);
|
|
|
|
if (claim.status === ClaimCaseStatus.COMPLETED) {
|
|
return {
|
|
phaseKey: "COMPLETED",
|
|
headline: "Claim completed",
|
|
detail:
|
|
claim.userRating?.createdAt
|
|
? "Rating already submitted."
|
|
: "You may submit a satisfaction rating once.",
|
|
nextActions: [
|
|
act(
|
|
"USER_RATING",
|
|
"PUT",
|
|
`${BASE}/request/${id}/user-rating`,
|
|
"Optional satisfaction rating",
|
|
),
|
|
],
|
|
objectionAllowed: false,
|
|
};
|
|
}
|
|
|
|
if (claim.status === ClaimCaseStatus.REJECTED || claim.claimStatus === ClaimStatus.REJECTED) {
|
|
return {
|
|
phaseKey: "REJECTED",
|
|
headline: "Claim rejected",
|
|
detail: "This case is closed as rejected.",
|
|
nextActions: [],
|
|
objectionAllowed: false,
|
|
};
|
|
}
|
|
|
|
if (claim.status === ClaimCaseStatus.CANCELLED) {
|
|
return {
|
|
phaseKey: "CANCELLED",
|
|
headline: "Claim cancelled",
|
|
nextActions: [],
|
|
objectionAllowed: false,
|
|
};
|
|
}
|
|
|
|
if (claim.status === ClaimCaseStatus.WAITING_FOR_USER_RESEND) {
|
|
return {
|
|
phaseKey: "EXPERT_RESEND",
|
|
headline: "Expert requested more information",
|
|
detail:
|
|
"Upload requested documents or damaged-part photos, or acknowledge if the expert gave instructions only.",
|
|
nextActions: [
|
|
act(
|
|
"UPLOAD_DOC",
|
|
"POST",
|
|
`${BASE}/upload-document/${id}`,
|
|
"Multipart: required document upload",
|
|
),
|
|
act(
|
|
"CAPTURE_PART",
|
|
"POST",
|
|
`${BASE}/capture-part/${id}`,
|
|
"Multipart: damaged-part or angle capture",
|
|
),
|
|
act(
|
|
"RESEND_ACK",
|
|
"POST",
|
|
`${BASE}/request/${id}/expert-resend/acknowledge`,
|
|
"If no document/part list — instructions only",
|
|
),
|
|
],
|
|
objectionAllowed: ownerMaySubmitObjectionForClaimDetails(claim),
|
|
objectionHint: objectionHintWhen(ownerMaySubmitObjectionForClaimDetails(claim)),
|
|
};
|
|
}
|
|
|
|
if (claim.status === ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT) {
|
|
return {
|
|
phaseKey: "WAITING_DAMAGE_EXPERT",
|
|
headline: "Waiting for damage expert",
|
|
detail: "Your file is in the expert queue.",
|
|
nextActions: [],
|
|
objectionAllowed: false,
|
|
};
|
|
}
|
|
|
|
if (claim.status === ClaimCaseStatus.EXPERT_REVIEWING) {
|
|
return {
|
|
phaseKey: "EXPERT_REVIEWING",
|
|
headline: "Expert is reviewing",
|
|
detail: "A damage expert is assessing your claim.",
|
|
nextActions: [],
|
|
objectionAllowed: false,
|
|
};
|
|
}
|
|
|
|
if (!claimCaseStatusIsV2OwnerPostExpertPipeline(claim.status)) {
|
|
return {
|
|
phaseKey: "USER_FLOW",
|
|
headline: "Continue your claim registration",
|
|
detail: "Complete remaining selection, captures, documents, or bank info.",
|
|
nextActions: [
|
|
act(
|
|
"CAPTURE_REQUIREMENTS",
|
|
"GET",
|
|
`${BASE}/capture-requirements/${id}`,
|
|
"Shows remaining angles, parts, documents",
|
|
),
|
|
act(
|
|
"UPLOAD_DOC",
|
|
"POST",
|
|
`${BASE}/upload-document/${id}`,
|
|
"Upload required document when workflow allows",
|
|
),
|
|
act(
|
|
"CAPTURE_PART",
|
|
"POST",
|
|
`${BASE}/capture-part/${id}`,
|
|
"Upload angle or damaged-part photo when workflow allows",
|
|
),
|
|
],
|
|
objectionAllowed: false,
|
|
};
|
|
}
|
|
|
|
const step = claim.workflow?.currentStep;
|
|
const cs = claim.claimStatus;
|
|
|
|
if (step === ClaimWorkflowStep.OWNER_UPLOAD_FACTOR_DOCUMENTS && cs === ClaimStatus.NEEDS_REVISION) {
|
|
return {
|
|
phaseKey: "UPLOAD_FACTORS",
|
|
headline: "Upload repair factor files",
|
|
detail:
|
|
"One upload per factor-needed part. When all factors are uploaded, the expert validates costs.",
|
|
nextActions: [
|
|
act(
|
|
"UPLOAD_FACTOR",
|
|
"PATCH",
|
|
`${BASE}/request/reply/${id}/{partId}/upload-factor`,
|
|
"Multipart `file` — use each expert reply `partId` with factorNeeded=true",
|
|
),
|
|
],
|
|
objectionAllowed: false,
|
|
};
|
|
}
|
|
|
|
if (step === ClaimWorkflowStep.EXPERT_COST_EVALUATION && cs === ClaimStatus.UNDER_REVIEW) {
|
|
return {
|
|
phaseKey: "EXPERT_VALIDATING_FACTORS",
|
|
headline: "Expert is validating factors",
|
|
detail: "No owner action until the expert finishes validating repair-factor pricing.",
|
|
nextActions: [],
|
|
objectionAllowed: false,
|
|
};
|
|
}
|
|
|
|
if (step === ClaimWorkflowStep.INSURER_REVIEW) {
|
|
if (cs === ClaimStatus.NEEDS_REVISION) {
|
|
const active = getActiveV2ExpertReply(
|
|
claim as { evaluation?: Record<string, unknown> },
|
|
);
|
|
const shape = classifyV2ExpertPricingParts(active?.parts ?? []);
|
|
if (
|
|
shape.mixedFactorAndPrice &&
|
|
!claim.evaluation?.ownerPricedPartsApproval?.signedAt
|
|
) {
|
|
const ow = objectionWindowForOwner(claim);
|
|
const allowObj = ow.pricingEligible;
|
|
return {
|
|
phaseKey: "SIGN_PRICED_LINES",
|
|
headline: "Sign acceptance of priced lines",
|
|
detail:
|
|
"Required before uploading repair-factor files for mixed priced + factor-needed replies.",
|
|
nextActions: [
|
|
act(
|
|
"SIGN_PRICED_PARTS",
|
|
"PUT",
|
|
`${BASE}/request/${id}/owner-insurer-approval/sign`,
|
|
"Multipart sign + agree + branchId",
|
|
),
|
|
],
|
|
objectionAllowed: allowObj,
|
|
objectionHint: objectionHintWhen(allowObj),
|
|
};
|
|
}
|
|
|
|
/** NEEDS_REVISION at INSURER_REVIEW without mixed gate — all-factor initial sign not used; fallback */
|
|
const allowObj = objectionWindowForOwner(claim).pricingEligible;
|
|
return {
|
|
phaseKey: "INSURER_REVIEW_NEEDS_REVISION",
|
|
headline: "Insurer approval — action pending",
|
|
detail:
|
|
"If you see priced lines without factors, use sign or objection; otherwise verify workflow data.",
|
|
nextActions: [
|
|
act(
|
|
"SIGN_OR_CHECK",
|
|
"PUT",
|
|
`${BASE}/request/${id}/owner-insurer-approval/sign`,
|
|
"Sign if prompted by app state",
|
|
),
|
|
],
|
|
objectionAllowed: allowObj,
|
|
objectionHint: objectionHintWhen(allowObj),
|
|
};
|
|
}
|
|
|
|
if (cs === ClaimStatus.APPROVED && !claim.evaluation?.ownerInsurerApproval?.signedAt) {
|
|
const allowObj = objectionWindowForOwner(claim).pricingEligible;
|
|
return {
|
|
phaseKey: "FINAL_SIGN_OR_REJECT",
|
|
headline: "Accept or reject final pricing",
|
|
detail: "Sign to complete the claim, or reject. You may object to priced lines instead of signing.",
|
|
nextActions: [
|
|
act(
|
|
"FINAL_SIGN",
|
|
"PUT",
|
|
`${BASE}/request/${id}/owner-insurer-approval/sign`,
|
|
"Multipart sign + agree + branchId — completes or rejects claim",
|
|
),
|
|
act(
|
|
"OBJECTION",
|
|
"PUT",
|
|
`${BASE}/request/${id}/objection`,
|
|
"Dispute priced lines before signing",
|
|
),
|
|
],
|
|
objectionAllowed: allowObj,
|
|
objectionHint: objectionHintWhen(allowObj),
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
phaseKey: "INSURER_APPROVAL_FALLBACK",
|
|
headline: "Awaiting next step",
|
|
detail:
|
|
"Inspect `status`, `claimStatus`, `workflow.currentStep`/`nextStep` or contact support.",
|
|
nextActions: [],
|
|
objectionAllowed,
|
|
objectionHint,
|
|
};
|
|
}
|