From cc926d466826e4125c684a4bdf57cfd06e23a765 Mon Sep 17 00:00:00 2001 From: SepehrYahyaee <7heycallmegray@gmail.com> Date: Sun, 10 May 2026 14:29:32 +0330 Subject: [PATCH 1/2] deprecated some old APIs + added examples for login --- .../actor/actor.auth.controller.ts | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/auth/auth-controllers/actor/actor.auth.controller.ts b/src/auth/auth-controllers/actor/actor.auth.controller.ts index 04d020c..dd83ea8 100644 --- a/src/auth/auth-controllers/actor/actor.auth.controller.ts +++ b/src/auth/auth-controllers/actor/actor.auth.controller.ts @@ -11,6 +11,7 @@ import { import { ApiBody, ApiAcceptedResponse, + ApiOperation, ApiResponse, ApiTags, ApiBearerAuth, @@ -39,13 +40,33 @@ import { CurrentUser } from "src/decorators/user.decorator"; export class ActorAuthController { constructor(private readonly actorAuthService: ActorAuthService) {} + /** + * @deprecated Use the unified actor onboarding flow instead. This endpoint + * will be removed in a future release. + */ @Post("register/genuine") + @ApiOperation({ + deprecated: true, + summary: "[DEPRECATED] Genuine actor registration", + description: + "Deprecated — kept only for legacy clients. Use the unified actor onboarding flow instead. Will be removed.", + }) @ApiBody({ type: GenuineRegisterDto }) async registerGenuine(@Body() body: GenuineRegisterDto) { return await this.actorAuthService.genuineRegister(body); } + /** + * @deprecated Use the unified actor onboarding flow instead. This endpoint + * will be removed in a future release. + */ @Post("register/legal") + @ApiOperation({ + deprecated: true, + summary: "[DEPRECATED] Legal actor registration", + description: + "Deprecated — kept only for legacy clients. Use the unified actor onboarding flow instead. Will be removed.", + }) @ApiBody({ type: LegalRegisterDto }) async registerLegal(@Body() body: LegalRegisterDto) { return await this.actorAuthService.legalRegister(body); @@ -76,9 +97,44 @@ export class ActorAuthController { @UseGuards(LocalActorAuthGuard) @Post("login") @Roles() + @ApiOperation({ + summary: "Actor login (returns access + refresh tokens)", + description: + "Authenticate any non-end-user actor (insurer/company, blame expert, damage expert, registrar, field expert, admin). Submit `role` as an array — e.g. `[\"damage_expert\"]` — together with the actor's email/`username` and password. On success the response contains the JWT pair and the resolved profile.", + }) @ApiBody({ type: LoginActorDto, - description: "user verify otp -- call this api and get a tokens", + description: + "Login payload. Pick one of the swagger examples below to see the exact body shape per role.", + examples: { + company: { + summary: "Insurer / company portal", + description: "Sample tenant credentials for the insurer (company) panel.", + value: { + role: "company", + username: "saman_insurer@gmail.com", + password: "123321", + }, + }, + expert: { + summary: "Blame expert panel", + description: "Sample credentials for a blame (`expert`) account.", + value: { + role: "expert", + username: "blame@gmail.com", + password: "123321", + }, + }, + damage_expert: { + summary: "Damage expert (claim) panel", + description: "Sample credentials for a damage-expert account.", + value: { + role: "damage_expert", + username: "claim@gmail.com", + password: "123321", + }, + }, + }, }) @ApiAcceptedResponse() async login(@Body() body, @Req() req, @ClientKey() client) { From 010846acd94e2c5d07051154e7e37fd6f09c8359 Mon Sep 17 00:00:00 2001 From: SepehrYahyaee <7heycallmegray@gmail.com> Date: Sun, 10 May 2026 17:00:41 +0330 Subject: [PATCH 2/2] Fixed bugs --- .../claim-request-management.service.ts | 75 ++++++++++++++++++- src/expert-claim/expert-claim.service.ts | 54 +++++++++---- .../expert-claim.v2.controller.ts | 5 +- 3 files changed, 116 insertions(+), 18 deletions(-) diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index 674ea8a..670b9f5 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -4880,12 +4880,75 @@ export class ClaimRequestManagementService { let allDocumentsUploaded = false; 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 (isCapturePhaseDocUpload) { const afterThis = (k: string) => k === body.documentKey || this.isRequiredDocumentUploadedOnClaim(claimCase, k); remaining = CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS.filter((k) => !afterThis(k)).length; 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 { allDocumentsUploaded = this.allV2OwnerDocumentsComplete( claimCase, @@ -4965,9 +5028,11 @@ export class ClaimRequestManagementService { } const message = isCapturePhaseDocUpload - ? remaining > 0 - ? `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." + ? 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. All capture-phase documents are uploaded; finish car angles and part photos to proceed." : allDocumentsUploaded ? "All documents uploaded successfully. Your claim is now ready for damage expert review." : `Document uploaded successfully. ${remaining} documents remaining.`; @@ -4978,7 +5043,9 @@ export class ClaimRequestManagementService { fileUrl, allDocumentsUploaded, currentStep: isCapturePhaseDocUpload - ? ClaimWorkflowStep.CAPTURE_PART_DAMAGES + ? captureStepCompletedOnThisUpload + ? ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS + : ClaimWorkflowStep.CAPTURE_PART_DAMAGES : allDocumentsUploaded ? ClaimWorkflowStep.USER_SUBMISSION_COMPLETE : ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS, diff --git a/src/expert-claim/expert-claim.service.ts b/src/expert-claim/expert-claim.service.ts index bc4c707..1a6ef96 100644 --- a/src/expert-claim/expert-claim.service.ts +++ b/src/expert-claim/expert-claim.service.ts @@ -2278,14 +2278,22 @@ export class ExpertClaimService { * * Validations: * - Claim must exist - * - Status must be WAITING_FOR_DAMAGE_EXPERT - * - Must not already be locked by another expert + * - Status must be either: + * • `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: - * - Sets workflow.locked = true, workflow.lockedBy = actor - * - Sets status = EXPERT_REVIEWING - * - Sets claimStatus = UNDER_REVIEW - * - Sets currentStep = EXPERT_DAMAGE_ASSESSMENT + * - Always sets `workflow.locked=true`, `workflow.lockedBy=actor`, + * `workflow.lockedAt/expiredAt`, and snapshots pre-lock queue state. + * - For the **damage review** path: status → `EXPERT_REVIEWING`, + * `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) { requireActorClientKey(actor); @@ -2298,7 +2306,11 @@ export class ExpertClaimService { 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( `Claim is not available for locking. Current status: ${claim.status}`, ); @@ -2325,9 +2337,8 @@ export class ExpertClaimService { const lockAt = new Date(); const expiredAt = new Date(lockAt.getTime() + this.claimV2WorkflowLockTtlMs); - await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, { - status: ClaimCaseStatus.EXPERT_REVIEWING, - claimStatus: ClaimStatus.UNDER_REVIEW, + // Common lock fields written in both branches. + const baseLockUpdate: Record = { 'workflow.locked': true, 'workflow.lockedAt': lockAt, 'workflow.expiredAt': expiredAt, @@ -2352,7 +2363,6 @@ export class ExpertClaimService { } : {}), }, - 'workflow.currentStep': ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT, $push: { history: { type: "CLAIM_LOCKED", @@ -2362,10 +2372,28 @@ export class ExpertClaimService { actorType: "damage_expert", }, 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 = 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({ expertId: String(actor.sub), diff --git a/src/expert-claim/expert-claim.v2.controller.ts b/src/expert-claim/expert-claim.v2.controller.ts index eabc057..04c46a0 100644 --- a/src/expert-claim/expert-claim.v2.controller.ts +++ b/src/expert-claim/expert-claim.v2.controller.ts @@ -99,7 +99,10 @@ export class ExpertClaimV2Controller { @ApiOperation({ summary: "Lock a claim request for review", 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" }) async lockClaimRequestV2(