forked from Yara724/api
locations dynamically in fanavaran
This commit is contained in:
@@ -102,6 +102,7 @@ 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 { 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";
|
||||
import { SandHubService } from "src/sand-hub/sand-hub.service";
|
||||
import {
|
||||
@@ -393,6 +394,7 @@ export class ClaimRequestManagementService {
|
||||
private readonly fileMakerDbService: FileMakerDbService,
|
||||
private readonly fieldExpertDbService: FieldExpertDbService,
|
||||
private readonly plateNormalizer: PlateNormalizerService,
|
||||
private readonly fanavaranLocationService: FanavaranLocationService,
|
||||
) {}
|
||||
|
||||
private requiredDocumentKeysV2(isCarBody: boolean): string[] {
|
||||
@@ -4365,12 +4367,63 @@ export class ClaimRequestManagementService {
|
||||
private async getFanavaranAuthHeaders(
|
||||
clientKey: FanavaranClientKey,
|
||||
auditSession?: FanavaranAuditSession,
|
||||
claimCaseId?: string,
|
||||
) {
|
||||
const caseId = claimCaseId ?? auditSession?.claimCaseId;
|
||||
const locationOverride = caseId
|
||||
? await this.resolveFanavaranLocationForClaim(caseId, clientKey)
|
||||
: undefined;
|
||||
return this.fanavaranAuthService.getRequestHeaders(clientKey, {
|
||||
auditSession,
|
||||
locationOverride,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsian + V4/V5 FileMaker flows: Location from file-maker.locations[].id,
|
||||
* else file-reviewer.locations[].id, else tenant auth.location.
|
||||
*/
|
||||
private async resolveFanavaranLocationForClaim(
|
||||
claimCaseId: string,
|
||||
clientKey: FanavaranClientKey,
|
||||
): Promise<string> {
|
||||
const defaultLocation = getFanavaranClientProfile(clientKey).auth.location;
|
||||
if (clientKey !== "parsian") {
|
||||
return defaultLocation;
|
||||
}
|
||||
|
||||
try {
|
||||
const claimCase = await this.claimCaseDbService.findById(claimCaseId);
|
||||
if (!claimCase?.blameRequestId) {
|
||||
return defaultLocation;
|
||||
}
|
||||
|
||||
const blame = await this.blameRequestDbService.findById(
|
||||
String(claimCase.blameRequestId),
|
||||
);
|
||||
if (!(blame as any)?.isMadeByFileMaker) {
|
||||
return defaultLocation;
|
||||
}
|
||||
|
||||
return this.fanavaranLocationService.resolveBusinessLocation({
|
||||
clientKey,
|
||||
isMadeByFileMaker: true,
|
||||
fileMakerId: (blame as any)?.initiatedByFieldExpertId
|
||||
? String((blame as any).initiatedByFieldExpertId)
|
||||
: null,
|
||||
fileReviewerId: (blame as any)?.assignedFileReviewerId
|
||||
? String((blame as any).assignedFileReviewerId)
|
||||
: null,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.warn(
|
||||
`[Fanavaran Location] Failed to resolve for claimCaseId=${claimCaseId}; using tenant default`,
|
||||
error,
|
||||
);
|
||||
return defaultLocation;
|
||||
}
|
||||
}
|
||||
|
||||
private async getPolicyIdFromNationalCode(
|
||||
nationalCodeOfInsurer: string,
|
||||
config: {
|
||||
@@ -4417,6 +4470,7 @@ export class ClaimRequestManagementService {
|
||||
const headers = await this.getFanavaranAuthHeaders(
|
||||
clientKey,
|
||||
auditSession,
|
||||
options?.claimCaseId,
|
||||
);
|
||||
const requestHeaders = {
|
||||
...headers,
|
||||
@@ -5220,6 +5274,8 @@ export class ClaimRequestManagementService {
|
||||
content,
|
||||
[{ path: candidate.path, fileName: candidate.fileName }],
|
||||
clientKey,
|
||||
undefined,
|
||||
claimCaseId,
|
||||
);
|
||||
this.logger.log(
|
||||
`${logPrefix} [${i + 1}/${pending.length}] SUCCESS: status=${response.status} body=${JSON.stringify(response.data)}`,
|
||||
@@ -6276,9 +6332,14 @@ export class ClaimRequestManagementService {
|
||||
payload: Record<string, unknown>,
|
||||
clientKey: FanavaranClientKey,
|
||||
auditSession?: FanavaranAuditSession,
|
||||
claimCaseId?: string,
|
||||
) {
|
||||
this.fanavaranAuthService.assertNotInBackoff(clientKey);
|
||||
const headers = await this.getFanavaranAuthHeaders(clientKey, auditSession);
|
||||
const headers = await this.getFanavaranAuthHeaders(
|
||||
clientKey,
|
||||
auditSession,
|
||||
claimCaseId,
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
@@ -6303,9 +6364,14 @@ export class ClaimRequestManagementService {
|
||||
files: Array<{ path: string; fileName: string }>,
|
||||
clientKey: FanavaranClientKey,
|
||||
auditSession?: FanavaranAuditSession,
|
||||
claimCaseId?: string,
|
||||
) {
|
||||
this.fanavaranAuthService.assertNotInBackoff(clientKey);
|
||||
const headers = await this.getFanavaranAuthHeaders(clientKey, auditSession);
|
||||
const headers = await this.getFanavaranAuthHeaders(
|
||||
clientKey,
|
||||
auditSession,
|
||||
claimCaseId,
|
||||
);
|
||||
const form = new FormData();
|
||||
|
||||
form.append("Param", JSON.stringify(content), {
|
||||
@@ -7242,6 +7308,7 @@ export class ClaimRequestManagementService {
|
||||
const headers = await this.getFanavaranAuthHeaders(
|
||||
clientKey,
|
||||
auditSession,
|
||||
claimCaseId,
|
||||
);
|
||||
|
||||
const requestHeaders = {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { RequestManagementModule } from "src/request-management/request-manageme
|
||||
import { SandHubModule } from "src/sand-hub/sand-hub.module";
|
||||
import { SmsOrchestrationModule } from "src/sms-orchestration/sms-orchestration.module";
|
||||
import { UsersModule } from "src/users/users.module";
|
||||
import { FanavaranLookupModule } from "src/fanavaran/fanavaran-lookup.module";
|
||||
import { ExpertClaimController } from "./expert-claim.controller";
|
||||
import { ExpertClaimV2Controller } from "./expert-claim.v2.controller";
|
||||
import { ExpertClaimService } from "./expert-claim.service";
|
||||
@@ -41,6 +42,7 @@ import { ExpertClaimService } from "./expert-claim.service";
|
||||
SmsOrchestrationModule,
|
||||
UsersModule,
|
||||
ClientModule,
|
||||
FanavaranLookupModule,
|
||||
],
|
||||
controllers: [ExpertClaimController, ExpertClaimV2Controller],
|
||||
providers: [ExpertClaimService, ClaimFactorsImageDbService],
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
FanavaranAutoSubmitResult,
|
||||
FanavaranExpertiseSubmitResult,
|
||||
} from "src/claim-request-management/claim-request-management.service";
|
||||
import { FanavaranLocationService } from "src/fanavaran/fanavaran-location.service";
|
||||
import { ClaimSignDbService } from "src/claim-request-management/entites/db-service/claim-sign.db.service";
|
||||
import { DamageImageDbService } from "src/claim-request-management/entites/db-service/damage-image.db.service";
|
||||
import { VideoCaptureDbService } from "src/claim-request-management/entites/db-service/video-capture.db.service";
|
||||
@@ -291,6 +292,7 @@ export class ExpertClaimService {
|
||||
private readonly expertFileActivityDbService: ExpertFileActivityDbService,
|
||||
private readonly claimSignDbService: ClaimSignDbService,
|
||||
private readonly claimRequestManagementService: ClaimRequestManagementService,
|
||||
private readonly fanavaranLocationService: FanavaranLocationService,
|
||||
) {}
|
||||
|
||||
private appendFanavaranAutoSubmitToMessage(
|
||||
@@ -2590,6 +2592,12 @@ export class ExpertClaimService {
|
||||
|
||||
// Atomically claim it — findOneAndUpdate with null/missing guard
|
||||
const reviewerOid = new Types.ObjectId(actor.sub);
|
||||
await this.fanavaranLocationService.assertMakerReviewerLocationCompatible({
|
||||
fileMakerId: (blame as any)?.initiatedByFieldExpertId
|
||||
? String((blame as any).initiatedByFieldExpertId)
|
||||
: null,
|
||||
fileReviewerId: actor.sub,
|
||||
});
|
||||
const updated = await this.blameRequestDbService.findOneAndUpdate(
|
||||
{
|
||||
_id: (blame as any)._id,
|
||||
|
||||
@@ -1,8 +1,30 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsEmail, IsEnum, IsMongoId, IsNotEmpty, IsOptional, IsString } from "class-validator";
|
||||
import { Type } from "class-transformer";
|
||||
import {
|
||||
IsArray,
|
||||
IsEmail,
|
||||
IsEnum,
|
||||
IsMongoId,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { UserType } from "src/Types&Enums/userType.enum";
|
||||
|
||||
export class ExpertLocationDto {
|
||||
@ApiProperty({ example: "210050", description: "Fanavaran Location / OpBUId" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
id: string;
|
||||
|
||||
@ApiProperty({ example: "شعبه غرب" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class CreateInsurerExpertDto {
|
||||
@ApiProperty({ example: "Ali" })
|
||||
@IsString()
|
||||
@@ -88,6 +110,17 @@ export class CreateFileMakerByInsurerDto extends CreateInsurerExpertDto {
|
||||
default: RoleEnum.FILE_MAKER,
|
||||
})
|
||||
role?: RoleEnum.FILE_MAKER;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: [ExpertLocationDto],
|
||||
description:
|
||||
"Fanavaran Location entries (id = OpBUId). Used for Parsian V4/V5 submits.",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ExpertLocationDto)
|
||||
locations?: ExpertLocationDto[];
|
||||
}
|
||||
|
||||
export class CreateFileReviewerByInsurerDto extends CreateInsurerExpertDto {
|
||||
@@ -96,4 +129,15 @@ export class CreateFileReviewerByInsurerDto extends CreateInsurerExpertDto {
|
||||
default: RoleEnum.FILE_REVIEWER,
|
||||
})
|
||||
role?: RoleEnum.FILE_REVIEWER;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: [ExpertLocationDto],
|
||||
description:
|
||||
"Fanavaran Location entries. Must overlap FileMaker locations on Parsian V4/V5 cases.",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ExpertLocationDto)
|
||||
locations?: ExpertLocationDto[];
|
||||
}
|
||||
|
||||
@@ -2018,6 +2018,19 @@ export class ExpertInsurerService {
|
||||
return rest;
|
||||
}
|
||||
|
||||
private normalizeExpertLocations(
|
||||
locations?: Array<{ id?: string; name?: string }> | null,
|
||||
): Array<{ id: string; name: string }> | undefined {
|
||||
if (!Array.isArray(locations) || locations.length === 0) return undefined;
|
||||
const normalized = locations
|
||||
.map((entry) => ({
|
||||
id: String(entry?.id ?? "").trim(),
|
||||
name: String(entry?.name ?? "").trim(),
|
||||
}))
|
||||
.filter((entry) => entry.id && entry.name);
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
private async createExpertForInsurer(
|
||||
insurerClientKey: string,
|
||||
payload: CreateInsurerExpertDto,
|
||||
@@ -2132,6 +2145,7 @@ export class ExpertInsurerService {
|
||||
}
|
||||
|
||||
const hashedPassword = await this.hashService.hash(payload.password);
|
||||
const locations = this.normalizeExpertLocations(payload.locations);
|
||||
const created = await this.fileMakerDbService.create({
|
||||
...payload,
|
||||
email,
|
||||
@@ -2140,6 +2154,7 @@ export class ExpertInsurerService {
|
||||
role: RoleEnum.FILE_MAKER,
|
||||
clientKey: clientObjectId,
|
||||
branchId: new Types.ObjectId(payload.branchId),
|
||||
...(locations ? { locations } : {}),
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -2184,6 +2199,7 @@ export class ExpertInsurerService {
|
||||
}
|
||||
|
||||
const hashedPassword = await this.hashService.hash(payload.password);
|
||||
const locations = this.normalizeExpertLocations(payload.locations);
|
||||
const created = await this.fileReviewerDbService.create({
|
||||
...payload,
|
||||
email,
|
||||
@@ -2192,6 +2208,7 @@ export class ExpertInsurerService {
|
||||
role: RoleEnum.FILE_REVIEWER,
|
||||
clientKey: clientObjectId,
|
||||
branchId: new Types.ObjectId(payload.branchId),
|
||||
...(locations ? { locations } : {}),
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -249,6 +249,8 @@ export class FanavaranAuthService {
|
||||
options?: {
|
||||
auditSession?: FanavaranAuditSession;
|
||||
forceRefresh?: boolean;
|
||||
/** Per-request Location override (does not affect token fingerprint). */
|
||||
locationOverride?: string;
|
||||
},
|
||||
): Promise<{
|
||||
authenticationToken: string;
|
||||
@@ -261,11 +263,12 @@ export class FanavaranAuthService {
|
||||
clientKey,
|
||||
options,
|
||||
);
|
||||
const locationOverride = options?.locationOverride?.trim();
|
||||
return {
|
||||
authenticationToken,
|
||||
CorpId: profile.auth.corpId,
|
||||
ContractId: profile.auth.contractId,
|
||||
Location: profile.auth.location,
|
||||
Location: locationOverride || profile.auth.location,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
139
src/fanavaran/fanavaran-location.service.ts
Normal file
139
src/fanavaran/fanavaran-location.service.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { ForbiddenException, Injectable, Logger } from "@nestjs/common";
|
||||
import {
|
||||
FanavaranClientKey,
|
||||
getFanavaranClientProfile,
|
||||
resolveFanavaranClientKey,
|
||||
} from "src/core/config/fanavaran-client.config";
|
||||
import { FileMakerDbService } from "src/users/entities/db-service/file-maker.db.service";
|
||||
import { FileReviewerDbService } from "src/users/entities/db-service/file-reviewer.db.service";
|
||||
|
||||
type LocationEntry = { id?: string; name?: string };
|
||||
|
||||
/**
|
||||
* Parsian V4/V5 FileMaker flows: resolve Fanavaran HTTP `Location` from
|
||||
* file-maker / file-reviewer `locations[].id`, and enforce maker/reviewer match.
|
||||
*/
|
||||
@Injectable()
|
||||
export class FanavaranLocationService {
|
||||
private readonly logger = new Logger(FanavaranLocationService.name);
|
||||
|
||||
constructor(
|
||||
private readonly fileMakerDbService: FileMakerDbService,
|
||||
private readonly fileReviewerDbService: FileReviewerDbService,
|
||||
) {}
|
||||
|
||||
isParsianClient(clientKey?: FanavaranClientKey): boolean {
|
||||
return (clientKey ?? resolveFanavaranClientKey()) === "parsian";
|
||||
}
|
||||
|
||||
extractLocationIds(locations?: LocationEntry[] | null): string[] {
|
||||
if (!Array.isArray(locations) || locations.length === 0) return [];
|
||||
const ids: string[] = [];
|
||||
for (const entry of locations) {
|
||||
const id = String(entry?.id ?? "").trim();
|
||||
if (id && !ids.includes(id)) ids.push(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
primaryLocationId(locations?: LocationEntry[] | null): string | undefined {
|
||||
return this.extractLocationIds(locations)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maker primary location → reviewer primary → tenant auth.location.
|
||||
* Non-parsian or non-FileMaker flows always use the tenant default.
|
||||
*/
|
||||
async resolveBusinessLocation(input: {
|
||||
clientKey: FanavaranClientKey;
|
||||
isMadeByFileMaker?: boolean;
|
||||
fileMakerId?: string | null;
|
||||
fileReviewerId?: string | null;
|
||||
}): Promise<string> {
|
||||
const defaultLocation = getFanavaranClientProfile(input.clientKey).auth
|
||||
.location;
|
||||
|
||||
if (input.clientKey !== "parsian" || input.isMadeByFileMaker !== true) {
|
||||
return defaultLocation;
|
||||
}
|
||||
|
||||
const makerLocation = await this.loadPrimaryLocationId(input.fileMakerId, "maker");
|
||||
if (makerLocation) {
|
||||
this.logger.debug(
|
||||
`Fanavaran Location from file-maker id=${input.fileMakerId} → ${makerLocation}`,
|
||||
);
|
||||
return makerLocation;
|
||||
}
|
||||
|
||||
const reviewerLocation = await this.loadPrimaryLocationId(
|
||||
input.fileReviewerId,
|
||||
"reviewer",
|
||||
);
|
||||
if (reviewerLocation) {
|
||||
this.logger.debug(
|
||||
`Fanavaran Location from file-reviewer id=${input.fileReviewerId} → ${reviewerLocation}`,
|
||||
);
|
||||
return reviewerLocation;
|
||||
}
|
||||
|
||||
this.logger.debug(
|
||||
`Fanavaran Location fallback to tenant default → ${defaultLocation}`,
|
||||
);
|
||||
return defaultLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* When both maker and reviewer have locations, they must share at least one id.
|
||||
* No-op for non-parsian deployments or when either side has no locations.
|
||||
*/
|
||||
async assertMakerReviewerLocationCompatible(input: {
|
||||
fileMakerId?: string | null;
|
||||
fileReviewerId?: string | null;
|
||||
clientKey?: FanavaranClientKey;
|
||||
}): Promise<void> {
|
||||
if (!this.isParsianClient(input.clientKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const makerIds = await this.loadLocationIds(input.fileMakerId, "maker");
|
||||
const reviewerIds = await this.loadLocationIds(
|
||||
input.fileReviewerId,
|
||||
"reviewer",
|
||||
);
|
||||
|
||||
if (makerIds.length === 0 || reviewerIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shared = makerIds.some((id) => reviewerIds.includes(id));
|
||||
if (!shared) {
|
||||
throw new ForbiddenException(
|
||||
"FileReviewer location must match the FileMaker location for this case.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadPrimaryLocationId(
|
||||
userId: string | null | undefined,
|
||||
kind: "maker" | "reviewer",
|
||||
): Promise<string | undefined> {
|
||||
const ids = await this.loadLocationIds(userId, kind);
|
||||
return ids[0];
|
||||
}
|
||||
|
||||
private async loadLocationIds(
|
||||
userId: string | null | undefined,
|
||||
kind: "maker" | "reviewer",
|
||||
): Promise<string[]> {
|
||||
if (!userId) return [];
|
||||
try {
|
||||
const doc =
|
||||
kind === "maker"
|
||||
? await this.fileMakerDbService.findById(String(userId))
|
||||
: await this.fileReviewerDbService.findById(String(userId));
|
||||
return this.extractLocationIds((doc as any)?.locations);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,11 @@ import {
|
||||
LookupModel,
|
||||
LookupSchema,
|
||||
} from "src/lookups/entities/schema/lookup.schema";
|
||||
import { UsersModule } from "src/users/users.module";
|
||||
import { FanavaranAuditModule } from "./fanavaran-audit.module";
|
||||
import { FanavaranAuthService } from "./fanavaran-auth.service";
|
||||
import { FanavaranClientConfigService } from "./fanavaran-client-config.service";
|
||||
import { FanavaranLocationService } from "./fanavaran-location.service";
|
||||
import { FanavaranLookupService } from "./fanavaran-lookup.service";
|
||||
import {
|
||||
FanavaranAuthToken,
|
||||
@@ -36,16 +38,19 @@ import {
|
||||
{ name: LookupModel.name, schema: LookupSchema },
|
||||
]),
|
||||
FanavaranAuditModule,
|
||||
UsersModule,
|
||||
],
|
||||
providers: [
|
||||
FanavaranClientConfigService,
|
||||
FanavaranAuthService,
|
||||
FanavaranLookupService,
|
||||
FanavaranLocationService,
|
||||
],
|
||||
exports: [
|
||||
FanavaranClientConfigService,
|
||||
FanavaranAuthService,
|
||||
FanavaranLookupService,
|
||||
FanavaranLocationService,
|
||||
],
|
||||
})
|
||||
export class FanavaranLookupModule {}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { RequestManagementDbService } from "src/request-management/entities/db-s
|
||||
import { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service";
|
||||
import { SandHubModule } from "src/sand-hub/sand-hub.module";
|
||||
import { UsersModule } from "src/users/users.module";
|
||||
import { FanavaranLookupModule } from "src/fanavaran/fanavaran-lookup.module";
|
||||
import { CronModule } from "src/utils/cron/cron.module";
|
||||
import { PublicIdModule } from "src/utils/public-id/public-id.module";
|
||||
import { HashModule } from "src/utils/hash/hash.module";
|
||||
@@ -64,6 +65,7 @@ import { CallCenterBlameV6Controller } from "./call-center-blame-v6.controller";
|
||||
PlateNormalizerModule,
|
||||
WorkflowStepManagementModule,
|
||||
PlatesModule,
|
||||
FanavaranLookupModule,
|
||||
MulterModule.register({
|
||||
dest: "./files/video",
|
||||
}),
|
||||
|
||||
@@ -48,6 +48,7 @@ import { ReqBlameStatus } from "src/Types&Enums/blame-request-management/status.
|
||||
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 { isOtpExpiryActive } from "src/helpers/user-otp-expiry";
|
||||
import { parseIranLocalDateTime } from "src/helpers/iran-datetime";
|
||||
import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
||||
@@ -617,6 +618,7 @@ export class RequestManagementService {
|
||||
private readonly workflowStepDbService: WorkflowStepDbService,
|
||||
private readonly hashService: HashService,
|
||||
private readonly userAuthService: UserAuthService,
|
||||
private readonly fanavaranLocationService: FanavaranLocationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -4707,6 +4709,12 @@ export class RequestManagementService {
|
||||
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).
|
||||
await this.fanavaranLocationService.assertMakerReviewerLocationCompatible({
|
||||
fileMakerId: req?.initiatedByFieldExpertId
|
||||
? String(req.initiatedByFieldExpertId)
|
||||
: null,
|
||||
fileReviewerId: String(expert.sub),
|
||||
});
|
||||
await this.blameRequestDbService.findOneAndUpdate(
|
||||
{
|
||||
_id: req._id,
|
||||
@@ -4718,6 +4726,14 @@ export class RequestManagementService {
|
||||
{ $set: { assignedFileReviewerId: new Types.ObjectId(String(expert.sub)) } },
|
||||
{ new: false },
|
||||
);
|
||||
} else {
|
||||
// Already assigned to this reviewer — re-check location compatibility.
|
||||
await this.fanavaranLocationService.assertMakerReviewerLocationCompatible({
|
||||
fileMakerId: req?.initiatedByFieldExpertId
|
||||
? String(req.initiatedByFieldExpertId)
|
||||
: null,
|
||||
fileReviewerId: String(expert.sub),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user