forked from Yara724/api
YARA-948
This commit is contained in:
@@ -10,7 +10,14 @@ import {
|
|||||||
Query,
|
Query,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from "@nestjs/swagger";
|
import {
|
||||||
|
ApiBearerAuth,
|
||||||
|
ApiBody,
|
||||||
|
ApiOperation,
|
||||||
|
ApiParam,
|
||||||
|
ApiResponse,
|
||||||
|
ApiTags,
|
||||||
|
} from "@nestjs/swagger";
|
||||||
import { ExpertFileAssignResultDto } from "src/common/dto/expert-file-assign-result.dto";
|
import { ExpertFileAssignResultDto } from "src/common/dto/expert-file-assign-result.dto";
|
||||||
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
|
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
|
||||||
import { RolesGuard } from "src/auth/guards/role.guard";
|
import { RolesGuard } from "src/auth/guards/role.guard";
|
||||||
@@ -63,7 +70,9 @@ export class ExpertBlameV2Controller {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof HttpException) throw error;
|
if (error instanceof HttpException) throw error;
|
||||||
throw new InternalServerErrorException(
|
throw new InternalServerErrorException(
|
||||||
error instanceof Error ? error.message : "Failed to load blame status report",
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Failed to load blame status report",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +85,9 @@ export class ExpertBlameV2Controller {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof HttpException) throw error;
|
if (error instanceof HttpException) throw error;
|
||||||
throw new InternalServerErrorException(
|
throw new InternalServerErrorException(
|
||||||
error instanceof Error ? error.message : "Failed to get blame case details",
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Failed to get blame case details",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,7 +107,10 @@ export class ExpertBlameV2Controller {
|
|||||||
})
|
})
|
||||||
async assignForReview(@Param("id") id: string, @CurrentUser() actor: any) {
|
async assignForReview(@Param("id") id: string, @CurrentUser() actor: any) {
|
||||||
try {
|
try {
|
||||||
return await this.expertBlameService.assignBlameCaseForReviewV2(id, actor);
|
return await this.expertBlameService.assignBlameCaseForReviewV2(
|
||||||
|
id,
|
||||||
|
actor,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof HttpException) throw error;
|
if (error instanceof HttpException) throw error;
|
||||||
throw new InternalServerErrorException(
|
throw new InternalServerErrorException(
|
||||||
@@ -110,6 +124,7 @@ export class ExpertBlameV2Controller {
|
|||||||
summary: "Lock blame case for review (legacy)",
|
summary: "Lock blame case for review (legacy)",
|
||||||
description:
|
description:
|
||||||
"Same assignment logic as POST `:id/assign`, but returns the legacy `{ _id, lock }` shape and maps conflicts to 400 instead of 409.",
|
"Same assignment logic as POST `:id/assign`, but returns the legacy `{ _id, lock }` shape and maps conflicts to 400 instead of 409.",
|
||||||
|
deprecated: true,
|
||||||
})
|
})
|
||||||
@ApiParam({ name: "id", description: "Blame case request id" })
|
@ApiParam({ name: "id", description: "Blame case request id" })
|
||||||
async lockRequest(@Param("id") id: string, @CurrentUser() actor: any) {
|
async lockRequest(@Param("id") id: string, @CurrentUser() actor: any) {
|
||||||
@@ -156,7 +171,9 @@ export class ExpertBlameV2Controller {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof HttpException) throw error;
|
if (error instanceof HttpException) throw error;
|
||||||
throw new InternalServerErrorException(
|
throw new InternalServerErrorException(
|
||||||
error instanceof Error ? error.message : "Failed to submit expert reply",
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Failed to submit expert reply",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,7 +191,9 @@ export class ExpertBlameV2Controller {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof HttpException) throw error;
|
if (error instanceof HttpException) throw error;
|
||||||
throw new InternalServerErrorException(
|
throw new InternalServerErrorException(
|
||||||
error instanceof Error ? error.message : "Failed to submit expert reply for in person",
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Failed to submit expert reply for in person",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,16 @@ export class UpsertClaimPriceDropV2Dto {
|
|||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => PriceDropPartSeverityV2Dto)
|
@Type(() => PriceDropPartSeverityV2Dto)
|
||||||
partSeverities: PriceDropPartSeverityV2Dto[];
|
partSeverities: PriceDropPartSeverityV2Dto[];
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description:
|
||||||
|
"Override: expert manually sets the final price-drop amount (in Rials). " +
|
||||||
|
"When provided, carPrice and partSeverities are not required and calculation is skipped entirely.",
|
||||||
|
example: 150000000,
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
manualPriceDrop?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ClaimPriceDropContextV2Dto {
|
export class ClaimPriceDropContextV2Dto {
|
||||||
@@ -99,7 +109,9 @@ export class ClaimPriceDropContextV2Dto {
|
|||||||
})
|
})
|
||||||
severityOptions: Array<{ value: string; label_fa: string }>;
|
severityOptions: Array<{ value: string; label_fa: string }>;
|
||||||
|
|
||||||
@ApiProperty({ description: "Full coefficient table for all price-drop parts" })
|
@ApiProperty({
|
||||||
|
description: "Full coefficient table for all price-drop parts",
|
||||||
|
})
|
||||||
priceDropCatalog: Array<Record<string, unknown>>;
|
priceDropCatalog: Array<Record<string, unknown>>;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
|
|||||||
@@ -4226,6 +4226,49 @@ export class ExpertClaimService {
|
|||||||
}
|
}
|
||||||
this.assertExpertCanEditClaimDuringReviewV2(claim, actor);
|
this.assertExpertCanEditClaimDuringReviewV2(claim, actor);
|
||||||
|
|
||||||
|
const vehicleSet: Record<string, string> = {};
|
||||||
|
if (body.carName != null && String(body.carName).trim()) {
|
||||||
|
vehicleSet["vehicle.carName"] = String(body.carName).trim();
|
||||||
|
}
|
||||||
|
if (body.carModel != null && String(body.carModel).trim()) {
|
||||||
|
vehicleSet["vehicle.carModel"] = String(body.carModel).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manual override path — expert knows the number, skip calculation entirely
|
||||||
|
if (body.manualPriceDrop != null) {
|
||||||
|
if (!Number.isFinite(body.manualPriceDrop) || body.manualPriceDrop < 0) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
"manualPriceDrop must be a non-negative finite number.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const manualPayload = {
|
||||||
|
manualOverride: true,
|
||||||
|
totalPriceDrop: body.manualPriceDrop,
|
||||||
|
partLines: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||||
|
$set: {
|
||||||
|
...vehicleSet,
|
||||||
|
"evaluation.priceDrop": manualPayload,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updated = await this.claimCaseDbService.findById(claimRequestId);
|
||||||
|
return {
|
||||||
|
claimRequestId,
|
||||||
|
vehicle: {
|
||||||
|
carName: updated?.vehicle?.carName,
|
||||||
|
carModel: updated?.vehicle?.carModel,
|
||||||
|
carType: updated?.vehicle?.carType,
|
||||||
|
},
|
||||||
|
priceDrop: manualPayload,
|
||||||
|
partLines: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculated path — existing logic unchanged
|
||||||
const carType = claim.vehicle?.carType as ClaimVehicleTypeV2 | undefined;
|
const carType = claim.vehicle?.carType as ClaimVehicleTypeV2 | undefined;
|
||||||
const selectedNorm = normalizeDamageSelectedParts(
|
const selectedNorm = normalizeDamageSelectedParts(
|
||||||
claim.damage?.selectedParts,
|
claim.damage?.selectedParts,
|
||||||
@@ -4235,7 +4278,7 @@ export class ExpertClaimService {
|
|||||||
|
|
||||||
if (!body.partSeverities?.length) {
|
if (!body.partSeverities?.length) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
"Provide at least one partSeverities line for a damaged part.",
|
"Provide at least one partSeverities line, or use manualPriceDrop for a direct override.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4290,14 +4333,6 @@ export class ExpertClaimService {
|
|||||||
partLines: lines,
|
partLines: lines,
|
||||||
};
|
};
|
||||||
|
|
||||||
const vehicleSet: Record<string, string> = {};
|
|
||||||
if (body.carName != null && String(body.carName).trim()) {
|
|
||||||
vehicleSet["vehicle.carName"] = String(body.carName).trim();
|
|
||||||
}
|
|
||||||
if (body.carModel != null && String(body.carModel).trim()) {
|
|
||||||
vehicleSet["vehicle.carModel"] = String(body.carModel).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||||
$set: {
|
$set: {
|
||||||
...vehicleSet,
|
...vehicleSet,
|
||||||
@@ -4306,7 +4341,6 @@ export class ExpertClaimService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updated = await this.claimCaseDbService.findById(claimRequestId);
|
const updated = await this.claimCaseDbService.findById(claimRequestId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claimRequestId,
|
claimRequestId,
|
||||||
vehicle: {
|
vehicle: {
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
import { Body, Controller, Get, Header, Param, Headers, Patch, Post, Put, UseGuards, Query, Res } from "@nestjs/common";
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Header,
|
||||||
|
Param,
|
||||||
|
Headers,
|
||||||
|
Patch,
|
||||||
|
Post,
|
||||||
|
Put,
|
||||||
|
UseGuards,
|
||||||
|
Query,
|
||||||
|
Res,
|
||||||
|
} from "@nestjs/common";
|
||||||
import {
|
import {
|
||||||
ApiBearerAuth,
|
ApiBearerAuth,
|
||||||
ApiBody,
|
ApiBody,
|
||||||
@@ -19,7 +32,10 @@ import { ExpertFileAssignResultDto } from "src/common/dto/expert-file-assign-res
|
|||||||
import { ExpertClaimService } from "./expert-claim.service";
|
import { ExpertClaimService } from "./expert-claim.service";
|
||||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
||||||
import { GetClaimListV2ResponseDto } from "./dto/claim-list-v2.dto";
|
import { GetClaimListV2ResponseDto } from "./dto/claim-list-v2.dto";
|
||||||
import { ClaimSubmitResendV2Dto, 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";
|
||||||
import {
|
import {
|
||||||
ClaimPriceDropContextV2Dto,
|
ClaimPriceDropContextV2Dto,
|
||||||
@@ -32,7 +48,7 @@ import { OuterPartCatalogItemDto } from "src/claim-request-management/dto/select
|
|||||||
import { ClaimVehicleTypeV2 } from "src/static/outer-car-parts-catalog";
|
import { ClaimVehicleTypeV2 } from "src/static/outer-car-parts-catalog";
|
||||||
|
|
||||||
class InPersonVisitV2Dto {
|
class InPersonVisitV2Dto {
|
||||||
@ApiPropertyOptional({ example: 'Paint damage requires physical inspection' })
|
@ApiPropertyOptional({ example: "Paint damage requires physical inspection" })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
note?: string;
|
note?: string;
|
||||||
@@ -120,7 +136,10 @@ export class ExpertClaimV2Controller {
|
|||||||
@Param("claimRequestId") claimRequestId: string,
|
@Param("claimRequestId") claimRequestId: string,
|
||||||
@CurrentUser() actor,
|
@CurrentUser() actor,
|
||||||
) {
|
) {
|
||||||
return await this.expertClaimService.getClaimDetailV2(claimRequestId, actor);
|
return await this.expertClaimService.getClaimDetailV2(
|
||||||
|
claimRequestId,
|
||||||
|
actor,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("request/:claimRequestId/price-drop")
|
@Get("request/:claimRequestId/price-drop")
|
||||||
@@ -193,13 +212,17 @@ export class ExpertClaimV2Controller {
|
|||||||
"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" +
|
"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" +
|
"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. Same assignment logic as POST `assign/:claimRequestId`, but returns the legacy lock shape and maps conflicts to 400 instead of 409.",
|
"Only one expert can hold the lock at a time. Re-locking by the same expert is idempotent. Same assignment logic as POST `assign/:claimRequestId`, but returns the legacy lock shape and maps conflicts to 400 instead of 409.",
|
||||||
|
deprecated: true,
|
||||||
})
|
})
|
||||||
@ApiParam({ name: "claimRequestId" })
|
@ApiParam({ name: "claimRequestId" })
|
||||||
async lockClaimRequestV2(
|
async lockClaimRequestV2(
|
||||||
@Param("claimRequestId") claimRequestId: string,
|
@Param("claimRequestId") claimRequestId: string,
|
||||||
@CurrentUser() actor,
|
@CurrentUser() actor,
|
||||||
) {
|
) {
|
||||||
return await this.expertClaimService.lockClaimRequestV2(claimRequestId, actor);
|
return await this.expertClaimService.lockClaimRequestV2(
|
||||||
|
claimRequestId,
|
||||||
|
actor,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put("reply/submit/:claimRequestId")
|
@Put("reply/submit/:claimRequestId")
|
||||||
@@ -221,7 +244,11 @@ export class ExpertClaimV2Controller {
|
|||||||
@Body() body: SubmitExpertReplyV2Dto,
|
@Body() body: SubmitExpertReplyV2Dto,
|
||||||
@CurrentUser() actor,
|
@CurrentUser() actor,
|
||||||
) {
|
) {
|
||||||
return await this.expertClaimService.submitExpertReplyV2(claimRequestId, body, actor);
|
return await this.expertClaimService.submitExpertReplyV2(
|
||||||
|
claimRequestId,
|
||||||
|
body,
|
||||||
|
actor,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put("reply/resend/:claimRequestId")
|
@Put("reply/resend/:claimRequestId")
|
||||||
@@ -239,7 +266,11 @@ export class ExpertClaimV2Controller {
|
|||||||
@Body() body: ClaimSubmitResendV2Dto,
|
@Body() body: ClaimSubmitResendV2Dto,
|
||||||
@CurrentUser() actor,
|
@CurrentUser() actor,
|
||||||
) {
|
) {
|
||||||
return await this.expertClaimService.submitResendDocsV2(claimRequestId, body, actor);
|
return await this.expertClaimService.submitResendDocsV2(
|
||||||
|
claimRequestId,
|
||||||
|
body,
|
||||||
|
actor,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Patch(":claimRequestId/visit")
|
@Patch(":claimRequestId/visit")
|
||||||
|
|||||||
Reference in New Issue
Block a user