diff --git a/src/.env.example b/.env.example similarity index 100% rename from src/.env.example rename to .env.example diff --git a/src/app.module.ts b/src/app.module.ts index e8c4149..6617ee0 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -84,8 +84,10 @@ import * as Joi from "joi"; useFactory: (configService: ConfigService) => { return { uri: `mongodb://${configService.get("MONGO_USER")}:${configService.get("MONGO_PASS")}@${configService.get("MONGO_HOST")}:${configService.get("MONGO_PORT")}/${configService.get("MONGO_DB_NAME")}?authSource=admin&${configService.get("MONGO_OPTIONS")}`, - tls: true, - tlsAllowInvalidCertificates: true, + tls: configService.get("MONGO_TLS") === "true", + tlsAllowInvalidCertificates: + configService.get("MONGO_TLS_ALLOW_INVALID_CERTS") === + "true", autoIndex: configService.get("NODE_ENV") !== "production", connectionFactory: (connection) => { applyIranFaTimestampPlugin(connection); diff --git a/src/auth/auth-controllers/user/user.auth.controller.ts b/src/auth/auth-controllers/user/user.auth.controller.ts index 732c483..fee6fad 100644 --- a/src/auth/auth-controllers/user/user.auth.controller.ts +++ b/src/auth/auth-controllers/user/user.auth.controller.ts @@ -30,6 +30,7 @@ export class UserAuthController { linkToken: body.linkToken, linkContext: body.linkContext, }); + if (res) { throw new HttpException(res, HttpStatus.ACCEPTED); } diff --git a/src/auth/auth-services/user-auth-error.ts b/src/auth/auth-services/user-auth-error.ts index e663ea4..efc2de4 100644 --- a/src/auth/auth-services/user-auth-error.ts +++ b/src/auth/auth-services/user-auth-error.ts @@ -34,9 +34,9 @@ export function userAuthErrorBody(code: UserAuthErrorCode) { } export function throwUserAuthError(code: UserAuthErrorCode): never { - if (code === UserAuthErrorCode.OTP_REQUEST_TOO_SOON) { - throw new BadRequestException(userAuthErrorBody(code)); - } + // if (code === UserAuthErrorCode.OTP_REQUEST_TOO_SOON) { + // throw new BadRequestException(userAuthErrorBody(code)); + // } if (code === UserAuthErrorCode.LINK_MOBILE_MISMATCH) { throw new ForbiddenException(userAuthErrorBody(code)); diff --git a/src/auth/auth-services/user.auth.service.ts b/src/auth/auth-services/user.auth.service.ts index c6b505e..abf038f 100644 --- a/src/auth/auth-services/user.auth.service.ts +++ b/src/auth/auth-services/user.auth.service.ts @@ -139,7 +139,8 @@ export class UserAuthService { } if (isOtpExpiryActive(userExist.otpExpire, nowMs)) { - throwUserAuthError(UserAuthErrorCode.OTP_REQUEST_TOO_SOON); + // throwUserAuthError(UserAuthErrorCode.OTP_REQUEST_TOO_SOON); + return new LoginDtoRs(userExist, "OTP Still valid"); } await this.smsSender(otp, canonicalMobile); diff --git a/src/auth/dto/user/login.dto.ts b/src/auth/dto/user/login.dto.ts index 9340863..ad25b8a 100644 --- a/src/auth/dto/user/login.dto.ts +++ b/src/auth/dto/user/login.dto.ts @@ -35,7 +35,9 @@ export class UserLoginDto { } export class LoginDtoRs extends UserModel { + @ApiProperty({ type: "string" }) message: string; + @ApiProperty({ example: "09226187419", type: "string", @@ -61,8 +63,10 @@ export class LoginDtoRs extends UserModel { username: string; mobile: string; nationalCode: string; - constructor(loginData) { + + constructor(loginData, message: string = "") { super(); this.mobile = loginData.mobile; + this.message = message; } } diff --git a/src/captcha/captcha.module.ts b/src/captcha/captcha.module.ts index 1d9be6b..4eb4830 100644 --- a/src/captcha/captcha.module.ts +++ b/src/captcha/captcha.module.ts @@ -8,9 +8,11 @@ import { CaptchaChallenge, CaptchaChallengeSchema, } from "./entities/schema/captcha-challenge.schema"; +import { ConfigModule } from "@nestjs/config"; @Module({ imports: [ + ConfigModule, HashModule, MongooseModule.forFeature([ { name: CaptchaChallenge.name, schema: CaptchaChallengeSchema }, diff --git a/src/captcha/captcha.service.ts b/src/captcha/captcha.service.ts index 6088199..387b7dd 100644 --- a/src/captcha/captcha.service.ts +++ b/src/captcha/captcha.service.ts @@ -1,14 +1,17 @@ import { Injectable } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; import * as svgCaptcha from "svg-captcha"; export interface GeneratedCaptcha { - text: string; + text?: string; image: string; expiresAt: number; } @Injectable() export class CaptchaService { + constructor(private readonly configService: ConfigService) {} + generate(): GeneratedCaptcha { const captcha = svgCaptcha.create({ size: 5, @@ -21,6 +24,13 @@ export class CaptchaService { fontSize: 52, }); + if (this.configService.get("NODE_ENV") === "production") { + return { + image: `data:image/svg+xml;base64,${Buffer.from(captcha.data, "utf8").toString("base64")}`, + expiresAt: this.buildExpireAt(), + }; + } + return { text: captcha.text, image: `data:image/svg+xml;base64,${Buffer.from(captcha.data, "utf8").toString("base64")}`, diff --git a/src/expert-claim/expert-claim.service.ts b/src/expert-claim/expert-claim.service.ts index 926a778..6d040ef 100644 --- a/src/expert-claim/expert-claim.service.ts +++ b/src/expert-claim/expert-claim.service.ts @@ -3850,7 +3850,7 @@ export class ExpertClaimService { }; } - // Build damaged parts list (aligned with normalized selected parts) + // Build damaged parts list (aligned with normalized selected parts and enriched with evaluation context) const carTypeExpert = claim.vehicle?.carType as | ClaimVehicleTypeV2 | undefined; @@ -3860,6 +3860,13 @@ export class ExpertClaimService { (claim.damage as any)?.selectedOuterParts, ); const damagedPartsDataExpert = claim.media?.damagedParts as any; + + // Extract optimization lookups from the DB document safely + const evaluationBlock = (claim as any).evaluation; + const priceDropLines = evaluationBlock?.priceDrop?.partLines || []; + const objectionParts = evaluationBlock?.objection?.objectionParts || []; + const newObjectionParts = evaluationBlock?.objection?.newParts || []; + const damagedParts = selectedNormExpert.map((sp, index) => { const ck = sp.catalogKey ?? catalogLikeKeyFromPart(sp); const cap = getDamagedPartCaptureBlob( @@ -3867,6 +3874,22 @@ export class ExpertClaimService { ck, selectedNormExpert, ) as { url?: string; path?: string } | undefined; + + // 1. Cross-reference Price Drop items via partId or name match + const matchingPriceDrop = priceDropLines.find( + (line: any) => + line.partId === sp.id || + line.priceDropPartKey?.toLowerCase() === sp.name?.toLowerCase(), + ); + + // 2. Evaluate if this specific part is flag-linked within an active user objection + const isObjected = objectionParts.some( + (objPart: any) => objPart.id === sp.id || objPart.name === sp.name, + ); + const isNewAddedPart = newObjectionParts.some( + (newPart: any) => newPart.id === sp.id || newPart.name === sp.name, + ); + return { index, partId: catalogPartIdFromSelectedPart(sp), @@ -3880,6 +3903,15 @@ export class ExpertClaimService { selectedNormExpert, ), url: cap?.url || (cap?.path ? buildFileLink(cap.path) : undefined), + + // --- Added Consolidated Production Enrichments --- + severity: matchingPriceDrop ? matchingPriceDrop.severity : null, + coefficient: matchingPriceDrop ? matchingPriceDrop.coefficient : null, + objectionStatus: { + hasObjection: isObjected, + isNewlyAddedByObjection: isNewAddedPart, + // You can map extra parameters here if the front-end requires reason texts + }, }; }); diff --git a/src/request-management/request-management.service.ts b/src/request-management/request-management.service.ts index 743a3e9..b7e179a 100644 --- a/src/request-management/request-management.service.ts +++ b/src/request-management/request-management.service.ts @@ -1068,10 +1068,19 @@ export class RequestManagementService { const companyCode = inquiryMapped?.CompanyCode; const client = await this.clientService.findClientWithCompanyCode(+companyCode); + if (!client) { - throw new HttpException("Client not found", HttpStatus.CONFLICT); + await this.clientService.addClient({ + clientName, + clientCode: companyCode, + useExpertMode: "legal", + }); } + // if (!client) { + // throw new HttpException("Client not found", HttpStatus.CONFLICT); + // } + // Persist inquiry/body data into new model if (!party.person) party.person = {} as any; const resolvedClientId = (client as any)?._id ?? (client as any)?._doc?._id;