diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index d4ea98e..b79edd0 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -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[] = []; diff --git a/src/core/config/fanavaran-client.config.ts b/src/core/config/fanavaran-client.config.ts index 42adbae..b8f5a2f 100644 --- a/src/core/config/fanavaran-client.config.ts +++ b/src/core/config/fanavaran-client.config.ts @@ -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 +> | null = null; + +export function setFanavaranClientProfilesCache( + profiles: Partial>, +): 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( diff --git a/src/fanavaran/fanavaran-client-config.service.ts b/src/fanavaran/fanavaran-client-config.service.ts new file mode 100644 index 0000000..66e59b1 --- /dev/null +++ b/src/fanavaran/fanavaran-client-config.service.ts @@ -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, + ) {} + + async onModuleInit(): Promise { + await this.seedMissingProfiles(); + await this.reloadCache(); + } + + async seedMissingProfiles(): Promise { + 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 { + const docs = await this.configModel.find().lean().exec(); + const cache: Partial> = + {}; + + 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(); + } +} diff --git a/src/fanavaran/fanavaran-lookup.module.ts b/src/fanavaran/fanavaran-lookup.module.ts index bc1d645..41699e7 100644 --- a/src/fanavaran/fanavaran-lookup.module.ts +++ b/src/fanavaran/fanavaran-lookup.module.ts @@ -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 {} diff --git a/src/fanavaran/schema/fanavaran-client-config.schema.ts b/src/fanavaran/schema/fanavaran-client-config.schema.ts new file mode 100644 index 0000000..b0a2ff3 --- /dev/null +++ b/src/fanavaran/schema/fanavaran-client-config.schema.ts @@ -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; +export const FanavaranClientConfigSchema = SchemaFactory.createForClass( + FanavaranClientConfig, +);