|
|
@@ -1,12 +1,27 @@
|
|
|
package cn.iocoder.yudao.module.system.job;
|
|
|
|
|
|
import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
|
|
|
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
|
|
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
|
|
+import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
|
|
+import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
|
|
|
+import cn.iocoder.yudao.module.system.controller.admin.nursing.vo.HighFrequencyPlanItemVO;
|
|
|
+import cn.iocoder.yudao.module.system.dal.dataobject.elderly.ElderlyNursingLogDO;
|
|
|
+import cn.iocoder.yudao.module.system.dal.mysql.biz.NursingPlanItemMapper;
|
|
|
+import cn.iocoder.yudao.module.system.dal.mysql.elderly.ElderlyNursingLogMapper;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.Duration;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
* 每天生成护理日志
|
|
|
@@ -15,14 +30,180 @@ import java.util.List;
|
|
|
@Slf4j
|
|
|
public class NurseLogSyncJob implements JobHandler {
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private ElderlyNursingLogMapper elderlyNursingLogMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private NursingPlanItemMapper nursingPlanItemMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前机构(tenantId)下,高频护理计划明细(frequency_category_type=1 且护理项目启用)列表。
|
|
|
+ *
|
|
|
+ * 返回字段来自:elderly_nursing_plan_item + nurse_item(例如 timeout)。
|
|
|
+ */
|
|
|
+ public List<HighFrequencyPlanItemVO> getCurrentOrgHighFrequencyNursingPlanItems() {
|
|
|
+ Long tenantId = TenantContextHolder.getTenantId();
|
|
|
+ if (tenantId == null) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ return nursingPlanItemMapper.selectHighFrequencyPlanItems(tenantId, LocalDateTime.now().toLocalDate());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据护理明细的 frequencyType + frequency,计算“最小生成间隔”。
|
|
|
+ *
|
|
|
+ * 规则:在一个自然周期(天/周/月/年)内,期望生成 frequency 次,则间隔约等于 周期时长 / frequency。
|
|
|
+ * 这里使用向上取整(ceil)避免生成过于频繁。
|
|
|
+ *
|
|
|
+ * frequencyType:
|
|
|
+ * 1:次/日;2:次/周;3:次/月;4:次/年;5:时/次
|
|
|
+ */
|
|
|
+ private Duration calculateInterval(BigDecimal frequency, Integer frequencyType, LocalDateTime now) {
|
|
|
+ if (frequency == null || frequency.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (frequencyType == null) {
|
|
|
+ return Duration.ofDays(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // frequencyType=5(时/次)语义不同:frequency 表示“每次间隔多少小时”,例如 frequency=2 => 间隔 2h
|
|
|
+ if (frequencyType == 5) {
|
|
|
+ long intervalSeconds = (long) Math.ceil(frequency.doubleValue() * Duration.ofHours(1).getSeconds());
|
|
|
+ intervalSeconds = Math.max(1L, intervalSeconds);
|
|
|
+ return Duration.ofSeconds(intervalSeconds);
|
|
|
+ }
|
|
|
+
|
|
|
+ long secondsPerPeriod;
|
|
|
+ switch (frequencyType) {
|
|
|
+ case 1:
|
|
|
+ secondsPerPeriod = Duration.ofDays(1).getSeconds();
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ secondsPerPeriod = Duration.ofDays(7).getSeconds();
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ secondsPerPeriod = Duration.ofDays(now.toLocalDate().lengthOfMonth()).getSeconds();
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ secondsPerPeriod = Duration.ofDays(now.toLocalDate().lengthOfYear()).getSeconds();
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ secondsPerPeriod = Duration.ofDays(1).getSeconds();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ double freq = frequency.doubleValue();
|
|
|
+ if (freq <= 0D) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ long intervalSeconds = (long) Math.ceil(secondsPerPeriod / freq);
|
|
|
+ intervalSeconds = Math.max(1L, intervalSeconds);
|
|
|
+ return Duration.ofSeconds(intervalSeconds);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将已超时但仍处于“待完成”的护理日志,更新为“未完成”状态。
|
|
|
+ *
|
|
|
+ * 判定标准:timeout 不为空 且 timeout 早于当前时间。
|
|
|
+ */
|
|
|
+ private int markOverdueLogsAsUnfinished(Long tenantId, LocalDateTime now) {
|
|
|
+ List<ElderlyNursingLogDO> overdueLogs = elderlyNursingLogMapper.selectList(
|
|
|
+ new LambdaQueryWrapperX<ElderlyNursingLogDO>()
|
|
|
+ .eq(ElderlyNursingLogDO::getTenantId, tenantId)
|
|
|
+ .eq(ElderlyNursingLogDO::getStatus, 0)
|
|
|
+ .isNotNull(ElderlyNursingLogDO::getTimeout)
|
|
|
+ .lt(ElderlyNursingLogDO::getTimeout, now)
|
|
|
+ .orderByDesc(ElderlyNursingLogDO::getId)
|
|
|
+ );
|
|
|
+ if (overdueLogs.isEmpty()) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ int updated = 0;
|
|
|
+ for (ElderlyNursingLogDO logDO : overdueLogs) {
|
|
|
+ logDO.setStatus(2);
|
|
|
+ elderlyNursingLogMapper.updateById(logDO);
|
|
|
+ updated++;
|
|
|
+ }
|
|
|
+ return updated;
|
|
|
+ }
|
|
|
|
|
|
|
|
|
@Override
|
|
|
- @TenantIgnore
|
|
|
+ @TenantJob
|
|
|
public String execute(String param) throws Exception {
|
|
|
- log.info("护理日志记录定时器开始执行..........................");
|
|
|
- // NursingLog 模块已移除,跳过创建护理日志
|
|
|
+ Long tenantId = TenantContextHolder.getTenantId();
|
|
|
+ log.info("机构:{},护理日志记录定时器开始执行..........................",tenantId);
|
|
|
+
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+
|
|
|
+ // 1) 先刷新历史日志状态:超时且仍待完成 => 未完成
|
|
|
+ int overdueUpdated = markOverdueLogsAsUnfinished(tenantId, now);
|
|
|
+
|
|
|
+ // 2) 获取本机构高频护理明细,用于按频率生成护理日志
|
|
|
+ List<HighFrequencyPlanItemVO> items = getCurrentOrgHighFrequencyNursingPlanItems();
|
|
|
+ if (items.isEmpty()) {
|
|
|
+ log.info("护理日志记录定时器执行结束..........................");
|
|
|
+ return String.format("租户{}无高频护理项目需要生成,更新未完成{}条", tenantId, overdueUpdated);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 去重:同一个 nursingPlanItemId 只保留一条明细(避免重复生成)
|
|
|
+ Map<Long, HighFrequencyPlanItemVO> itemByPlanItemId = items.stream()
|
|
|
+ .filter(i -> i.getNursingPlanItemId() != null && i.getElderId() != null)
|
|
|
+ .collect(Collectors.toMap(HighFrequencyPlanItemVO::getNursingPlanItemId, i -> i, (a, b) -> a));
|
|
|
+ if (itemByPlanItemId.isEmpty()) {
|
|
|
+ log.info("护理日志记录定时器执行结束..........................");
|
|
|
+ return String.format("租户{}今日无有效护理明细需要生成,更新未完成{}条", tenantId, overdueUpdated);
|
|
|
+ }
|
|
|
+ List<Long> nursingPlanItemIds = new ArrayList<>(itemByPlanItemId.keySet());
|
|
|
+
|
|
|
+ // 3) 查询每个护理明细最近一次生成的日志 createTime
|
|
|
+ // 如果最新记录仍在“频率允许间隔”内,则不需要再生成
|
|
|
+ Map<Long, LocalDateTime> latestCreateTimeByPlanItemId = elderlyNursingLogMapper
|
|
|
+ .selectLatestCreateTimeByPlanItemIds(tenantId, nursingPlanItemIds)
|
|
|
+ .stream()
|
|
|
+ .filter(r -> r.getNursingPlanItemId() != null)
|
|
|
+ .collect(Collectors.toMap(ElderlyNursingLogMapper.LatestCreateTimeDTO::getNursingPlanItemId,
|
|
|
+ ElderlyNursingLogMapper.LatestCreateTimeDTO::getCreateTime, (a, b) -> a));
|
|
|
+
|
|
|
+ List<ElderlyNursingLogDO> toInsert = new ArrayList<>();
|
|
|
+ for (HighFrequencyPlanItemVO item : itemByPlanItemId.values()) {
|
|
|
+ // beginTime 未到,跳过生成
|
|
|
+ if (item.getBeginTime() != null && now.isBefore(item.getBeginTime())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Duration interval = calculateInterval(item.getFrequency(), item.getFrequencyType(), now);
|
|
|
+ if (interval == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDateTime latest = latestCreateTimeByPlanItemId.get(item.getNursingPlanItemId());
|
|
|
+ // 最新生成时间仍在间隔内:不生成
|
|
|
+ if (latest != null && !now.isAfter(latest.plus(interval))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成一条新的护理日志:
|
|
|
+ // - timeout 由护理项目的 timeout 推算(单位:小时)
|
|
|
+ LocalDateTime timeoutTime = null;
|
|
|
+ if (item.getTimeout() != null && item.getTimeout() > 0) {
|
|
|
+ timeoutTime = now.plusHours(item.getTimeout());
|
|
|
+ }
|
|
|
+
|
|
|
+ toInsert.add(ElderlyNursingLogDO.builder()
|
|
|
+ .nursingPlanItemId(item.getNursingPlanItemId())
|
|
|
+ .elderId(item.getElderId())
|
|
|
+ .tenantId(tenantId)
|
|
|
+ .timeout(timeoutTime)
|
|
|
+ .status(0)
|
|
|
+ .build());
|
|
|
+ }
|
|
|
+
|
|
|
+ int inserted = 0;
|
|
|
+ for (ElderlyNursingLogDO logDO : toInsert) {
|
|
|
+ elderlyNursingLogMapper.insert(logDO);
|
|
|
+ inserted++;
|
|
|
+ }
|
|
|
log.info("护理日志记录定时器执行结束..........................");
|
|
|
- return null;
|
|
|
+ return String.format("租户{}新增护理日志{}条,更新未完成{}条", tenantId, inserted, overdueUpdated);
|
|
|
}
|
|
|
}
|