forked from Yara724/api
Merge pull request 'Added API for externalAPI, added env for clients' (#116) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#116
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
NODE_ENV =
|
NODE_ENV =
|
||||||
PORT =
|
PORT =
|
||||||
|
CLIENT_ID =
|
||||||
|
CLIENT_NAME =
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
# 🌐 Application URLs
|
# 🌐 Application URLs
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { APP_INTERCEPTOR, APP_PIPE } from "@nestjs/core";
|
import { APP_INTERCEPTOR, APP_PIPE } from "@nestjs/core";
|
||||||
import { Module, ValidationPipe } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { ConfigModule, ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
import { UnicodeDigitsNormalizeInterceptor } from "./common/interceptors/unicode-digits-normalize.interceptor";
|
import { UnicodeDigitsNormalizeInterceptor } from "./common/interceptors/unicode-digits-normalize.interceptor";
|
||||||
import { MongooseModule } from "@nestjs/mongoose";
|
import { MongooseModule } from "@nestjs/mongoose";
|
||||||
import { ServeStaticModule } from "@nestjs/serve-static";
|
import { ServeStaticModule } from "@nestjs/serve-static";
|
||||||
@@ -24,79 +24,18 @@ import { applyIranFaTimestampPlugin } from "./helpers/mongoose-fa-timestamps.plu
|
|||||||
import { CronModule } from "./utils/cron/cron.module";
|
import { CronModule } from "./utils/cron/cron.module";
|
||||||
import { WorkflowStepManagementModule } from "./workflow-step-management/workflow-step-management.module";
|
import { WorkflowStepManagementModule } from "./workflow-step-management/workflow-step-management.module";
|
||||||
import { ExpertInitiatedModule } from "./expert-initiated/expert-initiated.module";
|
import { ExpertInitiatedModule } from "./expert-initiated/expert-initiated.module";
|
||||||
import * as Joi from "joi";
|
import { DatabaseModule } from "./core/database/database.module";
|
||||||
|
import { AppConfigModule } from "./core/config/config.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({
|
AppConfigModule,
|
||||||
isGlobal: true,
|
DatabaseModule,
|
||||||
validationSchema: Joi.object({
|
|
||||||
NODE_ENV: Joi.string()
|
|
||||||
.valid("development", "production")
|
|
||||||
.default("development"),
|
|
||||||
PORT: Joi.number().port().default(9001),
|
|
||||||
URL: Joi.string().uri().required(),
|
|
||||||
BASE_URL_DEV: Joi.string()
|
|
||||||
.uri()
|
|
||||||
.when("NODE_ENV", {
|
|
||||||
is: Joi.string().valid("development"),
|
|
||||||
then: Joi.required(),
|
|
||||||
otherwise: Joi.optional(),
|
|
||||||
}),
|
|
||||||
SWAGGER_USER_DEV: Joi.string().when("NODE_ENV", {
|
|
||||||
is: Joi.string().valid("development"),
|
|
||||||
then: Joi.required(),
|
|
||||||
otherwise: Joi.optional(),
|
|
||||||
}),
|
|
||||||
SWAGGER_PASSWORD_DEV: Joi.string().when("NODE_ENV", {
|
|
||||||
is: Joi.string().valid("development"),
|
|
||||||
then: Joi.required(),
|
|
||||||
otherwise: Joi.optional(),
|
|
||||||
}),
|
|
||||||
MONGO_HOST: Joi.string().required(),
|
|
||||||
MONGO_PORT: Joi.number().port().required(),
|
|
||||||
MONGO_USER: Joi.string().required(),
|
|
||||||
MONGO_PASS: Joi.string().required(),
|
|
||||||
MONGO_DB_NAME: Joi.string().required(),
|
|
||||||
JWT_SECRET: Joi.string().required(),
|
|
||||||
SANHUB_BASE_URL: Joi.string().uri(),
|
|
||||||
SANHUB_URL_LOGIN: Joi.string().uri(),
|
|
||||||
SANHUB_USERNAME: Joi.string(),
|
|
||||||
SANHUB_PASSWORD: Joi.string(),
|
|
||||||
AI_URL: Joi.string().uri(),
|
|
||||||
AI_URL_V2: Joi.string().uri(),
|
|
||||||
AI_USERNAME: Joi.string(),
|
|
||||||
AI_PASSWORD: Joi.string(),
|
|
||||||
SMS_PROVIDER: Joi.string()
|
|
||||||
.valid("kavenegar", "parsian")
|
|
||||||
.default("kavenegar"),
|
|
||||||
SMS_API_KEY: Joi.string(),
|
|
||||||
AUTH_SMS_TEMPLATE: Joi.string(),
|
|
||||||
EXP_OTP_TIME: Joi.number(),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
CronModule,
|
CronModule,
|
||||||
ServeStaticModule.forRoot({
|
ServeStaticModule.forRoot({
|
||||||
rootPath: join(__dirname, "..", "files"),
|
rootPath: join(__dirname, "..", "files"),
|
||||||
serveRoot: "/files",
|
serveRoot: "/files",
|
||||||
}),
|
}),
|
||||||
MongooseModule.forRootAsync({
|
|
||||||
inject: [ConfigService],
|
|
||||||
useFactory: (configService: ConfigService) => {
|
|
||||||
return {
|
|
||||||
uri: `mongodb://${configService.get<string>("MONGO_USER")}:${configService.get<string>("MONGO_PASS")}@${configService.get<string>("MONGO_HOST")}:${configService.get<string>("MONGO_PORT")}/${configService.get<string>("MONGO_DB_NAME")}?authSource=admin&${configService.get<string>("MONGO_OPTIONS")}`,
|
|
||||||
tls: configService.get<string>("MONGO_TLS") === "true",
|
|
||||||
tlsAllowInvalidCertificates:
|
|
||||||
configService.get<string>("MONGO_TLS_ALLOW_INVALID_CERTS") ===
|
|
||||||
"true",
|
|
||||||
autoIndex: configService.get<string>("NODE_ENV") !== "production",
|
|
||||||
connectionFactory: (connection) => {
|
|
||||||
applyIranFaTimestampPlugin(connection);
|
|
||||||
return connection;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
UsersModule,
|
UsersModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
ClientModule,
|
ClientModule,
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
import { Body, Controller, Get, Post, UseGuards } from "@nestjs/common";
|
import { Body, Controller, Get, Patch, Post } from "@nestjs/common";
|
||||||
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
import {
|
||||||
import { GlobalGuard } from "src/auth/guards/global.guard";
|
ApiBody,
|
||||||
import { RolesGuard } from "src/auth/guards/role.guard";
|
ApiOperation,
|
||||||
import { Roles } from "src/decorators/roles.decorator";
|
ApiResponse,
|
||||||
|
ApiTags,
|
||||||
|
} from "@nestjs/swagger";
|
||||||
import { CurrentUser } from "src/decorators/user.decorator";
|
import { CurrentUser } from "src/decorators/user.decorator";
|
||||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
import { SystemSettingsResponseDto } from "src/system-settings/dto/system-settings.dto";
|
||||||
|
import { SystemSettingsService } from "src/system-settings/system-settings.service";
|
||||||
import { ClientService } from "./client.service";
|
import { ClientService } from "./client.service";
|
||||||
import { ClientDto } from "./dto/create-client.dto";
|
import { ClientDto } from "./dto/create-client.dto";
|
||||||
|
import { SetExternalInquiriesLiveDto } from "./dto/external-inquiries-live.dto";
|
||||||
|
|
||||||
@Controller("client")
|
@Controller("client")
|
||||||
@ApiTags("client-management")
|
@ApiTags("client-management")
|
||||||
export class ClientController {
|
export class ClientController {
|
||||||
constructor(private readonly clientService: ClientService) {}
|
constructor(
|
||||||
|
private readonly clientService: ClientService,
|
||||||
|
private readonly systemSettingsService: SystemSettingsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
async addClient(@Body() client: ClientDto) {
|
async addClient(@Body() client: ClientDto) {
|
||||||
@@ -27,4 +34,33 @@ export class ClientController {
|
|||||||
async getClientList(@CurrentUser() user) {
|
async getClientList(@CurrentUser() user) {
|
||||||
return await this.clientService.getClientList();
|
return await this.clientService.getClientList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Toggle SandHub/Tejarat live HTTP vs mock inquiries (`system_settings.externalApis.sandHubUseLiveApi`). */
|
||||||
|
@Patch("external-inquiries-live")
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Enable or disable live external inquiries",
|
||||||
|
description:
|
||||||
|
"Updates `system_settings.externalApis.sandHubUseLiveApi`. No auth required. Use the request examples below to switch between live Tejarat/SandHub HTTP and offline mock mode.",
|
||||||
|
})
|
||||||
|
@ApiBody({
|
||||||
|
type: SetExternalInquiriesLiveDto,
|
||||||
|
examples: {
|
||||||
|
enableLive: {
|
||||||
|
summary: "Enable live inquiries",
|
||||||
|
description: "Call real SandHub/Tejarat HTTP APIs.",
|
||||||
|
value: { enabled: true },
|
||||||
|
},
|
||||||
|
disableLive: {
|
||||||
|
summary: "Disable live inquiries (mock mode)",
|
||||||
|
description: "Use mocked inquiry responses; flows continue without external connectivity.",
|
||||||
|
value: { enabled: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 200, type: SystemSettingsResponseDto })
|
||||||
|
async setExternalInquiriesLive(@Body() body?: SetExternalInquiriesLiveDto) {
|
||||||
|
return this.systemSettingsService.updateSettings({
|
||||||
|
externalApis: { sandHubUseLiveApi: body?.enabled === true },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { MongooseModule } from "@nestjs/mongoose";
|
import { MongooseModule } from "@nestjs/mongoose";
|
||||||
|
import { SystemSettingsModule } from "src/system-settings/system-settings.module";
|
||||||
import { ClientController } from "./client.controller";
|
import { ClientController } from "./client.controller";
|
||||||
import { ClientPanelController } from "./client-panel.controller";
|
import { ClientPanelController } from "./client-panel.controller";
|
||||||
import { ClientService } from "./client.service";
|
import { ClientService } from "./client.service";
|
||||||
@@ -10,6 +11,7 @@ import { ClientDbSchema, ClientModel } from "./entities/schema/client.schema";
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
SystemSettingsModule,
|
||||||
MongooseModule.forFeature([
|
MongooseModule.forFeature([
|
||||||
{ name: ClientModel.name, schema: ClientDbSchema },
|
{ name: ClientModel.name, schema: ClientDbSchema },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -142,6 +142,38 @@ export class ClientService {
|
|||||||
return await this.clientDbService.find({ clientCode: companyCode });
|
return await this.clientDbService.find({ clientCode: companyCode });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a client by insurer company code from an external inquiry.
|
||||||
|
* Creates the client when missing so inquiry flows don't fail on unknown codes.
|
||||||
|
*/
|
||||||
|
async findOrCreateClientByCompanyCode(
|
||||||
|
companyCode: number | string | null | undefined,
|
||||||
|
companyName: string | null | undefined,
|
||||||
|
) {
|
||||||
|
const name = typeof companyName === "string" ? companyName.trim() : "";
|
||||||
|
const code = Number(companyCode);
|
||||||
|
if (!name || !Number.isFinite(code)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = await this.clientDbService.find({ clientCode: code });
|
||||||
|
if (client) return client;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.addClient({
|
||||||
|
clientName: { persian: name, english: null },
|
||||||
|
clientCode: code,
|
||||||
|
useExpertMode: "legal",
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
client = await this.clientDbService.find({ clientCode: code });
|
||||||
|
if (client) return client;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.clientDbService.find({ clientCode: code });
|
||||||
|
}
|
||||||
|
|
||||||
async getClients(): Promise<ClientDtoRs[]> {
|
async getClients(): Promise<ClientDtoRs[]> {
|
||||||
const clients = await this.clientDbService.findAll();
|
const clients = await this.clientDbService.findAll();
|
||||||
const show = clients.map((c) => new ClientDtoRs(c));
|
const show = clients.map((c) => new ClientDtoRs(c));
|
||||||
|
|||||||
10
src/client/dto/external-inquiries-live.dto.ts
Normal file
10
src/client/dto/external-inquiries-live.dto.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
export class SetExternalInquiriesLiveDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description:
|
||||||
|
"When true, SandHub/Tejarat live HTTP inquiries run. When false, mocked inquiry data is used.",
|
||||||
|
example: true,
|
||||||
|
})
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
14
src/core/config/config.module.ts
Normal file
14
src/core/config/config.module.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { ConfigModule } from "@nestjs/config";
|
||||||
|
import { validate } from "./config.validation";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
isGlobal: true,
|
||||||
|
expandVariables: true,
|
||||||
|
validate,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppConfigModule {}
|
||||||
75
src/core/config/config.schema.ts
Normal file
75
src/core/config/config.schema.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import {
|
||||||
|
IsBooleanString,
|
||||||
|
IsEnum,
|
||||||
|
IsNumber,
|
||||||
|
IsPositive,
|
||||||
|
IsString,
|
||||||
|
IsUrl,
|
||||||
|
Matches,
|
||||||
|
Max,
|
||||||
|
Min,
|
||||||
|
MinLength,
|
||||||
|
} from "class-validator";
|
||||||
|
import { StringValue } from "ms";
|
||||||
|
|
||||||
|
export enum Environment {
|
||||||
|
Development = "development",
|
||||||
|
Production = "production",
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EnvironmentVariables {
|
||||||
|
@IsEnum(Environment, {
|
||||||
|
message: "NODE_ENV should be development or production",
|
||||||
|
})
|
||||||
|
NODE_ENV: Environment;
|
||||||
|
// --------------------------------------------------------- //
|
||||||
|
@IsNumber(
|
||||||
|
{
|
||||||
|
allowNaN: false,
|
||||||
|
allowInfinity: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "PORT must be a valid number",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@IsPositive({ message: "PORT must be positive" })
|
||||||
|
@Min(0, { message: "PORT must be between 0 and 65535" })
|
||||||
|
@Max(65535, { message: "PORT must be between 0 and 65535" })
|
||||||
|
PORT: number;
|
||||||
|
// --------------------------------------------------------- //
|
||||||
|
@IsString({ message: "MONGO_URI must be string" })
|
||||||
|
@IsUrl(
|
||||||
|
{
|
||||||
|
require_tld: false,
|
||||||
|
protocols: ["mongodb", "mongodb+srv"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "MONGO_URI must be in the correct format (mongodb://...)",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
MONGO_URI: string;
|
||||||
|
// --------------------------------------------------------- //
|
||||||
|
@IsBooleanString({ message: "MONGO_TLS must be boolean" })
|
||||||
|
MONGO_TLS: string;
|
||||||
|
// --------------------------------------------------------- //
|
||||||
|
@IsBooleanString({ message: "MONGO_TLS_ALLOW_INVALID_CERTS must be boolean" })
|
||||||
|
MONGO_TLS_ALLOW_INVALID_CERTS: string;
|
||||||
|
// --------------------------------------------------------- //
|
||||||
|
@IsString({ message: "JWT_SECRET must be string" })
|
||||||
|
@MinLength(32, {
|
||||||
|
message: "JWT_SECRET must be at least 32 characters",
|
||||||
|
})
|
||||||
|
JWT_SECRET: string;
|
||||||
|
// --------------------------------------------------------- //
|
||||||
|
@Matches(/^\d+(ms|s|m|h|d|w|y)$/, {
|
||||||
|
message: "JWT_EXPIRY must be in the correct format",
|
||||||
|
})
|
||||||
|
JWT_EXPIRY: StringValue;
|
||||||
|
// --------------------------------------------------------- //
|
||||||
|
// @IsString({ message: "HASH_PEPPER must be string" })
|
||||||
|
// @MinLength(32, {
|
||||||
|
// message: "HASH_PEPPER must be at least 32 characters",
|
||||||
|
// })
|
||||||
|
// HASH_PEPPER: string;
|
||||||
|
// --------------------------------------------------------- //
|
||||||
|
}
|
||||||
23
src/core/config/config.validation.ts
Normal file
23
src/core/config/config.validation.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { plainToInstance } from "class-transformer";
|
||||||
|
import { validateSync } from "class-validator";
|
||||||
|
import { EnvironmentVariables } from "./config.schema";
|
||||||
|
|
||||||
|
export function validate(config: Record<string, unknown>) {
|
||||||
|
const validatedConfig = plainToInstance(EnvironmentVariables, config, {
|
||||||
|
enableImplicitConversion: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const errors = validateSync(validatedConfig, {
|
||||||
|
skipMissingProperties: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
const messages = errors
|
||||||
|
.flatMap((error) => Object.values(error.constraints ?? {}))
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
throw new Error(`Environment validation failed:\n${messages}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return validatedConfig;
|
||||||
|
}
|
||||||
19
src/core/database/database.module.ts
Normal file
19
src/core/database/database.module.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { MongooseModule } from "@nestjs/mongoose";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
MongooseModule.forRootAsync({
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: (configService: ConfigService) => ({
|
||||||
|
uri: configService.get<string>("MONGO_URI"),
|
||||||
|
tls: configService.get<string>("MONGO_TLS") === "true",
|
||||||
|
tlsAllowInvalidCertificates:
|
||||||
|
configService.get<string>("MONGO_TLS_ALLOW_INVALID_CERTS") === "true",
|
||||||
|
autoIndex: configService.get<string>("NODE_ENV") !== "production",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class DatabaseModule {}
|
||||||
0
src/core/database/plugins/iran-timestamp.plugin.ts
Normal file
0
src/core/database/plugins/iran-timestamp.plugin.ts
Normal file
@@ -1160,24 +1160,16 @@ export class RequestManagementService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const companyCode = inquiryMapped?.CompanyCode;
|
const companyCode = inquiryMapped?.CompanyCode;
|
||||||
let client =
|
const client = await this.clientService.findOrCreateClientByCompanyCode(
|
||||||
await this.clientService.findClientWithCompanyCode(+companyCode);
|
companyCode,
|
||||||
|
clientName,
|
||||||
|
);
|
||||||
if (!client) {
|
if (!client) {
|
||||||
await this.clientService.addClient({
|
throw new BadRequestException(
|
||||||
clientName: {
|
`CompanyCode missing or invalid in inquiry response`,
|
||||||
persian: clientName,
|
);
|
||||||
english: null,
|
|
||||||
},
|
|
||||||
clientCode: Number(companyCode),
|
|
||||||
useExpertMode: "legal",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (!client) {
|
|
||||||
// throw new HttpException("Client not found", HttpStatus.CONFLICT);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Persist inquiry/body data into new model
|
// Persist inquiry/body data into new model
|
||||||
if (!party.person) party.person = {} as any;
|
if (!party.person) party.person = {} as any;
|
||||||
const resolvedClientId =
|
const resolvedClientId =
|
||||||
@@ -1286,27 +1278,13 @@ export class RequestManagementService {
|
|||||||
const carBodyCompanyCode = m.companyId ?? m.CompanyCode;
|
const carBodyCompanyCode = m.companyId ?? m.CompanyCode;
|
||||||
const carBodyCompanyName = m.CompanyName ?? m.companyPersianName;
|
const carBodyCompanyName = m.CompanyName ?? m.companyPersianName;
|
||||||
|
|
||||||
if (carBodyCompanyCode) {
|
if (carBodyCompanyCode && carBodyCompanyName) {
|
||||||
let carBodyClient: any =
|
const carBodyClient =
|
||||||
await this.clientService.findClientWithCompanyCode(
|
await this.clientService.findOrCreateClientByCompanyCode(
|
||||||
Number(carBodyCompanyCode),
|
carBodyCompanyCode,
|
||||||
|
carBodyCompanyName,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!carBodyClient && carBodyCompanyName) {
|
|
||||||
await this.clientService.addClient({
|
|
||||||
clientName: {
|
|
||||||
persian: carBodyCompanyName,
|
|
||||||
english: null,
|
|
||||||
},
|
|
||||||
clientCode: Number(carBodyCompanyCode),
|
|
||||||
useExpertMode: "legal",
|
|
||||||
});
|
|
||||||
// Re-fetch after creation so we get the actual document with _id
|
|
||||||
carBodyClient = await this.clientService.findClientWithCompanyCode(
|
|
||||||
Number(carBodyCompanyCode),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const carBodyClientId =
|
const carBodyClientId =
|
||||||
(carBodyClient as any)?._id ?? (carBodyClient as any)?._doc?._id;
|
(carBodyClient as any)?._id ?? (carBodyClient as any)?._doc?._id;
|
||||||
|
|
||||||
@@ -2075,8 +2053,10 @@ export class RequestManagementService {
|
|||||||
const clientName = sandHubReport?.CompanyName;
|
const clientName = sandHubReport?.CompanyName;
|
||||||
const companyCode = sandHubReport?.CompanyCode;
|
const companyCode = sandHubReport?.CompanyCode;
|
||||||
|
|
||||||
const client =
|
const client = await this.clientService.findOrCreateClientByCompanyCode(
|
||||||
await this.clientService.findClientWithCompanyCode(+companyCode);
|
companyCode,
|
||||||
|
clientName,
|
||||||
|
);
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new HttpException("Client not found", HttpStatus.CONFLICT);
|
throw new HttpException("Client not found", HttpStatus.CONFLICT);
|
||||||
@@ -5021,7 +5001,10 @@ export class RequestManagementService {
|
|||||||
sandHubReport?.CompanyName || sandHubReport?.LastCompanyName;
|
sandHubReport?.CompanyName || sandHubReport?.LastCompanyName;
|
||||||
const companyCode = sandHubReport?.CompanyCode;
|
const companyCode = sandHubReport?.CompanyCode;
|
||||||
const client = companyCode
|
const client = companyCode
|
||||||
? await this.clientService.findClientWithCompanyCode(+companyCode)
|
? await this.clientService.findOrCreateClientByCompanyCode(
|
||||||
|
companyCode,
|
||||||
|
clientName,
|
||||||
|
)
|
||||||
: await this.clientService.findOne({ clientName });
|
: await this.clientService.findOne({ clientName });
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new NotFoundException(
|
throw new NotFoundException(
|
||||||
@@ -5228,7 +5211,10 @@ export class RequestManagementService {
|
|||||||
sandHubReport?.CompanyName || sandHubReport?.LastCompanyName;
|
sandHubReport?.CompanyName || sandHubReport?.LastCompanyName;
|
||||||
const companyCode = sandHubReport?.CompanyCode;
|
const companyCode = sandHubReport?.CompanyCode;
|
||||||
const client = companyCode
|
const client = companyCode
|
||||||
? await this.clientService.findClientWithCompanyCode(+companyCode)
|
? await this.clientService.findOrCreateClientByCompanyCode(
|
||||||
|
companyCode,
|
||||||
|
clientName,
|
||||||
|
)
|
||||||
: await this.clientService.findOne({ clientName });
|
: await this.clientService.findOne({ clientName });
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new NotFoundException(
|
throw new NotFoundException(
|
||||||
@@ -5834,7 +5820,10 @@ export class RequestManagementService {
|
|||||||
|
|
||||||
// Try to find client by company code first (more reliable)
|
// Try to find client by company code first (more reliable)
|
||||||
const client = companyCode
|
const client = companyCode
|
||||||
? await this.clientService.findClientWithCompanyCode(+companyCode)
|
? await this.clientService.findOrCreateClientByCompanyCode(
|
||||||
|
companyCode,
|
||||||
|
clientName,
|
||||||
|
)
|
||||||
: await this.clientService.findOne({ clientName: clientName });
|
: await this.clientService.findOne({ clientName: clientName });
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
@@ -5898,7 +5887,10 @@ export class RequestManagementService {
|
|||||||
|
|
||||||
// Try to find client by company code first (more reliable)
|
// Try to find client by company code first (more reliable)
|
||||||
const client = companyCode
|
const client = companyCode
|
||||||
? await this.clientService.findClientWithCompanyCode(+companyCode)
|
? await this.clientService.findOrCreateClientByCompanyCode(
|
||||||
|
companyCode,
|
||||||
|
clientName,
|
||||||
|
)
|
||||||
: await this.clientService.findOne({ clientName: clientName });
|
: await this.clientService.findOne({ clientName: clientName });
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
@@ -6168,7 +6160,10 @@ export class RequestManagementService {
|
|||||||
|
|
||||||
// Try to find client by company code first (more reliable)
|
// Try to find client by company code first (more reliable)
|
||||||
const client = companyCode
|
const client = companyCode
|
||||||
? await this.clientService.findClientWithCompanyCode(+companyCode)
|
? await this.clientService.findOrCreateClientByCompanyCode(
|
||||||
|
companyCode,
|
||||||
|
clientName,
|
||||||
|
)
|
||||||
: await this.clientService.findOne({ clientName: clientName });
|
: await this.clientService.findOne({ clientName: clientName });
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
|
|||||||
@@ -40,40 +40,49 @@ export class SandHubService {
|
|||||||
return this.systemSettingsService.isSandHubLiveEnabled();
|
return this.systemSettingsService.isSandHubLiveEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Tenant-specific company fields for mocked external inquiries (MOCK_INQUIRY_COMPANY_*). */
|
||||||
|
private getMockInquiryCompanyId(): string {
|
||||||
|
return process.env.CLIENT_ID ?? "15";
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMockInquiryCompanyName(): string {
|
||||||
|
return process.env.CLIENT_NAME ?? "بیمه سامان";
|
||||||
|
}
|
||||||
|
|
||||||
/** Fixed plate/insurance inquiry payload used everywhere we mock block-inquiry style APIs. */
|
/** Fixed plate/insurance inquiry payload used everywhere we mock block-inquiry style APIs. */
|
||||||
private getDefaultMockPlateInquiryRaw(): Record<string, unknown> {
|
private getDefaultMockPlateInquiryRaw(): Record<string, unknown> {
|
||||||
return {
|
return {
|
||||||
PrntPlcyCmpDocNo: "1403/1143-70591/200/35",
|
PrntPlcyCmpDocNo: "1404/1143-70591/200/123",
|
||||||
MapTypNam: "پرايد هاچ بک -111",
|
MapTypNam: "فائو ( FAW )",
|
||||||
MtrNum: "5215907",
|
MtrNum: "TZ196XYAP223A210074",
|
||||||
ShsNum: "NAS431100E5798656",
|
ShsNum: "LFP8C7PC3R1K12157",
|
||||||
DisFnYrNum: "0",
|
DisFnYrNum: null,
|
||||||
DisLfYrNum: "0",
|
DisLfYrNum: null,
|
||||||
DisPrsnYrNum: "0",
|
DisPrsnYrNum: null,
|
||||||
DisPrsnYrPrcnt: "0",
|
DisPrsnYrPrcnt: null,
|
||||||
DisFnYrPrcnt: "0",
|
DisFnYrPrcnt: "5",
|
||||||
DisLfYrPrcnt: "0",
|
DisLfYrPrcnt: "5",
|
||||||
vin: "IRPC941V2BD798656",
|
vin: "LFP8C7PC3R1K12157",
|
||||||
MapVehicleSystemName: "ثبت نشده",
|
MapVehicleSystemName: "ثبت نشده",
|
||||||
LfCvrCptl: 0,
|
LfCvrCptl: 0,
|
||||||
FnCvrCptl: 0,
|
FnCvrCptl: 0,
|
||||||
PrsnCvrCptl: 0,
|
PrsnCvrCptl: 0,
|
||||||
VehicleSystemCode: 1,
|
VehicleSystemCode: 1,
|
||||||
EdrsJson: '[{"id":1,"Dsc":" الحاقيه توضيحات ندارد"}]',
|
EdrsJson: "",
|
||||||
PersonCvrCptl: 12000000000,
|
PersonCvrCptl: 12000000000,
|
||||||
LifeCvrCptl: 16000000000,
|
LifeCvrCptl: 16000000000,
|
||||||
FinancialCvrCptl: 4000000000,
|
FinancialCvrCptl: 4000000000,
|
||||||
CarGroupCode: 3,
|
CarGroupCode: 3,
|
||||||
CylCnt: 4,
|
CylCnt: 4,
|
||||||
LastCompanyDocumentNumber: "03/1031/2835/1001/662",
|
LastCompanyDocumentNumber: "31/3100/03/18350",
|
||||||
UsageCode: "8",
|
UsageCode: "8",
|
||||||
MapUsageCode: 1,
|
MapUsageCode: 1,
|
||||||
MapUsageName: "شخصي",
|
MapUsageName: "شخصي",
|
||||||
Plk1: 59,
|
Plk1: 16,
|
||||||
Plk2: 16,
|
Plk2: 12,
|
||||||
Plk3: 419,
|
Plk3: 498,
|
||||||
PlkSrl: 78,
|
PlkSrl: 60,
|
||||||
PrintEndorsCompanyDocumentNumber: "بيمه نامه الحاقيه ندارد.",
|
PrintEndorsCompanyDocumentNumber: "",
|
||||||
EndorseDate: null,
|
EndorseDate: null,
|
||||||
InsuranceFullName: null,
|
InsuranceFullName: null,
|
||||||
SystemField: "سايپا",
|
SystemField: "سايپا",
|
||||||
@@ -81,12 +90,12 @@ export class SandHubService {
|
|||||||
UsageField: "سواري",
|
UsageField: "سواري",
|
||||||
MainColorField: "سفيد",
|
MainColorField: "سفيد",
|
||||||
SecondColorField: "سفيد شيري",
|
SecondColorField: "سفيد شيري",
|
||||||
ModelField: "1394",
|
ModelField: "2024",
|
||||||
CapacityField: "جمعا 4 نفر",
|
CapacityField: "جمعا 4 نفر",
|
||||||
CylinderNumberField: "4",
|
CylinderNumberField: "4",
|
||||||
EngineNumberField: "5215907",
|
EngineNumberField: "5215907",
|
||||||
ChassisNumberField: "NAS431100E5798656",
|
ChassisNumberField: "LFP8C7PC3R1K12157",
|
||||||
VinNumberField: "IRPC941V2BD798656",
|
VinNumberField: "LFP8C7PC3R1K12157",
|
||||||
InstallDateField: "1403/03/01",
|
InstallDateField: "1403/03/01",
|
||||||
AxelNumberField: "2",
|
AxelNumberField: "2",
|
||||||
WheelNumberField: "4",
|
WheelNumberField: "4",
|
||||||
@@ -107,7 +116,7 @@ export class SandHubService {
|
|||||||
SystemCodeCii: 0,
|
SystemCodeCii: 0,
|
||||||
SystemNameCii: "ثبت نشده",
|
SystemNameCii: "ثبت نشده",
|
||||||
TypeCodeCii: 12189,
|
TypeCodeCii: 12189,
|
||||||
TypeNameCii: "پرايد هاچ بک -111",
|
TypeNameCii: "فائو ( FAW )",
|
||||||
UsageNameCii: "سواري",
|
UsageNameCii: "سواري",
|
||||||
UsageCodeCii: 1,
|
UsageCodeCii: 1,
|
||||||
ModelCii: 1394,
|
ModelCii: 1394,
|
||||||
@@ -116,6 +125,47 @@ export class SandHubService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getDefaultMockCarBodyInquiryRaw(
|
||||||
|
userDetail: SandHubDetailDto,
|
||||||
|
): Record<string, unknown> {
|
||||||
|
const companyId = Number(this.getMockInquiryCompanyId());
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
id: 70016075946,
|
||||||
|
printNumber: "1405/1143-70591/220/1/0",
|
||||||
|
companyId: Number.isNaN(companyId) ? 15 : companyId,
|
||||||
|
companyName: this.getMockInquiryCompanyName(),
|
||||||
|
beginDate: "1405/01/17",
|
||||||
|
endDate: "1406/01/17",
|
||||||
|
issueDate: "1405/01/16",
|
||||||
|
hasEndorsement: null,
|
||||||
|
motorNumber: "TZ196XYAP223A210074",
|
||||||
|
chassisNumber: "LFP8C7PC3R1K12157",
|
||||||
|
vin: "LFP8C7PC3R1K12157",
|
||||||
|
plateTypeId: 9,
|
||||||
|
plateTypeTitle: "پلاک قدیمی",
|
||||||
|
platePartOne: 16,
|
||||||
|
plateSerialNumber: 60,
|
||||||
|
plateLetterid: 12,
|
||||||
|
plateLetterTitle: "م ",
|
||||||
|
vehicleSystemTitle: "فائو ( FAW )",
|
||||||
|
vehicleGroupId: 2,
|
||||||
|
vehicleGroupTitle: "سواری چهار سیلندر",
|
||||||
|
insurerNationalCode: userDetail.nationalCodeOfInsurer,
|
||||||
|
insurerName: "هانيه کارخانهء",
|
||||||
|
ownerNationalCode: userDetail.nationalCodeOfInsurer,
|
||||||
|
previousId: 70006331822,
|
||||||
|
noLossYearsCount: 4,
|
||||||
|
platePartThree: 498,
|
||||||
|
lossDocuments: [],
|
||||||
|
},
|
||||||
|
isSuccess: true,
|
||||||
|
statusCode: 200,
|
||||||
|
message: "عملیات با موفقیت انجام شد",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bodies returned from `makeSandHubRequest` in mock mode (same shape callers expect from real API).
|
* Bodies returned from `makeSandHubRequest` in mock mode (same shape callers expect from real API).
|
||||||
*/
|
*/
|
||||||
@@ -401,30 +451,7 @@ export class SandHubService {
|
|||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`[MOCK] getTejaratCarBodyInquiry plate=${JSON.stringify(requestPayload)}`,
|
`[MOCK] getTejaratCarBodyInquiry plate=${JSON.stringify(requestPayload)}`,
|
||||||
);
|
);
|
||||||
raw = {
|
raw = this.getDefaultMockCarBodyInquiryRaw(userDetail);
|
||||||
data: {
|
|
||||||
printNumber: "MOCK-BADANE-001",
|
|
||||||
companyId: 34,
|
|
||||||
companyName: "بیمه تجارت نو",
|
|
||||||
beginDate: "1404/06/15",
|
|
||||||
endDate: "1405/06/15",
|
|
||||||
issueDate: "1404/06/13",
|
|
||||||
hasEndorsement: null,
|
|
||||||
motorNumber: "MOCK-ENGINE",
|
|
||||||
chassisNumber: "MOCK-CHASSIS",
|
|
||||||
vin: "MOCK-VIN",
|
|
||||||
vehicleSystemTitle: "سایپا",
|
|
||||||
vehicleGroupTitle: "سواری چهار سیلندر",
|
|
||||||
insurerNationalCode: userDetail.nationalCodeOfInsurer,
|
|
||||||
insurerName: "نام آزمایشی",
|
|
||||||
ownerNationalCode: userDetail.nationalCodeOfInsurer,
|
|
||||||
noLossYearsCount: 0,
|
|
||||||
lossDocuments: [],
|
|
||||||
},
|
|
||||||
isSuccess: true,
|
|
||||||
statusCode: 200,
|
|
||||||
message: "mock-ok",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapped = this.mapCarBodyInquiryResponse(raw);
|
const mapped = this.mapCarBodyInquiryResponse(raw);
|
||||||
|
|||||||
Reference in New Issue
Block a user