feat: 新增客户拜访、建筑、房间及项目全流程业务模块

本次提交新增了客户拜访、客户建筑、客户房间的完整业务模块,同时补充了项目勘察、项目方案、合同管理、项目验收、回访记录的全项目流程相关功能,包括:
1.  新增对应业务的实体类、DTO、控制器、服务层代码
2.  完善RBAC权限映射配置
3.  添加前端API、路由及页面菜单配置
4.  更新数据库种子数据补充菜单和权限绑定
This commit is contained in:
andyesfly committed 2026-08-26 14:08:08 +08:00
1 parent f61ade107b
commit 219091f864
53 files changed
+5990 -6

No files matched your search

+154
View File
@@ -0,0 +1,154 @@
import { Provide } from "@midwayjs/core";
import { InjectRepository } from "@midwayjs/sequelize";
import { Repository } from "sequelize-typescript";
import { Op, UniqueConstraintError } from "sequelize";
import { Context } from "@midwayjs/koa";
import ProjectPlan from "../entity/project_plan.entity";
import ProjectPlanDetail from "../entity/project_plan_detail.entity";
import Customer from "../entity/customer.entity";
import SysUserRole from "../entity/sysUserRole.entity";
import SysRole from "../entity/sysRole.entity";
@Provide()
export class ProjectPlanService {
@InjectRepository(ProjectPlan)
planModel!: Repository<ProjectPlan>;
@InjectRepository(ProjectPlanDetail)
detailModel!: Repository<ProjectPlanDetail>;
@InjectRepository(Customer)
customerModel!: Repository<Customer>;
@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;
}
private async generatePlanNo(retry: number = 0): Promise<string> {
const now = new Date();
const d = new Date(now.getTime() + 8 * 60 * 60 * 1000);
const yyyymmdd = `${d.getUTCFullYear()}${String(d.getUTCMonth() + 1).padStart(2, "0")}${String(d.getUTCDate()).padStart(2, "0")}`;
const prefix = `FA${yyyymmdd}`;
const last = await this.planModel.findOne({ where: { planNo: { [Op.like]: `${prefix}%` } }, order: [["plan_no", "DESC"]] });
let seq = 1;
if (last) seq = (parseInt(last.planNo.slice(-3), 10) || 0) + 1;
const result = `${prefix}${String(seq).padStart(3, "0")}`;
const exists = await this.planModel.findOne({ where: { planNo: result } });
if (exists) {
if (retry < 1) return this.generatePlanNo(retry + 1);
throw new Error("方案单号生成冲突,请重试");
}
return result;
}
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] = [{ registrar: { [Op.like]: `%${query.keyword}%` } }, { planDesc: { [Op.like]: `%${query.keyword}%` } }];
}
if (query.customerId !== undefined) where.customerId = query.customerId;
if (query.status !== undefined) where.status = query.status;
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 { code: 200, message: "获取项目方案列表成功", data: { list: [], total: 0, page: Number(page), pageSize: Number(pageSize) } };
if (where.customerId !== undefined) {
if (!ids.includes(where.customerId)) return { code: 200, message: "获取项目方案列表成功", data: { list: [], total: 0, page: Number(page), pageSize: Number(pageSize) } };
} else {
where.customerId = { [Op.in]: ids };
}
}
const { count, rows } = await this.planModel.findAndCountAll({ where, limit: pageSize, offset: (page - 1) * pageSize, order: [["created_at", "DESC"]] });
return { code: 200, message: "获取项目方案列表成功", data: { list: rows.map((r) => r.toJSON()), total: count, page: Number(page), pageSize: Number(pageSize) } };
}
async detail(id: number, ctx: Context): Promise<any> {
const record = await this.planModel.findByPk(id, { include: [this.detailModel] });
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 details = data.details || [];
delete data.details;
try {
data.planNo = await this.generatePlanNo();
const created = await this.planModel.create(data);
if (details.length > 0) {
await this.detailModel.bulkCreate(details.map((d: any) => ({ ...d, planId: created.id, customerId: created.customerId })));
}
const result = await this.planModel.findByPk(created.id, { include: [this.detailModel] });
return { code: 200, message: "创建项目方案成功", data: result?.toJSON() };
} catch (err: any) {
if (err instanceof UniqueConstraintError) {
data.planNo = await this.generatePlanNo();
const created = await this.planModel.create(data);
if (details.length > 0) {
await this.detailModel.bulkCreate(details.map((d: any) => ({ ...d, planId: created.id, customerId: created.customerId })));
}
const result = await this.planModel.findByPk(created.id, { include: [this.detailModel] });
return { code: 200, message: "创建项目方案成功", data: result?.toJSON() };
}
throw err;
}
}
async update(id: number, data: any, ctx: Context): Promise<any> {
const record = await this.planModel.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;
}
const details = data.details;
delete data.details;
delete data.planNo;
await record.update(data);
if (details && Array.isArray(details)) {
await this.detailModel.destroy({ where: { planId: id } });
if (details.length > 0) {
await this.detailModel.bulkCreate(details.map((d: any) => ({ ...d, planId: id, customerId: record.customerId })));
}
}
const result = await this.planModel.findByPk(id, { include: [this.detailModel] });
return { code: 200, message: "更新项目方案成功", data: result?.toJSON() };
}
async delete(id: number, ctx: Context): Promise<any> {
const record = await this.planModel.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 this.detailModel.destroy({ where: { planId: id } });
await record.destroy();
return { code: 200, message: "删除项目方案成功", data: null };
}
async findOptions(customerId: number): Promise<any> {
const where: any = {};
if (customerId) where.customerId = customerId;
const records = await this.planModel.findAll({ where, order: [["created_at", "DESC"]] });
return { code: 200, message: "获取项目方案选项成功", data: records.map((r) => r.toJSON()) };
}
}