Merge pull request 'Rollback uploadDocument resume, added carPrice' (#324) from s.yahyaee/yara724-api:main into main

Reviewed-on: Yara724/api#324
This commit is contained in:
2026-09-16 15:41:17 +03:30
6 changed files with 61 additions and 163 deletions

View File

@@ -36,6 +36,9 @@ export class ClaimVehicleSnapshot {
@Prop({ type: ClaimPlateSchema }) @Prop({ type: ClaimPlateSchema })
plate?: ClaimPlate; plate?: ClaimPlate;
@Prop({ type: String })
price?: string;
} }
export const ClaimVehicleSnapshotSchema = export const ClaimVehicleSnapshotSchema =
SchemaFactory.createForClass(ClaimVehicleSnapshot); SchemaFactory.createForClass(ClaimVehicleSnapshot);

View File

@@ -121,6 +121,14 @@ export class SubmitExpertReplyV2Dto {
@IsString() @IsString()
description?: string; description?: string;
@ApiPropertyOptional({
example: '1_000_000_000',
description: "Today's car price",
})
@IsOptional()
@IsMoneyAmountString()
carPrice?: string;
@ApiProperty({ @ApiProperty({
type: [PartPricingV2Dto], type: [PartPricingV2Dto],
description: description:

View File

@@ -3263,6 +3263,7 @@ export class ExpertClaimService {
) { ) {
if (actor.role !== RoleEnum.FIELD_EXPERT) requireActorClientKey(actor); if (actor.role !== RoleEnum.FIELD_EXPERT) requireActorClientKey(actor);
const claim = await this.claimCaseDbService.findById(claimRequestId); const claim = await this.claimCaseDbService.findById(claimRequestId);
const blame = await this.blameRequestDbService.findById(claim.blameRequestId);
if (!claim) { if (!claim) {
throw new NotFoundException( throw new NotFoundException(
@@ -3303,6 +3304,14 @@ export class ExpertClaimService {
); );
} }
if (reply.carPrice && blame.type !== BlameRequestType.CAR_BODY) {
throw new ForbiddenException("قیمت روز خودرو فقط در پرونده های بدنه باید بررسی شود")
}
if (blame.type === BlameRequestType.THIRD_PARTY && reply.carPrice) {
throw new ForbiddenException("قیمت روز خودرو فقط در پرونده های بدنه باید بررسی شود")
}
const pricingValidationError = getExpertReplyPricingValidationError( const pricingValidationError = getExpertReplyPricingValidationError(
reply.parts, reply.parts,
{ locale: "fa", enforceAmountBounds: true }, { locale: "fa", enforceAmountBounds: true },
@@ -3531,6 +3540,7 @@ export class ExpertClaimService {
"evaluation.ownerInsurerApproval": "", "evaluation.ownerInsurerApproval": "",
"evaluation.ownerPricedPartsApproval": "", "evaluation.ownerPricedPartsApproval": "",
}, },
"vehicle.price": reply.carPrice,
"workflow.currentStep": currentStep, "workflow.currentStep": currentStep,
"workflow.nextStep": nextWorkflowStep, "workflow.nextStep": nextWorkflowStep,
[`evaluation.${replyField}`]: replyPayload, [`evaluation.${replyField}`]: replyPayload,

View File

@@ -1,112 +0,0 @@
import { Types } from "mongoose";
import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum";
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
import { RoleEnum } from "src/Types&Enums/role.enum";
import { RequestManagementService } from "./request-management.service";
describe("FileMaker V4/V5 status resume bridge", () => {
const makerId = new Types.ObjectId();
const blameId = new Types.ObjectId();
const blameFile = {
_id: blameId,
publicId: "BL-FM-001",
requestNo: "R-1",
type: "THIRD_PARTY",
status: CaseStatus.OPEN,
blameStatus: "IN_PROGRESS",
isMadeByFileMaker: true,
initiatedByFieldExpertId: makerId,
requiresFileMakerApproval: false,
parties: [],
workflow: {
currentStep: "SECOND_COMPLETED",
nextStep: "WAITING_FOR_GUILT_DECISION",
completedSteps: ["SECOND_COMPLETED"],
},
createdAt: new Date(),
updatedAt: new Date(),
};
it("overlays claim UPLOADING_REQUIRED_DOCUMENTS onto detail status", async () => {
const service =
new (RequestManagementService as any)() as RequestManagementService;
(service as any).blameRequestDbService = {
findById: jest.fn().mockResolvedValue(blameFile),
};
(service as any).claimCaseDbService = {
findOne: jest.fn().mockResolvedValue({
_id: new Types.ObjectId(),
blameRequestId: blameId,
status: ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
workflow: {
currentStep: "UPLOAD_REQUIRED_DOCUMENTS",
nextStep: "SELECT_OUTER_PARTS",
},
}),
};
const detail = await service.getMyFileMakerFileDetail(
{ sub: String(makerId), role: RoleEnum.FILE_MAKER },
String(blameId),
);
expect(detail.status).toBe(ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS);
expect(detail.blameCaseStatus).toBe(CaseStatus.OPEN);
expect(detail.claimStatus).toBe(
ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
);
});
it("does not overlay status when claim docs phase is finished", async () => {
const service =
new (RequestManagementService as any)() as RequestManagementService;
(service as any).blameRequestDbService = {
findById: jest.fn().mockResolvedValue({
...blameFile,
status: CaseStatus.WAITING_FOR_FILE_REVIEWER,
}),
};
(service as any).claimCaseDbService = {
findOne: jest.fn().mockResolvedValue({
_id: new Types.ObjectId(),
blameRequestId: blameId,
status: ClaimCaseStatus.WAITING_FOR_FILE_REVIEWER,
workflow: { currentStep: "SELECT_OUTER_PARTS" },
}),
};
const detail = await service.getMyFileMakerFileDetail(
{ sub: String(makerId), role: RoleEnum.FILE_MAKER },
String(blameId),
);
expect(detail.status).toBe(CaseStatus.WAITING_FOR_FILE_REVIEWER);
expect(detail.blameCaseStatus).toBeUndefined();
});
it("overlays the same bridge on my-files list rows", async () => {
const service =
new (RequestManagementService as any)() as RequestManagementService;
(service as any).blameRequestDbService = {
find: jest.fn().mockResolvedValue([blameFile]),
};
(service as any).claimCaseDbService = {
find: jest.fn().mockResolvedValue([
{
blameRequestId: blameId,
status: ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
},
]),
};
const rows = await service.getMyFileMakerFiles({
sub: String(makerId),
role: RoleEnum.FILE_MAKER,
});
expect(rows).toHaveLength(1);
expect(rows[0].status).toBe(ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS);
expect(rows[0].blameCaseStatus).toBe(CaseStatus.OPEN);
});
});

View File

@@ -12733,25 +12733,25 @@ export class RequestManagementService {
return { ...workflow, completedSteps }; return { ...workflow, completedSteps };
} }
/** // /**
* V4/V5 dirty bridge: FileMaker FE resumes from blame `status`, but pre-capture // * V4/V5 dirty bridge: FileMaker FE resumes from blame `status`, but pre-capture
* document upload lives on the claim (`UPLOADING_REQUIRED_DOCUMENTS`) while blame // * document upload lives on the claim (`UPLOADING_REQUIRED_DOCUMENTS`) while blame
* is still at FIRST/SECOND_COMPLETED. Mirror claim status into `status` only for // * is still at FIRST/SECOND_COMPLETED. Mirror claim status into `status` only for
* that phase so leave/re-enter can continue; keep real blame status as // * that phase so leave/re-enter can continue; keep real blame status as
* `blameCaseStatus`. Remove once FE keys off `claimStatus` / a unified resume pointer. // * `blameCaseStatus`. Remove once FE keys off `claimStatus` / a unified resume pointer.
*/ // */
private fileMakerStatusForResume( // private fileMakerStatusForResume(
blameStatus: unknown, // blameStatus: unknown,
claimStatus: unknown, // claimStatus: unknown,
): { status: unknown; blameCaseStatus?: unknown } { // ): { status: unknown; blameCaseStatus?: unknown } {
if (claimStatus === ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS) { // if (claimStatus === ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS) {
return { // return {
status: ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS, // status: ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
blameCaseStatus: blameStatus, // blameCaseStatus: blameStatus,
}; // };
} // }
return { status: blameStatus }; // return { status: blameStatus };
} // }
async getMyFileMakerFiles(fileMaker: any): Promise<any[]> { async getMyFileMakerFiles(fileMaker: any): Promise<any[]> {
if (fileMaker?.role !== RoleEnum.FILE_MAKER) { if (fileMaker?.role !== RoleEnum.FILE_MAKER) {
@@ -12762,35 +12762,32 @@ export class RequestManagementService {
isMadeByFileMaker: true, isMadeByFileMaker: true,
initiatedByFieldExpertId: makerId, initiatedByFieldExpertId: makerId,
}); });
const blameIds = (files || []).map((f: any) => f._id).filter(Boolean); // const blameIds = (files || []).map((f: any) => f._id).filter(Boolean);
const claims = // const claims =
blameIds.length > 0 // blameIds.length > 0
? await this.claimCaseDbService.find( // ? await this.claimCaseDbService.find(
{ blameRequestId: { $in: blameIds } }, // { blameRequestId: { $in: blameIds } },
{ lean: true, select: "blameRequestId status" }, // { lean: true, select: "blameRequestId status" },
) // )
: []; // : [];
const claimStatusByBlameId = new Map<string, unknown>(); // const claimStatusByBlameId = new Map<string, unknown>();
for (const c of claims as any[]) { // for (const c of claims as any[]) {
const blameId = c?.blameRequestId != null ? String(c.blameRequestId) : ""; // const blameId = c?.blameRequestId != null ? String(c.blameRequestId) : "";
if (blameId) claimStatusByBlameId.set(blameId, c.status); // if (blameId) claimStatusByBlameId.set(blameId, c.status);
} // }
return (files || []).map((f: any) => { return (files || []).map((f: any) => {
const workflow = this.fileMakerWorkflowProjection(f); const workflow = this.fileMakerWorkflowProjection(f);
const resume = this.fileMakerStatusForResume( // const resume = this.fileMakerStatusForResume(
f.status, // f.status,
claimStatusByBlameId.get(String(f._id)), // claimStatusByBlameId.get(String(f._id)),
); // );
return { return {
_id: f._id, _id: f._id,
publicId: f.publicId, publicId: f.publicId,
requestNo: f.requestNo, requestNo: f.requestNo,
type: f.type, type: f.type,
status: resume.status, status: f.status,
...(resume.blameCaseStatus !== undefined
? { blameCaseStatus: resume.blameCaseStatus }
: {}),
blameStatus: f.blameStatus, blameStatus: f.blameStatus,
workflow: { workflow: {
currentStep: workflow.currentStep, currentStep: workflow.currentStep,
@@ -12835,20 +12832,12 @@ export class RequestManagementService {
: claim : claim
? { ...(claim as any) } ? { ...(claim as any) }
: null; : null;
const resume = this.fileMakerStatusForResume(
plain.status,
claimPlain?.status,
);
return { return {
_id: plain._id, _id: plain._id,
publicId: plain.publicId, publicId: plain.publicId,
requestNo: plain.requestNo, requestNo: plain.requestNo,
type: plain.type, type: plain.type,
status: resume.status, status: plain.status,
...(resume.blameCaseStatus !== undefined
? { blameCaseStatus: resume.blameCaseStatus }
: {}),
blameStatus: plain.blameStatus, blameStatus: plain.blameStatus,
workflow: this.fileMakerWorkflowProjection(plain), workflow: this.fileMakerWorkflowProjection(plain),
requiresFileMakerApproval: plain.requiresFileMakerApproval, requiresFileMakerApproval: plain.requiresFileMakerApproval,

View File

@@ -28,8 +28,8 @@ export function mapEsgCarBodyPolicyToInquiry(
// Provider responses do not currently include an insurer company identity. // Provider responses do not currently include an insurer company identity.
companyId: policy.CompanyId ?? null, companyId: policy.CompanyId ?? null,
CompanyCode: policy.CompanyId ?? null, CompanyCode: policy.CompanyId ?? "8",
CompanyName: policy.CompanyName ?? null, CompanyName: policy.CompanyName ?? "بیمه پارسیان",
// The lookup's customer is the policyholder/owner, not the insurer. // The lookup's customer is the policyholder/owner, not the insurer.
insurerName: null, insurerName: null,