import { HttpStatus, Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service.js'; import { getRequestContext } from '../common/context/request-context.js'; import { ErrorCodes } from '../common/error-codes.js'; import { BusinessException } from '../common/business-exception.js'; import { ConfigSvcOrgDto } from './dto.js'; /** * 服务目录四层(L1 国家基准库 / L2 属地目录 / L3 映射(机构不可见) / L4 机构项目库)。 * * - SvcNational/SvcLocal/SvcMapping 是全局表(TENANT_FREE),读取不会注入 tenantCode; * - SvcOrg 是租户级表(读自动注入 tenantCode + 软删),tenantCode 由本 service 显式写入 * (读操作再次被 PrismaService 扩展注入同值 tenantCode,幂等)。 */ @Injectable() export class CatalogService { constructor(private readonly prisma: PrismaService) {} /** 按属地加载目录:优先 SvcLocal;无本地目录(新建制州市)回退 SvcNational 全量。 */ async listLocal(locCode: string) { const locals = await this.prisma.client.svcLocal.findMany({ where: { locCode } }); if (locals.length > 0) return locals; return this.prisma.client.svcNational.findMany(); } /** 当前租户的机构项目库(SvcOrg)。 */ listOrg() { const ctx = getRequestContext()!; return this.prisma.client.svcOrg.findMany({ where: { tenantCode: ctx.tenantCode } }); } /** 勾选+配价:只允许改 isEnabled/price,夹带 srcType/srcId 等业务字段一律拒绝。 */ async config(dto: ConfigSvcOrgDto) { this.assertOnlyMutableFields(dto); const ctx = getRequestContext()!; const existing = await this.prisma.client.svcOrg.findFirst({ where: { id: dto.id, tenantCode: ctx.tenantCode }, }); if (!existing) { throw new BusinessException(ErrorCodes.SVC_ORG_NOT_FOUND, '机构项目不存在', HttpStatus.NOT_FOUND); } const data: Record = {}; if (dto.isEnabled !== undefined) data.isEnabled = dto.isEnabled; if (dto.price !== undefined) data.price = dto.price; return this.prisma.client.svcOrg.update({ where: { id: dto.id }, data }); } /** 国家新增项(昆明目录缺、国家目录有的 5 项,机构勾选界面需显式提示)。 */ newNational() { // TODO(C1): SvcNational 无 is_new_national 列,「国家新增项」= 昆明 L2 缺而国家 L1 有的 // 差集(L3 映射 national_only)。C1 需按 L3 映射补齐并标记 is_new_national,此处暂返回全量。 return this.prisma.client.svcNational.findMany(); } /** 白名单校验:config 只允许 id/isEnabled/price,夹带其他业务字段抛 SVC_LOC_IMMUTABLE。 */ private assertOnlyMutableFields(dto: ConfigSvcOrgDto) { const allowed = new Set(['id', 'isEnabled', 'price']); const extra = Object.keys(dto).filter((key) => !allowed.has(key)); if (extra.length > 0) { throw new BusinessException(ErrorCodes.SVC_LOC_IMMUTABLE, '仅可调整启用状态与价格', HttpStatus.BAD_REQUEST); } } }