forked from Yara724/api
Use FileMaker and FileReviewer Fanavaran expert ids on V4/V5 flows.
Block file create and review when the matching third-party or car-body code is missing, and override GEN.03/GEN.06 ClaimExpertId from those profiles instead of tenant defaults. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -141,6 +141,13 @@ import { PublicIdService } from "src/utils/public-id/public-id.service";
|
||||
import { ImageRequiredModel } from "./entites/schema/image-required.schema";
|
||||
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
|
||||
import { FileMakerDbService } from "src/users/entities/db-service/file-maker.db.service";
|
||||
import { FileReviewerDbService } from "src/users/entities/db-service/file-reviewer.db.service";
|
||||
import {
|
||||
assertFileMakerCanCreateBlameType,
|
||||
assertFileReviewerCanReviewBlameType,
|
||||
resolveFileMakerStageOneExpertId,
|
||||
resolveFileReviewerExpertiseExpertId,
|
||||
} from "src/fanavaran/fanavaran-file-role-expert-ids";
|
||||
import { FieldExpertDbService } from "src/users/entities/db-service/field-expert.db.service";
|
||||
import { FanavaranLocationService } from "src/fanavaran/fanavaran-location.service";
|
||||
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service";
|
||||
@@ -474,6 +481,7 @@ export class ClaimRequestManagementService {
|
||||
private readonly fanavaranLookupService: FanavaranLookupService,
|
||||
private readonly smsOrchestrationService: SmsOrchestrationService,
|
||||
private readonly fileMakerDbService: FileMakerDbService,
|
||||
private readonly fileReviewerDbService: FileReviewerDbService,
|
||||
private readonly fieldExpertDbService: FieldExpertDbService,
|
||||
private readonly plateNormalizer: PlateNormalizerService,
|
||||
private readonly fanavaranLocationService: FanavaranLocationService,
|
||||
@@ -5179,6 +5187,55 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* V4/V5 FileMaker flows: GEN.03 ClaimExpertId from file-maker; GEN.06 from file-reviewer.
|
||||
* Returns null for non–file-maker blames (tenant defaults apply).
|
||||
*/
|
||||
private async resolveFanavaranFileRoleExpertIdsForBlame(blame: {
|
||||
isMadeByFileMaker?: boolean;
|
||||
type?: BlameRequestType;
|
||||
initiatedByFieldExpertId?: unknown;
|
||||
assignedFileReviewerId?: unknown;
|
||||
}): Promise<{
|
||||
baseClaimExpertId: number | null;
|
||||
expertiseClaimExpertId: number | null;
|
||||
} | null> {
|
||||
if (!blame?.isMadeByFileMaker || blame.type == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let baseClaimExpertId: number | null = null;
|
||||
let expertiseClaimExpertId: number | null = null;
|
||||
|
||||
if (blame.initiatedByFieldExpertId) {
|
||||
const fileMaker = await this.fileMakerDbService.findById(
|
||||
String(blame.initiatedByFieldExpertId),
|
||||
);
|
||||
if (fileMaker) {
|
||||
assertFileMakerCanCreateBlameType(fileMaker, blame.type);
|
||||
baseClaimExpertId = resolveFileMakerStageOneExpertId(
|
||||
fileMaker,
|
||||
blame.type,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (blame.assignedFileReviewerId) {
|
||||
const fileReviewer = await this.fileReviewerDbService.findById(
|
||||
String(blame.assignedFileReviewerId),
|
||||
);
|
||||
if (fileReviewer) {
|
||||
assertFileReviewerCanReviewBlameType(fileReviewer, blame.type);
|
||||
expertiseClaimExpertId = resolveFileReviewerExpertiseExpertId(
|
||||
fileReviewer,
|
||||
blame.type,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return { baseClaimExpertId, expertiseClaimExpertId };
|
||||
}
|
||||
|
||||
private async getPolicyIdFromNationalCode(
|
||||
nationalCodeOfInsurer: string,
|
||||
config: {
|
||||
@@ -6075,6 +6132,19 @@ export class ClaimRequestManagementService {
|
||||
Object.assign(payload, hull);
|
||||
}
|
||||
|
||||
const fileRoleExpertIds =
|
||||
await this.resolveFanavaranFileRoleExpertIdsForBlame(blameCase);
|
||||
if (fileRoleExpertIds) {
|
||||
if (fileRoleExpertIds.baseClaimExpertId == null) {
|
||||
throw new BadRequestException({
|
||||
code: "FILE_MAKER_FANAVARAN_EXPERT_CODE_MISSING",
|
||||
message:
|
||||
"This FileMaker file cannot be submitted to Fanavaran: stage-1 claim expert id is missing for this product line.",
|
||||
});
|
||||
}
|
||||
payload.ClaimExpertId = fileRoleExpertIds.baseClaimExpertId;
|
||||
}
|
||||
|
||||
if (persistPayload) {
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
||||
$set: {
|
||||
@@ -7048,10 +7118,35 @@ export class ClaimRequestManagementService {
|
||||
const profile = getFanavaranClientProfile(input.clientKey);
|
||||
const active = getActiveV2ExpertReply(input.claimCase as any);
|
||||
|
||||
// GEN.08 ClaimExpertId = tenant ExpertiseClaimExpertId (assessor role).
|
||||
// Tejaratno: 2709. Parsian: 29. Must NOT reuse GEN.03 ClaimExpertId
|
||||
// (مسئول پرونده مالی — Tejaratno 4543092 / Parsian 154).
|
||||
const claimExpertId = profile.defaults.ExpertiseClaimExpertId;
|
||||
const blameForExpertIds = input.claimCase?.blameRequestId
|
||||
? await this.blameRequestDbService.findById(
|
||||
String(input.claimCase.blameRequestId),
|
||||
)
|
||||
: null;
|
||||
|
||||
// GEN.06 ClaimExpertId = tenant ExpertiseClaimExpertId (assessor role), unless
|
||||
// V4/V5 FileMaker flow → file-reviewer expertise code for this product line.
|
||||
let claimExpertId = profile.defaults.ExpertiseClaimExpertId;
|
||||
if (blameForExpertIds) {
|
||||
const roleExpertIds =
|
||||
await this.resolveFanavaranFileRoleExpertIdsForBlame(
|
||||
blameForExpertIds as {
|
||||
isMadeByFileMaker?: boolean;
|
||||
type?: BlameRequestType;
|
||||
initiatedByFieldExpertId?: unknown;
|
||||
assignedFileReviewerId?: unknown;
|
||||
},
|
||||
);
|
||||
if (roleExpertIds?.expertiseClaimExpertId != null) {
|
||||
claimExpertId = roleExpertIds.expertiseClaimExpertId;
|
||||
} else if (roleExpertIds) {
|
||||
throw new BadRequestException({
|
||||
code: "FILE_REVIEWER_FANAVARAN_EXPERT_CODE_MISSING",
|
||||
message:
|
||||
"Fanavaran expertise cannot be submitted: assign a FileReviewer with an expertise claim expert id for this product line.",
|
||||
});
|
||||
}
|
||||
}
|
||||
const warnings: string[] = [];
|
||||
|
||||
if (!active?.parts?.length) {
|
||||
@@ -7106,11 +7201,7 @@ export class ClaimRequestManagementService {
|
||||
);
|
||||
|
||||
if (product === "car-body") {
|
||||
const blame = input.claimCase?.blameRequestId
|
||||
? await this.blameRequestDbService.findById(
|
||||
String(input.claimCase.blameRequestId),
|
||||
)
|
||||
: null;
|
||||
const blame = blameForExpertIds;
|
||||
return this.buildCarBodyFanavaranExpertisePayload({
|
||||
clientKey: input.clientKey,
|
||||
claimCase: input.claimCase,
|
||||
|
||||
57
src/fanavaran/fanavaran-file-role-expert-ids.spec.ts
Normal file
57
src/fanavaran/fanavaran-file-role-expert-ids.spec.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||
import {
|
||||
assertFileMakerCanCreateBlameType,
|
||||
assertFileReviewerCanReviewBlameType,
|
||||
resolveFileMakerStageOneExpertId,
|
||||
resolveFileReviewerExpertiseExpertId,
|
||||
} from "./fanavaran-file-role-expert-ids";
|
||||
|
||||
describe("fanavaran-file-role-expert-ids", () => {
|
||||
it("resolves stage-1 and expertise ids by blame type", () => {
|
||||
expect(
|
||||
resolveFileMakerStageOneExpertId(
|
||||
{
|
||||
ThirdPartyClaimExpertId: "154",
|
||||
CarBodyClaimExpertId: "29",
|
||||
},
|
||||
BlameRequestType.THIRD_PARTY,
|
||||
),
|
||||
).toBe(154);
|
||||
expect(
|
||||
resolveFileMakerStageOneExpertId(
|
||||
{
|
||||
ThirdPartyClaimExpertId: "154",
|
||||
CarBodyClaimExpertId: "29",
|
||||
},
|
||||
BlameRequestType.CAR_BODY,
|
||||
),
|
||||
).toBe(29);
|
||||
expect(
|
||||
resolveFileReviewerExpertiseExpertId(
|
||||
{
|
||||
ThirdPartyExpertiseClaim: "2709",
|
||||
CarBodyExpertiseClaim: "15",
|
||||
},
|
||||
BlameRequestType.CAR_BODY,
|
||||
),
|
||||
).toBe(15);
|
||||
});
|
||||
|
||||
it("blocks file maker create when the product code is missing", () => {
|
||||
expect(() =>
|
||||
assertFileMakerCanCreateBlameType(
|
||||
{ ThirdPartyClaimExpertId: "154" },
|
||||
BlameRequestType.CAR_BODY,
|
||||
),
|
||||
).toThrow(/stage-1 claim expert id/);
|
||||
});
|
||||
|
||||
it("blocks file reviewer when the product code is missing", () => {
|
||||
expect(() =>
|
||||
assertFileReviewerCanReviewBlameType(
|
||||
{ CarBodyExpertiseClaim: "15" },
|
||||
BlameRequestType.THIRD_PARTY,
|
||||
),
|
||||
).toThrow(/expertise claim expert id/);
|
||||
});
|
||||
});
|
||||
90
src/fanavaran/fanavaran-file-role-expert-ids.ts
Normal file
90
src/fanavaran/fanavaran-file-role-expert-ids.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||
import { parseFanavaranId } from "src/lookups/fanavaran-last-car-policy";
|
||||
|
||||
export type FileMakerFanavaranExpertCodes = {
|
||||
ThirdPartyClaimExpertId?: string;
|
||||
CarBodyClaimExpertId?: string;
|
||||
};
|
||||
|
||||
export type FileReviewerFanavaranExpertCodes = {
|
||||
ThirdPartyExpertiseClaim?: string;
|
||||
CarBodyExpertiseClaim?: string;
|
||||
};
|
||||
|
||||
export function parseFanavaranExpertId(raw?: string | null): number | null {
|
||||
if (raw == null) return null;
|
||||
const trimmed = String(raw).trim();
|
||||
if (!trimmed) return null;
|
||||
return parseFanavaranId(trimmed) ?? parseFanavaranId(Number(trimmed));
|
||||
}
|
||||
|
||||
export function fileMakerStageOneExpertCode(
|
||||
fileMaker: FileMakerFanavaranExpertCodes,
|
||||
blameType: BlameRequestType,
|
||||
): string | undefined {
|
||||
return blameType === BlameRequestType.CAR_BODY
|
||||
? fileMaker.CarBodyClaimExpertId
|
||||
: fileMaker.ThirdPartyClaimExpertId;
|
||||
}
|
||||
|
||||
export function fileReviewerExpertiseExpertCode(
|
||||
fileReviewer: FileReviewerFanavaranExpertCodes,
|
||||
blameType: BlameRequestType,
|
||||
): string | undefined {
|
||||
return blameType === BlameRequestType.CAR_BODY
|
||||
? fileReviewer.CarBodyExpertiseClaim
|
||||
: fileReviewer.ThirdPartyExpertiseClaim;
|
||||
}
|
||||
|
||||
function blameTypeLabel(blameType: BlameRequestType): string {
|
||||
return blameType === BlameRequestType.CAR_BODY
|
||||
? "car body (بدنه)"
|
||||
: "third party (ثالث)";
|
||||
}
|
||||
|
||||
export function assertFileMakerCanCreateBlameType(
|
||||
fileMaker: FileMakerFanavaranExpertCodes,
|
||||
blameType: BlameRequestType,
|
||||
): void {
|
||||
const raw = fileMakerStageOneExpertCode(fileMaker, blameType);
|
||||
if (parseFanavaranExpertId(raw) != null) {
|
||||
return;
|
||||
}
|
||||
throw new BadRequestException({
|
||||
code: "FILE_MAKER_FANAVARAN_EXPERT_CODE_MISSING",
|
||||
message: `Your FileMaker profile has no Fanavaran stage-1 claim expert id for ${blameTypeLabel(blameType)} files. You cannot create this file type.`,
|
||||
});
|
||||
}
|
||||
|
||||
export function assertFileReviewerCanReviewBlameType(
|
||||
fileReviewer: FileReviewerFanavaranExpertCodes,
|
||||
blameType: BlameRequestType,
|
||||
): void {
|
||||
const raw = fileReviewerExpertiseExpertCode(fileReviewer, blameType);
|
||||
if (parseFanavaranExpertId(raw) != null) {
|
||||
return;
|
||||
}
|
||||
throw new BadRequestException({
|
||||
code: "FILE_REVIEWER_FANAVARAN_EXPERT_CODE_MISSING",
|
||||
message: `Your FileReviewer profile has no Fanavaran expertise claim expert id for ${blameTypeLabel(blameType)} files. You cannot review this file type.`,
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveFileMakerStageOneExpertId(
|
||||
fileMaker: FileMakerFanavaranExpertCodes,
|
||||
blameType: BlameRequestType,
|
||||
): number | null {
|
||||
return parseFanavaranExpertId(
|
||||
fileMakerStageOneExpertCode(fileMaker, blameType),
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveFileReviewerExpertiseExpertId(
|
||||
fileReviewer: FileReviewerFanavaranExpertCodes,
|
||||
blameType: BlameRequestType,
|
||||
): number | null {
|
||||
return parseFanavaranExpertId(
|
||||
fileReviewerExpertiseExpertCode(fileReviewer, blameType),
|
||||
);
|
||||
}
|
||||
@@ -52,6 +52,12 @@ import { StepsEnum } from "src/Types&Enums/blame-request-management/steps.enum";
|
||||
import { ExpertDbService } from "src/users/entities/db-service/expert.db.service";
|
||||
import { UserDbService } from "src/users/entities/db-service/user.db.service";
|
||||
import { FanavaranLocationService } from "src/fanavaran/fanavaran-location.service";
|
||||
import {
|
||||
assertFileMakerCanCreateBlameType,
|
||||
assertFileReviewerCanReviewBlameType,
|
||||
} from "src/fanavaran/fanavaran-file-role-expert-ids";
|
||||
import { FileMakerDbService } from "src/users/entities/db-service/file-maker.db.service";
|
||||
import { FileReviewerDbService } from "src/users/entities/db-service/file-reviewer.db.service";
|
||||
import { isOtpExpiryActive } from "src/helpers/user-otp-expiry";
|
||||
import { parseIranLocalDateTime } from "src/helpers/iran-datetime";
|
||||
import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
||||
@@ -1088,6 +1094,8 @@ export class RequestManagementService {
|
||||
private readonly hashService: HashService,
|
||||
private readonly userAuthService: UserAuthService,
|
||||
private readonly fanavaranLocationService: FanavaranLocationService,
|
||||
private readonly fileMakerDbService: FileMakerDbService,
|
||||
private readonly fileReviewerDbService: FileReviewerDbService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -5325,6 +5333,14 @@ export class RequestManagementService {
|
||||
"This file has been taken by another FileReviewer.",
|
||||
);
|
||||
}
|
||||
const fileReviewer = await this.fileReviewerDbService.findById(
|
||||
String(expert.sub),
|
||||
);
|
||||
if (!fileReviewer) {
|
||||
throw new ForbiddenException("FileReviewer account not found.");
|
||||
}
|
||||
assertFileReviewerCanReviewBlameType(fileReviewer, req.type);
|
||||
|
||||
if (!assignedId) {
|
||||
// Atomically claim — ignore if another reviewer won the race (they would have
|
||||
// been caught by the assignedId check above on their own first call).
|
||||
@@ -5633,6 +5649,13 @@ export class RequestManagementService {
|
||||
}
|
||||
|
||||
const isFileMakerRole = (expert as any)?.role === RoleEnum.FILE_MAKER;
|
||||
if (isFileMakerRole) {
|
||||
const fileMaker = await this.fileMakerDbService.findById(String(expert.sub));
|
||||
if (!fileMaker) {
|
||||
throw new ForbiddenException("FileMaker account not found.");
|
||||
}
|
||||
assertFileMakerCanCreateBlameType(fileMaker, type);
|
||||
}
|
||||
const created = await this.blameRequestDbService.create({
|
||||
publicId,
|
||||
requestNo: publicId,
|
||||
|
||||
Reference in New Issue
Block a user