/* * Copyright (c) 2018-2028, Chill Zhuang All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the dreamlu.net developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: Chill 庄骞 (smallchill@163.com) */ package org.sxkj.gd.workorder.service.impl; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import org.apache.commons.lang3.StringUtils; import org.springblade.core.mp.base.BaseServiceImpl; import org.springblade.core.oss.model.BladeFile; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.utils.DateUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.sxkj.common.constant.WordOrderConstant; import org.sxkj.common.utils.OrderNumUtils; import org.springblade.core.tool.api.R; import org.springblade.core.tool.constant.BladeConstant; import org.springblade.core.tool.utils.FileUtil; import org.springblade.core.tool.utils.StringUtil; import org.sxkj.gd.workorder.entity.GdPatrolTaskEntity; import org.sxkj.gd.workorder.entity.GdTaskResultEntity; import org.sxkj.gd.workorder.entity.GdWorkOrderEntity; import org.sxkj.gd.workorder.entity.GdWorkOrderFlowEntity; import org.sxkj.gd.workorder.enums.PatrolTaskStatusEnum; import org.sxkj.gd.workorder.enums.WorkOrderStatusEnum; import org.sxkj.gd.workorder.enums.WorkOrderTypeEnum; import org.sxkj.gd.workorder.excel.GdPatrolTaskExcel; import org.sxkj.gd.workorder.mapper.GdPatrolTaskMapper; import org.sxkj.gd.workorder.param.GdPatrolTaskAuditParam; import org.sxkj.gd.workorder.param.GdPatrolTaskPageParam; import org.sxkj.gd.workorder.service.IGdPatrolTaskService; import org.sxkj.gd.workorder.service.IGdTaskResultService; import org.sxkj.gd.workorder.service.IGdWorkOrderFlowService; import org.sxkj.gd.workorder.utils.GdPatrolReportWordUtil; import org.sxkj.gd.workorder.vo.GdPatrolTaskVO; import org.sxkj.gd.common.GdMultipartFileUtil; import org.sxkj.resource.entity.Attach; import org.sxkj.resource.feign.IAttachClient; import org.sxkj.system.cache.SysCache; import org.sxkj.system.entity.Dept; import org.sxkj.system.entity.User; import org.sxkj.system.feign.ISysClient; import org.sxkj.system.feign.IUserClient; import java.io.File; import java.io.IOException; import java.util.Date; import java.util.List; /** * 巡查任务表 服务实现类 * * @author lw * @since 2026-01-14 */ @Service public class GdPatrolTaskServiceImpl extends BaseServiceImpl implements IGdPatrolTaskService { @Autowired private IGdWorkOrderFlowService gdWorkOrderFlowService; // @Autowired // private IGdWorkOrderService gdWorkOrderService; @Autowired private ISysClient sysClient; @Autowired private IUserClient userClient; @Autowired private IAttachClient attachClient; @Autowired private IGdTaskResultService gdTaskResultService; @Override public IPage selectGdPatrolTaskPage(IPage page, GdPatrolTaskPageParam gdPatrolTask) { return page.setRecords(baseMapper.selectGdPatrolTaskPage(page, gdPatrolTask, SysCache.getDeptChildIds(Long.valueOf(AuthUtil.getDeptId())))); } @Override public List exportGdPatrolTask(Wrapper queryWrapper) { List gdPatrolTaskList = baseMapper.exportGdPatrolTask(queryWrapper); // gdPatrolTaskList.forEach(gdPatrolTask -> { // gdPatrolTask.setTypeName(DictCache.getValue(DictEnum.YES_NO, GdPatrolTask.getType())); //}); return gdPatrolTaskList; } @Override @Transactional(rollbackFor = Exception.class) public boolean savePatrolTask(List taskEntityList) { // 更新流程节点 for (GdPatrolTaskEntity gdPatrolTaskEntity : taskEntityList) { // 生成巡查任务编号 if (StringUtils.isBlank(gdPatrolTaskEntity.getTaskNo())) { // 使用时间戳生成唯一巡查任务编号 String timestamp = OrderNumUtils.initOrderNum(WordOrderConstant.PATROL_TASK_KEY); gdPatrolTaskEntity.setTaskNo(WordOrderConstant.PATROL_TASK_PREFIX + timestamp); } // 设置初始状态为待签收(对应流程图:等待接单) gdPatrolTaskEntity.setTaskStatus(PatrolTaskStatusEnum.PENDING_SIGNING.getValue()); // 保存巡逻任务 boolean save = save(gdPatrolTaskEntity); if (!save) { // 任务保存失败,抛出异常触发回滚 throw new RuntimeException("保存巡逻任务失败:" + gdPatrolTaskEntity.toString()); } // 保存流转记录(发布任务,等待接单) GdWorkOrderFlowEntity gdWorkOrderFlowEntity = new GdWorkOrderFlowEntity(); gdWorkOrderFlowEntity.setCreateUser(AuthUtil.getUserId()); gdWorkOrderFlowEntity.setWorkOrderId(gdPatrolTaskEntity.getId()); gdWorkOrderFlowEntity.setFlowName(PatrolTaskStatusEnum.PENDING_SIGNING.getStageDesc()); gdWorkOrderFlowEntity.setFlowDesc(AuthUtil.getUserName() + " " + DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " 发布任务,等待接单"); gdWorkOrderFlowEntity.setOperator(AuthUtil.getUserName()); gdWorkOrderFlowEntity.setType(WorkOrderTypeEnum.PATROL_TASK.getValue()); gdWorkOrderFlowEntity.setAreaCode(gdPatrolTaskEntity.getAreaCode()); gdWorkOrderFlowEntity.setCreateDept(Long.valueOf(AuthUtil.getDeptId())); boolean save1 = gdWorkOrderFlowService.save(gdWorkOrderFlowEntity); if (!save1) { // 流转记录保存失败,抛出异常触发回滚 throw new RuntimeException("保存流转记录失败:" + gdWorkOrderFlowEntity.toString()); } } // 所有任务和流转记录都保存成功 return true; } /** * 审核巡查任务 * * @param auditParam 审核参数,包含任务ID、审核状态和拒绝原因 * @return 是否审核成功 * @throws RuntimeException 当任务不存在、状态不允许转换或操作失败时抛出 */ @Override @Transactional(rollbackFor = Exception.class) public boolean auditPatrolTask(GdPatrolTaskAuditParam auditParam) { // 1. 获取巡查任务 GdPatrolTaskEntity task = getById(auditParam.getId()); if (task == null) { throw new RuntimeException("巡查任务不存在"); } // 2. 状态权限校验 PatrolTaskStatusEnum currentStatus = PatrolTaskStatusEnum.getByValue(task.getTaskStatus()); Integer auditStatus = auditParam.getAuditStatus(); if (currentStatus == null || !currentStatus.isAllowedTransition(auditStatus)) { throw new RuntimeException("当前任务状态无法进行该操作"); } // 1.同意签收、2.拒绝签收 、3.撤回任务 、4.同意审核、 5.拒绝审核、 6.验收通过、 7.验收拒绝 // 3. 确定新状态和流转名称 PatrolTaskStatusEnum newStatusEnum; switch (auditStatus) { case 1: // 接单 newStatusEnum = PatrolTaskStatusEnum.PENDING_AUDIT; break; case 2: // 拒接接单 newStatusEnum = PatrolTaskStatusEnum.REJECTED_SIGNING; break; case 3: // 撤回任务 newStatusEnum = PatrolTaskStatusEnum.WITHDRAWN; break; case 4: // 同意审核 newStatusEnum = PatrolTaskStatusEnum.AUDIT_PASSED; break; case 5: // 拒绝审核 newStatusEnum = PatrolTaskStatusEnum.AUDIT_REJECTED; break; case 6: // 验收通过 newStatusEnum = PatrolTaskStatusEnum.ACCEPTANCE_PASSED; break; case 7: // 验收拒绝 newStatusEnum = PatrolTaskStatusEnum.REJECTED_ACCEPTANCE; break; default: // 无效操作类型 return false; } // 4. 更新任务状态 Date now = new Date(); task.setTaskStatus(newStatusEnum.getValue()); task.setUpdateUser(AuthUtil.getUserId()); task.setUpdateTime(now); task.setRejectReason(auditParam.getRejectReason()); boolean updateResult = updateById(task); if (!updateResult) { throw new RuntimeException("更新任务状态失败"); } // 5. 保存流转记录 boolean b = saveWorkOrderFlow(task.getId(), newStatusEnum, auditParam.getRejectReason(), task.getAreaCode()); // 6. 判断是否审核成功,如果是,需要判断是否整个订单的子任务都审核成功了,除了被撤回和验收拒绝的 if (auditStatus == 4) { // 查询同一个工单下的所有子任务 List subTasks = list(Wrappers.lambdaQuery() .eq(GdPatrolTaskEntity::getWorkOrderId, task.getWorkOrderId())); // 检查是否所有子任务都审核成功(除了被撤回和验收拒绝的) boolean allSubTasksAudited = checkAllSubTasksStatus(subTasks, PatrolTaskStatusEnum.AUDIT_PASSED); // 示例:如果所有子任务都审核成功,可以更新工单状态为已完成 if (allSubTasksAudited) { // 执行工单状态更新逻辑 return updateWorkOrderStatusAndFlow(task.getWorkOrderId(), WorkOrderStatusEnum.EXECUTING_TO_BE_COMPLETED); } } // 7. 验收拒绝 if (auditStatus == 7) { return updateWorkOrderStatusAndFlow(task.getWorkOrderId(), WorkOrderStatusEnum.EXECUTING_TO_BE_COMPLETED); } // 8. 验收通过 if (auditStatus == 6) { // 查询同一个工单下的所有子任务 List subTasks = list(Wrappers.lambdaQuery() .eq(GdPatrolTaskEntity::getWorkOrderId, task.getWorkOrderId())); // 检查是否所有子任务都验收通过(除了被撤回和验收拒绝的) boolean allSubTasksAudited = checkAllSubTasksStatus(subTasks, PatrolTaskStatusEnum.ACCEPTANCE_PASSED); // 这里可以根据allSubTasksAudited的结果执行后续逻辑,比如更新工单状态等 if (allSubTasksAudited) { // 1.验收通过,生成报告 asyncExportPatrolReport(task.getId()); // 2.执行工单状态更新逻辑 return updateWorkOrderStatusAndFlow(task.getWorkOrderId(), WorkOrderStatusEnum.ACCEPTED_TO_BE_SETTLED); } } return b; } /** * 检查是否所有子任务都达到指定状态(除了被撤回和验收拒绝的) * * @param subTasks 子任务列表 * @param targetStatus 目标状态枚举 * @return 是否所有子任务都达到指定状态 */ private boolean checkAllSubTasksStatus(List subTasks, PatrolTaskStatusEnum targetStatus) { for (GdPatrolTaskEntity subTask : subTasks) { String taskStatus = subTask.getTaskStatus(); PatrolTaskStatusEnum statusEnum = PatrolTaskStatusEnum.getByValue(taskStatus); // 如果任务状态不是目标状态,且不是被撤回和验收拒绝的,则返回false if (statusEnum != targetStatus && statusEnum != PatrolTaskStatusEnum.WITHDRAWN && statusEnum != PatrolTaskStatusEnum.REJECTED_ACCEPTANCE) { return false; } } return true; } /** * 更新工单状态并保存流转记录 * * @param workOrderId 工单ID * @param newStatus 新的工单状态枚举 * @return 是否更新成功 */ private boolean updateWorkOrderStatusAndFlow(Long workOrderId, WorkOrderStatusEnum newStatus) { // 1. 更新工单状态 GdWorkOrderEntity gdWorkOrderEntity = new GdWorkOrderEntity(); gdWorkOrderEntity.setId(workOrderId); gdWorkOrderEntity.setWorkOrderStatus(newStatus.getCode()); Integer updateResult = baseMapper.updateWorkOrderById(gdWorkOrderEntity); if (updateResult <= 0) { return false; } // 2. 保存流转记录 GdWorkOrderFlowEntity flowEntity = new GdWorkOrderFlowEntity(); flowEntity.setCreateUser(AuthUtil.getUserId()); flowEntity.setWorkOrderId(workOrderId); flowEntity.setFlowName(newStatus.getDesc()); flowEntity.setFlowDesc(String.format("%s %s 更新了任务信息,状态转换为:%s", AuthUtil.getUserName(), DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"), newStatus.getDesc())); flowEntity.setOperator(AuthUtil.getUserName()); flowEntity.setType(WorkOrderTypeEnum.WORK_ORDER_TASK.getValue()); flowEntity.setCreateDept(Long.valueOf(AuthUtil.getDeptId())); return gdWorkOrderFlowService.save(flowEntity); } @Override @Transactional(rollbackFor = Exception.class) public boolean republishPatrolTask(GdPatrolTaskEntity taskEntity) { if (taskEntity == null || taskEntity.getId() == null) { throw new RuntimeException("任务信息或ID不能为空"); } // 1. 验证任务是否存在 GdPatrolTaskEntity existingTask = getById(taskEntity.getId()); taskEntity.setId(existingTask.getId()); if (existingTask == null) { throw new RuntimeException("巡查任务不存在"); } // 2. 先确定枚举状态:根据当前状态确定新的枚举状态 PatrolTaskStatusEnum targetStatus; String currentStatus = existingTask.getTaskStatus(); // 1.发布中_拒绝接单 、 4.响应中_审核驳回 switch (currentStatus) { case "1": // 发布中_拒绝接单 → 发布中_接单中 targetStatus = PatrolTaskStatusEnum.PENDING_SIGNING; break; case "4": // 响应中_审核驳回 → 响应中_待审核 targetStatus = PatrolTaskStatusEnum.PENDING_AUDIT; break; default: // 其他状态,保持原有状态 targetStatus = PatrolTaskStatusEnum.getByValue(currentStatus); // 如果无法获取枚举,使用待签收作为默认状态 if (targetStatus == null) { targetStatus = PatrolTaskStatusEnum.PENDING_SIGNING; } break; } // 3. 设置任务状态:使用枚举设置任务状态 taskEntity.setTaskStatus(targetStatus.getValue()); // 4. 更新任务信息:合并传入的任务信息 taskEntity.setUpdateUser(AuthUtil.getUserId()); taskEntity.setUpdateTime(new Date()); // 5. 更新数据库 boolean updateResult = updateById(taskEntity); if (!updateResult) { throw new RuntimeException("更新任务信息失败"); } // 6. 保存流转记录:使用枚举设置flowName GdWorkOrderFlowEntity flowEntity = new GdWorkOrderFlowEntity(); flowEntity.setCreateUser(AuthUtil.getUserId()); flowEntity.setWorkOrderId(taskEntity.getId()); flowEntity.setFlowName(targetStatus.getDesc()); flowEntity.setFlowDesc(AuthUtil.getUserName() + " " + DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss") + " 更新了任务信息,状态转换为:" + targetStatus.getDesc()); flowEntity.setOperator(AuthUtil.getUserName()); flowEntity.setType(WorkOrderTypeEnum.PATROL_TASK.getValue()); flowEntity.setAreaCode(existingTask.getAreaCode()); flowEntity.setCreateDept(Long.valueOf(AuthUtil.getDeptId())); return gdWorkOrderFlowService.save(flowEntity); } @Override public boolean exportPatrolReport(Long patrolTaskId) { if (patrolTaskId == null) { throw new RuntimeException("巡查任务主键不能为空"); } GdPatrolTaskEntity taskEntity = getById(patrolTaskId); if (taskEntity == null) { throw new RuntimeException("巡查任务不存在"); } List resultList = gdTaskResultService.list(Wrappers.lambdaQuery() .eq(GdTaskResultEntity::getPatrolTaskId, patrolTaskId) .eq(GdTaskResultEntity::getIsDeleted, BladeConstant.DB_NOT_DELETED)); String creatorName = getUserName(taskEntity.getCreateUser()); String deptName = getDeptName(taskEntity.getCreateDept()); try { File reportFile = GdPatrolReportWordUtil.generateReportFile(taskEntity, resultList, creatorName, deptName); return saveAttachFile(reportFile, taskEntity); } catch (Exception e) { throw new RuntimeException("生成巡查报告失败", e); } } /** * 异步生成巡查报告 * * @param patrolTaskId 巡查任务ID */ @Async public void asyncExportPatrolReport(Long patrolTaskId) { try { exportPatrolReport(patrolTaskId); } catch (Exception e) { // 记录异步执行错误日志 log.error("异步生成巡查报告失败,任务ID: {}"); } } /** * 检查拒绝原因是否必填 * * @param newStatus 新状态 * @param rejectReason 拒绝原因 * @throws RuntimeException 当需要拒绝原因但未提供时抛出 */ // private void checkRejectReason(PatrolTaskStatusEnum newStatus, String rejectReason) { // // 拒绝类状态需要填写拒绝原因 // if ((newStatus == PatrolTaskStatusEnum.REJECTED_SIGNING || // newStatus == PatrolTaskStatusEnum.AUDIT_REJECTED || // newStatus == PatrolTaskStatusEnum.REJECTED_ACCEPTANCE) && // (rejectReason == null || rejectReason.trim().isEmpty())) { // throw new RuntimeException("拒绝操作时必须填写拒绝原因"); // } // } /** * 保存工单流转记录 * * @param workOrderId 工单ID * @param newStatus 新状态 * @param rejectReason 拒绝原因 * @param areaCode 区域编码 * @return 是否保存成功 * @throws RuntimeException 当保存失败时抛出 */ private boolean saveWorkOrderFlow(Long workOrderId, PatrolTaskStatusEnum newStatus, String rejectReason, String areaCode) { GdWorkOrderFlowEntity flowEntity = new GdWorkOrderFlowEntity(); flowEntity.setCreateUser(AuthUtil.getUserId()); flowEntity.setWorkOrderId(workOrderId); flowEntity.setFlowName(newStatus.getStageDesc()); // 构建流转描述 String flowDesc = buildFlowDesc(newStatus, rejectReason); flowEntity.setFlowDesc(flowDesc); flowEntity.setOperator(AuthUtil.getUserName()); flowEntity.setType(WorkOrderTypeEnum.PATROL_TASK.getValue()); flowEntity.setAreaCode(areaCode); flowEntity.setCreateDept(Long.valueOf(AuthUtil.getDeptId())); boolean saveResult = gdWorkOrderFlowService.save(flowEntity); if (!saveResult) { throw new RuntimeException("保存流转记录失败"); } return true; } /** * 构建流转描述 * * @param newStatus 新状态 * @param rejectReason 拒绝原因 * @return 流转描述 */ private String buildFlowDesc(PatrolTaskStatusEnum newStatus, String rejectReason) { String baseDesc = String.format("%s %s ", AuthUtil.getUserName(), DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")); // 拒绝类状态需要添加拒绝原因 if (newStatus == PatrolTaskStatusEnum.REJECTED_SIGNING || newStatus == PatrolTaskStatusEnum.AUDIT_REJECTED || newStatus == PatrolTaskStatusEnum.REJECTED_ACCEPTANCE) { return String.format("%s%s:%s", baseDesc, newStatus.getDesc(), rejectReason); } // 其他状态直接使用枚举描述 return baseDesc + newStatus.getDesc(); } private String getDeptName(Long deptId) { if (deptId == null) { return "/"; } R deptResult = sysClient.getDept(deptId); Dept dept = deptResult != null ? deptResult.getData() : null; if (dept == null || StringUtil.isBlank(dept.getDeptName())) { return "/"; } return dept.getDeptName(); } private String getUserName(Long userId) { if (userId == null) { return "/"; } R userResult = userClient.userInfoById(userId); User user = userResult != null ? userResult.getData() : null; if (user == null) { return "/"; } String realName = user.getRealName(); if (StringUtil.isBlank(realName)) { realName = user.getName(); } return StringUtil.isBlank(realName) ? "/" : realName; } private boolean saveAttachFile(File reportFile, GdPatrolTaskEntity taskEntity) throws IOException { if (reportFile == null || !reportFile.exists()) { throw new RuntimeException("巡查报告文件不存在"); } String desiredName = buildReportAttachName(taskEntity, reportFile.getName()); BladeFile bladeFile = attachClient.putFile( GdMultipartFileUtil.fromFile(reportFile, "application/vnd.openxmlformats-officedocument.wordprocessingml.document"), desiredName ); if (bladeFile == null || StringUtil.isBlank(bladeFile.getLink())) { throw new RuntimeException("附件上传失败"); } Integer resultType = parseResultType(taskEntity != null ? taskEntity.getPatrolTaskType() : null); Attach attach = buildAttachInfo(taskEntity, reportFile, bladeFile, resultType, desiredName); Boolean saved = attachClient.saveAttachInfo(attach); if (!Boolean.TRUE.equals(saved)) { log.error("附件信息保存失败,报告文件:" + desiredName); return false; } return true; } private Attach buildAttachInfo(GdPatrolTaskEntity taskEntity, File reportFile, BladeFile bladeFile, Integer resultType, String desiredName) { Attach attach = new Attach(); attach.setDomainUrl(bladeFile.getDomain()); attach.setLink(bladeFile.getLink()); attach.setName(desiredName); attach.setOriginalName(desiredName); attach.setAttachSize(reportFile.length()); attach.setExtension(FileUtil.getFileExtension(reportFile.getName())); attach.setResultType(resultType); attach.setPatrolTaskId(String.valueOf(taskEntity != null ? taskEntity.getId() : null)); Long createUser = taskEntity != null ? taskEntity.getCreateUser() : null; Long updateUser = taskEntity != null ? taskEntity.getUpdateUser() : null; Long createDept = taskEntity != null ? taskEntity.getCreateDept() : null; if (createUser == null) { createUser = AuthUtil.getUserId(); } if (updateUser == null) { updateUser = createUser; } if (createDept == null) { createDept = Long.valueOf(AuthUtil.getDeptId()); } Date now = new Date(); attach.setCreateUser(createUser); attach.setUpdateUser(updateUser); attach.setCreateDept(createDept); attach.setCreateTime(now); attach.setUpdateTime(now); return attach; } private String buildReportAttachName(GdPatrolTaskEntity taskEntity, String fallbackName) { String taskNo = taskEntity != null ? taskEntity.getTaskNo() : null; if (StringUtil.isBlank(taskNo)) { return fallbackName; } return taskNo + ".docx"; } private Integer parseResultType(String patrolTaskType) { if (StringUtil.isBlank(patrolTaskType)) { return null; } try { Integer type = Integer.valueOf(patrolTaskType); if (type < 1 || type > 5) { return null; } return type; } catch (NumberFormatException ex) { return null; } } }