1
0
forked from Yara724/api

update the expert claim dto

This commit is contained in:
Soheil Hajizadeh
2026-04-11 09:46:54 +03:30
parent 25958bb966
commit 494e3d93ab
3 changed files with 166 additions and 44 deletions

View File

@@ -8,6 +8,8 @@ import {
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
import { ClaimRequiredDocumentType } from 'src/Types&Enums/claim-request-management/required-document-type.enum';
import { DamagedPartItem } from 'src/claim-request-management/dto/capture-requirements-v2.dto';
export class CarPartDamageV2Dto {
@ApiProperty({ example: 'left' })
@@ -68,3 +70,14 @@ export class SubmitExpertReplyV2Dto {
@Type(() => PartPricingV2Dto)
parts: PartPricingV2Dto[];
}
export class ClaimSubmitResendV2Dto {
@ApiProperty({ required: true })
resendDescription: string;
@ApiProperty({ required: true, examples: ClaimRequiredDocumentType })
resendDocuments: ClaimRequiredDocumentType[];
@ApiProperty({ required: true, type: [DamagedPartItem] })
resendCarParts: DamagedPartItem[];
}

View File

@@ -42,6 +42,8 @@ import { ClaimRequiredDocumentDbService } from "src/claim-request-management/ent
import { FactorStatus } from "src/Types&Enums/claim-request-management/factor-status.enum";
import { DaghiOption } from "src/Types&Enums/claim-request-management/daghi-option.enum";
import { BranchDbService } from "src/client/entities/db-service/branch.db.service";
import { ClaimSubmitResendV2Dto } from "./dto/expert-claim-v2.dto";
import { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service";
@Injectable()
export class ExpertClaimService {
@@ -154,13 +156,14 @@ export class ExpertClaimService {
private readonly claimVideoCaptureDbService: VideoCaptureDbService,
private readonly httpService: HttpService,
private readonly claimCaseDbService: ClaimCaseDbService,
private readonly blameRequestDbService: BlameRequestDbService,
private readonly sandHubService: SandHubService,
private readonly damageExpertDbService: DamageExpertDbService,
private readonly clientDbService: ClientDbService,
private readonly claimFactorsImageDbService: ClaimFactorsImageDbService,
private readonly claimRequiredDocumentDbService: ClaimRequiredDocumentDbService,
private readonly branchDbService: BranchDbService,
) {}
) { }
private isVisibleToClientType(client: any, actor: any): boolean {
if (actor.userType === UserType.GENUINE) {
@@ -474,7 +477,7 @@ export class ExpertClaimService {
async calculatePriceDrop(request: any, carPartsAi: any, query?: any) {
const requestId = request?._id?.toString() || 'unknown';
try {
// Step 1: Check SandHub data
if (!request?.blameFile?.sanHubId) {
@@ -532,7 +535,7 @@ export class ExpertClaimService {
} else if (severityValues.length > 0) {
// Only fetch car prices if we have severity values to calculate
const carPrices = await this.fetchCarPrices();
if (!carPrices || carPrices.length === 0) {
this.logger.error(
`[PriceDrop] Request ${requestId}: Failed to fetch car prices or car prices list is empty. Cannot find matching car.`,
@@ -542,9 +545,9 @@ export class ExpertClaimService {
`[PriceDrop] Request ${requestId}: Fetched ${carPrices.length} car prices. Searching for: "${request.carDetail.carName}"`,
);
const closestCar = this.findClosestCar(
request.carDetail.carName,
carPrices,
const closestCar = this.findClosestCar(
request.carDetail.carName,
carPrices,
);
if (!closestCar) {
@@ -558,11 +561,11 @@ export class ExpertClaimService {
} else {
this.logger.debug(
`[PriceDrop] Request ${requestId}: Found matching car "${closestCar.carName}" with marketPrice: ${closestCar.marketPrice}`,
);
autoCalculatedData = {
);
autoCalculatedData = {
carPrice: closestCar.marketPrice,
carValue: severityValues,
};
carValue: severityValues,
};
}
}
}
@@ -600,10 +603,10 @@ export class ExpertClaimService {
}
// Step 6: Save to database
await this.claimRequestManagementDbService.findOneAndUpdate(
{ _id: new Types.ObjectId(request._id) },
{ $set: { priceDrop: finalPriceDropData } },
);
await this.claimRequestManagementDbService.findOneAndUpdate(
{ _id: new Types.ObjectId(request._id) },
{ $set: { priceDrop: finalPriceDropData } },
);
this.logger.log(
`[PriceDrop] Request ${requestId}: Price drop calculation completed and saved.`,
@@ -671,7 +674,7 @@ export class ExpertClaimService {
const severityValue =
this.priceDropPart[originalPriceDropKey]?.[
damagedPartInfo.severity
damagedPartInfo.severity
];
if (severityValue !== undefined) {
@@ -968,13 +971,13 @@ export class ExpertClaimService {
private isCurrentUserAllowed(request: any, currentUser: any): boolean {
// Check if locked by current user and lock is still active
const isLockedByCurrentUser =
const isLockedByCurrentUser =
String(request?.actorLocked?.actorId) === currentUser.sub &&
request.lockFile;
if (!isLockedByCurrentUser) {
return false;
}
}
// Also check if lock has expired (unlockTime has passed)
if (request.unlockTime) {
@@ -986,7 +989,7 @@ export class ExpertClaimService {
return true;
}
}
return true;
}
@@ -994,7 +997,7 @@ export class ExpertClaimService {
if (!request.lockFile || request.claimStatus !== ReqClaimStatus.ReviewRequest) {
return false;
}
// Check if lock has expired
if (request.unlockTime) {
const unlockTime = new Date(request.unlockTime).getTime();
@@ -1004,15 +1007,15 @@ export class ExpertClaimService {
return false;
}
}
// If currentUser is provided, allow access if they are the one who locked it
if (currentUser) {
const isLockedByCurrentUser =
const isLockedByCurrentUser =
String(request?.actorLocked?.actorId) === currentUser.sub;
// Return false (not locked) if locked by current user, true if locked by someone else
return !isLockedByCurrentUser;
}
// If no currentUser provided, treat as locked
return true;
}
@@ -1035,18 +1038,18 @@ export class ExpertClaimService {
) {
throw new ForbiddenException("Access denied to this request");
}
// Check if lock has expired (unlockTime has passed)
if (request?.unlockTime && !request?.objection) {
const unlockTime = new Date(request.unlockTime).getTime();
const now = Date.now();
if (now >= unlockTime) {
throw new ForbiddenException("Your time has expired");
}
throw new ForbiddenException("Your time has expired");
}
} else if (request?.unlockTime == null && !request?.objection) {
throw new ForbiddenException("Your time has expired");
}
if (!request.lockFile && !request?.objection) {
throw new ForbiddenException(
"For submit reply you must lock the request",
@@ -1255,6 +1258,66 @@ export class ExpertClaimService {
return request;
}
async streamServiceV2(requestId, query, res, header) {
const request = await this.claimCaseDbService.findById(requestId);
console.log(request)
// const blameCase = await this.blameCaseDbService
return request;
// let videoPath: string = "";
// switch (query) {
// case "accident":
// videoPath = await this.getAccidentVideoPath(
// String(
// request?.blameFile?.firstPartyDetails?.firstPartyFile
// ?.firstPartyVideoId,
// ),
// );
// break;
// case "car-capture":
// videoPath = await this.getCarCapture(requestId);
// break;
// default:
// throw new NotFoundException("Invalid query type");
// }
// if (!videoPath) throw new NotFoundException("video_not_found");
// const absolutePath = join(process.cwd(), videoPath.replace(/^\/+/, ""));
// if (!existsSync(absolutePath)) {
// throw new NotFoundException(
// `Video file not found at path: ${absolutePath}`,
// );
// }
// const { size } = statSync(absolutePath);
// const range = header.range;
// if (range) {
// const [startStr, endStr] = range.replace(/bytes=/, "").split("-");
// const start = parseInt(startStr, 10);
// const end = endStr ? parseInt(endStr, 10) : size - 1;
// const chunkSize = end - start + 1;
// const file = createReadStream(absolutePath, { start, end });
// res.writeHead(206, {
// "Content-Range": `bytes ${start}-${end}/${size}`,
// "Accept-Ranges": "bytes",
// "Content-Length": chunkSize,
// "Content-Type": "video/mp4",
// });
// file.pipe(res);
// } else {
// res.writeHead(200, {
// "Content-Length": size,
// "Content-Type": "video/mp4",
// });
// createReadStream(absolutePath).pipe(res);
// }
}
async streamService(requestId, query, res, header) {
const request =
await this.claimRequestManagementDbService.findOne(requestId);
@@ -1635,6 +1698,18 @@ export class ExpertClaimService {
};
}
async submitResendDocsV2(
claimRequestId: string,
reply: ClaimSubmitResendV2Dto,
actor: any,
) {
try {
} catch (err) {
console.log(err);
}
}
/**
* V2: Submit damage expert reply for a claim.
*
@@ -1839,16 +1914,16 @@ export class ExpertClaimService {
locked: c.workflow?.locked || false,
lockedBy: c.workflow?.lockedBy
? {
actorId: c.workflow.lockedBy.actorId?.toString(),
actorName: c.workflow.lockedBy.actorName,
}
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,
}
carName: c.vehicle.carName,
carModel: c.vehicle.carModel,
carType: c.vehicle.carType,
}
: undefined,
createdAt: c.createdAt,
})) as ClaimListItemV2Dto[];
@@ -1944,16 +2019,16 @@ export class ExpertClaimService {
locked: claim.workflow?.locked || false,
lockedBy: claim.workflow?.lockedBy
? {
actorId: claim.workflow.lockedBy.actorId?.toString(),
actorName: claim.workflow.lockedBy.actorName,
lockedAt: (claim.workflow as any).lockedAt?.toISOString(),
}
actorId: claim.workflow.lockedBy.actorId?.toString(),
actorName: claim.workflow.lockedBy.actorName,
lockedAt: (claim.workflow as any).lockedAt?.toISOString(),
}
: undefined,
owner: claim.owner
? {
userId: claim.owner.userId?.toString(),
fullName: claim.owner.fullName,
}
userId: claim.owner.userId?.toString(),
fullName: claim.owner.fullName,
}
: undefined,
vehicle: claim.vehicle,
blameRequestId: claim.blameRequestId?.toString(),

View File

@@ -1,10 +1,11 @@
import { Body, Controller, Get, Param, Patch, Put, UseGuards } from "@nestjs/common";
import { Body, Controller, Get, Header, Param, Headers, Patch, Put, UseGuards, Query, Res } from "@nestjs/common";
import {
ApiBearerAuth,
ApiBody,
ApiOperation,
ApiParam,
ApiPropertyOptional,
ApiQuery,
ApiTags,
} from "@nestjs/swagger";
import { IsOptional, IsString } from "class-validator";
@@ -14,7 +15,7 @@ import { Roles } from "src/decorators/roles.decorator";
import { CurrentUser } from "src/decorators/user.decorator";
import { RoleEnum } from "src/Types&Enums/role.enum";
import { ExpertClaimService } from "./expert-claim.service";
import { SubmitExpertReplyV2Dto } from "./dto/expert-claim-v2.dto";
import { ClaimSubmitResendV2Dto, SubmitExpertReplyV2Dto } from "./dto/expert-claim-v2.dto";
import { UpdateClaimDamagedPartsV2Dto } from "./dto/update-claim-damaged-parts-v2.dto";
class InPersonVisitV2Dto {
@@ -30,7 +31,7 @@ class InPersonVisitV2Dto {
@UseGuards(LocalActorAuthGuard, RolesGuard)
@Roles(RoleEnum.DAMAGE_EXPERT)
export class ExpertClaimV2Controller {
constructor(private readonly expertClaimService: ExpertClaimService) {}
constructor(private readonly expertClaimService: ExpertClaimService) { }
@Get("requests")
@ApiOperation({
@@ -89,6 +90,22 @@ export class ExpertClaimV2Controller {
return await this.expertClaimService.submitExpertReplyV2(claimRequestId, body, actor);
}
@Put("reply/resend/:claimRequestId")
@ApiOperation({
summary: "Submit expert damage resend request. ",
description:
"Claim must be locked by this expert (status EXPERT_REVIEWING). Submitting unlocks the claim. If any part has factorNeeded=true the claim moves to EXPERT_COST_EVALUATION; otherwise moves to INSURER_REVIEW. Total payment cap is 30,000,000.",
})
@ApiParam({ name: "claimRequestId" })
@ApiBody({ type: ClaimSubmitResendV2Dto })
async submitExpertResendV2(
@Param("claimRequestId") claimRequestId: string,
@Body() body: ClaimSubmitResendV2Dto,
@CurrentUser() actor,
) {
return await this.expertClaimService.submitResendDocsV2(claimRequestId, body, actor);
}
@Patch(":claimRequestId/visit")
@ApiOperation({
summary: "Request in-person visit for claim",
@@ -128,4 +145,21 @@ export class ExpertClaimV2Controller {
actor,
);
}
@ApiQuery({
name: "query",
enum: ["car-capture", "accident"],
required: true,
})
@Get("stream/:id/video")
@Header("Accept-Ranges", "bytes")
@Header("Content-Type", "video/mp4")
async claimStream(
@Headers() headers,
@Param("id") id: string,
@Query("query") query: "car-capture" | "accident",
@Res() res,
) {
return this.expertClaimService.streamServiceV2(id, query, res, headers);
}
}