feat: 新增客户管理全模块功能

实现客户信息、客户部门、客户账号三个子模块的完整CRUD,包含:
1. 数据库实体、DTO、控制器、服务层代码
2. RBAC权限映射与菜单种子数据扩展
3. 前端路由与API封装
4. 数据权限隔离与鉴权逻辑
5. 配套工具脚本与测试验证清单
This commit is contained in:
andyesfly committed 2026-08-23 19:15:54 +08:00
1 parent 026ec34471
commit 1c80a24d1d
31 files changed
+26321 -653

No files matched your search

+321
View File
@@ -0,0 +1,321 @@
import { Provide, Inject } from "@midwayjs/core";
import { InjectRepository } from "@midwayjs/sequelize";
import { Repository } from "sequelize-typescript";
import { Op, UniqueConstraintError } from "sequelize";
import { Context } from "@midwayjs/koa";
import Customer from "../entity/customer.entity";
import CustomerDepartment from "../entity/customer_department.entity";
import CustomerAccount from "../entity/customer_account.entity";
import SysUserRole from "../entity/sysUserRole.entity";
import SysRole from "../entity/sysRole.entity";
@Provide()
export class CustomerService {
@InjectRepository(Customer)
customerModel!: Repository<Customer>;
@InjectRepository(CustomerDepartment)
customerDepModel!: Repository<CustomerDepartment>;
@InjectRepository(CustomerAccount)
customerAccModel!: Repository<CustomerAccount>;
@InjectRepository(SysUserRole)
userRoleModel!: Repository<SysUserRole>;
@InjectRepository(SysRole)
roleModel!: Repository<SysRole>;
async list(
query: { page?: any; pageSize?: any; keyword?: string; status?: number; salesmanId?: number },
ctx: Context
): Promise<any> {
const page = Number(query.page) || 1;
const pageSize = Number(query.pageSize) || 10;
const where: any = {};
if (query.keyword) {
where[Op.or] = [
{ customerName: { [Op.like]: `%${query.keyword}%` } },
{ contactPerson: { [Op.like]: `%${query.keyword}%` } },
{ contactPhone: { [Op.like]: `%${query.keyword}%` } }
];
}
if (query.status !== undefined) {
where.status = query.status;
}
if (query.salesmanId) {
where.salesmanId = query.salesmanId;
}
const isAdmin = await this.isAdmin(ctx.state.user.userId);
if (!isAdmin) {
where.salesmanId = ctx.state.user.userId;
}
const { count, rows } = await this.customerModel.findAndCountAll({
where,
limit: pageSize,
offset: (page - 1) * pageSize,
order: [["created_at", "DESC"]]
});
return {
code: 200,
message: "获取客户列表成功",
data: {
list: rows,
total: count,
page: Number(page),
pageSize: Number(pageSize)
}
};
}
async detail(id: number, ctx: Context): Promise<any> {
const record = await this.customerModel.findByPk(id);
if (!record) {
return {
code: 404,
message: "客户不存在",
data: null
};
}
const isAdmin = await this.isAdmin(ctx.state.user.userId);
if (!isAdmin && record.salesmanId !== ctx.state.user.userId) {
return {
code: 403,
message: "无权限访问该客户",
data: null
};
}
return {
code: 200,
message: "获取客户详情成功",
data: record
};
}
private async isAdmin(userId: number): Promise<boolean> {
const userRoles = await this.userRoleModel.findAll({
where: { userId },
include: [SysRole]
});
return userRoles.some((ur) => {
const role = (ur as any).role as SysRole;
return role && role.roleCode === "ADMIN";
});
}
private async generateCustomerNo(retry: number = 0): Promise<string> {
const now = new Date();
const offsetMs = now.getTime() + 8 * 60 * 60 * 1000;
const dateLocal = new Date(offsetMs);
const yyyy = dateLocal.getUTCFullYear();
const mm = String(dateLocal.getUTCMonth() + 1).padStart(2, "0");
const dd = String(dateLocal.getUTCDate()).padStart(2, "0");
const yyyymmdd = `${yyyy}${mm}${dd}`;
const lastRecord = await this.customerModel.findOne({
where: { customerNo: { [Op.like]: `KH${yyyymmdd}%` } },
order: [["customer_no", "DESC"]]
});
let newSeq = 1;
if (lastRecord) {
const lastNo = lastRecord.customerNo;
const seqStr = lastNo.slice(-3);
const seq = parseInt(seqStr, 10) || 0;
newSeq = seq + 1;
}
const newSeqStr = String(newSeq).padStart(3, "0");
const result = `KH${yyyymmdd}${newSeqStr}`;
const exists = await this.customerModel.findOne({
where: { customerNo: result }
});
if (exists) {
if (retry < 1) {
return this.generateCustomerNo(retry + 1);
} else {
throw new Error("客户编号生成冲突,请重试");
}
}
return result;
}
async create(data: any, ctx: Context): Promise<any> {
const isAdmin = await this.isAdmin(ctx.state.user.userId);
if (!isAdmin) {
data.salesmanId = ctx.state.user.userId;
}
try {
data.customerNo = await this.generateCustomerNo();
const created = await this.customerModel.create(data);
return {
code: 200,
message: "创建客户成功",
data: created.toJSON()
};
} catch (err: any) {
if (err instanceof UniqueConstraintError && err.errors?.some((e) => e.path === "customer_no" || e.path === "uk_customer_no")) {
try {
data.customerNo = await this.generateCustomerNo();
const created = await this.customerModel.create(data);
return {
code: 200,
message: "创建客户成功",
data: created.toJSON()
};
} catch (_e) {
return {
code: 400,
message: "客户编号冲突,请重试",
data: null
};
}
}
throw err;
}
}
async update(id: number, data: any, ctx: Context): Promise<any> {
const record = await this.customerModel.findByPk(id);
if (!record) {
return {
code: 404,
message: "客户不存在",
data: null
};
}
const isAdmin = await this.isAdmin(ctx.state.user.userId);
if (!isAdmin && record.salesmanId !== ctx.state.user.userId) {
return {
code: 403,
message: "无权限操作该客户",
data: null
};
}
delete data.customerNo;
delete data.createdAt;
delete data.updatedAt;
if (!isAdmin) {
delete data.salesmanId;
}
await record.update(data);
return {
code: 200,
message: "更新客户成功",
data: record
};
}
async delete(id: number, ctx: Context): Promise<any> {
const record = await this.customerModel.findByPk(id);
if (!record) {
return {
code: 404,
message: "客户不存在",
data: null
};
}
const isAdmin = await this.isAdmin(ctx.state.user.userId);
if (!isAdmin && record.salesmanId !== ctx.state.user.userId) {
return {
code: 403,
message: "无权限操作该客户",
data: null
};
}
const hasDep = await this.customerDepModel.findOne({ where: { customerId: id }, limit: 1 });
const hasAcc = await this.customerAccModel.findOne({ where: { customerId: id }, limit: 1 });
if (hasDep || hasAcc) {
return {
code: 400,
message: "该客户存在关联的部门或账号,无法删除,请先删除关联数据",
data: null
};
}
await record.destroy();
return {
code: 200,
message: "删除客户成功",
data: null
};
}
async updateStatus(id: number, status: number, ctx: Context): Promise<any> {
const record = await this.customerModel.findByPk(id);
if (!record) {
return {
code: 404,
message: "客户不存在",
data: null
};
}
const isAdmin = await this.isAdmin(ctx.state.user.userId);
if (!isAdmin && record.salesmanId !== ctx.state.user.userId) {
return {
code: 403,
message: "无权限操作该客户",
data: null
};
}
await record.update({ status });
return {
code: 200,
message: "更新客户状态成功",
data: null
};
}
async getSimpleList(ctx: Context): Promise<any> {
const userId = ctx.state.user.userId;
const isAdmin = await this.isAdmin(userId);
const where: any = {};
if (!isAdmin) {
where.salesmanId = userId;
}
const rows = await this.customerModel.findAll({
where,
attributes: ["id", "customerNo", "customerName", "status"],
order: [["created_at", "DESC"]]
});
return {
code: 200,
message: "获取客户选项成功",
data: rows.map((r) => r.toJSON())
};
}
}