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 { ImageRequiredModel } from "./entites/schema/image-required.schema";
|
||||||
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
|
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 { 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 { FieldExpertDbService } from "src/users/entities/db-service/field-expert.db.service";
|
||||||
import { FanavaranLocationService } from "src/fanavaran/fanavaran-location.service";
|
import { FanavaranLocationService } from "src/fanavaran/fanavaran-location.service";
|
||||||
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.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 fanavaranLookupService: FanavaranLookupService,
|
||||||
private readonly smsOrchestrationService: SmsOrchestrationService,
|
private readonly smsOrchestrationService: SmsOrchestrationService,
|
||||||
private readonly fileMakerDbService: FileMakerDbService,
|
private readonly fileMakerDbService: FileMakerDbService,
|
||||||
|
private readonly fileReviewerDbService: FileReviewerDbService,
|
||||||
private readonly fieldExpertDbService: FieldExpertDbService,
|
private readonly fieldExpertDbService: FieldExpertDbService,
|
||||||
private readonly plateNormalizer: PlateNormalizerService,
|
private readonly plateNormalizer: PlateNormalizerService,
|
||||||
private readonly fanavaranLocationService: FanavaranLocationService,
|
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(
|
private async getPolicyIdFromNationalCode(
|
||||||
nationalCodeOfInsurer: string,
|
nationalCodeOfInsurer: string,
|
||||||
config: {
|
config: {
|
||||||
@@ -6075,6 +6132,19 @@ export class ClaimRequestManagementService {
|
|||||||
Object.assign(payload, hull);
|
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) {
|
if (persistPayload) {
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
await this.claimCaseDbService.findByIdAndUpdate(claimCaseId, {
|
||||||
$set: {
|
$set: {
|
||||||
@@ -7048,10 +7118,35 @@ export class ClaimRequestManagementService {
|
|||||||
const profile = getFanavaranClientProfile(input.clientKey);
|
const profile = getFanavaranClientProfile(input.clientKey);
|
||||||
const active = getActiveV2ExpertReply(input.claimCase as any);
|
const active = getActiveV2ExpertReply(input.claimCase as any);
|
||||||
|
|
||||||
// GEN.08 ClaimExpertId = tenant ExpertiseClaimExpertId (assessor role).
|
const blameForExpertIds = input.claimCase?.blameRequestId
|
||||||
// Tejaratno: 2709. Parsian: 29. Must NOT reuse GEN.03 ClaimExpertId
|
? await this.blameRequestDbService.findById(
|
||||||
// (مسئول پرونده مالی — Tejaratno 4543092 / Parsian 154).
|
String(input.claimCase.blameRequestId),
|
||||||
const claimExpertId = profile.defaults.ExpertiseClaimExpertId;
|
)
|
||||||
|
: 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[] = [];
|
const warnings: string[] = [];
|
||||||
|
|
||||||
if (!active?.parts?.length) {
|
if (!active?.parts?.length) {
|
||||||
@@ -7106,11 +7201,7 @@ export class ClaimRequestManagementService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (product === "car-body") {
|
if (product === "car-body") {
|
||||||
const blame = input.claimCase?.blameRequestId
|
const blame = blameForExpertIds;
|
||||||
? await this.blameRequestDbService.findById(
|
|
||||||
String(input.claimCase.blameRequestId),
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
return this.buildCarBodyFanavaranExpertisePayload({
|
return this.buildCarBodyFanavaranExpertisePayload({
|
||||||
clientKey: input.clientKey,
|
clientKey: input.clientKey,
|
||||||
claimCase: input.claimCase,
|
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 { ExpertDbService } from "src/users/entities/db-service/expert.db.service";
|
||||||
import { UserDbService } from "src/users/entities/db-service/user.db.service";
|
import { UserDbService } from "src/users/entities/db-service/user.db.service";
|
||||||
import { FanavaranLocationService } from "src/fanavaran/fanavaran-location.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 { isOtpExpiryActive } from "src/helpers/user-otp-expiry";
|
||||||
import { parseIranLocalDateTime } from "src/helpers/iran-datetime";
|
import { parseIranLocalDateTime } from "src/helpers/iran-datetime";
|
||||||
import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
||||||
@@ -1088,6 +1094,8 @@ export class RequestManagementService {
|
|||||||
private readonly hashService: HashService,
|
private readonly hashService: HashService,
|
||||||
private readonly userAuthService: UserAuthService,
|
private readonly userAuthService: UserAuthService,
|
||||||
private readonly fanavaranLocationService: FanavaranLocationService,
|
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.",
|
"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) {
|
if (!assignedId) {
|
||||||
// Atomically claim — ignore if another reviewer won the race (they would have
|
// Atomically claim — ignore if another reviewer won the race (they would have
|
||||||
// been caught by the assignedId check above on their own first call).
|
// 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;
|
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({
|
const created = await this.blameRequestDbService.create({
|
||||||
publicId,
|
publicId,
|
||||||
requestNo: publicId,
|
requestNo: publicId,
|
||||||
|
|||||||
Reference in New Issue
Block a user