diff --git a/src/auth/auth-services/actor.auth.service.ts b/src/auth/auth-services/actor.auth.service.ts index e37a58b..41d0655 100644 --- a/src/auth/auth-services/actor.auth.service.ts +++ b/src/auth/auth-services/actor.auth.service.ts @@ -28,7 +28,7 @@ import { FieldExpertDbService } from "src/users/entities/db-service/field-expert import { RegistrarDbService } from "src/users/entities/db-service/registrar.db.service"; import { HashService } from "src/utils/hash/hash.service"; // import { MailService } from "src/utils/mail/mail.service"; -import { OtpService } from "src/utils/otp/otp.service"; +import { OtpGeneratorService } from "src/sms-orchestration/otp-generator.service"; function pick(obj: Record, keys: string[]) { const out: Record = {}; @@ -51,7 +51,7 @@ export class ActorAuthService { private readonly insurerExpertDbService: InsurerExpertDbService, // private readonly mailService: MailService, // Mailer disabled – not used private readonly clientDbService: ClientDbService, - private readonly otpService: OtpService, + private readonly otpService: OtpGeneratorService, ) {} // TODO convrt to class for dynamic controller diff --git a/src/auth/auth-services/user.auth.service.ts b/src/auth/auth-services/user.auth.service.ts index 6aea3fd..35246d1 100644 --- a/src/auth/auth-services/user.auth.service.ts +++ b/src/auth/auth-services/user.auth.service.ts @@ -10,10 +10,10 @@ import { import { JwtService } from "@nestjs/jwt"; import { Types } from "mongoose"; import { LoginDtoRs } from "src/auth/dto/user/login.dto"; +import { OtpGeneratorService } from "src/sms-orchestration/otp-generator.service"; import { UserDbService } from "src/users/entities/db-service/user.db.service"; -import { HashService } from "src/utils/hash/hash.service"; -import { OtpService } from "src/utils/otp/otp.service"; import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service"; +import { HashService } from "src/utils/hash/hash.service"; // TODO FIX REGISTER TO USER.SERVICE AND AUTH IN THIS MODULE @Injectable() @@ -24,7 +24,7 @@ export class UserAuthService { private readonly jwtService: JwtService, private readonly userDbService: UserDbService, private readonly hashService: HashService, - private readonly otpCreator: OtpService, + private readonly otpCreator: OtpGeneratorService, private readonly smsOrchestrationService: SmsOrchestrationService, ) {} diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 34b25e4..5eee53e 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -11,7 +11,6 @@ import { ClientModule } from "src/client/client.module"; import { UsersModule } from "src/users/users.module"; import { HashModule } from "src/utils/hash/hash.module"; // import { MailModule } from "src/utils/mail/mail.module"; -import { OtpModule } from "src/utils/otp/otp.module"; import { SmsOrchestrationModule } from "src/sms-orchestration/sms-orchestration.module"; @Module({ @@ -20,7 +19,6 @@ import { SmsOrchestrationModule } from "src/sms-orchestration/sms-orchestration. UsersModule, ClientModule, HashModule, - OtpModule, PassportModule, SmsOrchestrationModule, JwtModule.register({ diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index 04bd61d..e21a783 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -68,6 +68,7 @@ import { PublicIdService } from "src/utils/public-id/public-id.service"; import { ImageRequiredModel } from "./entites/schema/image-required.schema"; import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service"; import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service"; +import { SandHubService } from "src/sand-hub/sand-hub.service"; import { ExpertFileActivityType, ExpertFileKind, @@ -76,6 +77,7 @@ import { FactorStatus } from "src/Types&Enums/claim-request-management/factor-st import { BranchDbService } from "src/client/entities/db-service/branch.db.service"; import { CreationMethod } from "src/request-management/entities/schema/request-management.schema"; import { RoleEnum } from "src/Types&Enums/role.enum"; +import { PartyRole } from "src/request-management/entities/schema/partyRole.enum"; import { UserRatingDto } from "./dto/user-rating.dto"; import { canFinalizeExpertResend, @@ -158,8 +160,61 @@ export class ClaimRequestManagementService { private readonly branchDbService: BranchDbService, private readonly claimRequiredDocumentDbService: ClaimRequiredDocumentDbService, private readonly publicIdService: PublicIdService, + private readonly sandHubService: SandHubService, ) {} + private parsePlateFromCompactString( + plateId: string | undefined, + ): { leftDigits: number; centerAlphabet: string; centerDigits: number; ir: number } | null { + if (!plateId) return null; + const parts = String(plateId).split("-"); + if (parts.length !== 4) return null; + const [irRaw, leftRaw, alphaRaw, centerRaw] = parts; + const ir = Number(irRaw); + const leftDigits = Number(leftRaw); + const centerDigits = Number(centerRaw); + const centerAlphabet = String(alphaRaw || "").trim(); + if ( + !Number.isFinite(ir) || + !Number.isFinite(leftDigits) || + !Number.isFinite(centerDigits) || + !centerAlphabet + ) { + return null; + } + return { leftDigits, centerAlphabet, centerDigits, ir }; + } + + private resolveOwnershipPlateForClaim( + claimCase: any, + blameRequest?: any, + ): { leftDigits: number; centerAlphabet: string; centerDigits: number; ir: number } | null { + const p = claimCase?.vehicle?.plate; + if ( + p && + Number.isFinite(Number(p.leftDigits)) && + Number.isFinite(Number(p.centerDigits)) && + Number.isFinite(Number(p.ir)) && + String(p.centerAlphabet || "").trim() + ) { + return { + leftDigits: Number(p.leftDigits), + centerAlphabet: String(p.centerAlphabet).trim(), + centerDigits: Number(p.centerDigits), + ir: Number(p.ir), + }; + } + + if (!blameRequest || !Array.isArray(blameRequest.parties)) return null; + const ownerUserId = claimCase?.owner?.userId?.toString?.(); + const matchedParty = + blameRequest.parties.find( + (party: any) => String(party?.person?.userId || "") === String(ownerUserId || ""), + ) || blameRequest.parties.find((party: any) => party?.role === PartyRole.FIRST); + const compactPlateId = matchedParty?.vehicle?.plateId; + return this.parsePlateFromCompactString(compactPlateId); + } + private delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } @@ -4008,17 +4063,17 @@ export class ClaimRequestManagementService { ); } - // At most two non-top sides allowed - const sideSet = new Set( - selectedItems - .map((p) => p.side) - .filter((s) => s !== "top"), - ); - if (sideSet.size > 2) { - throw new BadRequestException( - `At most two of left/right/front/back can be selected. Selected: ${[...sideSet].join(", ")}`, - ); - } + // DISABLED: At most two non-top sides allowed + // const sideSet = new Set( + // selectedItems + // .map((p) => p.side) + // .filter((s) => s !== "top"), + // ); + // if (sideSet.size > 2) { + // throw new BadRequestException( + // `At most two of left/right/front/back can be selected. Selected: ${[...sideSet].join(", ")}`, + // ); + // } const selectedPartDocs = selectedItems.map((p) => catalogItemToSelectedPart(p, catalog), @@ -4196,6 +4251,22 @@ export class ClaimRequestManagementService { ); } + const blameRequest = claimCase.blameRequestId + ? await this.blameRequestDbService.findById(claimCase.blameRequestId.toString()) + : null; + const ownershipPlate = this.resolveOwnershipPlateForClaim(claimCase, blameRequest); + if (!ownershipPlate) { + throw new BadRequestException( + "Could not resolve vehicle plate for ownership validation.", + ); + } + + // External inquiry checks before moving workflow: + // 1) ownership check for nationalCode + plate + // await this.sandHubService.getCarOwnershipInfo(ownershipPlate, nationalCode); + // 2) sheba check for nationalCode + sheba + await this.sandHubService.getShebaValidation(nationalCode, shebaNumber); + const updatePayload: any = { 'damage.otherParts': otherParts.length > 0 ? otherParts : undefined, 'money.sheba': shebaNumber, diff --git a/src/client/dto/create-branch.dto.ts b/src/client/dto/create-branch.dto.ts index fa704ea..ad9b5ce 100644 --- a/src/client/dto/create-branch.dto.ts +++ b/src/client/dto/create-branch.dto.ts @@ -1,5 +1,11 @@ import { ApiProperty } from "@nestjs/swagger"; -import { IsString, IsNotEmpty, IsOptional } from "class-validator"; +import { + IsBoolean, + IsDateString, + IsNotEmpty, + IsOptional, + IsString, +} from "class-validator"; export class CreateBranchDto { @ApiProperty({ example: "شهرک غرب" }) @@ -31,4 +37,18 @@ export class CreateBranchDto { @IsOptional() @IsString() phoneNumber?: string; + + @ApiProperty({ required: false, example: true, default: true }) + @IsOptional() + @IsBoolean() + isActive?: boolean; + + @ApiProperty({ + required: false, + example: "2025-01-01T00:00:00.000Z", + description: "Branch activity start datetime (ISO string)", + }) + @IsOptional() + @IsDateString() + activityStartDate?: string; } diff --git a/src/client/entities/db-service/branch.db.service.ts b/src/client/entities/db-service/branch.db.service.ts index 66b79ed..8e40fa3 100644 --- a/src/client/entities/db-service/branch.db.service.ts +++ b/src/client/entities/db-service/branch.db.service.ts @@ -25,7 +25,71 @@ export class BranchDbService { async findAll(insuranceId: string): Promise { return await this.branchModel.find({ clientKey: new Types.ObjectId(insuranceId), - }, { _id: 1, name: 1, code: 1, city: 1, state: 1, address: 1, phoneNumber: 1, createdAtFa: 1, updatedAtFa: 1 }); + }); + } + + async findAllWithFilters( + insuranceId: string, + opts?: { + search?: string; + from?: Date; + to?: Date; + isActive?: boolean; + }, + ): Promise { + const filter: any = { clientKey: new Types.ObjectId(insuranceId) }; + + if (typeof opts?.isActive === "boolean") { + filter.isActive = opts.isActive; + } + + if (opts?.from || opts?.to) { + filter.$or = [ + { + createdAt: { + ...(opts.from ? { $gte: opts.from } : {}), + ...(opts.to ? { $lte: opts.to } : {}), + }, + }, + { + activityStartDate: { + ...(opts.from ? { $gte: opts.from } : {}), + ...(opts.to ? { $lte: opts.to } : {}), + }, + }, + ]; + } + + if (opts?.search?.trim()) { + const q = opts.search.trim(); + const rx = new RegExp(q, "i"); + filter.$and = [ + ...(filter.$and || []), + { + $or: [ + { name: rx }, + { code: rx }, + { city: rx }, + { state: rx }, + { address: rx }, + { phoneNumber: rx }, + ], + }, + ]; + } + + return this.branchModel.find(filter).lean(); + } + + async findByIds(ids: Types.ObjectId[]) { + return this.branchModel + .find({ _id: { $in: ids } }) + .select({ _id: 1, name: 1, code: 1, city: 1, state: 1, address: 1, phoneNumber: 1, isActive: 1, activityStartDate: 1, createdAt: 1, updatedAt: 1 }) + .lean(); + } + + async findByIdAndUpdate(id: string, update: any): Promise { + return this.branchModel.findByIdAndUpdate(id, update, { new: true }).lean(); } async findById(id: string): Promise { diff --git a/src/client/entities/schema/branch.schema.ts b/src/client/entities/schema/branch.schema.ts index 86d9b44..2f4ffe3 100644 --- a/src/client/entities/schema/branch.schema.ts +++ b/src/client/entities/schema/branch.schema.ts @@ -25,6 +25,12 @@ export class BranchModel { @Prop() phoneNumber?: string; + + @Prop({ type: Boolean, default: true }) + isActive?: boolean; + + @Prop({ type: Date }) + activityStartDate?: Date; } export const BranchSchema = SchemaFactory.createForClass(BranchModel); diff --git a/src/expert-claim/expert-claim.v2.controller.ts b/src/expert-claim/expert-claim.v2.controller.ts index 0570eb7..eabc057 100644 --- a/src/expert-claim/expert-claim.v2.controller.ts +++ b/src/expert-claim/expert-claim.v2.controller.ts @@ -6,6 +6,7 @@ import { ApiParam, ApiPropertyOptional, ApiQuery, + ApiResponse, ApiTags, } from "@nestjs/swagger"; import { IsOptional, IsString } from "class-validator"; @@ -18,6 +19,9 @@ import { ExpertClaimService } from "./expert-claim.service"; import { ClaimSubmitResendV2Dto, SubmitExpertReplyV2Dto } from "./dto/expert-claim-v2.dto"; import { UpdateClaimDamagedPartsV2Dto } from "./dto/update-claim-damaged-parts-v2.dto"; import { FactorValidationV2Dto } from "./dto/factor-validation.dto"; +import { ClaimRequestManagementService } from "src/claim-request-management/claim-request-management.service"; +import { OuterPartCatalogItemDto } from "src/claim-request-management/dto/select-outer-parts-v2.dto"; +import { ClaimVehicleTypeV2 } from "src/static/outer-car-parts-catalog"; class InPersonVisitV2Dto { @ApiPropertyOptional({ example: 'Paint damage requires physical inspection' }) @@ -32,7 +36,10 @@ class InPersonVisitV2Dto { @UseGuards(LocalActorAuthGuard, RolesGuard) @Roles(RoleEnum.DAMAGE_EXPERT) export class ExpertClaimV2Controller { - constructor(private readonly expertClaimService: ExpertClaimService) { } + constructor( + private readonly expertClaimService: ExpertClaimService, + private readonly claimRequestManagementService: ClaimRequestManagementService, + ) {} @Get("report/status-counts") @ApiOperation({ @@ -44,6 +51,26 @@ export class ExpertClaimV2Controller { return await this.expertClaimService.getStatusReportBucketsV2(actor); } + /** + * Same catalog as `GET v2/claim-request-management/outer-parts-catalog` so factor/resend payloads use identical part ids. + */ + @Get("outer-parts-catalog") + @ApiOperation({ + summary: "Get outer parts catalog (V2)", + description: + "Returns outer-damage parts with id/key/side. Optional `carType` filter returns only that type catalog.", + }) + @ApiResponse({ + status: 200, + description: "Outer parts catalog", + type: [OuterPartCatalogItemDto], + }) + async getOuterPartsCatalog( + @Query("carType") carType?: ClaimVehicleTypeV2, + ): Promise { + return this.claimRequestManagementService.getOuterPartsCatalogV2(carType); + } + @Get("requests") @ApiOperation({ summary: "List available claim requests for damage expert", diff --git a/src/expert-insurer/expert-insurer.controller.ts b/src/expert-insurer/expert-insurer.controller.ts index f0193be..7282b06 100644 --- a/src/expert-insurer/expert-insurer.controller.ts +++ b/src/expert-insurer/expert-insurer.controller.ts @@ -41,12 +41,35 @@ export class ExpertInsurerController { constructor(private readonly expertInsurerService: ExpertInsurerService) {} @Get("branches") - async getInsuranceBranches(@CurrentUser() insurer) { + @ApiQuery({ name: "search", required: false, type: String }) + @ApiQuery({ + name: "from", + required: false, + description: "Optional start datetime (ISO string)", + }) + @ApiQuery({ + name: "to", + required: false, + description: "Optional end datetime (ISO string)", + }) + @ApiQuery({ + name: "isActive", + required: false, + description: "Filter active state (true/false)", + }) + async getInsuranceBranches( + @CurrentUser() insurer, + @Query("search") search?: string, + @Query("from") from?: string, + @Query("to") to?: string, + @Query("isActive") isActive?: string, + ) { if (!insurer) { throw new UnauthorizedException("Could not identify the current user."); } return await this.expertInsurerService.retrieveInsuranceBranches( insurer.clientKey, + { search, from, to, isActive }, ); } @@ -66,6 +89,29 @@ export class ExpertInsurerController { ); } + @Put("branches/:branchId/status") + @ApiParam({ name: "branchId" }) + @ApiQuery({ name: "isActive", type: Boolean }) + async setBranchStatus( + @CurrentUser() insurer, + @Param("branchId") branchId: string, + @Query("isActive") isActive: string, + ) { + if (!insurer) { + throw new UnauthorizedException("Could not identify the current user."); + } + const normalized = String(isActive).trim().toLowerCase(); + if (!["true", "false", "1", "0", "yes", "no"].includes(normalized)) { + throw new BadRequestException("isActive must be true/false"); + } + const active = ["true", "1", "yes"].includes(normalized); + return this.expertInsurerService.setBranchActive( + insurer.clientKey, + branchId, + active, + ); + } + @Post("experts/blame") @ApiBody({ type: CreateBlameExpertByInsurerDto }) async addBlameExpert( diff --git a/src/expert-insurer/expert-insurer.service.ts b/src/expert-insurer/expert-insurer.service.ts index cb33bdd..135a7b5 100644 --- a/src/expert-insurer/expert-insurer.service.ts +++ b/src/expert-insurer/expert-insurer.service.ts @@ -1207,12 +1207,66 @@ export class ExpertInsurerService { const newBranch = await this.branchDbService.create({ ...branchDto, + isActive: branchDto.isActive ?? true, + activityStartDate: branchDto.activityStartDate + ? new Date(branchDto.activityStartDate) + : undefined, clientKey: clientId, }); return newBranch; } + private parseOptionalBoolean(value?: string): boolean | undefined { + if (value == null || value === "") return undefined; + const normalized = String(value).trim().toLowerCase(); + if (["true", "1", "yes"].includes(normalized)) return true; + if (["false", "0", "no"].includes(normalized)) return false; + throw new BadRequestException("Invalid boolean value for 'isActive'"); + } + + private buildHandlingBranchStatusSets() { + const blameInHandling = new Set([ + CaseStatus.OPEN, + CaseStatus.WAITING_FOR_SECOND_PARTY, + CaseStatus.WAITING_FOR_EXPERT, + CaseStatus.WAITING_FOR_DOCUMENT_RESEND, + CaseStatus.WAITING_FOR_SIGNATURES, + ]); + const claimInHandling = new Set([ + ClaimCaseStatus.CREATED, + ClaimCaseStatus.SELECTING_OUTER_PARTS, + ClaimCaseStatus.SELECTING_OTHER_PARTS, + ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS, + ClaimCaseStatus.CAPTURING_PART_DAMAGES, + ClaimCaseStatus.WAITING_FOR_USER_RESEND, + ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT, + ClaimCaseStatus.EXPERT_REVIEWING, + ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL, + ClaimCaseStatus.INSURER_REVIEW_AWAITING_OWNER_SIGN, + ClaimCaseStatus.INSURER_REVIEW_MIXED_FACTORS_PENDING, + ClaimCaseStatus.OWNER_REPAIR_FACTOR_UPLOAD_PENDING, + ClaimCaseStatus.EXPERT_VALIDATING_REPAIR_FACTORS, + ]); + return { blameInHandling, claimInHandling }; + } + + async setBranchActive( + clientKey: string, + branchId: string, + isActive: boolean, + ) { + const clientId = this.getClientId(clientKey); + await this.assertBranchBelongsToClient(branchId, clientId); + const updated = await this.branchDbService.findByIdAndUpdate(branchId, { + $set: { isActive }, + }); + if (!updated) { + throw new NotFoundException("Branch not found"); + } + return updated; + } + private async assertBranchBelongsToClient( branchId: string, clientId: Types.ObjectId, @@ -1445,8 +1499,108 @@ export class ExpertInsurerService { }; } - async retrieveInsuranceBranches(insuranceId: string) { - return this.branchDbService.findAll(insuranceId); + async retrieveInsuranceBranches( + insuranceId: string, + opts?: { + search?: string; + from?: string; + to?: string; + isActive?: string; + }, + ) { + const clientObjectId = this.getClientId(insuranceId); + const { fromDate, toDate } = this.parseDateRange(opts?.from, opts?.to); + const isActiveFilter = this.parseOptionalBoolean(opts?.isActive); + const branches = await this.branchDbService.findAllWithFilters(String(clientObjectId), { + search: opts?.search, + from: fromDate, + to: toDate, + isActive: isActiveFilter, + }); + + const branchIdSet = new Set(branches.map((b: any) => String(b._id))); + const ckFilter = this.clientKeyScopeFilter(clientObjectId); + const [experts, damageExperts, blameFiles, claimFiles] = await Promise.all([ + this.expertDbService.findAll(ckFilter as never), + this.damageExpertDbService.findAll(ckFilter as never), + this.getClientBlameFiles(clientObjectId), + this.getClientClaimFiles(clientObjectId), + ]); + + const activeExpertsByBranch = new Map>(); + const expertBranchById = new Map(); + const claimExpertBranchById = new Map(); + for (const e of experts as any[]) { + const bid = e?.branchId ? String(e.branchId) : ""; + if (!bid || !branchIdSet.has(bid)) continue; + const eid = String(e?._id || ""); + if (!eid) continue; + expertBranchById.set(eid, bid); + if (!activeExpertsByBranch.has(bid)) activeExpertsByBranch.set(bid, new Set()); + activeExpertsByBranch.get(bid)!.add(eid); + } + for (const e of damageExperts as any[]) { + const bid = e?.branchId ? String(e.branchId) : ""; + if (!bid || !branchIdSet.has(bid)) continue; + const eid = String(e?._id || ""); + if (!eid) continue; + claimExpertBranchById.set(eid, bid); + if (!activeExpertsByBranch.has(bid)) activeExpertsByBranch.set(bid, new Set()); + activeExpertsByBranch.get(bid)!.add(eid); + } + + const completedByBranch = new Map>(); + const handlingByBranch = new Map>(); + const { blameInHandling, claimInHandling } = this.buildHandlingBranchStatusSets(); + const addPublicId = (map: Map>, branchId: string, fileKey: string) => { + if (!map.has(branchId)) map.set(branchId, new Set()); + map.get(branchId)!.add(fileKey); + }; + + for (const b of blameFiles as any[]) { + const expertId = String(b?.expert?.assignedExpertId || ""); + if (!expertId) continue; + const bid = expertBranchById.get(expertId); + if (!bid) continue; + const fileKey = String(b?.publicId || b?._id || ""); + if (!fileKey) continue; + const status = String(b?.status || ""); + if (status === CaseStatus.COMPLETED) addPublicId(completedByBranch, bid, fileKey); + if (blameInHandling.has(status)) addPublicId(handlingByBranch, bid, fileKey); + } + + for (const c of claimFiles as any[]) { + const expertId = String(this.claimDamageExpertActorId(c) || ""); + if (!expertId) continue; + const bid = claimExpertBranchById.get(expertId); + if (!bid) continue; + const fileKey = String(c?.publicId || c?._id || ""); + if (!fileKey) continue; + const status = String(c?.status || ""); + if (status === ClaimCaseStatus.COMPLETED) addPublicId(completedByBranch, bid, fileKey); + if (claimInHandling.has(status)) addPublicId(handlingByBranch, bid, fileKey); + } + + const list = branches.map((branch: any) => { + const bid = String(branch._id); + return { + ...branch, + totalActiveExperts: activeExpertsByBranch.get(bid)?.size ?? 0, + totalFinishedFiles: completedByBranch.get(bid)?.size ?? 0, + totalFilesInHandling: handlingByBranch.get(bid)?.size ?? 0, + }; + }); + + return { + total: list.length, + filters: { + search: opts?.search ?? null, + from: fromDate ?? null, + to: toDate ?? null, + isActive: isActiveFilter ?? null, + }, + branches: list, + }; } private parseDateRange(from?: string, to?: string) { diff --git a/src/request-management/request-management.service.ts b/src/request-management/request-management.service.ts index 30b1046..3b3df8b 100644 --- a/src/request-management/request-management.service.ts +++ b/src/request-management/request-management.service.ts @@ -869,7 +869,68 @@ export class RequestManagementService { ); } - // NOTE: personal inquiry is not part of Tejarat block inquiry flow. + // ---- External inquiry 2: personal identity check (insurer/driver nationalCode + birthDate) ---- + const personalNationalCode = + body.nationalCodeOfInsurer || body.nationalCodeOfDriver; + const birthDateRaw = + body.insurerBirthday ?? body.driverBirthday ?? null; + const birthDateDigits = String(birthDateRaw ?? "") + .replace(/\D/g, "") + .trim(); + const personalBirthDate = Number(birthDateDigits); + if (!personalNationalCode || !Number.isFinite(personalBirthDate) || personalBirthDate <= 0) { + throw new BadRequestException( + "Valid nationalCode and birthDate are required for personal inquiry.", + ); + } + + try { + const personalInquiry = await this.sandHubService.getPersonalInquiry( + personalNationalCode, + personalBirthDate, + ); + this.logger.log( + `[SANDHUB] personal inquiry success request=${req._id} nationalCode=${personalNationalCode}: ${JSON.stringify( + personalInquiry, + )}`, + ); + } catch (err: any) { + this.logger.error( + `[SANDHUB] personal inquiry failed request=${req._id} nationalCode=${personalNationalCode}: ${err?.message || err}`, + ); + throw new HttpException( + "Personal identity inquiry failed", + HttpStatus.BAD_REQUEST, + ); + } + + // ---- External inquiry 3: driving license check (insurerLicense + nationalCode) ---- + // const licenseNationalCode = body.nationalCodeOfInsurer || body.nationalCodeOfDriver; + // const licenseNumber = body.insurerLicense; + // if (!licenseNationalCode || !licenseNumber) { + // throw new BadRequestException( + // "nationalCode and insurerLicense are required for driving license inquiry.", + // ); + // } + // try { + // const licenseInquiry = await this.sandHubService.getDrivingLicenseInfo( + // licenseNationalCode, + // licenseNumber, + // ); + // this.logger.log( + // `[SANDHUB] license inquiry success request=${req._id} nationalCode=${licenseNationalCode}: ${JSON.stringify( + // licenseInquiry, + // )}`, + // ); + // } catch (err: any) { + // this.logger.error( + // `[SANDHUB] license inquiry failed request=${req._id} nationalCode=${licenseNationalCode}: ${err?.message || err}`, + // ); + // throw new HttpException( + // "Driving license inquiry failed", + // HttpStatus.BAD_REQUEST, + // ); + // } // Find client by company code const clientName = inquiryMapped?.CompanyName; diff --git a/src/sand-hub/sand-hub.service.ts b/src/sand-hub/sand-hub.service.ts index 5f18a09..6505ac6 100644 --- a/src/sand-hub/sand-hub.service.ts +++ b/src/sand-hub/sand-hub.service.ts @@ -577,12 +577,11 @@ export class SandHubService { async getShebaValidation(nationalId: string, shebaId: string) { try { - const requestUrl = `${process.env.SANDHUB_BASE_URL}/sheba`; + const requestUrl = `${process.env.SANDHUB_BASE_URL}/sheba-tejaratno`; const requestPayload = { - accountOwnerType: "1", - nationalId: nationalId, - legalId: "", - shebaId: shebaId, + AccountOwnerType: "1", + NationalId: nationalId, + ShebaId: shebaId, }; this.logger.log(`Validating Sheba ID for national code: ${nationalId}`); diff --git a/src/utils/otp/otp.service.ts b/src/sms-orchestration/otp-generator.service.ts similarity index 88% rename from src/utils/otp/otp.service.ts rename to src/sms-orchestration/otp-generator.service.ts index 70ef6df..417a416 100644 --- a/src/utils/otp/otp.service.ts +++ b/src/sms-orchestration/otp-generator.service.ts @@ -1,15 +1,15 @@ import { Injectable } from "@nestjs/common"; -// import crypto from "crypto" const crypto = require("node:crypto"); @Injectable() -export class OtpService { +export class OtpGeneratorService { createDigit(digits: number): string { const max = Math.pow(10, digits); const randomBytes = crypto.randomBytes(Math.ceil(digits / 2)); const randomNumber = parseInt(randomBytes.toString("hex"), 16) % max; return randomNumber.toString().padStart(digits, "0"); } + create() { return this.createDigit(5); } diff --git a/src/sms-orchestration/sms-orchestration.module.ts b/src/sms-orchestration/sms-orchestration.module.ts index bcc1d20..e68d836 100644 --- a/src/sms-orchestration/sms-orchestration.module.ts +++ b/src/sms-orchestration/sms-orchestration.module.ts @@ -3,6 +3,7 @@ import { MongooseModule } from "@nestjs/mongoose"; import { SmsTextDbService } from "./entities/db-service/sms-text.db.service"; import { SmsText, SmsTextSchema } from "./entities/schema/sms-text.schema"; import { SmsGatewayModule } from "./provider/sms-gateway.module"; +import { OtpGeneratorService } from "./otp-generator.service"; import { SmsOrchestrationService } from "./sms-orchestration.service"; @Module({ @@ -10,7 +11,7 @@ import { SmsOrchestrationService } from "./sms-orchestration.service"; SmsGatewayModule, MongooseModule.forFeature([{ name: SmsText.name, schema: SmsTextSchema }]), ], - providers: [SmsTextDbService, SmsOrchestrationService], - exports: [SmsOrchestrationService], + providers: [SmsTextDbService, SmsOrchestrationService, OtpGeneratorService], + exports: [SmsOrchestrationService, OtpGeneratorService], }) export class SmsOrchestrationModule {} diff --git a/src/users/users.module.ts b/src/users/users.module.ts index db86804..12e7e04 100644 --- a/src/users/users.module.ts +++ b/src/users/users.module.ts @@ -3,7 +3,6 @@ import { MongooseModule } from "@nestjs/mongoose"; import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service"; import { ExpertModel } from "src/users/entities/schema/expert.schema"; import { HashModule } from "src/utils/hash/hash.module"; -import { OtpModule } from "src/utils/otp/otp.module"; import { ExpertDbService } from "./entities/db-service/expert.db.service"; import { FieldExpertDbService } from "./entities/db-service/field-expert.db.service"; import { InsurerExpertDbService } from "./entities/db-service/insurer-expert.db.service"; @@ -44,7 +43,6 @@ import { UserModel, UserDbSchema } from "./entities/schema/user.schema"; { name: RegistrarModel.name, schema: RegistrarDbSchema }, { name: ExpertFileActivity.name, schema: ExpertFileActivitySchema }, ]), - OtpModule, HashModule, ], controllers: [], diff --git a/src/utils/otp/otp.module.ts b/src/utils/otp/otp.module.ts deleted file mode 100644 index ab986ca..0000000 --- a/src/utils/otp/otp.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Module } from "@nestjs/common"; -import { OtpService } from "./otp.service"; - -@Module({ - providers: [OtpService], - exports: [OtpService], -}) -export class OtpModule {}