forked from Yara724/api
Fixed bugs
This commit is contained in:
@@ -4880,12 +4880,75 @@ export class ClaimRequestManagementService {
|
|||||||
let allDocumentsUploaded = false;
|
let allDocumentsUploaded = false;
|
||||||
let remaining = 0;
|
let remaining = 0;
|
||||||
|
|
||||||
|
// Will be true when this upload also completes capture-phase docs AND
|
||||||
|
// every angle + every selected damaged part has already been captured.
|
||||||
|
// In that case we advance the workflow exactly like `capturePartV2` does
|
||||||
|
// when the user finishes the last capture — otherwise the user would be
|
||||||
|
// stuck in CAPTURE_PART_DAMAGES with all captures done but no more
|
||||||
|
// captures to upload to trigger the transition.
|
||||||
|
let captureStepCompletedOnThisUpload = false;
|
||||||
|
|
||||||
if (!isResendUpload) {
|
if (!isResendUpload) {
|
||||||
if (isCapturePhaseDocUpload) {
|
if (isCapturePhaseDocUpload) {
|
||||||
const afterThis = (k: string) =>
|
const afterThis = (k: string) =>
|
||||||
k === body.documentKey || this.isRequiredDocumentUploadedOnClaim(claimCase, k);
|
k === body.documentKey || this.isRequiredDocumentUploadedOnClaim(claimCase, k);
|
||||||
remaining = CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS.filter((k) => !afterThis(k)).length;
|
remaining = CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS.filter((k) => !afterThis(k)).length;
|
||||||
allDocumentsUploaded = false;
|
allDocumentsUploaded = false;
|
||||||
|
|
||||||
|
if (remaining === 0) {
|
||||||
|
// After this upload all 3 capture-phase docs will be on the claim.
|
||||||
|
// Check angles + parts captures using the in-memory claim media.
|
||||||
|
const carAnglesData = (claimCase.media as any)?.carAngles;
|
||||||
|
const damagedPartsData = (claimCase.media as any)?.damagedParts;
|
||||||
|
const selectedNorm = normalizeDamageSelectedParts(
|
||||||
|
claimCase.damage?.selectedParts,
|
||||||
|
claimCase.vehicle?.carType as ClaimVehicleTypeV2,
|
||||||
|
(claimCase.damage as any)?.selectedOuterParts,
|
||||||
|
);
|
||||||
|
const anglesKeys = ["front", "back", "left", "right"];
|
||||||
|
const anglesCaptured = anglesKeys.filter((k) =>
|
||||||
|
hasClaimCarAngleCapture(
|
||||||
|
carAnglesData,
|
||||||
|
damagedPartsData,
|
||||||
|
k as ClaimCarAngleKey,
|
||||||
|
),
|
||||||
|
).length;
|
||||||
|
const partsCaptured = selectedNorm.filter((sp) => {
|
||||||
|
const ck = sp.catalogKey ?? catalogLikeKeyFromPart(sp);
|
||||||
|
return hasDamagedPartCapture(damagedPartsData, ck, selectedNorm);
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
if (
|
||||||
|
anglesCaptured >= 4 &&
|
||||||
|
partsCaptured >= selectedNorm.length
|
||||||
|
) {
|
||||||
|
captureStepCompletedOnThisUpload = true;
|
||||||
|
updateData["status"] = ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS;
|
||||||
|
updateData["workflow.currentStep"] =
|
||||||
|
ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS;
|
||||||
|
updateData["workflow.nextStep"] =
|
||||||
|
ClaimWorkflowStep.USER_SUBMISSION_COMPLETE;
|
||||||
|
updateData.$push.history = [
|
||||||
|
updateData.$push.history,
|
||||||
|
{
|
||||||
|
type: "STEP_COMPLETED",
|
||||||
|
actor: {
|
||||||
|
actorId: new Types.ObjectId(currentUserId),
|
||||||
|
actorName: claimCase.owner?.fullName || "User",
|
||||||
|
actorType: "user",
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
metadata: {
|
||||||
|
stepKey: ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||||
|
description:
|
||||||
|
"Angles, damaged parts, and capture-phase vehicle evidence (chassis, engine, metal plate) are complete. Please upload remaining required documents.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
updateData.$push["workflow.completedSteps"] =
|
||||||
|
ClaimWorkflowStep.CAPTURE_PART_DAMAGES;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
allDocumentsUploaded = this.allV2OwnerDocumentsComplete(
|
allDocumentsUploaded = this.allV2OwnerDocumentsComplete(
|
||||||
claimCase,
|
claimCase,
|
||||||
@@ -4965,7 +5028,9 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const message = isCapturePhaseDocUpload
|
const message = isCapturePhaseDocUpload
|
||||||
? remaining > 0
|
? captureStepCompletedOnThisUpload
|
||||||
|
? "Capture-phase documents and all damage captures are complete. Please proceed to upload the remaining required documents."
|
||||||
|
: remaining > 0
|
||||||
? `Document saved. ${remaining} capture-phase document(s) still required (chassis, engine, metal plate) before you can finish damage capture.`
|
? `Document saved. ${remaining} capture-phase document(s) still required (chassis, engine, metal plate) before you can finish damage capture.`
|
||||||
: "Document saved. All capture-phase documents are uploaded; finish car angles and part photos to proceed."
|
: "Document saved. All capture-phase documents are uploaded; finish car angles and part photos to proceed."
|
||||||
: allDocumentsUploaded
|
: allDocumentsUploaded
|
||||||
@@ -4978,7 +5043,9 @@ export class ClaimRequestManagementService {
|
|||||||
fileUrl,
|
fileUrl,
|
||||||
allDocumentsUploaded,
|
allDocumentsUploaded,
|
||||||
currentStep: isCapturePhaseDocUpload
|
currentStep: isCapturePhaseDocUpload
|
||||||
? ClaimWorkflowStep.CAPTURE_PART_DAMAGES
|
? captureStepCompletedOnThisUpload
|
||||||
|
? ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS
|
||||||
|
: ClaimWorkflowStep.CAPTURE_PART_DAMAGES
|
||||||
: allDocumentsUploaded
|
: allDocumentsUploaded
|
||||||
? ClaimWorkflowStep.USER_SUBMISSION_COMPLETE
|
? ClaimWorkflowStep.USER_SUBMISSION_COMPLETE
|
||||||
: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||||
|
|||||||
@@ -2278,14 +2278,22 @@ export class ExpertClaimService {
|
|||||||
*
|
*
|
||||||
* Validations:
|
* Validations:
|
||||||
* - Claim must exist
|
* - Claim must exist
|
||||||
* - Status must be WAITING_FOR_DAMAGE_EXPERT
|
* - Status must be either:
|
||||||
* - Must not already be locked by another expert
|
* • `WAITING_FOR_DAMAGE_EXPERT` (initial damage review queue), OR
|
||||||
|
* • the factor-validation queue (`EXPERT_VALIDATING_REPAIR_FACTORS`,
|
||||||
|
* or legacy `WAITING_FOR_INSURER_APPROVAL`, with
|
||||||
|
* `claimStatus=UNDER_REVIEW` + `workflow.currentStep=EXPERT_COST_EVALUATION`).
|
||||||
|
* - Must not already be locked by another expert.
|
||||||
*
|
*
|
||||||
* On success:
|
* On success:
|
||||||
* - Sets workflow.locked = true, workflow.lockedBy = actor
|
* - Always sets `workflow.locked=true`, `workflow.lockedBy=actor`,
|
||||||
* - Sets status = EXPERT_REVIEWING
|
* `workflow.lockedAt/expiredAt`, and snapshots pre-lock queue state.
|
||||||
* - Sets claimStatus = UNDER_REVIEW
|
* - For the **damage review** path: status → `EXPERT_REVIEWING`,
|
||||||
* - Sets currentStep = EXPERT_DAMAGE_ASSESSMENT
|
* `claimStatus=UNDER_REVIEW`, `workflow.currentStep=EXPERT_DAMAGE_ASSESSMENT`.
|
||||||
|
* - For the **factor-validation** path: leaves `status`, `claimStatus`, and
|
||||||
|
* `workflow.currentStep` untouched so the claim stays in the factor-validation
|
||||||
|
* queue (so `expireClaimWorkflowLockV2IfStale` and the queue endpoint keep
|
||||||
|
* treating it correctly when the lock expires).
|
||||||
*/
|
*/
|
||||||
async lockClaimRequestV2(claimRequestId: string, actor: any) {
|
async lockClaimRequestV2(claimRequestId: string, actor: any) {
|
||||||
requireActorClientKey(actor);
|
requireActorClientKey(actor);
|
||||||
@@ -2298,7 +2306,11 @@ export class ExpertClaimService {
|
|||||||
|
|
||||||
assertClaimCaseForTenant(claim, actor);
|
assertClaimCaseForTenant(claim, actor);
|
||||||
|
|
||||||
if (claim.status !== ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT) {
|
const isFactorValidationLock = claimIsAwaitingExpertFactorValidationV2(claim);
|
||||||
|
const isDamageReviewLock =
|
||||||
|
claim.status === ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
||||||
|
|
||||||
|
if (!isDamageReviewLock && !isFactorValidationLock) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
`Claim is not available for locking. Current status: ${claim.status}`,
|
`Claim is not available for locking. Current status: ${claim.status}`,
|
||||||
);
|
);
|
||||||
@@ -2325,9 +2337,8 @@ export class ExpertClaimService {
|
|||||||
const lockAt = new Date();
|
const lockAt = new Date();
|
||||||
const expiredAt = new Date(lockAt.getTime() + this.claimV2WorkflowLockTtlMs);
|
const expiredAt = new Date(lockAt.getTime() + this.claimV2WorkflowLockTtlMs);
|
||||||
|
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
// Common lock fields written in both branches.
|
||||||
status: ClaimCaseStatus.EXPERT_REVIEWING,
|
const baseLockUpdate: Record<string, unknown> = {
|
||||||
claimStatus: ClaimStatus.UNDER_REVIEW,
|
|
||||||
'workflow.locked': true,
|
'workflow.locked': true,
|
||||||
'workflow.lockedAt': lockAt,
|
'workflow.lockedAt': lockAt,
|
||||||
'workflow.expiredAt': expiredAt,
|
'workflow.expiredAt': expiredAt,
|
||||||
@@ -2352,7 +2363,6 @@ export class ExpertClaimService {
|
|||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
},
|
},
|
||||||
'workflow.currentStep': ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT,
|
|
||||||
$push: {
|
$push: {
|
||||||
history: {
|
history: {
|
||||||
type: "CLAIM_LOCKED",
|
type: "CLAIM_LOCKED",
|
||||||
@@ -2362,10 +2372,28 @@ export class ExpertClaimService {
|
|||||||
actorType: "damage_expert",
|
actorType: "damage_expert",
|
||||||
},
|
},
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
metadata: { note: `Claim locked by damage expert ${actor.fullName}` },
|
metadata: {
|
||||||
|
note: isFactorValidationLock
|
||||||
|
? `Claim locked for factor validation by damage expert ${actor.fullName}`
|
||||||
|
: `Claim locked by damage expert ${actor.fullName}`,
|
||||||
|
...(isFactorValidationLock ? { phase: "factorValidation" } : {}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Damage-review lock advances status/step; factor-validation lock keeps them
|
||||||
|
// intact so the claim remains in the factor-validation queue when the lock expires.
|
||||||
|
const update: Record<string, unknown> = isFactorValidationLock
|
||||||
|
? baseLockUpdate
|
||||||
|
: {
|
||||||
|
...baseLockUpdate,
|
||||||
|
status: ClaimCaseStatus.EXPERT_REVIEWING,
|
||||||
|
claimStatus: ClaimStatus.UNDER_REVIEW,
|
||||||
|
'workflow.currentStep': ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, update);
|
||||||
|
|
||||||
await this.recordClaimExpertActivity({
|
await this.recordClaimExpertActivity({
|
||||||
expertId: String(actor.sub),
|
expertId: String(actor.sub),
|
||||||
|
|||||||
@@ -99,7 +99,10 @@ export class ExpertClaimV2Controller {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Lock a claim request for review",
|
summary: "Lock a claim request for review",
|
||||||
description:
|
description:
|
||||||
"Claim must have status WAITING_FOR_DAMAGE_EXPERT. Locking sets status to EXPERT_REVIEWING and claimStatus to UNDER_REVIEW. Only one expert can lock a claim at a time.",
|
"Lockable in two queues:\n" +
|
||||||
|
"1. **Damage review queue** — claim status `WAITING_FOR_DAMAGE_EXPERT`. Locking advances the claim to `status=EXPERT_REVIEWING`, `claimStatus=UNDER_REVIEW`, `workflow.currentStep=EXPERT_DAMAGE_ASSESSMENT`.\n" +
|
||||||
|
"2. **Factor validation queue** — claim status `EXPERT_VALIDATING_REPAIR_FACTORS` (or legacy `WAITING_FOR_INSURER_APPROVAL`) with `claimStatus=UNDER_REVIEW` and `workflow.currentStep=EXPERT_COST_EVALUATION`. Locking only sets the workflow lock fields; status/claimStatus/currentStep are left untouched so the claim stays in the factor-validation queue when the lock expires.\n\n" +
|
||||||
|
"Only one expert can hold the lock at a time. Re-locking by the same expert is idempotent.",
|
||||||
})
|
})
|
||||||
@ApiParam({ name: "claimRequestId" })
|
@ApiParam({ name: "claimRequestId" })
|
||||||
async lockClaimRequestV2(
|
async lockClaimRequestV2(
|
||||||
|
|||||||
Reference in New Issue
Block a user