feat: 新增现场勘察派单全流程业务能力
1. 新增共享电工管理模块:包含实体、DTO、控制器、服务以及前后端API 2. 重构勘察工单实体与查询逻辑,完善工单全状态流程与字段 3. 新增勘察SOP模板与工单回执业务体系 4. 更新数据库字典与初始化脚本,补充业务数据 5. 新增勘察派单增删改查、指派、复核、催办等全流程接口 6. 修复用户服务角色关联SQL生成问题 7. 新增勘察派单前后端完整交互链路
This commit is contained in:
1 parent
1c83e09fa1
commit
6b00f61550
21 files changed
+4739
-388
No files matched your search
@@ -0,0 +1,98 @@
|
||||
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 Worker from "../entity/worker.entity";
|
||||
|
||||
// 电工状态:1在岗 0离职
|
||||
const STATUS_ON_DUTY = 1;
|
||||
// 推荐位数量
|
||||
const RECOMMEND_LIMIT = 5;
|
||||
// 经验分基准场次(达到即满分)
|
||||
const EXP_BASE_COUNT = 50;
|
||||
|
||||
@Provide()
|
||||
export class WorkerService {
|
||||
@InjectRepository(Worker)
|
||||
workerModel!: Repository<Worker>;
|
||||
|
||||
// 列表:姓名/手机号/资质模糊检索 + 状态过滤 + 分页
|
||||
async list(query: any, ctx: Context): Promise<any> {
|
||||
const page = Number(query.page) || 1;
|
||||
const pageSize = Number(query.pageSize || query.limit) || 10;
|
||||
|
||||
const where: any = {};
|
||||
if (query.keyword) {
|
||||
where[Op.or] = [
|
||||
{ workerName: { [Op.like]: `%${query.keyword}%` } },
|
||||
{ phone: { [Op.like]: `%${query.keyword}%` } },
|
||||
{ certInfo: { [Op.like]: `%${query.keyword}%` } }
|
||||
];
|
||||
}
|
||||
if (query.status !== undefined && query.status !== "") where.status = Number(query.status);
|
||||
|
||||
const { count, rows } = await this.workerModel.findAndCountAll({
|
||||
where,
|
||||
limit: pageSize,
|
||||
offset: (page - 1) * pageSize,
|
||||
order: [["rating", "DESC"]],
|
||||
distinct: true
|
||||
});
|
||||
|
||||
return { code: 200, message: "获取共享电工列表成功", data: { list: rows.map((r) => r.toJSON()), total: count, page, pageSize } };
|
||||
}
|
||||
|
||||
// 下拉选项:仅在岗电工
|
||||
async options(ctx: Context): Promise<any> {
|
||||
const rows = await this.workerModel.findAll({
|
||||
where: { status: STATUS_ON_DUTY },
|
||||
attributes: ["id", "workerName", "workerTitle", "rating", "certInfo", "certVerified", "completedCount"],
|
||||
order: [["rating", "DESC"]]
|
||||
});
|
||||
return { code: 200, message: "获取共享电工选项成功", data: rows.map((r) => r.toJSON()) };
|
||||
}
|
||||
|
||||
// 智能推荐:评分 50% + 资质 30% + 经验 20% 加权,取前 5 名
|
||||
async recommend(query: any, ctx: Context): Promise<any> {
|
||||
const rows = await this.workerModel.findAll({ where: { status: STATUS_ON_DUTY } });
|
||||
if (rows.length === 0) return { code: 200, message: "获取推荐电工成功", data: [] };
|
||||
|
||||
const certKeyword: string = query.certKeyword || "";
|
||||
const list = rows.map((row) => {
|
||||
const certInfo: string = row.certInfo || "";
|
||||
const rating = Number(row.rating) || 0;
|
||||
const completedCount = Number(row.completedCount) || 0;
|
||||
|
||||
const scoreRating = rating / 5;
|
||||
let scoreCert = row.certVerified ? (certInfo ? 1 : 0.7) : 0.4;
|
||||
// 关键词命中持证资质时资质分置满
|
||||
if (certKeyword && certInfo.includes(certKeyword)) scoreCert = 1;
|
||||
const scoreExp = Math.min(completedCount / EXP_BASE_COUNT, 1);
|
||||
|
||||
const score = 0.5 * scoreRating + 0.3 * scoreCert + 0.2 * scoreExp;
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
workerName: row.workerName,
|
||||
workerTitle: row.workerTitle,
|
||||
rating,
|
||||
certInfo,
|
||||
certVerified: row.certVerified,
|
||||
completedCount,
|
||||
matchRate: Number((score * 100).toFixed(1)),
|
||||
rank: 0,
|
||||
isTop: false
|
||||
};
|
||||
});
|
||||
|
||||
list.sort((a, b) => b.matchRate - a.matchRate || b.rating - a.rating || b.completedCount - a.completedCount);
|
||||
|
||||
const data = list.slice(0, RECOMMEND_LIMIT).map((item, index) => {
|
||||
const rank = index + 1;
|
||||
return { ...item, rank, isTop: rank === 1 };
|
||||
});
|
||||
|
||||
return { code: 200, message: "获取推荐电工成功", data };
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user