Merge pull request 'fanavaran configs seeding' (#240) from s.hajizadeh/yara724api:main into main

Reviewed-on: Yara724/api#240
This commit is contained in:
2026-08-03 09:56:36 +03:30
5 changed files with 285 additions and 18 deletions

View File

@@ -299,10 +299,10 @@ export class ClaimRequestManagementService {
private readonly USERNAME = "fanhabUser";
private readonly PASSWORD = "Fan#@2U$3er";
// API headers
// API headers (legacy Tejaratno — prefer getFanavaranClientProfile)
private readonly CORP_ID = "3539";
private readonly CONTRACT_ID = "263";
private readonly LOCATION = "119";
private readonly LOCATION = "100";
private readonly PARSIAN_FANAVARAN_CONFIG = {
appName: "ParsianService",
@@ -314,12 +314,12 @@ export class ClaimRequestManagementService {
location: "210050",
} as const;
/** Tejaratno hardcoded Fanavaran lookup defaults (legacy). */
/** Tejaratno hardcoded Fanavaran lookup defaults (legacy fallback only). */
private readonly TEJARATNO_FANAVARAN_DEFAULTS = {
AccidentCityId: 701,
AccidentReportTypeId: 155,
AccidentVehicleUsedId: 1,
ClaimExpertId: 2709,// before 4543092
ClaimExpertId: 4543092,
CompensationReferenceId: 167,
CulpritLicenceTypeId: 2,
CulpritTypeId: 337,
@@ -4139,7 +4139,7 @@ export class ClaimRequestManagementService {
AccidentCityId: 701,
AccidentReportTypeId: 155,
AccidentVehicleUsedId: 1,
ClaimExpertId: 2709,
ClaimExpertId: 4543092,
CompensationReferenceId: 167,
CulpritLicenceTypeId: 2,
CulpritTypeId: 337,
@@ -5612,6 +5612,9 @@ 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 warnings: string[] = [];

View File

@@ -42,7 +42,15 @@ export interface FanavaranPayloadDefaults {
AccidentCityId: number;
AccidentReportTypeId: number;
AccidentVehicleUsedId: number;
/**
* GEN.03 base claim — کارشناس مسئول پرونده مالی
* (must NOT be the assessor role used on GEN.08).
*/
ClaimExpertId: number;
/**
* GEN.08 expertise — کارشناس ارزیاب خسارت ثالث مالی / وکیل معتمد / کارشناس تحقیق
* (Parsian: 29; Tejaratno proven: 2709).
*/
ExpertiseClaimExpertId: number;
CompensationReferenceId: number;
CulpritLicenceTypeId: number;
@@ -63,15 +71,14 @@ export interface FanavaranClientProfile {
}
/**
* Shared codebook-ish defaults used when a tenant has not supplied its own
* ClaimExpertId / plaque ids yet. Moallem auth is real; ClaimExpertId may need
* a Moallem-specific value from Fanavaran lookups after first deploy.
* Shared codebook-ish defaults. Each tenant must override ClaimExpertId /
* ExpertiseClaimExpertId (they are different Fanavaran roles).
*/
const SHARED_FANAVARAN_DEFAULTS: FanavaranPayloadDefaults = {
AccidentCityId: 701,
AccidentReportTypeId: 155,
AccidentVehicleUsedId: 1,
ClaimExpertId: 2709,
ClaimExpertId: 4543092,
ExpertiseClaimExpertId: 2709,
CompensationReferenceId: 167,
CulpritLicenceTypeId: 2,
@@ -85,7 +92,12 @@ const SHARED_FANAVARAN_DEFAULTS: FanavaranPayloadDefaults = {
ClaimFileTypeId: 23,
};
const FANAVARAN_CLIENT_PROFILES: Record<
/**
* Seed / code fallback profiles. On app boot these are inserted into
* `fanavaranClientConfigs` only when a key is missing (never overwrite DB edits).
* Runtime reads prefer the in-memory cache loaded from Mongo.
*/
export const SEED_FANAVARAN_CLIENT_PROFILES: Record<
FanavaranClientKey,
FanavaranClientProfile
> = {
@@ -102,7 +114,9 @@ const FANAVARAN_CLIENT_PROFILES: Record<
},
defaults: {
...SHARED_FANAVARAN_DEFAULTS,
ClaimExpertId: 2709,
// GEN.03 — کارشناس مسئول پرونده مالی
ClaimExpertId: 4543092,
// GEN.08 — ارزیاب (proven successful submit 2026-07-18 ClaimExpertId: 2709)
ExpertiseClaimExpertId: 2709,
ClaimFileTypeId: 23,
},
@@ -120,7 +134,9 @@ const FANAVARAN_CLIENT_PROFILES: Record<
},
defaults: {
...SHARED_FANAVARAN_DEFAULTS,
// GEN.03 — مسئول پرونده مالی (Fanavaran example used 154; west branch 4662)
ClaimExpertId: 154,
// GEN.08 — ارزیاب خسارت ثالث مالی (رسول کرکی / شعبه غرب → 29)
ExpertiseClaimExpertId: 29,
ClaimFileTypeId: 70,
},
@@ -136,14 +152,34 @@ const FANAVARAN_CLIENT_PROFILES: Record<
contractId: "304",
location: "30900",
},
// ClaimExpertId / ClaimFileTypeId not yet confirmed for Moallem — start from
// shared Fanavaran codebook defaults and override after lookup.
// Expert role ids not confirmed for Moallem yet — start from shared shape.
defaults: {
...SHARED_FANAVARAN_DEFAULTS,
},
},
};
/** @deprecated Use SEED_FANAVARAN_CLIENT_PROFILES — kept alias for older imports. */
export const FANAVARAN_CLIENT_PROFILES = SEED_FANAVARAN_CLIENT_PROFILES;
/**
* Runtime cache filled by FanavaranClientConfigService on boot from Mongo.
* Until then (and in unit tests), callers fall back to seed profiles.
*/
let runtimeProfiles: Partial<
Record<FanavaranClientKey, FanavaranClientProfile>
> | null = null;
export function setFanavaranClientProfilesCache(
profiles: Partial<Record<FanavaranClientKey, FanavaranClientProfile>>,
): void {
runtimeProfiles = profiles;
}
export function clearFanavaranClientProfilesCache(): void {
runtimeProfiles = null;
}
/** Resolve active Fanavaran tenant from env (`FANAVARAN_CLIENT`) with optional CLIENT_ID fallback. */
export function resolveFanavaranClientKey(): FanavaranClientKey {
const explicit = process.env.FANAVARAN_CLIENT?.trim().toLowerCase();
@@ -160,17 +196,19 @@ export function resolveFanavaranClientKey(): FanavaranClientKey {
}
export function resolveFanavaranClientProfile(): FanavaranClientProfile {
return FANAVARAN_CLIENT_PROFILES[resolveFanavaranClientKey()];
return getFanavaranClientProfile(resolveFanavaranClientKey());
}
export function getFanavaranClientProfile(
clientKey: FanavaranClientKey,
): FanavaranClientProfile {
return FANAVARAN_CLIENT_PROFILES[clientKey];
return (
runtimeProfiles?.[clientKey] ?? SEED_FANAVARAN_CLIENT_PROFILES[clientKey]
);
}
export function listFanavaranClientProfiles(): FanavaranClientProfile[] {
return FANAVARAN_CLIENT_KEYS.map((key) => FANAVARAN_CLIENT_PROFILES[key]);
return FANAVARAN_CLIENT_KEYS.map((key) => getFanavaranClientProfile(key));
}
export function fanavaranPreviewPath(

View File

@@ -0,0 +1,98 @@
import { Injectable, Logger, OnModuleInit } from "@nestjs/common";
import { InjectModel } from "@nestjs/mongoose";
import { Model } from "mongoose";
import {
FANAVARAN_CLIENT_KEYS,
SEED_FANAVARAN_CLIENT_PROFILES,
setFanavaranClientProfilesCache,
type FanavaranClientKey,
type FanavaranClientProfile,
} from "src/core/config/fanavaran-client.config";
import {
FanavaranClientConfig,
FanavaranClientConfigDocument,
} from "./schema/fanavaran-client-config.schema";
/**
* Loads / seeds per-tenant Fanavaran auth+defaults from Mongo and exposes them
* through the sync helpers in fanavaran-client.config.ts.
*
* Boot behaviour: for each known client key, if no document exists, insert the
* seed profile. Existing documents are never overwritten (ops can edit Mongo).
*/
@Injectable()
export class FanavaranClientConfigService implements OnModuleInit {
private readonly logger = new Logger(FanavaranClientConfigService.name);
constructor(
@InjectModel(FanavaranClientConfig.name)
private readonly configModel: Model<FanavaranClientConfigDocument>,
) {}
async onModuleInit(): Promise<void> {
await this.seedMissingProfiles();
await this.reloadCache();
}
async seedMissingProfiles(): Promise<void> {
for (const key of FANAVARAN_CLIENT_KEYS) {
const seed = SEED_FANAVARAN_CLIENT_PROFILES[key];
const existing = await this.configModel
.findOne({ key })
.select({ _id: 1 })
.lean()
.exec();
if (existing) {
continue;
}
await this.configModel.create({
key: seed.key,
auth: seed.auth,
defaults: seed.defaults,
});
this.logger.log(
`Seeded fanavaranClientConfigs for client "${key}" (ClaimExpertId=${seed.defaults.ClaimExpertId}, ExpertiseClaimExpertId=${seed.defaults.ExpertiseClaimExpertId})`,
);
}
}
async reloadCache(): Promise<void> {
const docs = await this.configModel.find().lean().exec();
const cache: Partial<Record<FanavaranClientKey, FanavaranClientProfile>> =
{};
for (const key of FANAVARAN_CLIENT_KEYS) {
const doc = docs.find((row) => row.key === key);
if (doc?.auth && doc?.defaults) {
cache[key] = {
key,
auth: {
appName: doc.auth.appName,
secret: doc.auth.secret,
username: doc.auth.username,
password: doc.auth.password,
corpId: doc.auth.corpId,
contractId: doc.auth.contractId,
location: doc.auth.location,
},
defaults: { ...doc.defaults },
};
} else {
cache[key] = SEED_FANAVARAN_CLIENT_PROFILES[key];
}
}
setFanavaranClientProfilesCache(cache);
this.logger.log(
`Loaded ${docs.length} Fanavaran client config(s) from Mongo into runtime cache`,
);
}
async findByKey(key: FanavaranClientKey) {
return this.configModel.findOne({ key }).lean().exec();
}
async list() {
return this.configModel.find().lean().exec();
}
}

View File

@@ -5,11 +5,16 @@ import { MongooseModule } from "@nestjs/mongoose";
import { createHttpModuleOptions } from "src/core/config/http-proxy.factory";
import { FanavaranAuditModule } from "./fanavaran-audit.module";
import { FanavaranAuthService } from "./fanavaran-auth.service";
import { FanavaranClientConfigService } from "./fanavaran-client-config.service";
import { FanavaranLookupService } from "./fanavaran-lookup.service";
import {
FanavaranAuthToken,
FanavaranAuthTokenSchema,
} from "./schema/fanavaran-auth-token.schema";
import {
FanavaranClientConfig,
FanavaranClientConfigSchema,
} from "./schema/fanavaran-client-config.schema";
@Module({
imports: [
@@ -20,10 +25,22 @@ import {
}),
MongooseModule.forFeature([
{ name: FanavaranAuthToken.name, schema: FanavaranAuthTokenSchema },
{
name: FanavaranClientConfig.name,
schema: FanavaranClientConfigSchema,
},
]),
FanavaranAuditModule,
],
providers: [FanavaranAuthService, FanavaranLookupService],
exports: [FanavaranAuthService, FanavaranLookupService],
providers: [
FanavaranClientConfigService,
FanavaranAuthService,
FanavaranLookupService,
],
exports: [
FanavaranClientConfigService,
FanavaranAuthService,
FanavaranLookupService,
],
})
export class FanavaranLookupModule {}

View File

@@ -0,0 +1,111 @@
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { HydratedDocument } from "mongoose";
import type { FanavaranClientKey } from "src/core/config/fanavaran-client.config";
@Schema({ _id: false })
export class FanavaranAuthConfigEmbed {
@Prop({ type: String, required: true })
appName: string;
@Prop({ type: String, required: true })
secret: string;
@Prop({ type: String, required: true })
username: string;
@Prop({ type: String, required: true })
password: string;
@Prop({ type: String, required: true })
corpId: string;
@Prop({ type: String, required: true })
contractId: string;
@Prop({ type: String, required: true })
location: string;
}
export const FanavaranAuthConfigEmbedSchema = SchemaFactory.createForClass(
FanavaranAuthConfigEmbed,
);
@Schema({ _id: false })
export class FanavaranPayloadDefaultsEmbed {
@Prop({ type: Number, required: true })
AccidentCityId: number;
@Prop({ type: Number, required: true })
AccidentReportTypeId: number;
@Prop({ type: Number, required: true })
AccidentVehicleUsedId: number;
/**
* GEN.03 base claim — کارشناس مسئول پرونده مالی
* (Parsian example: 154 / 4662 — NOT the assessor role).
*/
@Prop({ type: Number, required: true })
ClaimExpertId: number;
/**
* GEN.08 expertise — کارشناس ارزیاب خسارت ثالث مالی (or وکیل/تحقیق).
* (Parsian example: 29; Tejaratno proven send: 2709).
*/
@Prop({ type: Number, required: true })
ExpertiseClaimExpertId: number;
@Prop({ type: Number, required: true })
CompensationReferenceId: number;
@Prop({ type: Number, required: true })
CulpritLicenceTypeId: number;
@Prop({ type: Number, required: true })
CulpritTypeId: number;
@Prop({ type: Number, required: true })
DmgCaseTypeId: number;
@Prop({ type: Number, required: true })
DmgHistoryStatus: number;
@Prop({ type: Number, required: true })
PlaqueKindId: number;
@Prop({ type: Number, required: true })
PlaqueSampleId: number;
@Prop({ type: Number, required: true })
DriverIsOwner: number;
@Prop({ type: Number, required: true })
FaultPercent: number;
@Prop({ type: Number, required: true })
ClaimFileTypeId: number;
}
export const FanavaranPayloadDefaultsEmbedSchema = SchemaFactory.createForClass(
FanavaranPayloadDefaultsEmbed,
);
/**
* Per-tenant Fanavaran auth + payload defaults.
* Seeded on boot when missing; edit in Mongo without redeploying code.
*/
@Schema({ collection: "fanavaranClientConfigs", timestamps: true })
export class FanavaranClientConfig {
@Prop({ type: String, required: true, unique: true, index: true })
key: FanavaranClientKey;
@Prop({ type: FanavaranAuthConfigEmbedSchema, required: true })
auth: FanavaranAuthConfigEmbed;
@Prop({ type: FanavaranPayloadDefaultsEmbedSchema, required: true })
defaults: FanavaranPayloadDefaultsEmbed;
}
export type FanavaranClientConfigDocument =
HydratedDocument<FanavaranClientConfig>;
export const FanavaranClientConfigSchema = SchemaFactory.createForClass(
FanavaranClientConfig,
);