feat: 新增客户用电回路全链路功能

新增客户用电回路表及相关数据库迁移、后端服务、控制器、DTO、实体类和权限配置;新增前端API封装,同时调整路由名称以匹配新业务模块,复用现有菜单权限避免403错误。
This commit is contained in:
andyesfly committed 2026-10-12 15:20:08 +08:00
1 parent 3596462eca
commit f72644c24f
12 files changed
+1975 -411

No files matched your search

@@ -0,0 +1,178 @@
import { Provide } from "@midwayjs/core";
import { InjectRepository } from "@midwayjs/sequelize";
import { Repository } from "sequelize-typescript";
import { Op } from "sequelize";
import { Context } from "@midwayjs/koa";
import CustomerCircuit from "../entity/customer_circuit.entity";
import Customer from "../entity/customer.entity";
import CustomerBuilding from "../entity/customer_building.entity";
import CustomerRoom from "../entity/customer_room.entity";
import SysUserRole from "../entity/sysUserRole.entity";
import SysRole from "../entity/sysRole.entity";
// 楼层排序:数值楼层升序(-1 < 1 < 2 …),"未标注楼层"(空串)排最后,其余按字符串排序
function compareFloor(a: string, b: string): number {
if (a === b) return 0;
if (a === "") return 1;
if (b === "") return -1;
const na = Number(a);
const nb = Number(b);
const aIsNum = !Number.isNaN(na);
const bIsNum = !Number.isNaN(nb);
if (aIsNum && bIsNum) return na - nb;
if (aIsNum) return -1;
if (bIsNum) return 1;
return a.localeCompare(b);
}
@Provide()
export class CustomerCircuitService {
@InjectRepository(CustomerCircuit)
circuitModel!: Repository<CustomerCircuit>;
@InjectRepository(Customer)
customerModel!: Repository<Customer>;
@InjectRepository(CustomerBuilding)
buildingModel!: Repository<CustomerBuilding>;
@InjectRepository(CustomerRoom)
roomModel!: Repository<CustomerRoom>;
@InjectRepository(SysUserRole)
userRoleModel!: Repository<SysUserRole>;
@InjectRepository(SysRole)
roleModel!: Repository<SysRole>;
private async isAdmin(userId: number): Promise<boolean> {
const userRoles = await this.userRoleModel.findAll({ where: { userId }, include: [this.roleModel] });
return userRoles.some((ur) => (ur as any).role?.roleCode === "ADMIN");
}
private async isCustomerOwner(userId: number, customerId: number): Promise<boolean> {
if (await this.isAdmin(userId)) return true;
const customer = await this.customerModel.findByPk(customerId);
return !!customer && customer.salesmanId === userId;
}
async list(query: any, ctx: Context): Promise<any> {
const page = Number(query.page) || 1;
const pageSize = Number(query.pageSize) || 10;
const userId = ctx.state.user.userId;
const where: any = {};
if (query.keyword) {
where[Op.or] = [
{ circuitName: { [Op.like]: `%${query.keyword}%` } },
{ circuitCode: { [Op.like]: `%${query.keyword}%` } }
];
}
if (query.customerId !== undefined) where.customerId = query.customerId;
if (query.buildingId !== undefined) where.buildingId = query.buildingId;
if (query.roomId !== undefined) where.roomId = query.roomId;
if (query.floorNo !== undefined) where.floorNo = query.floorNo;
const emptyResult = { code: 200, message: "获取用电回路列表成功", data: { list: [], total: 0, page: Number(page), pageSize: Number(pageSize) } };
if (!(await this.isAdmin(userId))) {
const myCustomers = await this.customerModel.findAll({ where: { salesmanId: userId }, attributes: ["id"] });
const ids = myCustomers.map((c) => c.id);
if (ids.length === 0) return emptyResult;
if (where.customerId !== undefined) {
if (!ids.includes(where.customerId)) return emptyResult;
} else {
where.customerId = { [Op.in]: ids };
}
}
const { count, rows } = await this.circuitModel.findAndCountAll({
where,
limit: pageSize,
offset: (page - 1) * pageSize,
order: [["sort_order", "ASC"], ["created_at", "DESC"]]
});
const list = rows.map((r) => r.toJSON());
return { code: 200, message: "获取用电回路列表成功", data: { list, total: count, page: Number(page), pageSize: Number(pageSize) } };
}
// 一次返回整棵「建筑 → 楼层 → 房间 → 回路」结构,避免前端逐建筑请求(N+1)
async topology(query: any, ctx: Context): Promise<any> {
const customerId = Number(query.customerId);
if (!customerId) return { code: 400, message: "缺少 customerId 参数", data: null };
if (!(await this.isCustomerOwner(ctx.state.user.userId, customerId))) {
return { code: 403, message: "无权限访问该客户的空间拓扑", data: null };
}
const [buildings, rooms, circuits] = await Promise.all([
this.buildingModel.findAll({ where: { customerId }, order: [["id", "ASC"]] }),
this.roomModel.findAll({ where: { customerId }, order: [["id", "ASC"]] }),
this.circuitModel.findAll({ where: { customerId }, order: [["sort_order", "ASC"], ["id", "ASC"]] })
]);
const circuitJson = circuits.map((c) => c.toJSON()) as any[];
const data = buildings.map((b) => {
const bRooms = rooms.filter((r) => r.buildingId === b.id);
const bCircuits = circuitJson.filter((c) => c.buildingId === b.id);
const floorSet = Array.from(
new Set([...bRooms.map((r) => r.floorNo || ""), ...bCircuits.map((c) => c.floorNo || "")])
).sort(compareFloor);
return {
...b.toJSON(),
floors: floorSet.map((floorNo) => ({
floorNo,
rooms: bRooms
.filter((r) => (r.floorNo || "") === floorNo)
.map((r) => ({
...r.toJSON(),
circuits: bCircuits.filter((c) => c.roomId === r.id)
})),
// 公共区域回路:未挂房间,直接归属楼层
circuits: bCircuits.filter((c) => !c.roomId && (c.floorNo || "") === floorNo)
}))
};
});
return { code: 200, message: "获取空间用电拓扑成功", data: { buildings: data } };
}
async detail(id: number, ctx: Context): Promise<any> {
const record = await this.circuitModel.findByPk(id);
if (!record) return { code: 404, message: "用电回路不存在", data: null };
if (!(await this.isCustomerOwner(ctx.state.user.userId, record.customerId))) return { code: 403, message: "无权限访问该回路", data: null };
return { code: 200, message: "获取用电回路详情成功", data: record.toJSON() };
}
async create(data: any, ctx: Context): Promise<any> {
if (!(await this.isCustomerOwner(ctx.state.user.userId, data.customerId))) return { code: 403, message: "无权限为该客户创建回路", data: null };
const exists = await this.circuitModel.findOne({ where: { circuitCode: data.circuitCode } });
if (exists) return { code: 400, message: "回路编号已存在,请更换", data: null };
const created = await this.circuitModel.create(data);
return { code: 200, message: "创建用电回路成功", data: created.toJSON() };
}
async update(id: number, data: any, ctx: Context): Promise<any> {
const record = await this.circuitModel.findByPk(id);
if (!record) return { code: 404, message: "用电回路不存在", data: null };
if (!(await this.isAdmin(ctx.state.user.userId))) {
if (!(await this.isCustomerOwner(ctx.state.user.userId, record.customerId))) return { code: 403, message: "无权限修改该回路", data: null };
delete data.customerId;
}
if (data.circuitCode && data.circuitCode !== record.circuitCode) {
const exists = await this.circuitModel.findOne({ where: { circuitCode: data.circuitCode, id: { [Op.ne]: id } } });
if (exists) return { code: 400, message: "回路编号已存在,请更换", data: null };
}
await record.update(data);
return { code: 200, message: "更新用电回路成功", data: record.toJSON() };
}
async delete(id: number, ctx: Context): Promise<any> {
const record = await this.circuitModel.findByPk(id);
if (!record) return { code: 404, message: "用电回路不存在", data: null };
if (!(await this.isCustomerOwner(ctx.state.user.userId, record.customerId))) return { code: 403, message: "无权限删除该回路", data: null };
await record.destroy();
return { code: 200, message: "删除用电回路成功", data: null };
}
}