ec-nest/src/catalog/catalog.service.ts
liuyun 945fe5daa3 feat(c0): catalog 目录模块(属地加载 + 机构项目库)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-10-14 13:08:47 +08:00

64 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string, unknown> = {};
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);
}
}
}