背景:A3 需把行业 / 客户 / 客户组 / 客户级字段立为资产根节点;用户确认两级行业树与混搭 UI(行业 B / 客户列表 A / 详情 A / 新建 B / 客户组 B / 字段 A)。 改动: - 新增 ADR-002,行业改为两级树,客户挂末级行业;同步 PRD / 技术架构 / 开发计划。 - 新增 client 模块:industries / customers / customer-groups / customer-fields 接口、真实 PrismaCustomerDirectory、客户状态机与客户转交、客户组数据范围解析、跨客户 404。 - 新增迁移与幂等种子(电商/金融两级行业、2 客户、1 客户组、4 条字段)。 - 前端新增行业/客户/客户组/字段审批页面与路由、导航更名(客户管理 / 行业管理 / 客户组),复用现有 token。 - 补充 shared 契约单测、client 集成用例、A3 浏览器 e2e。 影响面:client 模块与 client 表、shared 契约、web 路由/导航、ScopeContext 数据范围、OpenAPI 与种子;A1 客户转交占位由 client 实现替换。 回滚:回滚本提交;数据库保留新增表或按报告执行 down SQL;CUSTOMER_DIRECTORY 可切回空集实现。 验证:format:check / lint / typecheck / test 全绿(shared 131 / api 90 / web 125,共 346);test:int 5 文件 39 条;A3 e2e 1/1;build 8/8;真实浏览器六页验证通过。
52 lines
1.8 KiB
TypeScript
52 lines
1.8 KiB
TypeScript
import { Inject, Injectable } from '@nestjs/common';
|
|
|
|
import type { CustomerDirectory } from '../../common/security/scope/customer-directory';
|
|
import type { PrismaService } from '../../infra/prisma/prisma.service';
|
|
import { PRISMA } from '../../infra/tokens';
|
|
|
|
/**
|
|
* 真实客户目录(A3 替换 A2 的 EmptyCustomerDirectory)。
|
|
* 只读,供 ScopeResolver 解析六类数据范围;不参与业务写操作。
|
|
*/
|
|
@Injectable()
|
|
export class PrismaCustomerDirectory implements CustomerDirectory {
|
|
constructor(@Inject(PRISMA) private readonly prisma: PrismaService) {}
|
|
|
|
async findOwnedCustomerIds(userId: string): Promise<string[]> {
|
|
const rows = await this.prisma.customer.findMany({
|
|
where: { ownerUserId: userId, deletedAt: null },
|
|
select: { id: true },
|
|
});
|
|
return rows.map((row) => row.id);
|
|
}
|
|
|
|
async findCustomerGroupCustomerIds(customerGroupIds: readonly string[]): Promise<string[]> {
|
|
if (customerGroupIds.length === 0) return [];
|
|
const rows = await this.prisma.customerGroupMember.findMany({
|
|
where: { groupId: { in: [...customerGroupIds] }, customer: { deletedAt: null } },
|
|
select: { customerId: true },
|
|
});
|
|
return [...new Set(rows.map((row) => row.customerId))].sort();
|
|
}
|
|
|
|
async findDepartmentCustomerIds(departmentIds: readonly string[]): Promise<string[]> {
|
|
if (departmentIds.length === 0) return [];
|
|
const rows = await this.prisma.customer.findMany({
|
|
where: {
|
|
deletedAt: null,
|
|
owner: { departmentId: { in: [...departmentIds] } },
|
|
},
|
|
select: { id: true },
|
|
});
|
|
return rows.map((row) => row.id);
|
|
}
|
|
|
|
async findCreatedCustomerIds(userId: string): Promise<string[]> {
|
|
const rows = await this.prisma.customer.findMany({
|
|
where: { createdBy: userId, deletedAt: null },
|
|
select: { id: true },
|
|
});
|
|
return rows.map((row) => row.id);
|
|
}
|
|
}
|