ruoyi-admin/src/main/java/com/ruoyi/web/component/RedisMqReceiver.java
@@ -56,6 +56,7 @@ import com.ruoyi.task.domain.SendMagParam; import com.ruoyi.task.service.IServiceSubtaskRecordService; import com.ruoyi.task.mapper.ServiceSubtaskPreachformMapper; import com.ruoyi.task.event.FollowupCompletedEventPublisher; import com.ruoyi.patarchive.mapper.PatArchiveMapper; @Slf4j @Component//监听此队列 @@ -72,6 +73,9 @@ @Value("${req_path}") private String req_path; @Autowired private FollowupCompletedEventPublisher followupCompletedEventPublisher; @Autowired private IServiceOutPathService iServiceOutPathService; @@ -898,6 +902,9 @@ serviceSubtask.setSendstate(6L); } serviceSubtaskMapper.updateServiceSubtask(serviceSubtask); if (Long.valueOf(6L).equals(serviceSubtask.getSendstate())) { followupCompletedEventPublisher.publish(serviceSubtask.getId()); } // 更新当前发送方式明细;发送成功时允许当前行已经是 sendstate=2。 currentPreachformRow.setSendstate(failSendstate); ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/RyTask.java
@@ -149,6 +149,12 @@ private ICollectHISService ichService; @Autowired private IPatSpecificArchiveGenerateService patSpecificArchiveGenerateService; @Autowired private com.ruoyi.task.event.FollowupCompletedEventPublisher followupCompletedEventPublisher; @Autowired private ISysConfigService configService; @Autowired @@ -1203,6 +1209,7 @@ if (finishState) { serviceSubtask.setSendstate(6L); serviceSubtaskMapper.updateServiceSubtask(serviceSubtask); followupCompletedEventPublisher.publish(serviceSubtask.getId()); continue; } @@ -1715,6 +1722,7 @@ //说明,在某种方式发送失败后,又成功了(例如短信或公众号,在规定时间外患者做完了,这个时候,状态改成了9) serviceSubtask.setSendstate(6L); serviceSubtaskMapper.updateServiceSubtask(serviceSubtask); followupCompletedEventPublisher.publish(serviceSubtask.getId()); return "-1"; } // 当sort等于列表size时,并且所有sendstate都不等于9,则返回-1 @@ -1942,6 +1950,9 @@ serviceSubtask.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark()) ? serviceSubtask.getRemark() + "," + remark : remark); log.info("serviceSubtask的入参是:{}",serviceSubtask); serviceSubtaskMapper.updateServiceSubtask(serviceSubtask); if (Long.valueOf(6L).equals(serviceSubtask.getSendstate())) { followupCompletedEventPublisher.publish(serviceSubtask.getId()); } return true; } @@ -2197,4 +2208,18 @@ } } /** * 住院随访完成生成专科档案补偿任务。 * * <p>扫描近 7 天已完成但未生成日志的住院随访,按配置补生成妇科/儿科档案。</p> */ public void generateInhospFollowupArchive() { try { int generated = patSpecificArchiveGenerateService.compensateInhospFollowups(); log.info("【generateInhospFollowupArchive】住院随访专科档案补偿完成,生成数量={}", generated); } catch (Exception e) { log.error("【generateInhospFollowupArchive】住院随访专科档案补偿失败", e); } } } ruoyi-task/src/main/java/com/ruoyi/task/api/TaskSubtaskApi.java
@@ -53,6 +53,19 @@ List<ServiceSubtask> findSubtasks(ServiceSubtaskEntity query); /** * 查询已完成、指定机构/服务类型、完成时间在 startTime 之后的住院随访子任务。 * 供住院随访档案补偿任务使用。 * * @param orgid 机构ID * @param serviceTypes 服务类型白名单 * @param startTime 完成时间下限 * @param limit 最大条数 * @return 子任务列表 */ List<ServiceSubtask> findCompletedInhospFollowups(String orgid, List<String> serviceTypes, java.util.Date startTime, int limit); /** * 更新子任务(原 {@code ServiceSubtaskMapper#updateServiceSubtask},动态 SQL 只改非空字段) * * @param subtask 待更新对象(必须带 id) ruoyi-task/src/main/java/com/ruoyi/task/api/contract/TaskSubtaskApiImpl.java
@@ -12,6 +12,7 @@ import com.ruoyi.task.mapper.ServiceSubtaskMapper; import com.ruoyi.task.service.IServiceSubtaskDetailTraceService; import com.ruoyi.task.service.IServiceTaskService; import com.ruoyi.task.event.FollowupCompletedEventPublisher; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -49,6 +50,9 @@ @Autowired private IServiceSubtaskDetailTraceService traceService; @Autowired private FollowupCompletedEventPublisher followupCompletedEventPublisher; @Override public ServiceSubtask getSubtask(Long subId) { return serviceSubtaskMapper.selectServiceSubtaskById(subId); @@ -60,8 +64,18 @@ } @Override public List<ServiceSubtask> findCompletedInhospFollowups(String orgid, List<String> serviceTypes, java.util.Date startTime, int limit) { return serviceSubtaskMapper.selectCompletedInhospFollowups(orgid, serviceTypes, startTime, limit); } @Override public int updateSubtask(ServiceSubtask subtask) { Boolean updated = serviceSubtaskMapper.updateServiceSubtask(subtask); if (Boolean.TRUE.equals(updated) && subtask.getSendstate() != null && subtask.getSendstate() == 6 && subtask.getId() != null) { followupCompletedEventPublisher.publish(subtask.getId()); } return Boolean.TRUE.equals(updated) ? 1 : 0; } ruoyi-task/src/main/java/com/ruoyi/task/mapper/ServiceSubtaskMapper.java
@@ -48,6 +48,20 @@ public List<ServiceSubtask> selectServiceSubtaskList(ServiceSubtaskEntity serviceSubtaskVO); /** * 查询已完成、且尚未被专科档案生成日志处理的住院随访子任务(补偿扫描用)。 * * @param orgid 机构ID * @param serviceTypes 服务类型白名单 * @param startTime 完成时间下限 * @param limit 最大条数 * @return 子任务集合 */ public List<ServiceSubtask> selectCompletedInhospFollowups(@Param("orgid") String orgid, @Param("serviceTypes") List<String> serviceTypes, @Param("startTime") Date startTime, @Param("limit") int limit); /** * 分页第一阶段:只取当前页的子任务 id。 * <p> * where / order 与 {@link #selectServiceSubtaskList} 共用同一份 XML 片段 ruoyi-task/src/main/java/com/ruoyi/task/service/impl/ServiceSubtaskServiceImpl.java
@@ -126,6 +126,7 @@ import com.ruoyi.system.service.ISysDeptService; import com.ruoyi.system.mapper.ext.SysDept2Mapper; import com.ruoyi.task.api.contract.OperationSubtaskWriter; import com.ruoyi.task.event.FollowupCompletedEventPublisher; /** * 单一任务(随访)Service业务层处理 @@ -145,6 +146,9 @@ */ @Autowired private OperationSubtaskWriter operationSubtaskWriter; @Autowired private FollowupCompletedEventPublisher followupCompletedEventPublisher; /** * 满意度模块对外门面(常驻 Bean)。 @@ -1544,7 +1548,12 @@ if (serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 6) serviceSubtask.setFinishtime(new Date()); return serviceSubtaskMapper.updateServiceSubtask(serviceSubtask); Boolean updated = serviceSubtaskMapper.updateServiceSubtask(serviceSubtask); if (Boolean.TRUE.equals(updated) && serviceSubtask.getSendstate() != null && serviceSubtask.getSendstate() == 6 && serviceSubtask.getId() != null) { followupCompletedEventPublisher.publish(serviceSubtask.getId()); } return updated; } @Override @@ -4817,6 +4826,7 @@ serviceSubtask.setSendstate(6L); serviceSubtask.setPreachform("1"); serviceSubtaskMapper.updateServiceSubtask(serviceSubtask); followupCompletedEventPublisher.publish(serviceSubtask.getId()); } } @@ -4961,6 +4971,7 @@ if (subtaskUpdated == 0) { throw new BaseException("微信公众号默认完成失败:子任务状态已变化,subid=" + current.getId()); } followupCompletedEventPublisher.publish(current.getId()); log.info("【微信公众号默认完成】subid:{},已按默认选项完成,填充题目数:{}", current.getId(), filled); return true; ruoyi-task/src/main/resources/mapper/task/ServiceSubtaskMapper.xml
@@ -1100,6 +1100,23 @@ where id = #{id} </select> <select id="selectCompletedInhospFollowups" resultMap="ServiceSubtaskResult"> <include refid="selectServiceSubtaskVo"/> where del_flag = '0' and sendstate = 6 and hosp_type in ('2', '6') and orgid = #{orgid} and finishtime >= #{startTime} <if test="serviceTypes != null and serviceTypes.size() > 0"> and service_type in <foreach collection="serviceTypes" item="serviceType" open="(" separator="," close=")"> #{serviceType} </foreach> </if> order by id asc limit #{limit} </select> <select id="selectServiceSubtaskByDiagname" parameterType="com.ruoyi.task.domain.ServiceSubtask" resultMap="ServiceSubtaskResult"> select sendstate,starttime,endtime,nurse_name,phone,sex,task_name,sendname,leavediagname,visit_time FROM smartor/src/main/java/com/smartor/common/SpecificArchiveConstants.java
@@ -16,6 +16,36 @@ */ public static final String DEFAULT_CAMPUS_ID = "1"; /** 来源类型:门诊接诊完成 */ public static final String SOURCE_TYPE_OUTHOSP = "1"; /** 来源类型:住院随访完成 */ public static final String SOURCE_TYPE_INHOSP_FOLLOWUP = "2"; /** 来源类型:手工新增/手工导入 */ public static final String SOURCE_TYPE_MANUAL = "3"; /** 部门分类:妇科 */ public static final String DEPT_CATEGORY_GYNECOLOGY = "1"; /** 部门分类:儿科 */ public static final String DEPT_CATEGORY_PEDIATRICS = "2"; /** 生成结果:已生成 */ public static final String RESULT_GENERATED = "1"; /** 生成结果:患者已有对应档案,跳过 */ public static final String RESULT_EXISTS = "2"; /** 生成结果:未命中配置 */ public static final String RESULT_NO_MATCH = "3"; /** 生成结果:失败 */ public static final String RESULT_FAILED = "4"; /** 住院随访来源的默认配置ID(未命中配置时写日志用) */ public static final int NO_CONFIG_ID = 0; private SpecificArchiveConstants() { } } smartor/src/main/java/com/smartor/domain/PatChildArchive.java
@@ -30,6 +30,23 @@ @ApiModelProperty(value = "儿科档案类型: 1 营养性疾病2 学习困难3 高危儿专案") private String archiveType; /** 来源类型:1门诊接诊 2住院随访 3手工新增 */ @Excel(name = "来源类型") @ApiModelProperty(value = "来源类型:1门诊接诊 2住院随访 3手工新增") private String sourceType; /** 来源业务ID:门诊记录ID/住院记录ID */ @ApiModelProperty(value = "来源业务ID:门诊记录ID/住院记录ID") private Long sourceBizId; /** 来源业务编号:门诊号/住院号 */ @ApiModelProperty(value = "来源业务编号:门诊号/住院号") private String sourceBizNo; /** 来源随访子任务ID:住院随访生成时记录触发档案生成的 service_subtask.id */ @ApiModelProperty(value = "来源随访子任务ID:住院随访生成时记录触发档案生成的 service_subtask.id") private Long sourceSubId; /** 门诊编号 */ @Excel(name = "门诊编号") @ApiModelProperty(value = "门诊编号") smartor/src/main/java/com/smartor/domain/PatChildArchiveDetail.java
@@ -35,11 +35,23 @@ @ApiModelProperty(value = "档案类型: 1 营养性疾病2 学习困难3 高危儿专案") private String archiveType; /** 来源类型: 1 门诊记录 */ /** 来源类型:1门诊接诊 2住院随访 3手工新增 */ @Excel(name = "来源类型") @ApiModelProperty(value = "来源类型: 1 门诊记录") @ApiModelProperty(value = "来源类型:1门诊接诊 2住院随访 3手工新增") private String sourceType; /** 来源业务ID:门诊记录ID/住院记录ID */ @ApiModelProperty(value = "来源业务ID:门诊记录ID/住院记录ID") private Long sourceBizId; /** 来源业务编号:门诊号/住院号 */ @ApiModelProperty(value = "来源业务编号:门诊号/住院号") private String sourceBizNo; /** 来源随访子任务ID:住院随访生成时记录触发档案生成的 service_subtask.id */ @ApiModelProperty(value = "来源随访子任务ID:住院随访生成时记录触发档案生成的 service_subtask.id") private Long sourceSubId; /** 门诊编号 */ @Excel(name = "门诊编号") @ApiModelProperty(value = "门诊编号") smartor/src/main/java/com/smartor/domain/PatMaternityArchive.java
@@ -29,6 +29,23 @@ @ApiModelProperty(value = "妇幼档案类型: 1 早孕关爱2 围绝经期MHI") private String archiveType; /** 来源类型:1门诊接诊 2住院随访 3手工新增 */ @Excel(name = "来源类型") @ApiModelProperty(value = "来源类型:1门诊接诊 2住院随访 3手工新增") private String sourceType; /** 来源业务ID:门诊记录ID/住院记录ID */ @ApiModelProperty(value = "来源业务ID:门诊记录ID/住院记录ID") private Long sourceBizId; /** 来源业务编号:门诊号/住院号 */ @ApiModelProperty(value = "来源业务编号:门诊号/住院号") private String sourceBizNo; /** 来源随访子任务ID:住院随访生成时记录触发档案生成的 service_subtask.id */ @ApiModelProperty(value = "来源随访子任务ID:住院随访生成时记录触发档案生成的 service_subtask.id") private Long sourceSubId; /** 门诊编号 */ @Excel(name = "门诊编号") @ApiModelProperty(value = "门诊编号") smartor/src/main/java/com/smartor/domain/PatMaternityArchiveDetail.java
@@ -34,11 +34,23 @@ @ApiModelProperty(value = "档案类型: 1 早孕关爱2 围绝经期MHI") private String archiveType; /** 来源类型: 1 门诊记录 */ /** 来源类型:1门诊接诊 2住院随访 3手工新增 */ @Excel(name = "来源类型") @ApiModelProperty(value = "来源类型: 1 门诊记录") @ApiModelProperty(value = "来源类型:1门诊接诊 2住院随访 3手工新增") private String sourceType; /** 来源业务ID:门诊记录ID/住院记录ID */ @ApiModelProperty(value = "来源业务ID:门诊记录ID/住院记录ID") private Long sourceBizId; /** 来源业务编号:门诊号/住院号 */ @ApiModelProperty(value = "来源业务编号:门诊号/住院号") private String sourceBizNo; /** 来源随访子任务ID:住院随访生成时记录触发档案生成的 service_subtask.id */ @ApiModelProperty(value = "来源随访子任务ID:住院随访生成时记录触发档案生成的 service_subtask.id") private Long sourceSubId; /** 门诊编号 */ @Excel(name = "门诊编号") @ApiModelProperty(value = "门诊编号") smartor/src/main/java/com/smartor/service/IPatSpecificArchiveGenerateService.java
@@ -19,4 +19,22 @@ * @return 本次生成/更新的档案数量 */ int generateByOuthosp(PatMedOuthosp patMedOuthosp, PatArchive patArchive); /** * 根据住院随访子任务生成专科档案。 * * <p>仅处理已完成的住院随访(hosp_type 2/6 + service_type 白名单), * 且仅对丽水市妇幼保健院生效。生成前按“患者 + 专科 + 档案类型”判断是否已有档案。</p> * * @param subId 随访子任务ID * @return 本次生成数量 */ int generateByInhospFollowup(Long subId); /** * 补偿扫描:查询近 7 天已完成但尚未处理的住院随访并尝试生成档案。 * * @return 本次生成数量 */ int compensateInhospFollowups(); } smartor/src/main/java/com/smartor/service/impl/PatChildArchiveServiceImpl.java
@@ -88,8 +88,9 @@ log.error("insertOrUpdateChildArchive入参为空,请检查入参"); throw new BaseException("儿科档案入参为空,请检查入参"); } if (StringUtils.isEmpty(patChildArchiveDetail.getOuthospno())) { throw new BaseException("儿科档案入参门诊编号outhospno为空,请检查入参门诊编号"); if (StringUtils.isEmpty(patChildArchiveDetail.getOuthospno()) && StringUtils.isEmpty(patChildArchiveDetail.getInhospno())) { throw new BaseException("儿科档案入参门诊编号outhospno和住院编号inhospno不能同时为空"); } if (StringUtils.isEmpty(patChildArchiveDetail.getArchiveType())) { throw new BaseException("儿科档案入参档案类型archiveType为空,请检查入参档案类型"); @@ -100,6 +101,7 @@ key.setOrgid(patChildArchiveDetail.getOrgid()); key.setPatid(patChildArchiveDetail.getPatid()); key.setOuthospno(patChildArchiveDetail.getOuthospno()); key.setInhospno(patChildArchiveDetail.getInhospno()); key.setArchiveType(patChildArchiveDetail.getArchiveType()); List<PatChildArchiveDetail> olds = patChildArchiveDetailService.selectPatChildArchiveDetailList(key); @@ -117,6 +119,7 @@ mainKey.setOrgid(patChildArchiveDetail.getOrgid()); mainKey.setPatid(patChildArchiveDetail.getPatid()); mainKey.setOuthospno(patChildArchiveDetail.getOuthospno()); mainKey.setInhospno(patChildArchiveDetail.getInhospno()); mainKey.setArchiveType(patChildArchiveDetail.getArchiveType()); List<PatChildArchive> mainOlds = patChildArchiveMapper.selectPatChildArchiveList(mainKey); @@ -133,6 +136,17 @@ } // 新增档案时补默认来源,更新时保留原来源 if (existArchiveId == null && patChildArchiveDetail.getId() == null) { if (StringUtils.isEmpty(patChildArchiveDetail.getSourceType())) { patChildArchiveDetail.setSourceType("3"); } if (StringUtils.isEmpty(patChildArchiveDetail.getSourceBizNo())) { patChildArchiveDetail.setSourceBizNo(StringUtils.isNotEmpty(patChildArchiveDetail.getOuthospno()) ? patChildArchiveDetail.getOuthospno() : patChildArchiveDetail.getInhospno()); } } // 将详情中的公共字段复制到主表对象 PatChildArchive patChildArchive = DtoConversionUtils.sourceToTarget(patChildArchiveDetail, PatChildArchive.class); if (patChildArchive == null) { smartor/src/main/java/com/smartor/service/impl/PatMaternityArchiveServiceImpl.java
@@ -99,13 +99,15 @@ patMaternityArchiveDetailVo.setOrgid(patMaternityArchiveDetail.getOrgid()); patMaternityArchiveDetailVo.setPatid(patMaternityArchiveDetail.getPatid()); patMaternityArchiveDetailVo.setOuthospno(patMaternityArchiveDetail.getOuthospno()); patMaternityArchiveDetailVo.setInhospno(patMaternityArchiveDetail.getInhospno()); patMaternityArchiveDetailVo.setArchiveType(patMaternityArchiveDetail.getArchiveType()); List<PatMaternityArchiveDetail> patMaternityArchiveDetailOlds = patMaternityArchiveDetailService.selectPatMaternityArchiveDetailList(patMaternityArchiveDetailVo); if (StringUtils.isEmpty(patMaternityArchiveDetail.getOuthospno())) { throw new BaseException("妇科档案入参门诊编号outhospno为空,请检查入参门诊编号"); if (StringUtils.isEmpty(patMaternityArchiveDetail.getOuthospno()) && StringUtils.isEmpty(patMaternityArchiveDetail.getInhospno())) { throw new BaseException("妇科档案入参门诊编号outhospno和住院编号inhospno不能同时为空"); } if (StringUtils.isEmpty(patMaternityArchiveDetail.getArchiveType())) { throw new BaseException("妇科档案入参档案类型archiveType为空,请检查入参档案类型"); @@ -116,6 +118,7 @@ key.setOrgid(patMaternityArchiveDetail.getOrgid()); key.setPatid(patMaternityArchiveDetail.getPatid()); key.setOuthospno(patMaternityArchiveDetail.getOuthospno()); key.setInhospno(patMaternityArchiveDetail.getInhospno()); key.setArchiveType(patMaternityArchiveDetail.getArchiveType()); List<PatMaternityArchiveDetail> olds = patMaternityArchiveDetailService.selectPatMaternityArchiveDetailList(key); @@ -133,6 +136,7 @@ mainKey.setOrgid(patMaternityArchiveDetail.getOrgid()); mainKey.setPatid(patMaternityArchiveDetail.getPatid()); mainKey.setOuthospno(patMaternityArchiveDetail.getOuthospno()); mainKey.setInhospno(patMaternityArchiveDetail.getInhospno()); mainKey.setArchiveType(patMaternityArchiveDetail.getArchiveType()); List<PatMaternityArchive> mainOlds = patMaternityArchiveMapper.selectPatMaternityArchiveList(mainKey); @@ -156,6 +160,17 @@ } } // 新增档案时补默认来源,更新时保留原来源 if (existArchiveId == null && patMaternityArchiveDetail.getId() == null) { if (StringUtils.isEmpty(patMaternityArchiveDetail.getSourceType())) { patMaternityArchiveDetail.setSourceType("3"); } if (StringUtils.isEmpty(patMaternityArchiveDetail.getSourceBizNo())) { patMaternityArchiveDetail.setSourceBizNo(StringUtils.isNotEmpty(patMaternityArchiveDetail.getOuthospno()) ? patMaternityArchiveDetail.getOuthospno() : patMaternityArchiveDetail.getInhospno()); } } // 将详情中的公共字段复制到主表对象 PatMaternityArchive patMaternityArchive = DtoConversionUtils.sourceToTarget(patMaternityArchiveDetail, PatMaternityArchive.class); if (patMaternityArchive == null) { smartor/src/main/java/com/smartor/service/impl/PatSpecificArchiveGenerateServiceImpl.java
@@ -3,20 +3,31 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.ruoyi.common.core.domain.entity.PatArchive; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.inhospvisit.api.InhospVisitFacade; import com.ruoyi.inhospvisit.domain.PatMedInhosp; import com.ruoyi.outpatientvisit.domain.PatMedOuthosp; import com.ruoyi.patarchive.domain.PatSpecificArchiveGenerateConfig; import com.ruoyi.patarchive.mapper.PatArchiveMapper; import com.ruoyi.patarchive.service.IPatSpecificArchiveGenerateConfigService; import com.ruoyi.task.api.TaskSubtaskApi; import com.ruoyi.task.domain.ServiceSubtask; import com.ruoyi.task.service.IServiceSubtaskService; import com.smartor.common.SpecificArchiveConstants; import com.smartor.domain.PatChildArchive; import com.smartor.domain.PatChildArchiveDetail; import com.smartor.domain.PatMaternityArchive; import com.smartor.domain.PatMaternityArchiveDetail; import com.smartor.domain.PatSpecificArchiveGenerateLog; import com.smartor.service.IPatChildArchiveService; import com.smartor.service.IPatMaternityArchiveService; import com.smartor.service.IPatSpecificArchiveGenerateLogService; import com.smartor.service.IPatSpecificArchiveGenerateService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -26,27 +37,37 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * 专科档案生成Service业务层处理。 * * <p>只处理丽水市妇幼保健院(orgid = 47226079133110211G1001)的门诊接诊完成记录, * 按 {@code pat_specific_archive_generate_config} 配置匹配科室和诊断后, * 生成对应类型的妇科档案或儿科档案。</p> * <p>只处理丽水市妇幼保健院(orgid = 47226079133110211G1001):</p> * <ul> * <li>门诊接诊完成:按科室/诊断匹配配置后生成妇科/儿科档案;</li> * <li>住院随访完成:按出院/在院科室、病区、诊断匹配配置后生成妇科/儿科档案, * 生成前按“患者 + 专科 + 档案类型”判断是否已有对应档案,已有则跳过。</li> * </ul> */ @Slf4j @Service public class PatSpecificArchiveGenerateServiceImpl implements IPatSpecificArchiveGenerateService { /** 部门分类:妇科 */ private static final String DEPT_CATEGORY_GYNECOLOGY = "1"; /** 部门分类:儿科 */ private static final String DEPT_CATEGORY_PEDIATRICS = "2"; /** 部门类型:科室 */ private static final String DEPT_TYPE_DEPARTMENT = "1"; /** 来源类型:门诊记录 */ private static final String SOURCE_TYPE_OUTHOSP = "1"; /** 部门类型:病区 */ private static final String DEPT_TYPE_WARD = "2"; /** 就诊类型:出院 */ private static final String HOSP_TYPE_DISCHARGE = "2"; /** 就诊类型:住院/入院 */ private static final String HOSP_TYPE_INHOSP = "6"; /** 住院随访完成状态 */ private static final Long SEND_STATE_FINISHED = 6L; /** 住院随访档案生成的最大重试次数 */ private static final int MAX_RETRY = 3; /** Redis 锁前缀 */ private static final String LOCK_PREFIX = "lock:archive:inhosp:"; @Autowired private IPatSpecificArchiveGenerateConfigService patSpecificArchiveGenerateConfigService; @@ -58,7 +79,32 @@ private IPatChildArchiveService patChildArchiveService; @Autowired private IPatSpecificArchiveGenerateLogService patSpecificArchiveGenerateLogService; @Autowired private PatArchiveMapper patArchiveMapper; @Autowired private IServiceSubtaskService serviceSubtaskService; @Autowired private InhospVisitFacade inhospVisitFacade; @Autowired private TaskSubtaskApi taskSubtaskApi; @Autowired private RedisCache redisCache; /** * 住院随访服务类型白名单,默认出院随访、专科随访、入院随访、手术随访。 */ @Value("${archive.inhosp.followup.service-types:2,13,18,19}") private String inhospFollowupServiceTypes; /** 住院随访生成专科档案开关,默认开启,便于按机构灰度。 */ @Value("${archive.inhosp.followup.enabled:true}") private boolean inhospFollowupEnabled; @Override public int generateByOuthosp(PatMedOuthosp patMedOuthosp, PatArchive patArchive) { @@ -86,7 +132,7 @@ } String campusid = StringUtils.isNotBlank(patMedOuthosp.getCampusid()) ? patMedOuthosp.getCampusid() : "1"; ? patMedOuthosp.getCampusid() : SpecificArchiveConstants.DEFAULT_CAMPUS_ID; List<PatSpecificArchiveGenerateConfig> configs = patSpecificArchiveGenerateConfigService.selectEffectiveConfigList(patMedOuthosp.getOrgid(), campusid); if (CollectionUtils.isEmpty(configs)) { @@ -99,7 +145,8 @@ Set<String> generatedKeys = new HashSet<>(); int generatedCount = 0; for (PatSpecificArchiveGenerateConfig config : configs) { if (!matchConfig(config, patMedOuthosp)) { if (!matchConfig(config, patMedOuthosp.getDeptcode(), null, patMedOuthosp.getIcd10code(), patMedOuthosp.getDiagname())) { continue; } String archiveType = config.getArchiveType(); @@ -116,12 +163,12 @@ } try { if (DEPT_CATEGORY_GYNECOLOGY.equals(deptCategory)) { if (SpecificArchiveConstants.DEPT_CATEGORY_GYNECOLOGY.equals(deptCategory)) { PatMaternityArchiveDetail detail = buildMaternityDetail(patMedOuthosp, targetArchive, config, campusid); patMaternityArchiveService.insertOrUpdateMaternityArchive(detail); generatedCount++; } else if (DEPT_CATEGORY_PEDIATRICS.equals(deptCategory)) { } else if (SpecificArchiveConstants.DEPT_CATEGORY_PEDIATRICS.equals(deptCategory)) { PatChildArchiveDetail detail = buildChildDetail(patMedOuthosp, targetArchive, config, campusid); patChildArchiveService.insertOrUpdateChildArchive(detail); @@ -138,12 +185,276 @@ return generatedCount; } @Override public int generateByInhospFollowup(Long subId) { if (!inhospFollowupEnabled || subId == null) { return 0; } ServiceSubtask subtask = serviceSubtaskService.selectServiceSubtaskById(subId); if (subtask == null || !SEND_STATE_FINISHED.equals(subtask.getSendstate())) { return 0; } if (!isInhospFollowup(subtask)) { return 0; } // 仅限丽水妇幼,机构为空时用住院记录/患者档案兜底 PatMedInhosp inhosp = resolveInhosp(subtask); PatArchive patArchive = resolvePatArchive(subtask.getPatid(), inhosp); String orgid = firstNotBlank(subtask.getOrgid(), inhosp == null ? null : inhosp.getOrgid(), patArchive == null ? null : patArchive.getOrgid()); if (!SpecificArchiveConstants.LISHUI_MCH_ORG_ID.equals(orgid)) { log.debug("住院随访生成专科档案跳过:非丽水妇幼机构,subId={},orgid={}", subId, orgid); return 0; } Long patid = firstNonNull(subtask.getPatid(), inhosp == null ? null : inhosp.getPatid(), patArchive == null ? null : patArchive.getId()); if (patid == null) { log.warn("住院随访生成专科档案跳过:未找到患者,subId={},inhospid={}", subId, subtask.getInhospid()); return 0; } if (patArchive == null) { patArchive = patArchiveMapper.selectPatArchiveByPatid(patid); } String campusid = firstNotBlank(subtask.getCampusid(), inhosp == null ? null : inhosp.getCampusid(), patArchive == null ? null : patArchive.getCampusid(), SpecificArchiveConstants.DEFAULT_CAMPUS_ID); boolean admission = HOSP_TYPE_INHOSP.equals(subtask.getHospType()); String deptCode = admission ? firstNotBlank(inhosp == null ? null : inhosp.getDeptcode(), subtask.getDeptcode(), subtask.getVisitDeptCode(), subtask.getLeaveldeptcode()) : firstNotBlank(inhosp == null ? null : inhosp.getLeaveldeptcode(), subtask.getLeaveldeptcode(), subtask.getVisitDeptCode(), inhosp == null ? null : inhosp.getDeptcode()); String deptName = admission ? firstNotBlank(inhosp == null ? null : inhosp.getDeptname(), subtask.getDeptname(), subtask.getVisitDeptName(), subtask.getLeaveldeptname()) : firstNotBlank(inhosp == null ? null : inhosp.getLeaveldeptname(), subtask.getLeaveldeptname(), subtask.getVisitDeptName(), inhosp == null ? null : inhosp.getDeptname()); String wardCode = admission ? firstNotBlank(inhosp == null ? null : inhosp.getHospitaldistrictcode(), subtask.getHospitaldistrictcode(), subtask.getLeavehospitaldistrictcode()) : firstNotBlank(inhosp == null ? null : inhosp.getLeavehospitaldistrictcode(), subtask.getLeavehospitaldistrictcode(), subtask.getHospitaldistrictcode()); String wardName = admission ? firstNotBlank(inhosp == null ? null : inhosp.getHospitaldistrictname(), subtask.getHospitaldistrictname(), subtask.getLeavehospitaldistrictname()) : firstNotBlank(inhosp == null ? null : inhosp.getLeavehospitaldistrictname(), subtask.getLeavehospitaldistrictname(), subtask.getHospitaldistrictname()); String icd10Code = admission ? firstNotBlank(inhosp == null ? null : inhosp.getIcd10code(), subtask.getLeaveicd10code()) : firstNotBlank(inhosp == null ? null : inhosp.getLeaveicd10code(), subtask.getLeaveicd10code(), inhosp == null ? null : inhosp.getIcd10code()); String diagName = admission ? firstNotBlank(inhosp == null ? null : inhosp.getDiagname(), subtask.getDiagname()) : firstNotBlank(inhosp == null ? null : inhosp.getLeavediagname(), subtask.getLeavediagname(), inhosp == null ? null : inhosp.getDiagname()); InhospArchiveContext context = new InhospArchiveContext(); context.subtask = subtask; context.inhosp = inhosp; context.patArchive = patArchive; context.patid = patid; context.orgid = orgid; context.campusid = campusid; context.deptCode = deptCode; context.deptName = deptName; context.wardCode = wardCode; context.wardName = wardName; context.icd10Code = icd10Code; context.diagName = diagName; context.diagTime = firstDate(subtask.getFinishtime(), inhosp == null ? null : inhosp.getEndtime()); List<PatSpecificArchiveGenerateConfig> configs = patSpecificArchiveGenerateConfigService.selectEffectiveConfigList(orgid, campusid); if (CollectionUtils.isEmpty(configs)) { recordLog(context, null, null, null, SpecificArchiveConstants.RESULT_NO_MATCH, null, "机构未配置档案生成规则"); return 0; } Set<String> generatedKeys = new HashSet<>(); int generatedCount = 0; boolean anyMatched = false; for (PatSpecificArchiveGenerateConfig config : configs) { String archiveType = config.getArchiveType(); String deptCategory = config.getDeptCategory(); if (StringUtils.isBlank(archiveType) || StringUtils.isBlank(deptCategory)) { log.warn("住院随访生成专科档案跳过:配置不完整,configId={},deptCategory={},archiveType={}", config.getId(), deptCategory, archiveType); continue; } if (!matchConfig(config, deptCode, wardCode, icd10Code, diagName)) { continue; } anyMatched = true; String generatedKey = deptCategory + "|" + archiveType; if (!generatedKeys.add(generatedKey)) { continue; } if (alreadyProcessed(subId, config.getId())) { continue; } String lockKey = LOCK_PREFIX + orgid + ":" + patid + ":" + deptCategory + ":" + archiveType; boolean locked = false; try { locked = redisCache.setCacheObjectIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS); } catch (Exception e) { // Redis 不可用时 fail-open,依赖生成日志和数据库查询兜底,避免档案生成整体停摆 locked = true; log.warn("住院随访生成专科档案获取Redis锁异常,降级为无锁执行,subId={},lockKey={}", subId, lockKey, e); } if (!locked) { // 未拿到锁时由补偿任务重试,避免并发重复生成 log.info("住院随访生成专科档案未获取到锁,等待补偿重试,subId={},lockKey={}", subId, lockKey); recordLog(context, config, deptCategory, archiveType, SpecificArchiveConstants.RESULT_FAILED, null, "获取生成锁失败"); continue; } try { Long existingArchiveId = findExistingArchiveId(orgid, patid, deptCategory, archiveType); if (existingArchiveId != null) { recordLog(context, config, deptCategory, archiveType, SpecificArchiveConstants.RESULT_EXISTS, existingArchiveId, "患者已有对应档案,跳过生成"); continue; } Long archiveId; if (SpecificArchiveConstants.DEPT_CATEGORY_GYNECOLOGY.equals(deptCategory)) { PatMaternityArchiveDetail detail = buildInhospMaternityDetail(context, config); archiveId = (long) patMaternityArchiveService.insertOrUpdateMaternityArchive(detail); } else if (SpecificArchiveConstants.DEPT_CATEGORY_PEDIATRICS.equals(deptCategory)) { PatChildArchiveDetail detail = buildInhospChildDetail(context, config); archiveId = (long) patChildArchiveService.insertOrUpdateChildArchive(detail); } else { log.warn("住院随访生成专科档案跳过:未知部门分类,configId={},deptCategory={}", config.getId(), deptCategory); continue; } recordLog(context, config, deptCategory, archiveType, SpecificArchiveConstants.RESULT_GENERATED, archiveId, "生成成功"); generatedCount++; } catch (Exception e) { log.error("住院随访生成专科档案失败,subId={},configId={},deptCategory={},archiveType={}", subId, config.getId(), deptCategory, archiveType, e); recordLog(context, config, deptCategory, archiveType, SpecificArchiveConstants.RESULT_FAILED, null, truncateMsg(e.getMessage())); } finally { if (locked) { try { redisCache.deleteObject(lockKey); } catch (Exception e) { log.warn("住院随访生成专科档案释放Redis锁异常,lockKey={}", lockKey, e); } } } } if (!anyMatched) { recordLog(context, null, null, null, SpecificArchiveConstants.RESULT_NO_MATCH, null, "未命中科室/病区/诊断配置"); } return generatedCount; } @Override public int compensateInhospFollowups() { if (!inhospFollowupEnabled) { return 0; } List<String> serviceTypes = parseCsv(inhospFollowupServiceTypes); if (CollectionUtils.isEmpty(serviceTypes)) { return 0; } Date startTime = new Date(System.currentTimeMillis() - 7L * 24 * 60 * 60 * 1000); List<ServiceSubtask> subtasks = taskSubtaskApi.findCompletedInhospFollowups( SpecificArchiveConstants.LISHUI_MCH_ORG_ID, serviceTypes, startTime, 200); if (CollectionUtils.isEmpty(subtasks)) { return 0; } int generated = 0; for (ServiceSubtask subtask : subtasks) { if (subtask.getId() == null || patSpecificArchiveGenerateLogService.existsProcessedBySourceSub( SpecificArchiveConstants.SOURCE_TYPE_INHOSP_FOLLOWUP, subtask.getId())) { continue; } try { generated += generateByInhospFollowup(subtask.getId()); } catch (Exception e) { log.error("补偿生成住院随访专科档案失败,subId={}", subtask.getId(), e); } } return generated; } /** * 判断门诊记录是否命中配置。 * 判断随访子任务是否属于需要生成档案的住院随访。 */ private boolean isInhospFollowup(ServiceSubtask subtask) { if (!HOSP_TYPE_DISCHARGE.equals(subtask.getHospType()) && !HOSP_TYPE_INHOSP.equals(subtask.getHospType())) { return false; } if (StringUtils.isBlank(subtask.getServiceType())) { return false; } return parseCsv(inhospFollowupServiceTypes).contains(subtask.getServiceType().trim()); } private boolean alreadyProcessed(Long subId, Long configId) { int cid = configId == null ? SpecificArchiveConstants.NO_CONFIG_ID : configId.intValue(); PatSpecificArchiveGenerateLog old = patSpecificArchiveGenerateLogService .selectBySourceSubAndConfig(SpecificArchiveConstants.SOURCE_TYPE_INHOSP_FOLLOWUP, subId, cid); if (old == null) { return false; } if (SpecificArchiveConstants.RESULT_FAILED.equals(old.getProcessResult())) { return old.getRetryCount() != null && old.getRetryCount() >= MAX_RETRY; } return true; } /** * 查询患者是否已有对应专科档案。 * * <p>用户确认口径:按“专科 + 档案类型”判断。妇科、儿科分表存储,因此妇科查妇科表, * 儿科查儿科表,再按档案类型区分。</p> */ private Long findExistingArchiveId(String orgid, Long patid, String deptCategory, String archiveType) { if (SpecificArchiveConstants.DEPT_CATEGORY_GYNECOLOGY.equals(deptCategory)) { PatMaternityArchive query = new PatMaternityArchive(); query.setOrgid(orgid); query.setPatid(patid); query.setArchiveType(archiveType); List<PatMaternityArchive> list = patMaternityArchiveService.selectPatMaternityArchiveList(query); return CollectionUtils.isNotEmpty(list) ? list.get(0).getId() : null; } if (SpecificArchiveConstants.DEPT_CATEGORY_PEDIATRICS.equals(deptCategory)) { PatChildArchive query = new PatChildArchive(); query.setOrgid(orgid); query.setPatid(patid); query.setArchiveType(archiveType); List<PatChildArchive> list = patChildArchiveService.selectPatChildArchiveList(query); return CollectionUtils.isNotEmpty(list) ? list.get(0).getId() : null; } return null; } /** * 判断科室/病区、诊断是否命中配置。 * * <p>部门条件与诊断条件为 OR 关系:任一条件命中即生成;两个集合都为空时配置无效。</p> */ private boolean matchConfig(PatSpecificArchiveGenerateConfig config, PatMedOuthosp patMedOuthosp) { private boolean matchConfig(PatSpecificArchiveGenerateConfig config, String deptCode, String wardCode, String icd10Code, String diagName) { List<String> deptCodes = parseCodes(config.getDeptCodes()); List<String> icd10Codes = parseCodes(config.getIcd10Codes()); if (CollectionUtils.isEmpty(deptCodes) && CollectionUtils.isEmpty(icd10Codes)) { @@ -152,22 +463,21 @@ boolean deptMatched = false; if (CollectionUtils.isNotEmpty(deptCodes)) { // 门诊记录只有科室,没有病区;dept_type=2(病区)不参与门诊匹配 if (DEPT_TYPE_DEPARTMENT.equals(config.getDeptType())) { deptMatched = containsIgnoreCase(deptCodes, patMedOuthosp.getDeptcode()); deptMatched = containsIgnoreCase(deptCodes, deptCode); } else if (DEPT_TYPE_WARD.equals(config.getDeptType())) { deptMatched = containsIgnoreCase(deptCodes, wardCode); } } boolean diagnosisMatched = false; if (CollectionUtils.isNotEmpty(icd10Codes)) { String icd10code = patMedOuthosp.getIcd10code(); String diagname = patMedOuthosp.getDiagname(); diagnosisMatched = icd10Codes.stream().anyMatch(code -> { if (equalsIgnoreCase(code, icd10code)) { if (equalsIgnoreCase(code, icd10Code)) { return true; } // 部分 HIS 数据只有诊断名称没有诊断码,此时允许按诊断名称兜底匹配 return StringUtils.isBlank(icd10code) && equalsIgnoreCase(code, diagname); return StringUtils.isBlank(icd10Code) && equalsIgnoreCase(code, diagName); }); } @@ -179,7 +489,9 @@ PatMaternityArchiveDetail detail = new PatMaternityArchiveDetail(); detail.setArchiveType(config.getArchiveType()); detail.setFollowupPeriod(config.getFollowupPeriod()); detail.setSourceType(SOURCE_TYPE_OUTHOSP); detail.setSourceType(SpecificArchiveConstants.SOURCE_TYPE_OUTHOSP); detail.setSourceBizId(patMedOuthosp.getId()); detail.setSourceBizNo(patMedOuthosp.getOuthospno()); fillCommonDetail(detail, patMedOuthosp, patArchive, campusid); if (patArchive != null) { detail.setAddress(patArchive.getPlaceOfResidence()); @@ -192,7 +504,9 @@ PatChildArchiveDetail detail = new PatChildArchiveDetail(); detail.setArchiveType(config.getArchiveType()); detail.setFollowupPeriod(config.getFollowupPeriod()); detail.setSourceType(SOURCE_TYPE_OUTHOSP); detail.setSourceType(SpecificArchiveConstants.SOURCE_TYPE_OUTHOSP); detail.setSourceBizId(patMedOuthosp.getId()); detail.setSourceBizNo(patMedOuthosp.getOuthospno()); fillCommonChildDetail(detail, patMedOuthosp, patArchive, campusid); detail.setParentPhone(patArchive != null ? firstNotBlank(patArchive.getTelcode(), patMedOuthosp.getTelcode()) @@ -205,6 +519,104 @@ detail.setNutritionIcd10Code(patMedOuthosp.getIcd10code()); detail.setNutritionDiagName(patMedOuthosp.getDiagname()); } return detail; } private PatMaternityArchiveDetail buildInhospMaternityDetail(InhospArchiveContext context, PatSpecificArchiveGenerateConfig config) { PatMaternityArchiveDetail detail = new PatMaternityArchiveDetail(); detail.setArchiveType(config.getArchiveType()); detail.setFollowupPeriod(config.getFollowupPeriod()); detail.setSourceType(SpecificArchiveConstants.SOURCE_TYPE_INHOSP_FOLLOWUP); detail.setSourceBizId(inInhospid(context)); detail.setSourceBizNo(inInhospno(context)); detail.setSourceSubId(context.subtask.getId()); detail.setOuthospno(null); detail.setInhospno(inInhospno(context)); detail.setSerialnum(inSerialnum(context)); detail.setPatid(context.patid); detail.setPatname(firstNotBlank(context.patArchive == null ? null : context.patArchive.getName(), context.inhosp == null ? null : context.inhosp.getPatname())); detail.setBirthdate(context.patArchive == null ? null : context.patArchive.getBirthdate()); detail.setIdcardno(firstNotBlank(context.patArchive == null ? null : context.patArchive.getIdcardno(), context.inhosp == null ? null : context.inhosp.getIdcardno())); detail.setPhone(firstNotBlank(context.patArchive == null ? null : context.patArchive.getTelcode(), context.inhosp == null ? null : context.inhosp.getTelcode())); detail.setSex(firstNonNull(context.patArchive == null ? null : context.patArchive.getSex(), context.inhosp == null ? null : context.inhosp.getSex(), context.subtask.getSex())); detail.setDiagTime(context.diagTime); detail.setArchiveTime(new Date()); detail.setDoctorCode(inDrcode(context)); detail.setDoctorName(inDrname(context)); detail.setManagementDoctorCode(firstNotBlank(context.inhosp == null ? null : context.inhosp.getManagementDoctorCode(), inDrcode(context))); detail.setManagementDoctorName(firstNotBlank(context.inhosp == null ? null : context.inhosp.getManagementDoctor(), inDrname(context))); detail.setFirstDoctorCode(inDrcode(context)); detail.setFirstDoctorName(inDrname(context)); detail.setDeptcode(context.deptCode); detail.setDeptname(context.deptName); detail.setWardcode(context.wardCode); detail.setWardname(context.wardName); detail.setIcd10code(context.icd10Code); detail.setDiagname(context.diagName); detail.setAddress(context.patArchive == null ? null : context.patArchive.getPlaceOfResidence()); detail.setRegisterUser("住院随访"); detail.setOrgid(context.orgid); detail.setCampusid(context.campusid); detail.setDelFlag("0"); return detail; } private PatChildArchiveDetail buildInhospChildDetail(InhospArchiveContext context, PatSpecificArchiveGenerateConfig config) { PatChildArchiveDetail detail = new PatChildArchiveDetail(); detail.setArchiveType(config.getArchiveType()); detail.setFollowupPeriod(config.getFollowupPeriod()); detail.setSourceType(SpecificArchiveConstants.SOURCE_TYPE_INHOSP_FOLLOWUP); detail.setSourceBizId(inInhospid(context)); detail.setSourceBizNo(inInhospno(context)); detail.setSourceSubId(context.subtask.getId()); detail.setOuthospno(null); detail.setInhospno(inInhospno(context)); detail.setSerialnum(inSerialnum(context)); detail.setPatid(context.patid); detail.setPatname(firstNotBlank(context.patArchive == null ? null : context.patArchive.getName(), context.inhosp == null ? null : context.inhosp.getPatname())); detail.setBirthdate(context.patArchive == null ? null : context.patArchive.getBirthdate()); detail.setIdcardno(firstNotBlank(context.patArchive == null ? null : context.patArchive.getIdcardno(), context.inhosp == null ? null : context.inhosp.getIdcardno())); detail.setPhone(firstNotBlank(context.patArchive == null ? null : context.patArchive.getTelcode(), context.inhosp == null ? null : context.inhosp.getTelcode())); detail.setSex(firstNonNull(context.patArchive == null ? null : context.patArchive.getSex(), context.inhosp == null ? null : context.inhosp.getSex(), context.subtask.getSex())); detail.setDiagTime(context.diagTime); detail.setArchiveTime(new Date()); detail.setDoctorCode(inDrcode(context)); detail.setDoctorName(inDrname(context)); detail.setManagementDoctorCode(firstNotBlank(context.inhosp == null ? null : context.inhosp.getManagementDoctorCode(), inDrcode(context))); detail.setManagementDoctorName(firstNotBlank(context.inhosp == null ? null : context.inhosp.getManagementDoctor(), inDrname(context))); detail.setDeptcode(context.deptCode); detail.setDeptname(context.deptName); detail.setWardcode(context.wardCode); detail.setWardname(context.wardName); detail.setIcd10code(context.icd10Code); detail.setDiagname(context.diagName); detail.setParentPhone(firstNotBlank(context.patArchive == null ? null : context.patArchive.getTelcode(), context.inhosp == null ? null : context.inhosp.getTelcode())); detail.setParentAddress(context.patArchive == null ? null : context.patArchive.getPlaceOfResidence()); if ("1".equals(config.getArchiveType())) { detail.setNutritionIcd10Code(context.icd10Code); detail.setNutritionDiagName(context.diagName); } detail.setRegisterUser("住院随访"); detail.setOrgid(context.orgid); detail.setCampusid(context.campusid); detail.setDelFlag("0"); return detail; } @@ -289,6 +701,85 @@ return CollectionUtils.isNotEmpty(archives) ? archives.get(0) : null; } private PatArchive resolvePatArchive(Long patid, PatMedInhosp inhosp) { if (patid != null) { PatArchive archive = patArchiveMapper.selectPatArchiveByPatid(patid); if (archive != null) { return archive; } } Long inhospPatid = inhosp == null ? null : inhosp.getPatid(); return inhospPatid == null ? null : patArchiveMapper.selectPatArchiveByPatid(inhospPatid); } private PatMedInhosp resolveInhosp(ServiceSubtask subtask) { if (subtask.getInhospid() != null) { PatMedInhosp inhosp = inhospVisitFacade.selectPatMedInhospByInhospid(subtask.getInhospid()); if (inhosp != null) { return inhosp; } } if (StringUtils.isNotBlank(subtask.getHospno())) { PatMedInhosp query = new PatMedInhosp(); query.setInhospno(subtask.getHospno()); query.setOrgid(subtask.getOrgid()); List<PatMedInhosp> list = inhospVisitFacade.selectPatMedInhosp(query); if (CollectionUtils.isNotEmpty(list)) { return list.get(0); } } return null; } private void recordLog(InhospArchiveContext context, PatSpecificArchiveGenerateConfig config, String deptCategory, String archiveType, String result, Long archiveId, String msg) { PatSpecificArchiveGenerateLog log = new PatSpecificArchiveGenerateLog(); log.setSourceType(SpecificArchiveConstants.SOURCE_TYPE_INHOSP_FOLLOWUP); log.setSourceBizId(inInhospid(context)); log.setSourceBizNo(inInhospno(context)); log.setSourceSubId(context.subtask.getId()); log.setPatid(context.patid); log.setOrgid(context.orgid); log.setCampusid(context.campusid); log.setDeptCategory(deptCategory); log.setArchiveType(archiveType); log.setConfigId(config == null || config.getId() == null ? SpecificArchiveConstants.NO_CONFIG_ID : config.getId().intValue()); log.setArchiveId(archiveId); log.setProcessResult(result); log.setProcessMsg(msg); PatSpecificArchiveGenerateLog old = patSpecificArchiveGenerateLogService.selectBySourceSubAndConfig( log.getSourceType(), log.getSourceSubId(), log.getConfigId()); int retry = old == null || old.getRetryCount() == null ? 0 : old.getRetryCount(); if (SpecificArchiveConstants.RESULT_FAILED.equals(result)) { retry++; } log.setRetryCount(retry); patSpecificArchiveGenerateLogService.saveOrUpdate(log); } private Long inInhospid(InhospArchiveContext context) { return firstNonNull(context.inhosp == null ? null : context.inhosp.getInhospid(), context.subtask.getInhospid()); } private String inInhospno(InhospArchiveContext context) { return firstNotBlank(context.inhosp == null ? null : context.inhosp.getInhospno(), context.subtask.getHospno()); } private String inSerialnum(InhospArchiveContext context) { return context.inhosp == null ? null : context.inhosp.getSerialnum(); } private String inDrcode(InhospArchiveContext context) { return firstNotBlank(context.inhosp == null ? null : context.inhosp.getDrcode(), context.subtask.getDrcode()); } private String inDrname(InhospArchiveContext context) { return firstNotBlank(context.inhosp == null ? null : context.inhosp.getDrname(), context.subtask.getDrname()); } private List<String> parseCodes(String raw) { if (StringUtils.isBlank(raw)) { return Collections.emptyList(); @@ -314,8 +805,42 @@ .collect(Collectors.toList()); } private String firstNotBlank(String first, String second) { return StringUtils.isNotBlank(first) ? first : second; private List<String> parseCsv(String raw) { if (StringUtils.isBlank(raw)) { return Collections.emptyList(); } return Arrays.stream(raw.split(",")) .map(String::trim) .filter(StringUtils::isNotBlank) .collect(Collectors.toList()); } private String firstNotBlank(String... values) { if (values == null) { return null; } for (String value : values) { if (StringUtils.isNotBlank(value)) { return value; } } return null; } private <T> T firstNonNull(T... values) { if (values == null) { return null; } for (T value : values) { if (value != null) { return value; } } return null; } private Date firstDate(Date first, Date second) { return first != null ? first : second; } private boolean containsIgnoreCase(List<String> source, String target) { @@ -328,4 +853,30 @@ private boolean equalsIgnoreCase(String first, String second) { return first != null && second != null && first.trim().equalsIgnoreCase(second.trim()); } private String truncateMsg(String msg) { if (msg == null) { return null; } return msg.length() <= 900 ? msg : msg.substring(0, 900); } /** * 住院随访生成档案时用到的上下文。 */ private static class InhospArchiveContext { private ServiceSubtask subtask; private PatMedInhosp inhosp; private PatArchive patArchive; private Long patid; private String orgid; private String campusid; private String deptCode; private String deptName; private String wardCode; private String wardName; private String icd10Code; private String diagName; private Date diagTime; } } smartor/src/main/java/com/smartor/service/impl/ServiceSubtaskAnswerServiceImpl.java
@@ -64,6 +64,7 @@ import com.ruoyi.task.domain.ServiceSubtaskPreachform; import com.ruoyi.task.service.IServiceTaskService; import com.ruoyi.task.mapper.ServiceSubtaskPreachformMapper; import com.ruoyi.task.event.FollowupCompletedEventPublisher; import com.ruoyi.patarchive.mapper.PatArchiveMapper; /** * 任务问卷问题选项Service业务层处理 @@ -91,6 +92,9 @@ @Autowired private ServiceSubtaskMapper serviceSubtaskMapper; @Autowired private FollowupCompletedEventPublisher followupCompletedEventPublisher; @Autowired private ServiceSubtaskPreachformMapper serviceSubtaskPreachformMapper; @@ -476,6 +480,7 @@ log.error("将该key和value从redis中,serviceSubtask的值为--------:{}", serviceSubtask); serviceSubtask.setScore(formatScore(scoreTotal)); serviceSubtaskMapper.updateServiceSubtaskByCondition(serviceSubtask); followupCompletedEventPublisher.publish(serviceSubtask.getId()); //将当前发送状态改成 9 完成(缓存 preachform 查询) String currentPreachform = serviceSubtask.getCurrentPreachform(); @@ -664,6 +669,7 @@ serviceSubtask.setUpdateTime(new Date()); serviceSubtask.setScore(formatScore(scoreTotal)); serviceSubtaskMapper.updateServiceSubtaskByCondition(serviceSubtask); followupCompletedEventPublisher.publish(serviceSubtask.getId()); //将当前发送状态改成9完成 ServiceSubtaskPreachform serviceSubtaskPreachform = new ServiceSubtaskPreachform(); @@ -823,6 +829,7 @@ serviceSubtask.setVisitTime(new Date()); serviceSubtask.setScore(formatScore(scoreTotal)); serviceSubtaskMapper.insertServiceSubtask(serviceSubtask); followupCompletedEventPublisher.publish(serviceSubtask.getId()); //将当前发送状态改成9完成 ServiceSubtaskPreachform serviceSubtaskPreachform = new ServiceSubtaskPreachform(); serviceSubtaskPreachform.setPreachform(serviceSubtask.getCurrentPreachform()); smartor/src/main/resources/mapper/smartor/PatChildArchiveDetailMapper.xml
@@ -9,6 +9,9 @@ <result property="archiveid" column="archiveid"/> <result property="archiveType" column="archive_type"/> <result property="sourceType" column="source_type"/> <result property="sourceBizId" column="source_biz_id"/> <result property="sourceBizNo" column="source_biz_no"/> <result property="sourceSubId" column="source_sub_id"/> <result property="outhospno" column="outhospno"/> <result property="inhospno" column="inhospno"/> <result property="serialnum" column="serialnum"/> @@ -70,6 +73,9 @@ archiveid, archive_type, source_type, source_biz_id, source_biz_no, source_sub_id, outhospno, inhospno, serialnum, @@ -136,6 +142,9 @@ <if test="archiveType != null and archiveType != ''">and archive_type = #{archiveType}</if> <if test="followupPeriod != null and followupPeriod != ''">and followup_period = #{followupPeriod}</if> <if test="sourceType != null and sourceType != ''">and source_type = #{sourceType}</if> <if test="sourceBizId != null">and source_biz_id = #{sourceBizId}</if> <if test="sourceBizNo != null and sourceBizNo != ''">and source_biz_no = #{sourceBizNo}</if> <if test="sourceSubId != null">and source_sub_id = #{sourceSubId}</if> <if test="outhospno != null and outhospno != ''">and outhospno = #{outhospno}</if> <if test="inhospno != null and inhospno != ''">and inhospno = #{inhospno}</if> <if test="serialnum != null and serialnum != ''">and serialnum = #{serialnum}</if> @@ -187,6 +196,9 @@ <if test="archiveid != null">archiveid,</if> <if test="archiveType != null">archive_type,</if> <if test="sourceType != null">source_type,</if> <if test="sourceBizId != null">source_biz_id,</if> <if test="sourceBizNo != null">source_biz_no,</if> <if test="sourceSubId != null">source_sub_id,</if> <if test="outhospno != null">outhospno,</if> <if test="inhospno != null">inhospno,</if> <if test="serialnum != null">serialnum,</if> @@ -246,6 +258,9 @@ <if test="archiveid != null">#{archiveid},</if> <if test="archiveType != null">#{archiveType},</if> <if test="sourceType != null">#{sourceType},</if> <if test="sourceBizId != null">#{sourceBizId},</if> <if test="sourceBizNo != null">#{sourceBizNo},</if> <if test="sourceSubId != null">#{sourceSubId},</if> <if test="outhospno != null">#{outhospno},</if> <if test="inhospno != null">#{inhospno},</if> <if test="serialnum != null">#{serialnum},</if> @@ -309,6 +324,9 @@ <if test="archiveid != null">archiveid = #{archiveid},</if> <if test="archiveType != null">archive_type = #{archiveType},</if> <if test="sourceType != null">source_type = #{sourceType},</if> <if test="sourceBizId != null">source_biz_id = #{sourceBizId},</if> <if test="sourceBizNo != null">source_biz_no = #{sourceBizNo},</if> <if test="sourceSubId != null">source_sub_id = #{sourceSubId},</if> <if test="outhospno != null">outhospno = #{outhospno},</if> <if test="inhospno != null">inhospno = #{inhospno},</if> <if test="serialnum != null">serialnum = #{serialnum},</if> smartor/src/main/resources/mapper/smartor/PatChildArchiveMapper.xml
@@ -7,6 +7,10 @@ <resultMap type="com.smartor.domain.PatChildArchive" id="PatChildArchiveResult"> <result property="id" column="id"/> <result property="archiveType" column="archive_type"/> <result property="sourceType" column="source_type"/> <result property="sourceBizId" column="source_biz_id"/> <result property="sourceBizNo" column="source_biz_no"/> <result property="sourceSubId" column="source_sub_id"/> <result property="outhospno" column="outhospno"/> <result property="inhospno" column="inhospno"/> <result property="serialnum" column="serialnum"/> @@ -52,6 +56,10 @@ <sql id="selectPatChildArchiveVo"> select id, archive_type, source_type, source_biz_id, source_biz_no, source_sub_id, outhospno, inhospno, serialnum, @@ -101,6 +109,10 @@ where 1=1 and del_flag = '0' <if test="archiveType != null and archiveType != ''">and archive_type = #{archiveType}</if> <if test="sourceType != null and sourceType != ''">and source_type = #{sourceType}</if> <if test="sourceBizId != null">and source_biz_id = #{sourceBizId}</if> <if test="sourceBizNo != null and sourceBizNo != ''">and source_biz_no = #{sourceBizNo}</if> <if test="sourceSubId != null">and source_sub_id = #{sourceSubId}</if> <if test="outhospno != null and outhospno != ''">and outhospno = #{outhospno}</if> <if test="inhospno != null and inhospno != ''">and inhospno = #{inhospno}</if> <if test="serialnum != null and serialnum != ''">and serialnum = #{serialnum}</if> @@ -152,6 +164,10 @@ insert into pat_child_archive <trim prefix="(" suffix=")" suffixOverrides=","> <if test="archiveType != null">archive_type,</if> <if test="sourceType != null">source_type,</if> <if test="sourceBizId != null">source_biz_id,</if> <if test="sourceBizNo != null">source_biz_no,</if> <if test="sourceSubId != null">source_sub_id,</if> <if test="outhospno != null">outhospno,</if> <if test="inhospno != null">inhospno,</if> <if test="serialnum != null">serialnum,</if> @@ -195,6 +211,10 @@ </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="archiveType != null">#{archiveType},</if> <if test="sourceType != null">#{sourceType},</if> <if test="sourceBizId != null">#{sourceBizId},</if> <if test="sourceBizNo != null">#{sourceBizNo},</if> <if test="sourceSubId != null">#{sourceSubId},</if> <if test="outhospno != null">#{outhospno},</if> <if test="inhospno != null">#{inhospno},</if> <if test="serialnum != null">#{serialnum},</if> @@ -242,6 +262,10 @@ update pat_child_archive <trim prefix="SET" suffixOverrides=","> <if test="archiveType != null">archive_type = #{archiveType},</if> <if test="sourceType != null">source_type = #{sourceType},</if> <if test="sourceBizId != null">source_biz_id = #{sourceBizId},</if> <if test="sourceBizNo != null">source_biz_no = #{sourceBizNo},</if> <if test="sourceSubId != null">source_sub_id = #{sourceSubId},</if> <if test="outhospno != null">outhospno = #{outhospno},</if> <if test="inhospno != null">inhospno = #{inhospno},</if> <if test="serialnum != null">serialnum = #{serialnum},</if> smartor/src/main/resources/mapper/smartor/PatMaternityArchiveDetailMapper.xml
@@ -9,6 +9,9 @@ <result property="archiveid" column="archiveid"/> <result property="archiveType" column="archive_type"/> <result property="sourceType" column="source_type"/> <result property="sourceBizId" column="source_biz_id"/> <result property="sourceBizNo" column="source_biz_no"/> <result property="sourceSubId" column="source_sub_id"/> <result property="outhospno" column="outhospno"/> <result property="inhospno" column="inhospno"/> <result property="serialnum" column="serialnum"/> @@ -67,6 +70,9 @@ archiveid, archive_type, source_type, source_biz_id, source_biz_no, source_sub_id, outhospno, inhospno, serialnum, @@ -129,6 +135,9 @@ <if test="archiveid != null">and archiveid = #{archiveid}</if> <if test="archiveType != null and archiveType != ''">and archive_type = #{archiveType}</if> <if test="sourceType != null and sourceType != ''">and source_type = #{sourceType}</if> <if test="sourceBizId != null">and source_biz_id = #{sourceBizId}</if> <if test="sourceBizNo != null and sourceBizNo != ''">and source_biz_no = #{sourceBizNo}</if> <if test="sourceSubId != null">and source_sub_id = #{sourceSubId}</if> <if test="outhospno != null and outhospno != ''">and outhospno = #{outhospno}</if> <if test="inhospno != null and inhospno != ''">and inhospno = #{inhospno}</if> <if test="serialnum != null and serialnum != ''">and serialnum = #{serialnum}</if> @@ -174,6 +183,9 @@ <if test="archiveid != null">archiveid,</if> <if test="archiveType != null">archive_type,</if> <if test="sourceType != null">source_type,</if> <if test="sourceBizId != null">source_biz_id,</if> <if test="sourceBizNo != null">source_biz_no,</if> <if test="sourceSubId != null">source_sub_id,</if> <if test="outhospno != null">outhospno,</if> <if test="inhospno != null">inhospno,</if> <if test="serialnum != null">serialnum,</if> @@ -230,6 +242,9 @@ <if test="archiveid != null">#{archiveid},</if> <if test="archiveType != null">#{archiveType},</if> <if test="sourceType != null">#{sourceType},</if> <if test="sourceBizId != null">#{sourceBizId},</if> <if test="sourceBizNo != null">#{sourceBizNo},</if> <if test="sourceSubId != null">#{sourceSubId},</if> <if test="outhospno != null">#{outhospno},</if> <if test="inhospno != null">#{inhospno},</if> <if test="serialnum != null">#{serialnum},</if> @@ -290,6 +305,9 @@ <if test="archiveid != null">archiveid = #{archiveid},</if> <if test="archiveType != null">archive_type = #{archiveType},</if> <if test="sourceType != null">source_type = #{sourceType},</if> <if test="sourceBizId != null">source_biz_id = #{sourceBizId},</if> <if test="sourceBizNo != null">source_biz_no = #{sourceBizNo},</if> <if test="sourceSubId != null">source_sub_id = #{sourceSubId},</if> <if test="outhospno != null">outhospno = #{outhospno},</if> <if test="inhospno != null">inhospno = #{inhospno},</if> <if test="serialnum != null">serialnum = #{serialnum},</if> smartor/src/main/resources/mapper/smartor/PatMaternityArchiveMapper.xml
@@ -7,6 +7,10 @@ <resultMap type="com.smartor.domain.PatMaternityArchive" id="PatMaternityArchiveResult"> <result property="id" column="id"/> <result property="archiveType" column="archive_type"/> <result property="sourceType" column="source_type"/> <result property="sourceBizId" column="source_biz_id"/> <result property="sourceBizNo" column="source_biz_no"/> <result property="sourceSubId" column="source_sub_id"/> <result property="outhospno" column="outhospno"/> <result property="inhospno" column="inhospno"/> <result property="serialnum" column="serialnum"/> @@ -47,6 +51,10 @@ <sql id="selectPatMaternityArchiveVo"> select id, archive_type, source_type, source_biz_id, source_biz_no, source_sub_id, outhospno, inhospno, serialnum, @@ -91,6 +99,10 @@ where 1=1 and del_flag = '0' <if test="archiveType != null and archiveType != ''">and archive_type = #{archiveType}</if> <if test="sourceType != null and sourceType != ''">and source_type = #{sourceType}</if> <if test="sourceBizId != null">and source_biz_id = #{sourceBizId}</if> <if test="sourceBizNo != null and sourceBizNo != ''">and source_biz_no = #{sourceBizNo}</if> <if test="sourceSubId != null">and source_sub_id = #{sourceSubId}</if> <if test="outhospno != null and outhospno != ''">and outhospno = #{outhospno}</if> <if test="inhospno != null and inhospno != ''">and inhospno = #{inhospno}</if> <if test="serialnum != null and serialnum != ''">and serialnum = #{serialnum}</if> @@ -138,6 +150,10 @@ insert into pat_maternity_archive <trim prefix="(" suffix=")" suffixOverrides=","> <if test="archiveType != null">archive_type,</if> <if test="sourceType != null">source_type,</if> <if test="sourceBizId != null">source_biz_id,</if> <if test="sourceBizNo != null">source_biz_no,</if> <if test="sourceSubId != null">source_sub_id,</if> <if test="outhospno != null">outhospno,</if> <if test="inhospno != null">inhospno,</if> <if test="serialnum != null">serialnum,</if> @@ -176,6 +192,10 @@ </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="archiveType != null">#{archiveType},</if> <if test="sourceType != null">#{sourceType},</if> <if test="sourceBizId != null">#{sourceBizId},</if> <if test="sourceBizNo != null">#{sourceBizNo},</if> <if test="sourceSubId != null">#{sourceSubId},</if> <if test="outhospno != null">#{outhospno},</if> <if test="inhospno != null">#{inhospno},</if> <if test="serialnum != null">#{serialnum},</if> @@ -218,6 +238,10 @@ update pat_maternity_archive <trim prefix="SET" suffixOverrides=","> <if test="archiveType != null">archive_type = #{archiveType},</if> <if test="sourceType != null">source_type = #{sourceType},</if> <if test="sourceBizId != null">source_biz_id = #{sourceBizId},</if> <if test="sourceBizNo != null">source_biz_no = #{sourceBizNo},</if> <if test="sourceSubId != null">source_sub_id = #{sourceSubId},</if> <if test="outhospno != null">outhospno = #{outhospno},</if> <if test="inhospno != null">inhospno = #{inhospno},</if> <if test="serialnum != null">serialnum = #{serialnum},</if>