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 MaintenanceOrder from "../entity/maintenance_order.entity"; import MaintenanceDetail from "../entity/maintenance_detail.entity"; import Customer from "../entity/customer.entity"; import Worker from "../entity/worker.entity"; import SysUserRole from "../entity/sysUserRole.entity"; import SysRole from "../entity/sysRole.entity"; import { LogService } from "./log.service"; import { NoticeRuleService } from "./notice_rule.service"; // 维保派单状态:0已派发 1已完成 const STATUS = { DISPATCHED: 0, COMPLETED: 1 }; @Provide() export class MaintenanceService { @InjectRepository(MaintenanceOrder) orderModel!: Repository; @InjectRepository(MaintenanceDetail) detailModel!: Repository; @InjectRepository(Customer) customerModel!: Repository; @InjectRepository(Worker) workerModel!: Repository; @InjectRepository(SysUserRole) userRoleModel!: Repository; @InjectRepository(SysRole) roleModel!: Repository; @Inject() logService!: LogService; @Inject() noticeRuleService!: NoticeRuleService; private async isAdmin(userId: number): Promise { 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 { if (await this.isAdmin(userId)) return true; const customer = await this.customerModel.findByPk(customerId); return !!customer && customer.salesmanId === userId; } // 工单可见性:管理员 / 客户归属业务员 private async isOrderVisible(userId: number, order: MaintenanceOrder): Promise { if (await this.isAdmin(userId)) return true; return this.isCustomerOwner(userId, order.customerId); } // query 参数 → Sequelize where(关键字/客户/电工/状态/维保日期范围) private buildWhere(query: any): any { const where: any = {}; if (query.keyword) { where[Op.or] = [ { maintenanceNo: { [Op.like]: `%${query.keyword}%` } }, { maintainer: { [Op.like]: `%${query.keyword}%` } }, { remark: { [Op.like]: `%${query.keyword}%` } } ]; } if (query.customerId !== undefined && query.customerId !== "") where.customerId = Number(query.customerId); if (query.workerId !== undefined && query.workerId !== "") where.workerId = Number(query.workerId); if (query.status !== undefined && query.status !== "") where.status = Number(query.status); // maintenance_date 范围 [startTime, endTime],YYYY-MM-DD if (query.startTime || query.endTime) { const cond: any = {}; if (query.startTime) cond[Op.gte] = query.startTime; if (query.endTime) cond[Op.lte] = query.endTime; where.maintenanceDate = cond; } return where; } // 行级隔离:非管理员仅可见「客户归属自己」的工单 private async scopeWhere(baseWhere: any, ctx: Context): Promise { const userId = ctx.state.user.userId; const where: any = { ...baseWhere }; if (await this.isAdmin(userId)) return where; const myCustomers = await this.customerModel.findAll({ where: { salesmanId: userId }, attributes: ["id"] }); const ids = myCustomers.map((c) => c.id); where.customerId = ids.length ? { [Op.in]: ids } : -1; return where; } // 批量补充客户/电工信息,避免 N+1 private async decorate(rows: any[]): Promise { if (rows.length === 0) return rows; const customerIds = [...new Set(rows.map((r) => r.customerId).filter(Boolean))]; const workerIds = [...new Set(rows.map((r) => r.workerId).filter(Boolean))]; const [customers, workers] = await Promise.all([ customerIds.length ? this.customerModel.findAll({ where: { id: { [Op.in]: customerIds } }, attributes: ["id", "customerNo", "customerName", "contactPerson", "contactPhone", "address"] }) : Promise.resolve([]), workerIds.length ? this.workerModel.findAll({ where: { id: { [Op.in]: workerIds } }, attributes: ["id", "workerName", "phone", "workerTitle"] }) : Promise.resolve([]) ]); const customerMap = new Map(customers.map((c) => [c.id, c])); const workerMap = new Map(workers.map((w) => [w.id, w])); for (const row of rows) { const customer: any = customerMap.get(row.customerId); const worker: any = workerMap.get(row.workerId); row.customer = customer ? customer.toJSON() : null; row.customerName = customer?.customerName || ""; row.worker = worker ? worker.toJSON() : null; row.workerName = worker?.workerName || ""; } return rows; } // 统一清洗入参:空串转 null private normalizePayload(data: any): any { const payload: any = { ...data }; for (const key of Object.keys(payload)) { if (payload[key] === "") payload[key] = null; } return payload; } async listOrders(query: any, ctx: Context): Promise { const page = Number(query.page) || 1; const pageSize = Number(query.pageSize) || 10; const where = await this.scopeWhere(this.buildWhere(query), ctx); const { count, rows } = await this.orderModel.findAndCountAll({ where, limit: pageSize, offset: (page - 1) * pageSize, order: [["id", "DESC"]], distinct: true }); const list = await this.decorate(rows.map((r) => r.toJSON())); return { code: 200, message: "获取维保派单列表成功", data: { list, total: count, page, pageSize } }; } async getOrderDetail(id: number, ctx: Context): Promise { const record = await this.orderModel.findByPk(id); if (!record) return { code: 404, message: "维保派单不存在", data: null }; if (!(await this.isOrderVisible(ctx.state.user.userId, record))) return { code: 403, message: "无权限访问该维保派单", data: null }; const vo: any = record.toJSON(); await this.decorate([vo]); const details = await this.detailModel.findAll({ where: { maintenanceId: id }, order: [["id", "ASC"]] }); vo.details = details.map((d) => d.toJSON()); return { code: 200, message: "获取维保派单详情成功", data: vo }; } async createOrder(data: any, ctx: Context): Promise { const userId = ctx.state.user.userId; if (!(await this.isCustomerOwner(userId, Number(data.customerId)))) return { code: 403, message: "无权限为该客户创建维保派单", data: null }; const exists = await this.orderModel.findOne({ where: { maintenanceNo: data.maintenanceNo } }); if (exists) return { code: 400, message: "维保单号已存在", data: null }; if (data.workerId) { const worker = await this.workerModel.findByPk(Number(data.workerId)); if (!worker) return { code: 400, message: "共享电工不存在", data: null }; } const payload = this.normalizePayload(data); const details = Array.isArray(payload.details) ? payload.details : []; delete payload.details; if (payload.status === undefined || payload.status === null) payload.status = STATUS.DISPATCHED; const t = await this.orderModel.sequelize!.transaction(); let created!: MaintenanceOrder; const detailRows: MaintenanceDetail[] = []; try { created = await this.orderModel.create(payload, { transaction: t }); for (const item of details) { const detailPayload = this.normalizePayload({ ...item, maintenanceId: created.id, customerId: created.customerId }); detailRows.push(await this.detailModel.create(detailPayload, { transaction: t })); } await t.commit(); } catch (err: any) { await t.rollback(); if (err instanceof UniqueConstraintError) return { code: 400, message: "维保单号已存在", data: null }; throw err; } const vo: any = created.toJSON(); await this.decorate([vo]); vo.details = detailRows.map((d) => d.toJSON()); await this.logService.logAction(ctx, "维保派单", "创建", created.maintenanceNo, `创建维保派单 ${created.maintenanceNo}`); // 派单站内通知(埋点式;通知失败不得阻断业务) if (created.workerId) { try { await this.noticeRuleService.notifyDispatchAssign({ orderTypeName: "维保", orderNo: created.maintenanceNo, customerId: Number(created.customerId), workerId: Number(created.workerId), detailText: created.maintenanceDate ? `维保日期:${created.maintenanceDate}。` : "", bizType: "maintenance_order", bizId: Number(created.id) }); } catch (err) { console.error("[notice] 维保派单通知失败:", err); } } return { code: 200, message: "创建维保派单成功", data: vo }; } async updateOrder(id: number, data: any, ctx: Context): Promise { const record = await this.orderModel.findByPk(id); if (!record) return { code: 404, message: "维保派单不存在", data: null }; if (!(await this.isOrderVisible(ctx.state.user.userId, record))) return { code: 403, message: "无权操作该维保派单", data: null }; if (record.status === STATUS.COMPLETED) return { code: 400, message: "已完成的派单不可修改", data: null }; if (data.maintenanceNo !== undefined && data.maintenanceNo !== record.maintenanceNo) { const exists = await this.orderModel.findOne({ where: { maintenanceNo: data.maintenanceNo, id: { [Op.ne]: id } } }); if (exists) return { code: 400, message: "维保单号已存在", data: null }; } if (data.customerId !== undefined && Number(data.customerId) !== Number(record.customerId)) { if (!(await this.isCustomerOwner(ctx.state.user.userId, Number(data.customerId)))) { return { code: 403, message: "无权限转移到该客户", data: null }; } } if (data.workerId) { const worker = await this.workerModel.findByPk(Number(data.workerId)); if (!worker) return { code: 400, message: "共享电工不存在", data: null }; } const payload = this.normalizePayload(data); const details = Array.isArray(payload.details) ? payload.details : []; delete payload.details; const t = await this.orderModel.sequelize!.transaction(); const detailRows: MaintenanceDetail[] = []; const oldWorkerId = record.workerId ? Number(record.workerId) : null; try { await record.update(payload, { transaction: t }); if (details.length) { await this.detailModel.destroy({ where: { maintenanceId: id }, transaction: t }); for (const item of details) { const detailPayload = this.normalizePayload({ ...item, maintenanceId: id, customerId: record.customerId }); detailRows.push(await this.detailModel.create(detailPayload, { transaction: t })); } } await t.commit(); } catch (err: any) { await t.rollback(); throw err; } const vo: any = record.toJSON(); await this.decorate([vo]); vo.details = detailRows.map((d) => d.toJSON()); await this.logService.logAction(ctx, "维保派单", "更新", record.maintenanceNo, `更新维保派单 ${record.maintenanceNo}`); // 改派站内通知(派单对象变化时提醒新电工) if (record.workerId && Number(record.workerId) !== oldWorkerId) { try { await this.noticeRuleService.notifyDispatchAssign({ orderTypeName: "维保", orderNo: record.maintenanceNo, customerId: Number(record.customerId), workerId: Number(record.workerId), detailText: record.maintenanceDate ? `维保日期:${record.maintenanceDate}。` : "", bizType: "maintenance_order", bizId: Number(record.id) }); } catch (err) { console.error("[notice] 维保派单通知失败:", err); } } return { code: 200, message: "更新维保派单成功", data: vo }; } // 完成派单:仅允许 0已派发 → 1已完成 async completeOrder(id: number, ctx: Context): Promise { const record = await this.orderModel.findByPk(id); if (!record) return { code: 404, message: "维保派单不存在", data: null }; if (!(await this.isOrderVisible(ctx.state.user.userId, record))) return { code: 403, message: "无权操作该维保派单", data: null }; if (record.status !== STATUS.DISPATCHED) return { code: 400, message: "仅已派发状态的派单可标记完成", data: null }; await record.update({ status: STATUS.COMPLETED }); const vo: any = record.toJSON(); await this.decorate([vo]); await this.logService.logAction(ctx, "维保派单", "完成", record.maintenanceNo, `完成维保派单 ${record.maintenanceNo}`); return { code: 200, message: "维保派单已完成", data: vo }; } async removeOrder(id: number, ctx: Context): Promise { const record = await this.orderModel.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 }; const maintenanceNo = record.maintenanceNo; const t = await this.orderModel.sequelize!.transaction(); try { await this.detailModel.destroy({ where: { maintenanceId: id }, transaction: t }); await record.destroy({ transaction: t }); await t.commit(); } catch (err: any) { await t.rollback(); throw err; } await this.logService.logAction(ctx, "维保派单", "删除", maintenanceNo, `删除维保派单 ${maintenanceNo}`); return { code: 200, message: "删除维保派单成功", data: null }; } }