This commit is contained in:
andyesfly committed 2026-10-02 16:11:04 +08:00
commit 7b6fe46d10
21 files changed
+3856 -110

No files matched your search

@@ -0,0 +1,16 @@
import { Controller, Get, Post, Put, Del, Inject, Query, Body, Param } from "@midwayjs/core";
import { Context } from "@midwayjs/koa";
import { MaintenanceService } from "../service/maintenance.service";
@Controller("/api/maintenance-orders")
export class MaintenanceController {
@Inject() ctx!: Context;
@Inject() maintenanceService!: MaintenanceService;
@Get("/") async list(@Query() query: any) { return this.maintenanceService.listOrders(query, this.ctx); }
@Get("/:id") async detail(@Param("id") id: string) { return this.maintenanceService.getOrderDetail(Number(id), this.ctx); }
@Post("/") async create(@Body() body: any) { return this.maintenanceService.createOrder(body, this.ctx); }
@Put("/:id") async update(@Param("id") id: string, @Body() body: any) { return this.maintenanceService.updateOrder(Number(id), body, this.ctx); }
@Put("/:id/complete") async complete(@Param("id") id: string) { return this.maintenanceService.completeOrder(Number(id), this.ctx); }
@Del("/:id") async delete(@Param("id") id: string) { return this.maintenanceService.removeOrder(Number(id), this.ctx); }
}
@@ -0,0 +1,27 @@
import { Controller, Get, Post, Put, Del, Inject, Query, Body, Param } from "@midwayjs/core";
import { Context } from "@midwayjs/koa";
import { RepairDispatchService } from "../service/repair_dispatch.service";
import { QueryRepairRequestDto } from "../dto/repair_request.dto";
import { QueryRepairDispatchDto, CreateRepairDispatchDto, UpdateRepairDispatchDto, CompleteRepairDispatchDto } from "../dto/repair_dispatch.dto";
@Controller("/api/repair-requests")
export class RepairRequestController {
@Inject() ctx!: Context;
@Inject() repairDispatchService!: RepairDispatchService;
@Get("/") async list(@Query() query: QueryRepairRequestDto) { return this.repairDispatchService.listRequests(query, this.ctx); }
@Get("/:id") async detail(@Param("id") id: string) { return this.repairDispatchService.getRequestDetail(Number(id), this.ctx); }
}
@Controller("/api/repair-dispatchs")
export class RepairDispatchController {
@Inject() ctx!: Context;
@Inject() repairDispatchService!: RepairDispatchService;
@Get("/") async list(@Query() query: QueryRepairDispatchDto) { return this.repairDispatchService.listDispatch(query, this.ctx); }
@Get("/:id") async detail(@Param("id") id: string) { return this.repairDispatchService.getDispatchDetail(Number(id), this.ctx); }
@Post("/") async create(@Body() dto: CreateRepairDispatchDto) { return this.repairDispatchService.createDispatch(dto, this.ctx); }
@Put("/:id") async update(@Param("id") id: string, @Body() dto: UpdateRepairDispatchDto) { return this.repairDispatchService.updateDispatch(Number(id), dto, this.ctx); }
@Put("/:id/complete") async complete(@Param("id") id: string, @Body() dto: CompleteRepairDispatchDto) { return this.repairDispatchService.completeDispatch(Number(id), dto, this.ctx); }
@Del("/:id") async remove(@Param("id") id: string) { return this.repairDispatchService.removeDispatch(Number(id), this.ctx); }
}
+62
View File
@@ -0,0 +1,62 @@
import {
IsNotEmpty,
IsOptional,
IsString,
IsInt,
IsArray,
IsDateString,
IsIn,
Min,
Max,
MaxLength,
ArrayMinSize,
ValidateNested
} from "class-validator";
import { Type } from "class-transformer";
export class MaintenanceOrderQueryDTO {
@IsOptional() @IsInt() @Min(1) page?: number = 1;
@IsOptional() @IsInt() @Min(1) @Max(200) pageSize?: number = 10;
@IsOptional() @IsString() @MaxLength(64) keyword?: string;
@IsOptional() @IsInt() customerId?: number;
@IsOptional() @IsInt() workerId?: number;
@IsOptional() @IsInt() @IsIn([0, 1]) status?: number;
// 维保日期范围 [startTime, endTime],YYYY-MM-DD
@IsOptional() @IsDateString() startTime?: string;
@IsOptional() @IsDateString() endTime?: string;
}
export class MaintenanceDetailItemDTO {
@IsOptional() @IsInt() equipId?: number;
// 若 equipId 已给而 equipName 为空,创建时按客户设备自动回填名称
@IsOptional() @IsString() @MaxLength(128) equipName?: string;
@IsOptional() @IsString() @MaxLength(32) equipType?: string;
@IsOptional() @IsInt() buildingId?: number;
@IsOptional() @IsString() @MaxLength(16) floorNo?: string;
@IsOptional() @IsString() @MaxLength(256) location?: string;
@IsOptional() @IsString() panelCondition?: string;
@IsOptional() @IsString() circuitCondition?: string;
@IsOptional() @IsString() terminalCondition?: string;
@IsOptional() @IsString() maintenanceDesc?: string;
@IsOptional() photos?: any;
}
export class MaintenanceOrderCreateDTO {
@IsNotEmpty({ message: "维保单号不能为空" }) @IsString() @MaxLength(64) maintenanceNo!: string;
@IsNotEmpty({ message: "所属客户不能为空" }) @IsInt() customerId!: number;
@IsOptional() @IsString() @MaxLength(64) maintainer?: string;
@IsOptional() @IsInt() workerId?: number;
@IsOptional() @IsDateString() maintenanceDate?: string;
@IsOptional() @IsString() @MaxLength(2000) remark?: string;
@IsArray() @ArrayMinSize(1) @ValidateNested({ each: true }) @Type(() => MaintenanceDetailItemDTO) details!: MaintenanceDetailItemDTO[];
}
export class MaintenanceOrderUpdateDTO {
@IsOptional() @IsString() @MaxLength(64) maintenanceNo?: string;
@IsOptional() @IsInt() customerId?: number;
@IsOptional() @IsString() @MaxLength(64) maintainer?: string;
@IsOptional() @IsInt() workerId?: number;
@IsOptional() @IsDateString() maintenanceDate?: string;
@IsOptional() @IsString() @MaxLength(2000) remark?: string;
@IsOptional() @IsArray() @ArrayMinSize(1) @ValidateNested({ each: true }) @Type(() => MaintenanceDetailItemDTO) details?: MaintenanceDetailItemDTO[];
}
+28
View File
@@ -0,0 +1,28 @@
import { IsNotEmpty, IsOptional, IsString, IsInt, IsDateString, IsIn, IsArray, Min, Max, MaxLength, Matches } from "class-validator";
export class QueryRepairDispatchDto {
@IsOptional() @IsInt() @Min(1) page?: number = 1;
@IsOptional() @IsInt() @Min(1) @Max(200) pageSize?: number = 10;
@IsOptional() @IsString() @MaxLength(64) keyword?: string;
@IsOptional() @IsInt() customerId?: number;
@IsOptional() @IsInt() workerId?: number;
@IsOptional() @IsInt() @IsIn([0, 1]) status?: number;
// 报修时间范围 [startTime, endTime]
@IsOptional() @IsDateString() startTime?: string;
@IsOptional() @IsDateString() endTime?: string;
}
export class CreateRepairDispatchDto {
@IsNotEmpty({ message: "报修单ID不能为空" }) @IsInt() repairId!: number;
@IsNotEmpty({ message: "共享电工不能为空" }) @IsInt() workerId!: number;
}
export class UpdateRepairDispatchDto {
@IsOptional() @IsInt() workerId?: number;
@IsOptional() @IsString() @MaxLength(64) @Matches(/^[^<>]*$/) dispatcher?: string;
}
export class CompleteRepairDispatchDto {
@IsOptional() @IsArray() processPhotos?: any[];
@IsOptional() @IsInt() @Min(0) @Max(5) rating?: number;
}
+12
View File
@@ -0,0 +1,12 @@
import { IsOptional, IsString, IsInt, IsDateString, IsIn, Min, Max, MaxLength } from "class-validator";
export class QueryRepairRequestDto {
@IsOptional() @IsInt() @Min(1) page?: number = 1;
@IsOptional() @IsInt() @Min(1) @Max(200) pageSize?: number = 10;
@IsOptional() @IsString() @MaxLength(64) keyword?: string;
@IsOptional() @IsInt() customerId?: number;
@IsOptional() @IsInt() @IsIn([0, 1, 2]) status?: number;
// 报修时间范围 [startTime, endTime]
@IsOptional() @IsDateString() startTime?: string;
@IsOptional() @IsDateString() endTime?: string;
}
@@ -0,0 +1,61 @@
import { Column, Model, Table, DataType, CreatedAt, ForeignKey, BelongsTo } from "sequelize-typescript";
import MaintenanceOrder from "./maintenance_order.entity";
import Customer from "./customer.entity";
@Table({
tableName: "maintenance_detail",
comment: "维保派单明细表",
timestamps: false,
indexes: [{ name: "idx_maint_detail_maint", fields: ["maintenance_id"] }]
})
export default class MaintenanceDetail extends Model {
@Column({ type: DataType.BIGINT, autoIncrement: true, primaryKey: true })
id!: number;
@ForeignKey(() => MaintenanceOrder)
@Column({ type: DataType.BIGINT, allowNull: false, field: "maintenance_id", comment: "维保派单ID" })
maintenanceId!: number;
@Column({ type: DataType.BIGINT, field: "equip_id", comment: "客户设备ID" })
equipId!: number;
@ForeignKey(() => Customer)
@Column({ type: DataType.BIGINT, allowNull: false, field: "customer_id", comment: "客户ID" })
customerId!: number;
@Column({ type: DataType.STRING(128), field: "equip_name", comment: "设备名称" })
equipName!: string;
@Column({ type: DataType.STRING(32), field: "equip_type", comment: "设备类型:配电间/井/箱" })
equipType!: string;
@Column({ type: DataType.BIGINT, field: "building_id", comment: "楼栋ID" })
buildingId!: number;
@Column({ type: DataType.STRING(16), field: "floor_no", comment: "楼层号" })
floorNo!: string;
@Column({ type: DataType.STRING(256), comment: "位置" })
location!: string;
@Column({ type: DataType.TEXT, field: "panel_condition", comment: "配电间或电箱情况" })
panelCondition!: string;
@Column({ type: DataType.TEXT, field: "circuit_condition", comment: "回路情况" })
circuitCondition!: string;
@Column({ type: DataType.TEXT, field: "terminal_condition", comment: "终端情况" })
terminalCondition!: string;
@Column({ type: DataType.TEXT, field: "maintenance_desc", comment: "维保情况说明" })
maintenanceDesc!: string;
@Column({ type: DataType.JSONB, field: "photos", comment: "现场照片(JSONB)" })
photos!: any;
@BelongsTo(() => MaintenanceOrder, { foreignKey: "maintenanceId", as: "order" })
order!: MaintenanceOrder;
@CreatedAt
created_at!: Date;
}
@@ -0,0 +1,49 @@
import { Column, Model, Table, DataType, CreatedAt, ForeignKey, BelongsTo } from "sequelize-typescript";
import Customer from "./customer.entity";
import Worker from "./worker.entity";
@Table({
tableName: "maintenance_order",
comment: "维保派单信息表",
timestamps: false,
indexes: [
{ name: "idx_maint_customer", fields: ["customer_id"] },
{ name: "idx_maint_worker", fields: ["worker_id"] }
]
})
export default class MaintenanceOrder extends Model {
@Column({ type: DataType.BIGINT, autoIncrement: true, primaryKey: true })
id!: number;
@Column({ type: DataType.STRING(64), allowNull: false, unique: true, field: "maintenance_no", comment: "维保单号(唯一,手工录入不自动生成)" })
maintenanceNo!: string;
@ForeignKey(() => Customer)
@Column({ type: DataType.BIGINT, allowNull: false, field: "customer_id", comment: "客户ID" })
customerId!: number;
@Column({ type: DataType.STRING(64), comment: "维保人" })
maintainer!: string;
@ForeignKey(() => Worker)
@Column({ type: DataType.BIGINT, field: "worker_id", comment: "共享电工ID" })
workerId!: number;
@Column({ type: DataType.DATEONLY, field: "maintenance_date", comment: "维保日期" })
maintenanceDate!: string;
@Column({ type: DataType.TEXT, comment: "备注" })
remark!: string;
@Column({ type: DataType.SMALLINT, allowNull: false, defaultValue: 0, comment: "状态:0已派发 1已完成" })
status!: number;
@BelongsTo(() => Customer, { foreignKey: "customerId", as: "customer" })
customer!: Customer;
@BelongsTo(() => Worker, { foreignKey: "workerId", as: "worker" })
worker!: Worker;
@CreatedAt
created_at!: Date;
}
@@ -0,0 +1,82 @@
import { Column, Model, Table, DataType, CreatedAt, ForeignKey, BelongsTo } from "sequelize-typescript";
import Customer from "./customer.entity";
import Worker from "./worker.entity";
import RepairRequest from "./repair_request.entity";
@Table({
tableName: "repair_dispatch",
comment: "报修派单信息表",
timestamps: false,
indexes: [
{ name: "idx_repair_disp_customer", fields: ["customer_id"] },
{ name: "idx_repair_disp_worker", fields: ["worker_id"] },
{ name: "idx_repair_disp_status", fields: ["status"] }
]
})
export default class RepairDispatch extends Model {
@Column({ type: DataType.BIGINT, autoIncrement: true, primaryKey: true })
id!: number;
@Column({ type: DataType.STRING(64), allowNull: false, unique: true, field: "dispatch_no", comment: "派单号(唯一)" })
dispatchNo!: string;
@ForeignKey(() => RepairRequest)
@Column({ type: DataType.BIGINT, allowNull: false, field: "repair_id", comment: "报修单ID" })
repairId!: number;
@ForeignKey(() => Customer)
@Column({ type: DataType.BIGINT, allowNull: false, field: "customer_id", comment: "客户ID" })
customerId!: number;
@Column({ type: DataType.STRING(64), comment: "报修人" })
requester!: string;
@Column({ type: DataType.STRING(20), field: "requester_phone", comment: "报修电话" })
requesterPhone!: string;
@Column({ type: DataType.DATE, field: "request_time", comment: "报修时间" })
requestTime!: Date;
@Column({ type: DataType.BIGINT, field: "building_id", comment: "建筑ID" })
buildingId!: number;
@Column({ type: DataType.STRING(16), field: "floor_no", comment: "楼层" })
floorNo!: string;
@Column({ type: DataType.STRING(256), comment: "位置" })
location!: string;
@Column({ type: DataType.TEXT, field: "repair_content", comment: "报修内容" })
repairContent!: string;
@Column({ type: DataType.STRING(64), comment: "派发人" })
dispatcher!: string;
@ForeignKey(() => Worker)
@Column({ type: DataType.BIGINT, field: "worker_id", comment: "共享电工ID" })
workerId!: number;
@Column({ type: DataType.SMALLINT, allowNull: false, defaultValue: 0, comment: "状态:0已派发 1已处理" })
status!: number;
@Column({ type: DataType.JSONB, field: "process_photos", comment: "处理完成照片(JSONB)" })
processPhotos!: any;
@Column({ type: DataType.SMALLINT, comment: "评分(客户评,0-5)" })
rating!: number;
@Column({ type: DataType.DATE, field: "completed_at", comment: "完成时间" })
completedAt!: Date;
@BelongsTo(() => Customer, { foreignKey: "customerId", as: "customer" })
customer!: Customer;
@BelongsTo(() => Worker, { foreignKey: "workerId", as: "worker" })
worker!: Worker;
@BelongsTo(() => RepairRequest, { foreignKey: "repairId", as: "repair" })
repair!: RepairRequest;
@CreatedAt
created_at!: Date;
}
@@ -0,0 +1,56 @@
import { Column, Model, Table, DataType, CreatedAt, ForeignKey, BelongsTo } from "sequelize-typescript";
import Customer from "./customer.entity";
@Table({
tableName: "repair_request",
comment: "报修单信息表",
timestamps: false,
indexes: [
{ name: "idx_repair_customer", fields: ["customer_id"] },
{ name: "idx_repair_status", fields: ["status"] }
]
})
export default class RepairRequest extends Model {
@Column({ type: DataType.BIGINT, autoIncrement: true, primaryKey: true })
id!: number;
@Column({ type: DataType.STRING(64), allowNull: false, unique: true, field: "repair_no", comment: "报修单号(唯一)" })
repairNo!: string;
@ForeignKey(() => Customer)
@Column({ type: DataType.BIGINT, allowNull: false, field: "customer_id", comment: "客户ID" })
customerId!: number;
@Column({ type: DataType.STRING(64), comment: "报修人" })
requester!: string;
@Column({ type: DataType.STRING(20), field: "requester_phone", comment: "报修电话" })
requesterPhone!: string;
@Column({ type: DataType.DATE, field: "request_time", comment: "报修时间" })
requestTime!: Date;
@Column({ type: DataType.BIGINT, field: "building_id", comment: "建筑ID" })
buildingId!: number;
@Column({ type: DataType.STRING(16), field: "floor_no", comment: "楼层" })
floorNo!: string;
@Column({ type: DataType.STRING(256), comment: "位置" })
location!: string;
@Column({ type: DataType.TEXT, field: "repair_content", comment: "报修内容" })
repairContent!: string;
@Column({ type: DataType.JSONB, comment: "报修照片(JSONB)" })
photos!: any;
@Column({ type: DataType.SMALLINT, allowNull: false, defaultValue: 0, comment: "状态:0待处理 1已派单 2已完成" })
status!: number;
@BelongsTo(() => Customer, { foreignKey: "customerId", as: "customer" })
customer!: Customer;
@CreatedAt
created_at!: Date;
}
+94 -101
View File
@@ -1,124 +1,117 @@
import { Middleware, IMiddleware } from '@midwayjs/core';
import { Context, NextFunction } from '@midwayjs/koa';
import { Provide, Inject } from '@midwayjs/core';
import { InjectRepository } from '@midwayjs/sequelize';
import { PermissionService } from '../service/permission.service';
import SysUserRole from '../entity/sysUserRole.entity';
import SysRole from '../entity/sysRole.entity';
import { Middleware, IMiddleware } from "@midwayjs/core";
import { Context, NextFunction } from "@midwayjs/koa";
import { Provide, Inject } from "@midwayjs/core";
import { InjectRepository } from "@midwayjs/sequelize";
import { PermissionService } from "../service/permission.service";
import SysUserRole from "../entity/sysUserRole.entity";
import SysRole from "../entity/sysRole.entity";
// 请求路径前缀 → 菜单权限标识映射
const PERMISSION_MAP: Record<string, string> = {
'/api/users': 'system:user',
'/api/roles': 'system:role',
'/api/menus': 'system:menu',
'/api/dicts': 'system:dict',
'/api/logs': 'system:log',
'/api/jobs': 'system:job',
'/api/customers': 'customer:list',
'/api/customer-departments': 'customer:department',
'/api/customer-accounts': 'customer:account',
'/api/customer-visits': 'customer:visit',
'/api/upload': 'customer:visit',
'/api/customer-buildings': 'customer:building',
'/api/customer-rooms': 'customer:room',
'/api/survey-orders': 'project:survey',
'/api/project-plans': 'project:plan',
'/api/contracts': 'project:contract',
'/api/install-orders': 'project:install',
'/api/acceptances': 'project:acceptance',
'/api/visit-records': 'project:visit',
'/api/service-prices': 'dispatch:price',
'/api/dispatch-prices': 'dispatch:cost',
'/api/workers': 'dispatch:worker',
"/api/users": "system:user",
"/api/roles": "system:role",
"/api/menus": "system:menu",
"/api/dicts": "system:dict",
"/api/logs": "system:log",
"/api/jobs": "system:job",
"/api/customers": "customer:list",
"/api/customer-departments": "customer:department",
"/api/customer-accounts": "customer:account",
"/api/customer-visits": "customer:visit",
"/api/upload": "customer:visit",
"/api/customer-buildings": "customer:building",
"/api/customer-rooms": "customer:room",
"/api/survey-orders": "project:survey",
"/api/project-plans": "project:plan",
"/api/contracts": "project:contract",
"/api/install-orders": "project:install",
"/api/acceptances": "project:acceptance",
"/api/visit-records": "project:visit",
"/api/repair-requests": "dispatch:repair",
"/api/repair-dispatchs": "dispatch:repair",
"/api/maintenance-orders": "dispatch:maintenance",
"/api/service-prices": "dispatch:price",
"/api/dispatch-prices": "dispatch:cost",
"/api/workers": "dispatch:worker"
};
// 超级管理员角色编码,拥有全部权限
const ADMIN_ROLE_CODE = 'ADMIN';
const ADMIN_ROLE_CODE = "ADMIN";
@Provide()
export class RbacMiddleware implements IMiddleware<Context, NextFunction> {
// 中间件匹配范围:全局生效,跳过公开接口、仪表盘与电工 APP 接口
// /api/workers/options 与 /api/workers/recommend 放行:业务员在「项目勘察派单」页
// 仍可调用电工下拉与推荐接口,不受 dispatch:worker 权限限制(本次需求有意设计)
ignore = [
'/api/auth/*',
'/api/health',
'/api/dashboard/*',
'/api/worker-app/*',
'/api/workers/options',
'/api/workers/recommend',
];
// 中间件匹配范围:全局生效,跳过公开接口、仪表盘与电工 APP 接口
// /api/workers/options 与 /api/workers/recommend 放行:业务员在「项目勘察派单」页
// 仍可调用电工下拉与推荐接口,不受 dispatch:worker 权限限制(本次需求有意设计)
ignore = ["/api/auth/*", "/api/health", "/api/dashboard/*", "/api/worker-app/*", "/api/workers/options", "/api/workers/recommend"];
@Inject()
permissionService!: PermissionService;
@Inject()
permissionService!: PermissionService;
@InjectRepository(SysUserRole)
userRoleModel!: typeof SysUserRole;
@InjectRepository(SysUserRole)
userRoleModel!: typeof SysUserRole;
@InjectRepository(SysRole)
roleModel!: typeof SysRole;
@InjectRepository(SysRole)
roleModel!: typeof SysRole;
resolve() {
return async (ctx: Context, next: NextFunction) => {
const user = ctx.state.user;
resolve() {
return async (ctx: Context, next: NextFunction) => {
const user = ctx.state.user;
if (!user) {
ctx.status = 401;
ctx.body = {
code: 401,
message: '未登录',
};
return;
}
if (!user) {
ctx.status = 401;
ctx.body = {
code: 401,
message: "未登录"
};
return;
}
// 路径前缀匹配,未匹配到任何权限映射的请求直接放行
const permissionCode = this.matchPermissionCode(ctx.path);
if (!permissionCode) {
await next();
return;
}
// 路径前缀匹配,未匹配到任何权限映射的请求直接放行
const permissionCode = this.matchPermissionCode(ctx.path);
if (!permissionCode) {
await next();
return;
}
// 查询用户角色编码,超级管理员直接放行
const roleCodes = await this.getUserRoleCodes(user.userId);
if (roleCodes.includes(ADMIN_ROLE_CODE)) {
await next();
return;
}
// 查询用户角色编码,超级管理员直接放行
const roleCodes = await this.getUserRoleCodes(user.userId);
if (roleCodes.includes(ADMIN_ROLE_CODE)) {
await next();
return;
}
// 检查用户菜单中是否包含该权限标识
const hasPermission = await this.permissionService.checkUserPermission(
user.userId,
permissionCode
);
// 检查用户菜单中是否包含该权限标识
const hasPermission = await this.permissionService.checkUserPermission(user.userId, permissionCode);
if (!hasPermission) {
ctx.status = 403;
ctx.body = {
code: 403,
message: '没有访问权限',
};
return;
}
if (!hasPermission) {
ctx.status = 403;
ctx.body = {
code: 403,
message: "没有访问权限"
};
return;
}
await next();
};
}
await next();
};
}
private matchPermissionCode(path: string): string {
for (const [prefix, code] of Object.entries(PERMISSION_MAP)) {
if (path === prefix || path.startsWith(prefix + '/')) {
return code;
}
}
return '';
}
private matchPermissionCode(path: string): string {
for (const [prefix, code] of Object.entries(PERMISSION_MAP)) {
if (path === prefix || path.startsWith(prefix + "/")) {
return code;
}
}
return "";
}
private async getUserRoleCodes(userId: number): Promise<string[]> {
const userRoles = await this.userRoleModel.findAll({
where: { userId },
include: [this.roleModel],
});
private async getUserRoleCodes(userId: number): Promise<string[]> {
const userRoles = await this.userRoleModel.findAll({
where: { userId },
include: [this.roleModel]
});
return userRoles.map(ur => ur.role?.roleCode).filter(Boolean) as string[];
}
return userRoles.map((ur) => ur.role?.roleCode).filter(Boolean) as string[];
}
}
+285
View File
@@ -0,0 +1,285 @@
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";
// 维保派单状态:0已派发 1已完成
const STATUS = { DISPATCHED: 0, COMPLETED: 1 };
@Provide()
export class MaintenanceService {
@InjectRepository(MaintenanceOrder)
orderModel!: Repository<MaintenanceOrder>;
@InjectRepository(MaintenanceDetail)
detailModel!: Repository<MaintenanceDetail>;
@InjectRepository(Customer)
customerModel!: Repository<Customer>;
@InjectRepository(Worker)
workerModel!: Repository<Worker>;
@InjectRepository(SysUserRole)
userRoleModel!: Repository<SysUserRole>;
@InjectRepository(SysRole)
roleModel!: Repository<SysRole>;
@Inject()
logService!: LogService;
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 isOrderVisible(userId: number, order: MaintenanceOrder): Promise<boolean> {
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<any> {
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<any[]> {
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<any> {
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<any> {
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<any> {
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}`);
return { code: 200, message: "创建维保派单成功", data: vo };
}
async updateOrder(id: number, data: any, ctx: Context): Promise<any> {
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[] = [];
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}`);
return { code: 200, message: "更新维保派单成功", data: vo };
}
// 完成派单:仅允许 0已派发 → 1已完成
async completeOrder(id: number, ctx: Context): Promise<any> {
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<any> {
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 };
}
}
@@ -0,0 +1,331 @@
import { Provide, Inject } from "@midwayjs/core";
import { InjectRepository, InjectDataSource } from "@midwayjs/sequelize";
import { Repository } from "sequelize-typescript";
import { Op, Sequelize } from "sequelize";
import { Context } from "@midwayjs/koa";
import RepairRequest from "../entity/repair_request.entity";
import RepairDispatch from "../entity/repair_dispatch.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";
// 报修单状态:0待处理 1已派单 2已完成
const REQUEST_STATUS = { PENDING: 0, DISPATCHED: 1, COMPLETED: 2 };
// 报修派单状态:0已派发 1已处理
const DISPATCH_STATUS = { DISPATCHED: 0, PROCESSED: 1 };
@Provide()
export class RepairDispatchService {
@InjectRepository(RepairRequest)
requestModel!: Repository<RepairRequest>;
@InjectRepository(RepairDispatch)
dispatchModel!: Repository<RepairDispatch>;
@InjectRepository(Customer)
customerModel!: Repository<Customer>;
@InjectRepository(Worker)
workerModel!: Repository<Worker>;
@InjectRepository(SysUserRole)
userRoleModel!: Repository<SysUserRole>;
@InjectRepository(SysRole)
roleModel!: Repository<SysRole>;
@InjectDataSource()
sequelize!: Sequelize;
@Inject()
logService!: LogService;
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 isOrderVisible(userId: number, order: RepairDispatch): Promise<boolean> {
if (await this.isAdmin(userId)) return true;
return this.isCustomerOwner(userId, order.customerId);
}
// 行级隔离:非管理员仅可见「客户归属自己」的工单
private async scopeWhere(baseWhere: any, ctx: Context): Promise<any> {
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;
}
// 排序白名单:仅允许 created_at / id,映射成实际列名
private buildOrder(sort?: string): [string, string][] {
const allow = new Set(["created_at", "id"]);
const field = sort && allow.has(sort) ? sort : "created_at";
return [[field, "DESC"]];
}
// 派单号规则:WH-REP-YYYY-NNNN(年内累计序号),冲突重试 1 次
private async generateDispatchNo(t?: any, retry: number = 0): Promise<string> {
const now = new Date();
const d = new Date(now.getTime() + 8 * 60 * 60 * 1000);
const year = d.getUTCFullYear();
const prefix = `WH-REP-${year}-`;
const last = await this.dispatchModel.findOne({ where: { dispatchNo: { [Op.like]: `${prefix}%` } }, order: [["dispatch_no", "DESC"]], transaction: t });
let seq = 1;
if (last) seq = (parseInt(last.dispatchNo.slice(-4), 10) || 0) + 1;
const result = `${prefix}${String(seq).padStart(4, "0")}`;
const exists = await this.dispatchModel.findOne({ where: { dispatchNo: result }, transaction: t });
if (exists) {
if (retry < 1) return this.generateDispatchNo(t, retry + 1);
throw new Error("派单号生成冲突,请重试");
}
return result;
}
// 报修单 query 参数 → Sequelize where(关键字/客户/状态/报修时间范围)
private buildRequestWhere(query: any): any {
const where: any = {};
if (query.keyword) {
where[Op.or] = [
{ repairNo: { [Op.like]: `%${query.keyword}%` } },
{ requester: { [Op.like]: `%${query.keyword}%` } },
{ requesterPhone: { [Op.like]: `%${query.keyword}%` } }
];
}
if (query.customerId !== undefined && query.customerId !== "") where.customerId = Number(query.customerId);
if (query.status !== undefined && query.status !== "") where.status = Number(query.status);
// startTime/endTime 报修时间范围,作用于 request_time
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.requestTime = cond;
}
return where;
}
// 派单 query 参数 → Sequelize where(关键字/客户/电工/状态/报修时间范围)
private buildDispatchWhere(query: any): any {
const where: any = this.buildRequestWhere(query);
if (query.workerId !== undefined && query.workerId !== "") where.workerId = Number(query.workerId);
return where;
}
// 统一清洗入参:空串转 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;
}
// 扁平化关联信息,兼容前端字段(与安装派单响应结构一致)
private decorateRequest(rows: any[]): any[] {
if (rows.length === 0) return rows;
for (const row of rows) {
row.customerName = row.customer?.customerName || "";
}
return rows;
}
private decorateDispatch(rows: any[]): any[] {
if (rows.length === 0) return rows;
for (const row of rows) {
row.customerName = row.customer?.customerName || "";
row.workerName = row.worker?.workerName || "";
row.repairNo = row.repair?.repairNo || "";
}
return rows;
}
// ================= 报修单 =================
async listRequests(query: any, ctx: Context): Promise<any> {
const page = Number(query.page) || 1;
const pageSize = Number(query.pageSize) || 10;
const where = await this.scopeWhere(this.buildRequestWhere(query), ctx);
const { count, rows } = await this.requestModel.findAndCountAll({
where,
include: [{ model: Customer, attributes: ["id", "customerNo", "customerName", "contactPerson", "contactPhone", "address"] }],
limit: pageSize,
offset: (page - 1) * pageSize,
order: this.buildOrder(query.sort),
distinct: true
});
const list = this.decorateRequest(rows.map((r: any) => r.toJSON()));
return { code: 200, message: "获取报修单列表成功", data: { list, total: count, page, pageSize } };
}
async getRequestDetail(id: number, ctx: Context): Promise<any> {
const record = await this.requestModel.findByPk(id, {
include: [{ model: Customer, attributes: ["id", "customerNo", "customerName", "contactPerson", "contactPhone", "address", "salesmanId"] }]
});
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 vo: any = record.toJSON();
this.decorateRequest([vo]);
return { code: 200, message: "获取报修单详情成功", data: vo };
}
// ================= 派单 =================
async listDispatch(query: any, ctx: Context): Promise<any> {
const page = Number(query.page) || 1;
const pageSize = Number(query.pageSize) || 10;
const where = await this.scopeWhere(this.buildDispatchWhere(query), ctx);
const { count, rows } = await this.dispatchModel.findAndCountAll({
where,
include: [
{ model: Customer, attributes: ["id", "customerNo", "customerName", "contactPerson", "contactPhone", "address"] },
{ model: Worker, attributes: ["id", "workerName", "phone", "workerTitle"] },
{ model: RepairRequest, as: "repair", attributes: ["id", "repairNo", "status"] }
],
limit: pageSize,
offset: (page - 1) * pageSize,
order: this.buildOrder(query.sort),
distinct: true
});
const list = this.decorateDispatch(rows.map((r: any) => r.toJSON()));
return { code: 200, message: "获取报修派单列表成功", data: { list, total: count, page, pageSize } };
}
async getDispatchDetail(id: number, ctx: Context): Promise<any> {
const record = await this.dispatchModel.findByPk(id, {
include: [
{ model: Customer, attributes: ["id", "customerNo", "customerName", "contactPerson", "contactPhone", "address", "salesmanId"] },
{ model: Worker, attributes: ["id", "workerName", "phone", "workerTitle"] },
{ model: RepairRequest, as: "repair", attributes: ["id", "repairNo", "status"] }
]
});
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();
this.decorateDispatch([vo]);
return { code: 200, message: "获取报修派单详情成功", data: vo };
}
// 创建派单:事务内校验报修单、生成派单号、创建派单并回写报修单状态
async createDispatch(data: any, ctx: Context): Promise<any> {
const userId = ctx.state.user.userId;
const repairId = Number(data.repairId);
const workerId = Number(data.workerId);
const created: any = await this.sequelize.transaction(async (t) => {
const repair = await this.requestModel.findByPk(repairId, { transaction: t });
if (!repair) return { code: 404, message: "报修单不存在", data: null };
if (repair.status !== REQUEST_STATUS.PENDING) return { code: 400, message: "该报修单已派单", data: null };
if (!(await this.isCustomerOwner(userId, repair.customerId))) return { code: 403, message: "无权限为该客户派单", data: null };
const worker = await this.workerModel.findByPk(workerId, { transaction: t });
if (!worker) return { code: 400, message: "共享电工不存在", data: null };
const dispatchNo = await this.generateDispatchNo(t);
const record = await this.dispatchModel.create({
dispatchNo,
repairId: repair.id,
customerId: repair.customerId,
requester: repair.requester,
requesterPhone: repair.requesterPhone,
requestTime: repair.requestTime,
buildingId: repair.buildingId,
floorNo: repair.floorNo,
location: repair.location,
repairContent: repair.repairContent,
dispatcher: (ctx.state.user as any)?.username || "",
workerId,
status: DISPATCH_STATUS.DISPATCHED
}, { transaction: t });
// 回写报修单状态:0待处理 → 1已派单
await repair.update({ status: REQUEST_STATUS.DISPATCHED }, { transaction: t });
return { code: 200, record };
});
if (created.code !== 200) return created;
const vo: any = created.record.toJSON();
this.decorateDispatch([vo]);
await this.logService.logAction(ctx, "报修派单", "创建", created.record.dispatchNo, `创建报修派单 ${created.record.dispatchNo}`);
return { code: 200, message: "创建报修派单成功", data: vo };
}
async updateDispatch(id: number, data: any, ctx: Context): Promise<any> {
const record = await this.dispatchModel.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 !== DISPATCH_STATUS.DISPATCHED) return { code: 400, message: "已处理的派单不可修改", data: null };
if (data.workerId !== undefined && data.workerId !== "" && data.workerId !== null) {
const worker = await this.workerModel.findByPk(Number(data.workerId));
if (!worker) return { code: 400, message: "共享电工不存在", data: null };
}
const payload = this.normalizePayload(data);
delete payload.repairId;
delete payload.customerId;
delete payload.status;
await record.update(payload);
const vo: any = record.toJSON();
this.decorateDispatch([vo]);
await this.logService.logAction(ctx, "报修派单", "更新", record.dispatchNo, `更新报修派单 ${record.dispatchNo}`);
return { code: 200, message: "更新报修派单成功", data: vo };
}
// 完成派单:仅允许 0已派发 → 1已处理
async completeDispatch(id: number, data: any, ctx: Context): Promise<any> {
const record = await this.dispatchModel.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 !== DISPATCH_STATUS.DISPATCHED) return { code: 400, message: "仅已派发状态的派单可标记完成", data: null };
const payload: any = { status: DISPATCH_STATUS.PROCESSED, completedAt: new Date() };
if (data.processPhotos !== undefined && data.processPhotos !== null) payload.processPhotos = data.processPhotos;
if (data.rating !== undefined && data.rating !== "" && data.rating !== null) payload.rating = Number(data.rating);
await record.update(payload);
const vo: any = record.toJSON();
this.decorateDispatch([vo]);
await this.logService.logAction(ctx, "报修派单", "完成", record.dispatchNo, `完成报修派单 ${record.dispatchNo}`);
return { code: 200, message: "报修派单已完成", data: vo };
}
// 删除派单:事务内删除并回退关联报修单状态(1已派单 → 0待处理)
async removeDispatch(id: number, ctx: Context): Promise<any> {
const record = await this.dispatchModel.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 dispatchNo = record.dispatchNo;
const repairId = record.repairId;
await this.sequelize.transaction(async (t) => {
await record.destroy({ transaction: t });
const repair = await this.requestModel.findByPk(repairId, { transaction: t });
if (repair && repair.status === REQUEST_STATUS.DISPATCHED) {
await repair.update({ status: REQUEST_STATUS.PENDING }, { transaction: t });
}
});
await this.logService.logAction(ctx, "报修派单", "删除", dispatchNo, `删除报修派单 ${dispatchNo}`);
return { code: 200, message: "删除报修派单成功", data: null };
}
}