1
0
forked from Yara724/api

Compare commits

...

2 Commits

Author SHA1 Message Date
SepehrYahyaee
010846acd9 Fixed bugs 2026-05-10 17:00:41 +03:30
SepehrYahyaee
cc926d4668 deprecated some old APIs + added examples for login 2026-05-10 14:29:32 +03:30
4 changed files with 173 additions and 19 deletions

View File

@@ -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) {

View File

@@ -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,7 +5028,9 @@ export class ClaimRequestManagementService {
}
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. All capture-phase documents are uploaded; finish car angles and part photos to proceed."
: allDocumentsUploaded
@@ -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,

View File

@@ -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<string, unknown> = {
'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<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({
expertId: String(actor.sub),

View File

@@ -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(