diff --git a/src/expert-claim/expert-claim.service.ts b/src/expert-claim/expert-claim.service.ts index d6d9c8f..d1c3b3a 100644 --- a/src/expert-claim/expert-claim.service.ts +++ b/src/expert-claim/expert-claim.service.ts @@ -245,6 +245,24 @@ export class ExpertClaimService { return String(actor.clientKey ?? ""); } + /** + * `preLockQueueSnapshot` must only store {@link ClaimWorkflowStep} values. + * Legacy / mistaken data sometimes put {@link ClaimCaseStatus} (e.g. WAITING_FOR_DAMAGE_EXPERT) + * in `workflow.currentStep`, which would fail Mongoose enum validation when snapshotted. + */ + private normalizeClaimWorkflowStepForSnapshot( + value: unknown, + fallback: ClaimWorkflowStep, + ): ClaimWorkflowStep { + if ( + typeof value === "string" && + (Object.values(ClaimWorkflowStep) as string[]).includes(value) + ) { + return value as ClaimWorkflowStep; + } + return fallback; + } + /** Owner mobile: linked blame party phone when available, else `users.mobile`. */ private async resolveClaimOwnerPhone(claim: any): Promise { if (!claim?.owner?.userId) return undefined; @@ -2226,20 +2244,30 @@ export class ExpertClaimService { }, 'workflow.preLockQueueSnapshot': { claimStatus: claim.claimStatus, - currentStep: - claim.workflow?.currentStep ?? ClaimWorkflowStep.USER_SUBMISSION_COMPLETE, + currentStep: this.normalizeClaimWorkflowStepForSnapshot( + claim.workflow?.currentStep, + ClaimWorkflowStep.USER_SUBMISSION_COMPLETE, + ), ...(claim.workflow?.nextStep != null - ? { nextStep: claim.workflow.nextStep } + ? { + nextStep: this.normalizeClaimWorkflowStepForSnapshot( + claim.workflow.nextStep, + ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT, + ), + } : {}), }, 'workflow.currentStep': ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT, $push: { history: { - event: 'CLAIM_LOCKED', - performedBy: actor.sub, - performedByName: actor.fullName, - performedAt: new Date(), - note: `Claim locked by damage expert ${actor.fullName}`, + type: "CLAIM_LOCKED", + actor: { + actorId: new Types.ObjectId(actor.sub), + actorName: actor.fullName, + actorType: "damage_expert", + }, + timestamp: new Date(), + metadata: { note: `Claim locked by damage expert ${actor.fullName}` }, }, }, }); @@ -2657,11 +2685,16 @@ export class ExpertClaimService { }), $push: { history: { - event: 'IN_PERSON_VISIT_REQUESTED', - performedBy: actor.sub, - performedByName: actor.fullName, - performedAt: new Date(), - note: note || 'Expert requested in-person visit', + type: "IN_PERSON_VISIT_REQUESTED", + actor: { + actorId: new Types.ObjectId(actor.sub), + actorName: actor.fullName, + actorType: "damage_expert", + }, + timestamp: new Date(), + metadata: { + note: note || "Expert requested in-person visit", + }, }, }, }); @@ -2678,7 +2711,7 @@ export class ExpertClaimService { claimRequestId, status: claim.status, claimStatus: ClaimStatus.NEEDS_REVISION, - message: 'In-person visit requested. User will be notified.', + message: "In-person visit requested. User will be notified.", }; } @@ -3106,11 +3139,16 @@ export class ExpertClaimService { | undefined; const restoreClaimStatus = snap?.claimStatus ?? ClaimStatus.PENDING; - const restoreCurrent = - snap?.currentStep ?? ClaimWorkflowStep.USER_SUBMISSION_COMPLETE; + const restoreCurrent = this.normalizeClaimWorkflowStepForSnapshot( + snap?.currentStep, + ClaimWorkflowStep.USER_SUBMISSION_COMPLETE, + ); const restoreNext = snap?.nextStep != null - ? snap.nextStep + ? this.normalizeClaimWorkflowStepForSnapshot( + snap.nextStep, + ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT, + ) : ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT; const lockFieldsUnset = { @@ -3455,42 +3493,52 @@ export class ExpertClaimService { : []; const selectedParts = body.selectedParts as string[]; - if (!(claim as any).damage) (claim as any).damage = {}; - (claim as any).damage.selectedParts = selectedParts; + const $set: Record = { + "damage.selectedParts": selectedParts, + }; - // Keep damaged-parts captures consistent with updated selection. const damagedPartsMedia = (claim as any).media?.damagedParts; if (damagedPartsMedia) { const selectedSet = new Set(selectedParts); if (damagedPartsMedia instanceof Map) { - for (const key of Array.from(damagedPartsMedia.keys())) { - if (!selectedSet.has(key)) damagedPartsMedia.delete(key); + const m = new Map(damagedPartsMedia as Map); + for (const key of Array.from(m.keys())) { + if (!selectedSet.has(key)) m.delete(key); } + $set["media.damagedParts"] = Object.fromEntries(m.entries()); } else { - for (const key of Object.keys(damagedPartsMedia)) { - if (!selectedSet.has(key)) delete damagedPartsMedia[key]; + const o = { + ...(damagedPartsMedia as Record), + }; + for (const key of Object.keys(o)) { + if (!selectedSet.has(key)) delete o[key]; } + $set["media.damagedParts"] = o; } } - if (!Array.isArray((claim as any).history)) (claim as any).history = []; - (claim as any).history.push({ - event: "EXPERT_DAMAGED_PARTS_UPDATED", - performedBy: actor.sub, - performedByName: actor.fullName, - performedAt: new Date(), - note: "Damage expert edited selected damaged parts", - metadata: { - previousSelectedParts: previous, - selectedParts, - ...(damagedPartsEditSnapshot && { - expertProfileSnapshot: damagedPartsEditSnapshot, - }), + await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, { + $set, + $push: { + history: { + type: "EXPERT_DAMAGED_PARTS_UPDATED", + actor: { + actorId: new Types.ObjectId(actor.sub), + actorName: actor.fullName, + actorType: "damage_expert", + }, + timestamp: new Date(), + metadata: { + previousSelectedParts: previous, + selectedParts, + ...(damagedPartsEditSnapshot && { + expertProfileSnapshot: damagedPartsEditSnapshot, + }), + }, + }, }, }); - await (claim as any).save(); - return { claimRequestId: claim._id.toString(), selectedParts,