diff --git a/src/expert-claim/dto/claim-detail-v2.dto.ts b/src/expert-claim/dto/claim-detail-v2.dto.ts index a2bd5e5..c6b0e59 100644 --- a/src/expert-claim/dto/claim-detail-v2.dto.ts +++ b/src/expert-claim/dto/claim-detail-v2.dto.ts @@ -134,13 +134,16 @@ export class ClaimDetailV2ResponseDto { @ApiPropertyOptional({ description: "Slice of `claim.evaluation` exposed to the damage expert. " + - "`damageExpertReply` / `damageExpertReplyFinal` are returned only while " + - "awaiting factor validation. `ownerInsurerApproval` and " + - "`ownerPricedPartsApproval` are returned whenever they exist on the " + - "claim — each carries `signLink` (resolved from `signDetailId`) so the " + - "front-end can render the user signature directly. Likewise, " + - "`damageExpertReply.userComment` / `damageExpertReplyFinal.userComment` " + - "expose `signLink` when a user comment signature is present.", + "`damageExpertReply` / `damageExpertReplyFinal` are returned whenever " + + "present, regardless of the current claim status — historical assessment " + + "data remains visible once the expert has submitted it. " + + "`ownerInsurerApproval` and `ownerPricedPartsApproval` are returned " + + "whenever they exist on the claim — each carries `signLink` (resolved " + + "from `signDetailId`) so the front-end can render the user signature " + + "directly. Likewise, `damageExpertReply.userComment` / " + + "`damageExpertReplyFinal.userComment` expose `signLink` when a user " + + "comment signature is present. `priceDrop` is included whenever it " + + "has been saved, not only during the expert review phase.", }) evaluation?: { damageExpertReply?: unknown; diff --git a/src/expert-claim/expert-claim.service.ts b/src/expert-claim/expert-claim.service.ts index 72d780c..7681b88 100644 --- a/src/expert-claim/expert-claim.service.ts +++ b/src/expert-claim/expert-claim.service.ts @@ -541,6 +541,23 @@ export class ExpertClaimService { return this.expertVehicleFromPartyVehicle(party?.vehicle); } + /** Extract car name / model from the inquiry snapshot embedded on the claim. */ + private vehicleNamesFromClaimInquiries(claim: any): { + carName?: string; + carModel?: string; + } { + // Claim copies inquiries from blame at creation time. + // Try thirdParty first, then carBody. + const mapped = + claim?.inquiries?.thirdParty?.data?.FIRST?.mapped ?? + claim?.inquiries?.carBody?.data?.FIRST?.mapped ?? + {}; + const carName: string | undefined = + mapped.MapTypNam || mapped.SystemField || undefined; + const carModel: string | undefined = mapped.TypeField || undefined; + return { carName, carModel }; + } + private vehicleForExpertFromClaimAndBlameMap( claim: any, blameById: Map, @@ -554,10 +571,24 @@ export class ExpertClaimService { | undefined { const cv = claim?.vehicle; if (cv && (cv.carName || cv.carModel || cv.carType || (cv as any).plate)) { + const carType: string | undefined = cv.carType; + // carName/carModel that match the enum value (e.g. "sedan") are invalid — + // the expert may have accidentally copied the carType value into those fields. + // Fall back to the inquiry snapshot for the real brand / variant strings. + let carName: string | undefined = cv.carName; + let carModel: string | undefined = cv.carModel; + if ( + (!carName || carName === carType) || + (!carModel || carModel === carType) + ) { + const fromInquiry = this.vehicleNamesFromClaimInquiries(claim); + if (!carName || carName === carType) carName = fromInquiry.carName; + if (!carModel || carModel === carType) carModel = fromInquiry.carModel; + } return { - carName: cv.carName, - carModel: cv.carModel, - carType: cv.carType, + carName, + carModel, + carType, ...((cv as any).plate ? { plate: (cv as any).plate } : {}), }; } @@ -4801,6 +4832,24 @@ export class ExpertClaimService { if (fromBlame) vehiclePayload = fromBlame; } + // Patch up carName/carModel when they equal carType (invalid — expert copied the enum value). + if (vehiclePayload) { + const carType: string | undefined = vehiclePayload.carType; + if ( + carType && + ((!vehiclePayload.carName || vehiclePayload.carName === carType) || + (!vehiclePayload.carModel || vehiclePayload.carModel === carType)) + ) { + const fromInquiry = this.vehicleNamesFromClaimInquiries(claim); + if (!vehiclePayload.carName || vehiclePayload.carName === carType) { + vehiclePayload = { ...vehiclePayload, carName: fromInquiry.carName }; + } + if (!vehiclePayload.carModel || vehiclePayload.carModel === carType) { + vehiclePayload = { ...vehiclePayload, carModel: fromInquiry.carModel }; + } + } + } + const blameFileContext = blameLean ? this.blameFileContextForExpert(blameLean) : {}; @@ -4853,15 +4902,18 @@ export class ExpertClaimService { let evaluationForApi: Record | undefined; if (enrichedEvaluation) { evaluationForApi = {}; - if (isFactorValidationPending) { - if (enrichedEvaluation.damageExpertReply !== undefined) { - evaluationForApi.damageExpertReply = - enrichedEvaluation.damageExpertReply; - } - if (enrichedEvaluation.damageExpertReplyFinal !== undefined) { - evaluationForApi.damageExpertReplyFinal = - enrichedEvaluation.damageExpertReplyFinal; - } + // damageExpertReply / damageExpertReplyFinal: include whenever present. + // Previously these were gated on isFactorValidationPending, which hid the + // expert assessment once the claim moved to user-side statuses such as + // INSURER_REVIEW_AWAITING_OWNER_SIGN. Historical assessment data should + // always be visible once submitted. + if (enrichedEvaluation.damageExpertReply !== undefined) { + evaluationForApi.damageExpertReply = + enrichedEvaluation.damageExpertReply; + } + if (enrichedEvaluation.damageExpertReplyFinal !== undefined) { + evaluationForApi.damageExpertReplyFinal = + enrichedEvaluation.damageExpertReplyFinal; } if (enrichedEvaluation.ownerInsurerApproval !== undefined) { evaluationForApi.ownerInsurerApproval = @@ -4871,7 +4923,8 @@ export class ExpertClaimService { evaluationForApi.ownerPricedPartsApproval = enrichedEvaluation.ownerPricedPartsApproval; } - if (isDamageExpertPhase && enrichedEvaluation.priceDrop !== undefined) { + // priceDrop: include whenever present, not only during the expert phase. + if (enrichedEvaluation.priceDrop !== undefined) { evaluationForApi.priceDrop = enrichedEvaluation.priceDrop; } if (Object.keys(evaluationForApi).length === 0) { diff --git a/src/expert-claim/expert-claim.v2.controller.ts b/src/expert-claim/expert-claim.v2.controller.ts index 44e6f3e..7ddc4a0 100644 --- a/src/expert-claim/expert-claim.v2.controller.ts +++ b/src/expert-claim/expert-claim.v2.controller.ts @@ -153,7 +153,7 @@ export class ExpertClaimV2Controller { @ApiOperation({ summary: "Get claim request detail for damage expert", description: - "Returns full claim details including captured images, required documents, damage selections, `evaluation.priceDrop` during damage review, `videoCapture` (from claim-video-capture via media.videoCaptureId), and `blameCase` (linked blameCases document with party video/voice URLs like expert-blame detail). Allowed when status is WAITING_FOR_DAMAGE_EXPERT (if locked, only the locking expert) or when awaiting factor validation.", + "Returns full claim details including captured images, required documents, damage selections, `evaluation.priceDrop` (included whenever saved, regardless of current status), `videoCapture` (from claim-video-capture via media.videoCaptureId), and `blameCase` (linked blameCases document with party video/voice URLs like expert-blame detail). `evaluation.damageExpertReply` / `damageExpertReplyFinal` are always returned once submitted. Allowed when status is WAITING_FOR_DAMAGE_EXPERT (if locked, only the locking expert) or when awaiting factor validation.", }) @ApiParam({ name: "claimRequestId" }) async getClaimDetailV2(