This commit is contained in:
2026-05-01 11:37:24 +03:30
parent 6f120b0066
commit 6429cb0f2b

View File

@@ -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<string | undefined> {
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,31 +3493,41 @@ 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<string, unknown> = {
"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<string, unknown>);
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<string, unknown>),
};
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",
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,
@@ -3487,10 +3535,10 @@ export class ExpertClaimService {
expertProfileSnapshot: damagedPartsEditSnapshot,
}),
},
},
},
});
await (claim as any).save();
return {
claimRequestId: claim._id.toString(),
selectedParts,