import { Controller, Get, Post, Inject, Query, Body, Param } from "@midwayjs/core"; import { Context } from "@midwayjs/koa"; import { InjectRepository } from "@midwayjs/sequelize"; import { Repository } from "sequelize-typescript"; import { Op, UniqueConstraintError } from "sequelize"; import DeviceAlarm from "../entity/device_alarm.entity"; import AlarmDispatch from "../entity/alarm_dispatch.entity"; import Worker from "../entity/worker.entity"; import Customer from "../entity/customer.entity"; import SysUser from "../entity/sysUser.entity"; import { NoticeService } from "../service/notice.service"; @Controller("/api/device-alarms") export class DeviceAlarmController { @Inject() ctx!: Context; @Inject() noticeService!: NoticeService; @InjectRepository(DeviceAlarm) alarmRepo!: Repository; @InjectRepository(AlarmDispatch) dispatchRepo!: Repository; @InjectRepository(Worker) workerRepo!: Repository; @InjectRepository(Customer) customerRepo!: Repository; @InjectRepository(SysUser) userRepo!: Repository; /** 报警分页列表(仅 line_id 非空的记录)+ 统计 */ @Get("/") async list(@Query() query: any) { const page = Math.max(Number(query.page) || 1, 1); const rawPageSize = Number(query.pageSize) || 10; const pageSize = Math.min(Math.max(rawPageSize, 1), 200); const where: any = { lineId: { [Op.ne]: null } }; if (query.customerId !== undefined && query.customerId !== "") where.customerId = Number(query.customerId); if (query.alarmGrade !== undefined && query.alarmGrade !== "") where.alarmGrade = Number(query.alarmGrade); if (query.keyword) { where[Op.or] = [ { lineName: { [Op.like]: `%${query.keyword}%` } }, { lineCode: { [Op.like]: `%${query.keyword}%` } }, { deviceName: { [Op.like]: `%${query.keyword}%` } } ]; } const { count, rows } = await this.alarmRepo.findAndCountAll({ where, limit: pageSize, offset: (page - 1) * pageSize, order: [["alarmTime", "DESC"]] }); // 统计:今日报警数(alarm_time >= 当天0点)/ 严重报警数(grade=1)/ 未派单数(无 status=0 派单) const d = new Date(new Date().getTime() + 8 * 60 * 60 * 1000); const today = `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")}`; const todayStart = new Date(`${today}T00:00:00+08:00`); const [todayCount, gradeOneCount] = await Promise.all([ this.alarmRepo.count({ where: { lineId: { [Op.ne]: null }, alarmTime: { [Op.gte]: todayStart } } }), this.alarmRepo.count({ where: { lineId: { [Op.ne]: null }, alarmGrade: 1 } }) ]); const dispatchedAlarms = await this.dispatchRepo.findAll({ where: { status: 0 }, attributes: ["alarmId"], raw: true }); const dispatchedIds = [...new Set(dispatchedAlarms.map((r: any) => Number(r.alarmId)))]; const undispatchedCount = await this.alarmRepo.count({ where: { lineId: { [Op.ne]: null }, id: dispatchedIds.length ? { [Op.notIn]: dispatchedIds } : { [Op.ne]: null } } }); return { code: 200, message: "获取报警列表成功", data: { list: rows.map((r) => r.toJSON()), total: count, page, pageSize, summary: { todayCount, gradeOneCount, undispatchedCount } } }; } /** 发起派单:校验报警/未完成派单/电工,生成派单号,写入派单并给电工发站内通知 */ @Post("/:id/dispatch") async dispatch(@Param("id") id: string, @Body() body: any) { const alarmId = Number(id); if (!alarmId || Number.isNaN(alarmId)) return { code: 400, message: "缺少有效的报警 ID", data: null }; const alarm = await this.alarmRepo.findByPk(alarmId); if (!alarm) return { code: 404, message: "报警记录不存在", data: null }; if (!alarm.lineId) return { code: 400, message: "该报警未关联线路,无法派单", data: null }; const existing = await this.dispatchRepo.findOne({ where: { alarmId, status: 0 } }); if (existing) return { code: 400, message: "该报警已有未完成的派单", data: null }; const workerId = Number(body.workerId); if (!workerId || Number.isNaN(workerId)) return { code: 400, message: "缺少有效的 workerId 参数", data: null }; const worker = await this.workerRepo.findByPk(workerId); if (!worker) return { code: 400, message: "共享电工不存在", data: null }; if (Number(worker.status) !== 1) return { code: 400, message: "该电工不在岗,无法派单", data: null }; if (Number(worker.auditStatus) !== 1) return { code: 400, message: "该电工审核未通过,无法派单", data: null }; // 派发人:当前登录用户 realName(缺失时回退 username) const loginUserId = (this.ctx.state.user as any)?.userId; const loginUser = loginUserId ? await this.userRepo.findByPk(loginUserId, { attributes: ["realName", "username"] }) : null; const dispatcher = loginUser?.realName || (loginUser as any)?.username || "未知"; // 客户名称(通知文案用,查不到时回退客户 ID) let customerName = `#${alarm.customerId}`; if (alarm.customerId) { const customer = await this.customerRepo.findByPk(alarm.customerId, { attributes: ["customerName"] }); if (customer) customerName = customer.customerName; } const payload: any = { alarmId: alarm.id, lineId: alarm.lineId, lineCode: alarm.lineCode, lineName: alarm.lineName, customerId: alarm.customerId, alarmTime: alarm.alarmTime, alarmGrade: alarm.alarmGrade, faultDesc: body.faultDesc || "", dispatcher, workerId: worker.id, status: 0 }; // 派单号生成 + 写入(唯一冲突时重试一次) let dispatch: AlarmDispatch; try { payload.dispatchNo = await this.generateDispatchNo(); dispatch = await this.dispatchRepo.create(payload); } catch (err: any) { if (err instanceof UniqueConstraintError) { payload.dispatchNo = await this.generateDispatchNo(); dispatch = await this.dispatchRepo.create(payload); } else { throw err; } } // 派单站内通知(通知失败不得阻断派单) try { await this.noticeService.deliverToReceivers( { noticeType: "alarm_dispatch", title: `新报警派单:${alarm.lineName}`, content: `线路:${alarm.lineName}\n客户:${customerName}\n报警时间:${this.formatTime(alarm.alarmTime)}\n报警等级:${alarm.alarmGrade}级`, bizType: "alarm_dispatch", bizId: Number(dispatch.id) }, { workers: { mode: "ids", ids: [worker.id] } } ); } catch (err) { console.error("[notice] 报警派单通知失败:", err); } return { code: 200, message: "报警派单成功", data: dispatch.toJSON() }; } // 派单号:AD + 东八区 yyyyMMdd + 3 位当日序号(当天已创建数量 + 1,冲突则递增重试) private async generateDispatchNo(): Promise { const d = new Date(new Date().getTime() + 8 * 60 * 60 * 1000); const yyyymmdd = `${d.getUTCFullYear()}${String(d.getUTCMonth() + 1).padStart(2, "0")}${String(d.getUTCDate()).padStart(2, "0")}`; const prefix = `AD${yyyymmdd}`; const dayStart = new Date(`${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")}T00:00:00+08:00`); const count = await this.dispatchRepo.count({ where: { createdAt: { [Op.gte]: dayStart } } }); for (let i = 1; i <= 100; i++) { const no = `${prefix}${String(count + i).padStart(3, "0")}`; const exists = await this.dispatchRepo.findOne({ where: { dispatchNo: no }, attributes: ["id"] }); if (!exists) return no; } throw new Error("派单号生成冲突,请重试"); } private formatTime(t?: Date): string { if (!t) return "-"; return new Date(t).toLocaleString("zh-CN", { hour12: false }); } }