forked from Yara724/api
Merge remote-tracking branch 'upstream/main' | Merge upstream into main and reapply local changes
This commit is contained in:
@@ -33,6 +33,11 @@ import { ClaimListDtoRs } from "./dto/claim-list-rs.dto";
|
||||
import { ClaimCaseDbService } from "src/claim-request-management/entites/db-service/claim-case.db.service";
|
||||
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
||||
import { ClaimStatus } from "src/Types&Enums/claim-request-management/claimStatus.enum";
|
||||
import {
|
||||
assertClaimCaseForTenant,
|
||||
claimCaseTouchesClient,
|
||||
requireActorClientKey,
|
||||
} from "src/helpers/tenant-scope";
|
||||
import { ClaimWorkflowStep } from "src/Types&Enums/claim-request-management/claim-workflow-steps.enum";
|
||||
import { GetClaimListV2ResponseDto, ClaimListItemV2Dto } from "./dto/claim-list-v2.dto";
|
||||
import { ClaimDetailV2ResponseDto } from "./dto/claim-detail-v2.dto";
|
||||
@@ -319,8 +324,18 @@ export class ExpertClaimService {
|
||||
await this.claimRequestManagementDbService.findOne(requestId);
|
||||
if (!request) throw new BadRequestException("Claim request not found");
|
||||
|
||||
if (request.lockFile) {
|
||||
throw new BadRequestException("Claim request is locked");
|
||||
const isLocked = !!request.lockFile;
|
||||
const lockedByCurrent =
|
||||
String(request?.actorLocked?.actorId || "") === String(actorDetail.sub);
|
||||
const isLockExpired =
|
||||
!!request.unlockTime && Date.now() >= new Date(request.unlockTime).getTime();
|
||||
|
||||
// Idempotent behavior: same expert can re-enter their locked file.
|
||||
if (isLocked && lockedByCurrent && !isLockExpired) {
|
||||
return { _id: requestId, lock: true, message: "Already locked by you" };
|
||||
}
|
||||
if (isLocked && !lockedByCurrent && !isLockExpired) {
|
||||
throw new BadRequestException("Claim request is locked by another expert");
|
||||
}
|
||||
|
||||
if (request.claimStatus === ReqClaimStatus.UserPending) {
|
||||
@@ -1602,13 +1617,15 @@ export class ExpertClaimService {
|
||||
async validateClaimFactorsV2(
|
||||
claimRequestId: string,
|
||||
body: FactorValidationV2Dto,
|
||||
actor: { sub: string; fullName?: string },
|
||||
actor: { sub: string; fullName?: string; clientKey?: string },
|
||||
) {
|
||||
const claim = await this.claimCaseDbService.findById(claimRequestId);
|
||||
if (!claim) {
|
||||
throw new NotFoundException("Claim request not found");
|
||||
}
|
||||
|
||||
assertClaimCaseForTenant(claim, actor);
|
||||
|
||||
if (
|
||||
claim.status !== ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL ||
|
||||
claim.claimStatus !== ClaimStatus.UNDER_REVIEW ||
|
||||
@@ -1842,12 +1859,15 @@ export class ExpertClaimService {
|
||||
* - Sets currentStep = EXPERT_DAMAGE_ASSESSMENT
|
||||
*/
|
||||
async lockClaimRequestV2(claimRequestId: string, actor: any) {
|
||||
requireActorClientKey(actor);
|
||||
const claim = await this.claimCaseDbService.findById(claimRequestId);
|
||||
|
||||
if (!claim) {
|
||||
throw new NotFoundException('Claim request not found');
|
||||
}
|
||||
|
||||
assertClaimCaseForTenant(claim, actor);
|
||||
|
||||
if (claim.status !== ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT) {
|
||||
throw new BadRequestException(
|
||||
`Claim is not available for locking. Current status: ${claim.status}`,
|
||||
@@ -1913,6 +1933,8 @@ export class ExpertClaimService {
|
||||
throw new NotFoundException('Claim request not found');
|
||||
}
|
||||
|
||||
assertClaimCaseForTenant(claim, actor);
|
||||
|
||||
if (claim.status !== ClaimCaseStatus.EXPERT_REVIEWING) {
|
||||
throw new BadRequestException(
|
||||
`Claim is not in EXPERT_REVIEWING state. Current status: ${claim.status}`,
|
||||
@@ -1994,12 +2016,15 @@ export class ExpertClaimService {
|
||||
reply: import('./dto/expert-claim-v2.dto').SubmitExpertReplyV2Dto,
|
||||
actor: any,
|
||||
) {
|
||||
requireActorClientKey(actor);
|
||||
const claim = await this.claimCaseDbService.findById(claimRequestId);
|
||||
|
||||
if (!claim) {
|
||||
throw new NotFoundException('Claim request not found');
|
||||
}
|
||||
|
||||
assertClaimCaseForTenant(claim, actor);
|
||||
|
||||
if (claim.status !== ClaimCaseStatus.EXPERT_REVIEWING) {
|
||||
throw new BadRequestException(
|
||||
`Claim is not in a reviewable state. Current status: ${claim.status}`,
|
||||
@@ -2131,12 +2156,15 @@ export class ExpertClaimService {
|
||||
* - Unlocks the workflow so user can act
|
||||
*/
|
||||
async requestInPersonVisitV2(claimRequestId: string, actor: any, note?: string) {
|
||||
requireActorClientKey(actor);
|
||||
const claim = await this.claimCaseDbService.findById(claimRequestId);
|
||||
|
||||
if (!claim) {
|
||||
throw new NotFoundException('Claim request not found');
|
||||
}
|
||||
|
||||
assertClaimCaseForTenant(claim, actor);
|
||||
|
||||
if (claim.status !== ClaimCaseStatus.EXPERT_REVIEWING) {
|
||||
throw new BadRequestException(
|
||||
`Claim is not in EXPERT_REVIEWING state. Current status: ${claim.status}`,
|
||||
@@ -2179,12 +2207,15 @@ export class ExpertClaimService {
|
||||
/**
|
||||
* V2: Get claim list for damage expert
|
||||
*
|
||||
* Returns claims that are:
|
||||
* 1. WAITING_FOR_DAMAGE_EXPERT status AND not locked (available for anyone to pick)
|
||||
* 2. WAITING_FOR_DAMAGE_EXPERT status AND not locked (claimStatus PENDING)
|
||||
* 3. Any status locked by THIS expert (their own in-progress work)
|
||||
* Returns claims that are (then filtered to the actor's insurer via `claimCaseTouchesClient`):
|
||||
* 1. WAITING_FOR_DAMAGE_EXPERT and not locked (open queue)
|
||||
* 2. Locked by this expert (in-progress)
|
||||
* 3. WAITING_FOR_INSURER_APPROVAL + UNDER_REVIEW at EXPERT_COST_EVALUATION (factor validation queue)
|
||||
*/
|
||||
async getClaimListV2(actorId: string): Promise<GetClaimListV2ResponseDto> {
|
||||
async getClaimListV2(actor: any): Promise<GetClaimListV2ResponseDto> {
|
||||
requireActorClientKey(actor);
|
||||
const actorId = actor.sub;
|
||||
const clientKey = actor.clientKey as string;
|
||||
const claims = await this.claimCaseDbService.find({
|
||||
$or: [
|
||||
// Available claims: waiting for expert, not locked
|
||||
@@ -2206,34 +2237,36 @@ export class ExpertClaimService {
|
||||
],
|
||||
});
|
||||
|
||||
const list = (claims as any[]).map((c) => {
|
||||
const awaitingFactorValidation =
|
||||
c.status === ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL &&
|
||||
c.claimStatus === ClaimStatus.UNDER_REVIEW &&
|
||||
c.workflow?.currentStep === ClaimWorkflowStep.EXPERT_COST_EVALUATION;
|
||||
return {
|
||||
claimRequestId: c._id.toString(),
|
||||
publicId: c.publicId,
|
||||
status: c.status,
|
||||
currentStep: c.workflow?.currentStep || '',
|
||||
locked: c.workflow?.locked || false,
|
||||
lockedBy: c.workflow?.lockedBy
|
||||
? {
|
||||
actorId: c.workflow.lockedBy.actorId?.toString(),
|
||||
actorName: c.workflow.lockedBy.actorName,
|
||||
}
|
||||
: undefined,
|
||||
vehicle: c.vehicle
|
||||
? {
|
||||
carName: c.vehicle.carName,
|
||||
carModel: c.vehicle.carModel,
|
||||
carType: c.vehicle.carType,
|
||||
}
|
||||
: undefined,
|
||||
createdAt: c.createdAt,
|
||||
awaitingFactorValidation,
|
||||
};
|
||||
}) as ClaimListItemV2Dto[];
|
||||
const list = (claims as any[])
|
||||
.filter((c) => claimCaseTouchesClient(c, clientKey))
|
||||
.map((c) => {
|
||||
const awaitingFactorValidation =
|
||||
c.status === ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL &&
|
||||
c.claimStatus === ClaimStatus.UNDER_REVIEW &&
|
||||
c.workflow?.currentStep === ClaimWorkflowStep.EXPERT_COST_EVALUATION;
|
||||
return {
|
||||
claimRequestId: c._id.toString(),
|
||||
publicId: c.publicId,
|
||||
status: c.status,
|
||||
currentStep: c.workflow?.currentStep || '',
|
||||
locked: c.workflow?.locked || false,
|
||||
lockedBy: c.workflow?.lockedBy
|
||||
? {
|
||||
actorId: c.workflow.lockedBy.actorId?.toString(),
|
||||
actorName: c.workflow.lockedBy.actorName,
|
||||
}
|
||||
: undefined,
|
||||
vehicle: c.vehicle
|
||||
? {
|
||||
carName: c.vehicle.carName,
|
||||
carModel: c.vehicle.carModel,
|
||||
carType: c.vehicle.carType,
|
||||
}
|
||||
: undefined,
|
||||
createdAt: c.createdAt,
|
||||
awaitingFactorValidation,
|
||||
};
|
||||
}) as ClaimListItemV2Dto[];
|
||||
|
||||
return { list, total: list.length };
|
||||
}
|
||||
@@ -2242,20 +2275,23 @@ export class ExpertClaimService {
|
||||
* V2: Get claim detail for damage expert
|
||||
*
|
||||
* Validations:
|
||||
* - Claim must exist
|
||||
* - Claim status must be WAITING_FOR_DAMAGE_EXPERT
|
||||
* - If claim is locked, only the locking expert can access it
|
||||
* - Claim must exist and belong to the actor's insurer (`assertClaimCaseForTenant`)
|
||||
* - Allowed when picking up/reviewing: WAITING_FOR_DAMAGE_EXPERT (if locked, only the locking expert)
|
||||
* - Or when validating factors: WAITING_FOR_INSURER_APPROVAL + UNDER_REVIEW at EXPERT_COST_EVALUATION
|
||||
*/
|
||||
async getClaimDetailV2(
|
||||
claimRequestId: string,
|
||||
actorId: string,
|
||||
actor: any,
|
||||
): Promise<ClaimDetailV2ResponseDto> {
|
||||
const actorId = actor.sub;
|
||||
const claim = await this.claimCaseDbService.findById(claimRequestId);
|
||||
|
||||
if (!claim) {
|
||||
throw new NotFoundException('Claim request not found');
|
||||
}
|
||||
|
||||
assertClaimCaseForTenant(claim, actor);
|
||||
|
||||
const isPickupReview =
|
||||
claim.status === ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
||||
const isFactorValidationPending =
|
||||
@@ -2377,10 +2413,12 @@ export class ExpertClaimService {
|
||||
body: UpdateClaimDamagedPartsV2Dto,
|
||||
actor: any,
|
||||
) {
|
||||
requireActorClientKey(actor);
|
||||
const claim = await this.claimCaseDbService.findById(claimRequestId);
|
||||
if (!claim) {
|
||||
throw new NotFoundException("Claim request not found");
|
||||
}
|
||||
assertClaimCaseForTenant(claim, actor);
|
||||
if (claim.status !== ClaimCaseStatus.EXPERT_REVIEWING) {
|
||||
throw new BadRequestException(
|
||||
`Claim is not in EXPERT_REVIEWING state. Current status: ${claim.status}`,
|
||||
|
||||
@@ -41,7 +41,7 @@ export class ExpertClaimV2Controller {
|
||||
"Returns claims that are WAITING_FOR_DAMAGE_EXPERT and not locked by another expert, this expert's locked/in-progress claims, and claims awaiting factor validation (UNDER_REVIEW at EXPERT_COST_EVALUATION).",
|
||||
})
|
||||
async getClaimListV2(@CurrentUser() actor) {
|
||||
return await this.expertClaimService.getClaimListV2(actor.sub);
|
||||
return await this.expertClaimService.getClaimListV2(actor);
|
||||
}
|
||||
|
||||
@Get("request/:claimRequestId")
|
||||
@@ -55,10 +55,7 @@ export class ExpertClaimV2Controller {
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@CurrentUser() actor,
|
||||
) {
|
||||
return await this.expertClaimService.getClaimDetailV2(
|
||||
claimRequestId,
|
||||
actor.sub,
|
||||
);
|
||||
return await this.expertClaimService.getClaimDetailV2(claimRequestId, actor);
|
||||
}
|
||||
|
||||
@Put("lock/:claimRequestId")
|
||||
|
||||
Reference in New Issue
Block a user