import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { DiagnosticCode, DiagnosticCodeDocument } from './schemas/diagnostic-code.schema'; import { ProcedureCode, ProcedureCodeDocument } from './schemas/procedure-code.schema'; import { CreateDiagnosticCodeDto } from './dto/create-diagnostic-code.dto'; import { CreateProcedureCodeDto } from './dto/create-procedure-code.dto'; import { SearchCodesDto } from './dto/search-codes.dto'; export interface GlobalSearchResult { icd10: Array & { type: string }>; cpt: Array & { type: string }>; } @Injectable() export class CodesService { constructor( @InjectModel(DiagnosticCode.name) private icd10Model: Model, @InjectModel(ProcedureCode.name) private cptModel: Model, ) {} // ─── ICD-10 ─────────────────────────────────────────────────────────────── async searchICD10(dto: SearchCodesDto) { const { q, page = 1, limit = 20, category, billable } = dto; const filter: any = { isActive: true }; if (q) { filter.$or = [ { code: { $regex: q, $options: 'i' } }, { description: { $regex: q, $options: 'i' } }, ]; } if (category) filter.category = category; if (billable !== undefined) filter.billable = billable; const [data, total] = await Promise.all([ this.icd10Model .find(filter) .skip((page - 1) * limit) .limit(limit) .sort({ code: 1 }), this.icd10Model.countDocuments(filter), ]); return { data, total, page, limit, pages: Math.ceil(total / limit) }; } async getICD10ByCode(code: string) { const result = await this.icd10Model.findOne({ code: code.toUpperCase() }); if (!result) throw new NotFoundException(`ICD-10 code ${code} not found`); return result; } async createICD10(dto: CreateDiagnosticCodeDto) { return this.icd10Model.create(dto); } async getICD10Categories() { return this.icd10Model.distinct('category', { isActive: true }); } // ─── CPT ────────────────────────────────────────────────────────────────── async searchCPT(dto: SearchCodesDto) { const { q, page = 1, limit = 20, category, billable } = dto; const filter: any = { isActive: true }; if (q) { filter.$or = [ { code: { $regex: q, $options: 'i' } }, { description: { $regex: q, $options: 'i' } }, ]; } if (category) filter.category = category; if (billable !== undefined) filter.billable = billable; const [data, total] = await Promise.all([ this.cptModel .find(filter) .skip((page - 1) * limit) .limit(limit) .sort({ code: 1 }), this.cptModel.countDocuments(filter), ]); return { data, total, page, limit, pages: Math.ceil(total / limit) }; } async getCPTByCode(code: string) { const result = await this.cptModel.findOne({ code }); if (!result) throw new NotFoundException(`CPT code ${code} not found`); return result; } async createCPT(dto: CreateProcedureCodeDto) { return this.cptModel.create(dto); } async getCPTCategories() { return this.cptModel.distinct('category', { isActive: true }); } // ─── Global search ──────────────────────────────────────────────────────── async globalSearch(q: string, limit = 10): Promise { const filter = { isActive: true, $or: [ { code: { $regex: q, $options: 'i' } }, { description: { $regex: q, $options: 'i' } }, ], }; const [icd10, cpt] = await Promise.all([ this.icd10Model.find(filter).limit(limit).lean(), this.cptModel.find(filter).limit(limit).lean(), ]); return { icd10: icd10.map((c) => ({ ...c, type: 'ICD-10' })), cpt: cpt.map((c) => ({ ...c, type: 'CPT' })), }; } }