xiaoyi
3 天以前 b5e1bceabb36fe1d1e5e88397280d12e7044c6dd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
package cn.iocoder.yudao.module.mes.service.safety;
 
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.hazard.*;
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.SafetyHazardDO;
import cn.iocoder.yudao.module.mes.dal.mysql.safety.SafetyHazardMapper;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyHazardLevelEnum;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyHazardSourceEnum;
import cn.iocoder.yudao.module.mes.enums.safety.SafetyHazardStatusEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
 
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
 
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.SafetyErrorCodeConstants.*;
 
/**
 * 安全生产 - 隐患登记与闭环 Service
 *
 * <p>完整闭环:上报 → 分派 → 整改 → 复查(通过则闭环,不通过退回整改)。
 * 每个环节都校验当前状态,防止跳步或重复操作。</p>
 *
 * @author 超级管理员
 */
@Service
@Validated
public class SafetyHazardService {
 
    private static final DateTimeFormatter CODE_DATE = DateTimeFormatter.ofPattern("yyyyMMdd");
 
    @Resource
    private SafetyHazardMapper hazardMapper;
    @Resource
    private SafetyRefResolver refResolver;
    @Resource
    private NotifyMessageSendApi notifyMessageSendApi;
    @Resource
    private AdminUserService adminUserService;
 
    /**
     * 隐患上报(含移动端拍照上传的 photoUrls)
     */
    @Transactional(rollbackFor = Exception.class)
    public Long createHazard(SafetyHazardSaveReqVO reqVO) {
        reqVO.setId(null);
        SafetyHazardDO hazard = BeanUtils.toBean(reqVO, SafetyHazardDO.class);
        hazard.setCode(hazardMapper.selectNextCode("HZ" + LocalDate.now().format(CODE_DATE)));
        hazard.setTitle(StrUtil.trim(reqVO.getTitle()));
        hazard.setReporterId(reqVO.getReporterId());
        hazard.setReporter(refResolver.requireEmployee(reqVO.getReporterId(), SAFETY_EMPLOYEE_REQUIRED).getName());
        hazard.setLevel(StrUtil.blankToDefault(reqVO.getLevel(), SafetyHazardLevelEnum.GENERAL.getLevel()));
        hazard.setSource(StrUtil.blankToDefault(reqVO.getSource(), SafetyHazardSourceEnum.SELF_CHECK.getSource()));
        hazard.setReportTime(reqVO.getReportTime() == null ? LocalDateTime.now() : reqVO.getReportTime());
        hazard.setStatus(SafetyHazardStatusEnum.REPORTED.getStatus());
        hazard.setRemark(reqVO.getRemark() == null ? "" : reqVO.getRemark().trim());
        hazardMapper.insert(hazard);
        return hazard.getId();
    }
 
    /**
     * 修改隐患基础信息(仅未分派前允许改关键字段)
     */
    public void updateHazard(SafetyHazardSaveReqVO reqVO) {
        SafetyHazardDO old = validateHazardExists(reqVO.getId());
        if (!SafetyHazardStatusEnum.REPORTED.getStatus().equals(old.getStatus())
                && !SafetyHazardStatusEnum.ASSIGNED.getStatus().equals(old.getStatus())) {
            throw exception(SAFETY_HAZARD_STATUS_ERROR);
        }
        SafetyHazardDO update = BeanUtils.toBean(reqVO, SafetyHazardDO.class);
        // 编号与状态不允许通过普通编辑修改
        update.setCode(null);
        update.setStatus(null);
        hazardMapper.updateById(update);
    }
 
    /**
     * 分派整改责任人
     */
    @Transactional(rollbackFor = Exception.class)
    public void assignHazard(SafetyHazardAssignReqVO reqVO) {
        SafetyHazardDO hazard = hazardMapper.selectByIdForUpdate(reqVO.getId());
        if (hazard == null) throw exception(SAFETY_HAZARD_NOT_EXISTS);
        if (!SafetyHazardStatusEnum.REPORTED.getStatus().equals(hazard.getStatus())
                && !SafetyHazardStatusEnum.REJECTED.getStatus().equals(hazard.getStatus())) {
            throw exception(SAFETY_HAZARD_STATUS_ERROR);
        }
        if (reqVO.getAssigneeId() == null) {
            throw exception(SAFETY_HAZARD_ASSIGNEE_REQUIRED);
        }
        var assignee = refResolver.requireEmployee(reqVO.getAssigneeId(), SAFETY_HAZARD_ASSIGNEE_REQUIRED);
        if (assignee.getUserId() == null) throw exception(SAFETY_HAZARD_ASSIGNEE_NO_ACCOUNT);
        adminUserService.validateUserList(List.of(assignee.getUserId()));
        SafetyHazardDO update = new SafetyHazardDO();
        update.setId(hazard.getId());
        update.setAssigneeId(assignee.getId());
        update.setAssignee(assignee.getName());
        update.setAssignTime(LocalDateTime.now());
        update.setRectifyDeadline(reqVO.getRectifyDeadline());
        update.setStatus(SafetyHazardStatusEnum.RECTIFYING.getStatus());
        if (StrUtil.isNotBlank(reqVO.getRemark())) {
            update.setRemark(reqVO.getRemark().trim());
        }
        hazardMapper.updateById(update);
        NotifySendSingleToUserReqDTO notice = new NotifySendSingleToUserReqDTO();
        notice.setUserId(assignee.getUserId());
        notice.setTemplateCode("safety_hazard_assigned");
        notice.setTemplateParams(Map.of("code", hazard.getCode(), "title", hazard.getTitle(),
                "deadline", reqVO.getRectifyDeadline() == null ? "未设置" : reqVO.getRectifyDeadline().toString()));
        if (notifyMessageSendApi.sendSingleMessageToAdmin(notice) == null) {
            throw exception(SAFETY_HAZARD_NOTICE_UNAVAILABLE);
        }
    }
 
    /**
     * 提交整改结果
     */
    @Transactional(rollbackFor = Exception.class)
    public void rectifyHazard(SafetyHazardRectifyReqVO reqVO) {
        SafetyHazardDO hazard = validateHazardExists(reqVO.getId());
        if (!SafetyHazardStatusEnum.RECTIFYING.getStatus().equals(hazard.getStatus())
                && !SafetyHazardStatusEnum.REJECTED.getStatus().equals(hazard.getStatus())) {
            throw exception(SAFETY_HAZARD_STATUS_ERROR);
        }
        SafetyHazardDO update = new SafetyHazardDO();
        update.setId(hazard.getId());
        update.setRectifyDesc(reqVO.getRectifyDesc().trim());
        update.setRectifyPhotoUrls(reqVO.getRectifyPhotoUrls());
        update.setRectifyTime(LocalDateTime.now());
        update.setStatus(SafetyHazardStatusEnum.RECHECKING.getStatus());
        hazardMapper.updateById(update);
    }
 
    /**
     * 复查:通过则闭环,不通过退回整改。
     *
     * <p>校验整改人与复查人不能是同一人,保证闭环的两级分离。</p>
     */
    @Transactional(rollbackFor = Exception.class)
    public void recheckHazard(SafetyHazardRecheckReqVO reqVO) {
        SafetyHazardDO hazard = validateHazardExists(reqVO.getId());
        if (!SafetyHazardStatusEnum.RECHECKING.getStatus().equals(hazard.getStatus())) {
            throw exception(SAFETY_HAZARD_STATUS_ERROR);
        }
        var rechecker = refResolver.requireEmployee(reqVO.getRecheckerId(), SAFETY_EMPLOYEE_REQUIRED);
        // 用员工编号比较,比姓名比较可靠(同名不同人 / 改名都不会误判)
        if (hazard.getAssigneeId() != null && hazard.getAssigneeId().equals(rechecker.getId())) {
            throw exception(SAFETY_HAZARD_SAME_OPERATOR);
        }
        boolean passed = "PASSED".equals(reqVO.getRecheckResult());
        SafetyHazardDO update = new SafetyHazardDO();
        update.setId(hazard.getId());
        update.setRecheckerId(rechecker.getId());
        update.setRechecker(rechecker.getName());
        update.setRecheckTime(LocalDateTime.now());
        update.setRecheckResult(reqVO.getRecheckResult());
        update.setRecheckRemark(reqVO.getRecheckRemark());
        if (passed) {
            update.setStatus(SafetyHazardStatusEnum.CLOSED.getStatus());
            update.setCloseTime(LocalDateTime.now());
        } else {
            update.setStatus(SafetyHazardStatusEnum.REJECTED.getStatus());
        }
        hazardMapper.updateById(update);
    }
 
    public void deleteHazard(Long id) {
        validateHazardExists(id);
        hazardMapper.deleteById(id);
    }
 
    public SafetyHazardDO getHazard(Long id) {
        return validateHazardExists(id);
    }
 
    public PageResult<SafetyHazardDO> getHazardPage(SafetyHazardPageReqVO reqVO) {
        reqVO.normalizeTimeRange();
        return hazardMapper.selectPage(reqVO);
    }
 
    /**
     * 批量回填上报人 / 整改责任人 / 复查人的最新姓名
     */
    public void fillPersonNames(List<SafetyHazardDO> hazards) {
        if (CollUtil.isEmpty(hazards)) {
            return;
        }
        var ids = new java.util.ArrayList<Long>(hazards.size() * 3);
        hazards.forEach(h -> { ids.add(h.getReporterId()); ids.add(h.getAssigneeId()); ids.add(h.getRecheckerId()); });
        var employeeMap = refResolver.employeeMap(ids);
        hazards.forEach(hazard -> {
            var reporter = employeeMap.get(hazard.getReporterId());
            if (reporter != null) hazard.setReporter(reporter.getName());
            var assignee = employeeMap.get(hazard.getAssigneeId());
            if (assignee != null) hazard.setAssignee(assignee.getName());
            var rechecker = employeeMap.get(hazard.getRecheckerId());
            if (rechecker != null) hazard.setRechecker(rechecker.getName());
        });
    }
 
    /**
     * 展示字段补全(等级/来源/状态名称、超期标记)
     */
    public static void fillHazardDisplay(SafetyHazardDO hazard, SafetyHazardRespVO respVO) {
        respVO.setLevelName(SafetyHazardLevelEnum.getNameByLevel(hazard.getLevel()));
        respVO.setSourceName(SafetyHazardSourceEnum.getNameBySource(hazard.getSource()));
        respVO.setStatusName(SafetyHazardStatusEnum.getNameByStatus(hazard.getStatus()));
        respVO.setRecheckResultName(switch (StrUtil.blankToDefault(hazard.getRecheckResult(), "")) {
            case "PASSED" -> "通过";
            case "REJECTED" -> "不通过";
            default -> "";
        });
        boolean overdue = hazard.getRectifyDeadline() != null
                && hazard.getRectifyDeadline().isBefore(LocalDate.now())
                && !SafetyHazardStatusEnum.isFinished(hazard.getStatus());
        respVO.setOverdue(overdue);
    }
 
    private SafetyHazardDO validateHazardExists(Long id) {
        SafetyHazardDO hazard = id == null ? null : hazardMapper.selectById(id);
        if (hazard == null) {
            throw exception(SAFETY_HAZARD_NOT_EXISTS);
        }
        return hazard;
    }
 
}