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, ValidateNested,
} from 'class-validator'; } from 'class-validator';
import { Type } from 'class-transformer'; 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 { export class CarPartDamageV2Dto {
@ApiProperty({ example: 'left' }) @ApiProperty({ example: 'left' })
@@ -68,3 +70,14 @@ export class SubmitExpertReplyV2Dto {
@Type(() => PartPricingV2Dto) @Type(() => PartPricingV2Dto)
parts: 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 { FactorStatus } from "src/Types&Enums/claim-request-management/factor-status.enum";
import { DaghiOption } from "src/Types&Enums/claim-request-management/daghi-option.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 { 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() @Injectable()
export class ExpertClaimService { export class ExpertClaimService {
@@ -154,13 +156,14 @@ export class ExpertClaimService {
private readonly claimVideoCaptureDbService: VideoCaptureDbService, private readonly claimVideoCaptureDbService: VideoCaptureDbService,
private readonly httpService: HttpService, private readonly httpService: HttpService,
private readonly claimCaseDbService: ClaimCaseDbService, private readonly claimCaseDbService: ClaimCaseDbService,
private readonly blameRequestDbService: BlameRequestDbService,
private readonly sandHubService: SandHubService, private readonly sandHubService: SandHubService,
private readonly damageExpertDbService: DamageExpertDbService, private readonly damageExpertDbService: DamageExpertDbService,
private readonly clientDbService: ClientDbService, private readonly clientDbService: ClientDbService,
private readonly claimFactorsImageDbService: ClaimFactorsImageDbService, private readonly claimFactorsImageDbService: ClaimFactorsImageDbService,
private readonly claimRequiredDocumentDbService: ClaimRequiredDocumentDbService, private readonly claimRequiredDocumentDbService: ClaimRequiredDocumentDbService,
private readonly branchDbService: BranchDbService, private readonly branchDbService: BranchDbService,
) {} ) { }
private isVisibleToClientType(client: any, actor: any): boolean { private isVisibleToClientType(client: any, actor: any): boolean {
if (actor.userType === UserType.GENUINE) { if (actor.userType === UserType.GENUINE) {
@@ -542,9 +545,9 @@ export class ExpertClaimService {
`[PriceDrop] Request ${requestId}: Fetched ${carPrices.length} car prices. Searching for: "${request.carDetail.carName}"`, `[PriceDrop] Request ${requestId}: Fetched ${carPrices.length} car prices. Searching for: "${request.carDetail.carName}"`,
); );
const closestCar = this.findClosestCar( const closestCar = this.findClosestCar(
request.carDetail.carName, request.carDetail.carName,
carPrices, carPrices,
); );
if (!closestCar) { if (!closestCar) {
@@ -558,11 +561,11 @@ export class ExpertClaimService {
} else { } else {
this.logger.debug( this.logger.debug(
`[PriceDrop] Request ${requestId}: Found matching car "${closestCar.carName}" with marketPrice: ${closestCar.marketPrice}`, `[PriceDrop] Request ${requestId}: Found matching car "${closestCar.carName}" with marketPrice: ${closestCar.marketPrice}`,
); );
autoCalculatedData = { autoCalculatedData = {
carPrice: closestCar.marketPrice, carPrice: closestCar.marketPrice,
carValue: severityValues, carValue: severityValues,
}; };
} }
} }
} }
@@ -600,10 +603,10 @@ export class ExpertClaimService {
} }
// Step 6: Save to database // Step 6: Save to database
await this.claimRequestManagementDbService.findOneAndUpdate( await this.claimRequestManagementDbService.findOneAndUpdate(
{ _id: new Types.ObjectId(request._id) }, { _id: new Types.ObjectId(request._id) },
{ $set: { priceDrop: finalPriceDropData } }, { $set: { priceDrop: finalPriceDropData } },
); );
this.logger.log( this.logger.log(
`[PriceDrop] Request ${requestId}: Price drop calculation completed and saved.`, `[PriceDrop] Request ${requestId}: Price drop calculation completed and saved.`,
@@ -671,7 +674,7 @@ export class ExpertClaimService {
const severityValue = const severityValue =
this.priceDropPart[originalPriceDropKey]?.[ this.priceDropPart[originalPriceDropKey]?.[
damagedPartInfo.severity damagedPartInfo.severity
]; ];
if (severityValue !== undefined) { if (severityValue !== undefined) {
@@ -974,7 +977,7 @@ export class ExpertClaimService {
if (!isLockedByCurrentUser) { if (!isLockedByCurrentUser) {
return false; return false;
} }
// Also check if lock has expired (unlockTime has passed) // Also check if lock has expired (unlockTime has passed)
if (request.unlockTime) { if (request.unlockTime) {
@@ -1041,8 +1044,8 @@ export class ExpertClaimService {
const unlockTime = new Date(request.unlockTime).getTime(); const unlockTime = new Date(request.unlockTime).getTime();
const now = Date.now(); const now = Date.now();
if (now >= unlockTime) { if (now >= unlockTime) {
throw new ForbiddenException("Your time has expired"); throw new ForbiddenException("Your time has expired");
} }
} else if (request?.unlockTime == null && !request?.objection) { } else if (request?.unlockTime == null && !request?.objection) {
throw new ForbiddenException("Your time has expired"); throw new ForbiddenException("Your time has expired");
} }
@@ -1255,6 +1258,66 @@ export class ExpertClaimService {
return request; 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) { async streamService(requestId, query, res, header) {
const request = const request =
await this.claimRequestManagementDbService.findOne(requestId); 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. * V2: Submit damage expert reply for a claim.
* *
@@ -1839,16 +1914,16 @@ export class ExpertClaimService {
locked: c.workflow?.locked || false, locked: c.workflow?.locked || false,
lockedBy: c.workflow?.lockedBy lockedBy: c.workflow?.lockedBy
? { ? {
actorId: c.workflow.lockedBy.actorId?.toString(), actorId: c.workflow.lockedBy.actorId?.toString(),
actorName: c.workflow.lockedBy.actorName, actorName: c.workflow.lockedBy.actorName,
} }
: undefined, : undefined,
vehicle: c.vehicle vehicle: c.vehicle
? { ? {
carName: c.vehicle.carName, carName: c.vehicle.carName,
carModel: c.vehicle.carModel, carModel: c.vehicle.carModel,
carType: c.vehicle.carType, carType: c.vehicle.carType,
} }
: undefined, : undefined,
createdAt: c.createdAt, createdAt: c.createdAt,
})) as ClaimListItemV2Dto[]; })) as ClaimListItemV2Dto[];
@@ -1944,16 +2019,16 @@ export class ExpertClaimService {
locked: claim.workflow?.locked || false, locked: claim.workflow?.locked || false,
lockedBy: claim.workflow?.lockedBy lockedBy: claim.workflow?.lockedBy
? { ? {
actorId: claim.workflow.lockedBy.actorId?.toString(), actorId: claim.workflow.lockedBy.actorId?.toString(),
actorName: claim.workflow.lockedBy.actorName, actorName: claim.workflow.lockedBy.actorName,
lockedAt: (claim.workflow as any).lockedAt?.toISOString(), lockedAt: (claim.workflow as any).lockedAt?.toISOString(),
} }
: undefined, : undefined,
owner: claim.owner owner: claim.owner
? { ? {
userId: claim.owner.userId?.toString(), userId: claim.owner.userId?.toString(),
fullName: claim.owner.fullName, fullName: claim.owner.fullName,
} }
: undefined, : undefined,
vehicle: claim.vehicle, vehicle: claim.vehicle,
blameRequestId: claim.blameRequestId?.toString(), 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 { import {
ApiBearerAuth, ApiBearerAuth,
ApiBody, ApiBody,
ApiOperation, ApiOperation,
ApiParam, ApiParam,
ApiPropertyOptional, ApiPropertyOptional,
ApiQuery,
ApiTags, ApiTags,
} from "@nestjs/swagger"; } from "@nestjs/swagger";
import { IsOptional, IsString } from "class-validator"; 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 { CurrentUser } from "src/decorators/user.decorator";
import { RoleEnum } from "src/Types&Enums/role.enum"; import { RoleEnum } from "src/Types&Enums/role.enum";
import { ExpertClaimService } from "./expert-claim.service"; 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"; import { UpdateClaimDamagedPartsV2Dto } from "./dto/update-claim-damaged-parts-v2.dto";
class InPersonVisitV2Dto { class InPersonVisitV2Dto {
@@ -30,7 +31,7 @@ class InPersonVisitV2Dto {
@UseGuards(LocalActorAuthGuard, RolesGuard) @UseGuards(LocalActorAuthGuard, RolesGuard)
@Roles(RoleEnum.DAMAGE_EXPERT) @Roles(RoleEnum.DAMAGE_EXPERT)
export class ExpertClaimV2Controller { export class ExpertClaimV2Controller {
constructor(private readonly expertClaimService: ExpertClaimService) {} constructor(private readonly expertClaimService: ExpertClaimService) { }
@Get("requests") @Get("requests")
@ApiOperation({ @ApiOperation({
@@ -89,6 +90,22 @@ export class ExpertClaimV2Controller {
return await this.expertClaimService.submitExpertReplyV2(claimRequestId, body, actor); 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") @Patch(":claimRequestId/visit")
@ApiOperation({ @ApiOperation({
summary: "Request in-person visit for claim", summary: "Request in-person visit for claim",
@@ -128,4 +145,21 @@ export class ExpertClaimV2Controller {
actor, 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);
}
} }