feat(ops): 巡检归属判定异步服务(Task 6)

- InspectionAttributionService + Impl: 归属判定核心逻辑
  - 回溯区域最近 COMPLETED 工单,获取 completionSeconds
  - 与 ops_bus_area.standardDuration 比较判定责任归属
  - T_stay >= threshold → 突发状况(2),< threshold → 个人责任(1)
  - 判定结果回写 inspection_record(lastOrderId, stayDuration, attributionResult)
- InspectionRecordServiceImpl: 注入 AttributionService,异步调用含异常兜底

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-03-05 19:18:56 +08:00
parent e4dde8dbc1
commit 3120b1911d
3 changed files with 119 additions and 3 deletions

View File

@@ -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);
}

View File

@@ -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<OpsOrderDO>()
.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);
}
}

View File

@@ -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);
}
}
}