diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/inspection/InspectionAttributionService.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/inspection/InspectionAttributionService.java new file mode 100644 index 0000000..52bd52c --- /dev/null +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/inspection/InspectionAttributionService.java @@ -0,0 +1,21 @@ +package com.viewsh.module.ops.environment.service.inspection; + +/** + * 巡检归属判定 Service 接口 + * + * 当巡检结果为不合格时,异步执行归属判定: + * 1. 回溯该区域最近一个 COMPLETED 工单 + * 2. 比较保洁员实际停留时长与区域标准时长 + * 3. 判定责任归属并回写巡检记录 + */ +public interface InspectionAttributionService { + + /** + * 执行归属判定 + * + * @param recordId 巡检记录ID + * @param areaId 区域ID + */ + void determineAttribution(Long recordId, Long areaId); + +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/inspection/InspectionAttributionServiceImpl.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/inspection/InspectionAttributionServiceImpl.java new file mode 100644 index 0000000..e185b3d --- /dev/null +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/inspection/InspectionAttributionServiceImpl.java @@ -0,0 +1,89 @@ +package com.viewsh.module.ops.environment.service.inspection; + +import com.viewsh.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.viewsh.module.ops.dal.dataobject.area.OpsBusAreaDO; +import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; +import com.viewsh.module.ops.dal.mysql.area.OpsBusAreaMapper; +import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; +import com.viewsh.module.ops.environment.dal.dataobject.inspection.OpsInspectionRecordDO; +import com.viewsh.module.ops.environment.dal.mysql.inspection.OpsInspectionRecordMapper; +import com.viewsh.module.ops.enums.WorkOrderStatusEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +/** + * 巡检归属判定 Service 实现 + */ +@Service +@Validated +@Slf4j +public class InspectionAttributionServiceImpl implements InspectionAttributionService { + + /** 归属判定:个人责任(停留时长不足) */ + private static final int ATTRIBUTION_PERSONAL = 1; + /** 归属判定:突发状况(停留时长达标) */ + private static final int ATTRIBUTION_EMERGENCY = 2; + + @Resource + private OpsInspectionRecordMapper inspectionRecordMapper; + + @Resource + private OpsOrderMapper opsOrderMapper; + + @Resource + private OpsBusAreaMapper opsBusAreaMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public void determineAttribution(Long recordId, Long areaId) { + // 1. 回溯该区域最近一个 COMPLETED 工单 + OpsOrderDO lastOrder = opsOrderMapper.selectOne(new LambdaQueryWrapperX() + .eq(OpsOrderDO::getAreaId, areaId) + .eq(OpsOrderDO::getStatus, WorkOrderStatusEnum.COMPLETED.getStatus()) + .orderByDesc(OpsOrderDO::getEndTime) + .last("LIMIT 1")); + + if (lastOrder == null) { + log.warn("[determineAttribution] 区域 {} 无已完成工单,跳过归属判定: recordId={}", areaId, recordId); + return; + } + + // 2. 获取保洁员实际停留时长(秒) + Integer stayDurationSeconds = lastOrder.getCompletionSeconds(); + if (stayDurationSeconds == null) { + stayDurationSeconds = 0; + } + + // 3. 获取区域标准时长(分钟 → 秒) + OpsBusAreaDO area = opsBusAreaMapper.selectById(areaId); + if (area == null || area.getStandardDuration() == null) { + log.warn("[determineAttribution] 区域 {} 无标准时长配置,跳过归属判定: recordId={}", areaId, recordId); + return; + } + int standardDurationSeconds = area.getStandardDuration() * 60; + + // 4. 判定归属 + // T_stay >= clean_threshold → 突发状况(保洁员已做到位,不扣分) + // T_stay < clean_threshold → 个人责任(保洁时长不足,扣信用分) + int attributionResult = stayDurationSeconds >= standardDurationSeconds + ? ATTRIBUTION_EMERGENCY + : ATTRIBUTION_PERSONAL; + + log.info("[determineAttribution] 归属判定完成: recordId={}, areaId={}, lastOrderId={}, " + + "stayDuration={}s, standardDuration={}s, result={}", + recordId, areaId, lastOrder.getId(), + stayDurationSeconds, standardDurationSeconds, attributionResult); + + // 5. 回写巡检记录 + OpsInspectionRecordDO update = new OpsInspectionRecordDO(); + update.setId(recordId); + update.setLastOrderId(lastOrder.getId()); + update.setStayDuration(stayDurationSeconds); + update.setAttributionResult(attributionResult); + inspectionRecordMapper.updateById(update); + } + +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/inspection/InspectionRecordServiceImpl.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/inspection/InspectionRecordServiceImpl.java index 422d558..454867b 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/inspection/InspectionRecordServiceImpl.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/inspection/InspectionRecordServiceImpl.java @@ -34,6 +34,9 @@ public class InspectionRecordServiceImpl implements InspectionRecordService { @Resource private OpsInspectionRecordItemMapper inspectionRecordItemMapper; + @Resource + private InspectionAttributionService inspectionAttributionService; + @Override @Transactional(rollbackFor = Exception.class) public Long submitInspection(InspectionSubmitReqVO submitReqVO, Long inspectorId) { @@ -72,12 +75,15 @@ public class InspectionRecordServiceImpl implements InspectionRecordService { } /** - * 异步触发归属判定(Task 6 实现完整逻辑) + * 异步触发归属判定 */ @Async public void triggerAttributionAsync(Long recordId, Long areaId) { - log.info("[triggerAttributionAsync] 巡检不合格,触发归属判定: recordId={}, areaId={}", recordId, areaId); - // TODO: Task 6 实现 InspectionAttributionService 归属判定逻辑 + try { + inspectionAttributionService.determineAttribution(recordId, areaId); + } catch (Exception e) { + log.error("[triggerAttributionAsync] 归属判定异常: recordId={}, areaId={}", recordId, areaId, e); + } } }