ruoyi-common/src/main/java/com/ruoyi/common/core/service/dto/VisitLongTaskDto.java
@@ -93,6 +93,14 @@ /** 创建人(原 createBy) */ private String createBy; /** * 点位编码(2026-11 新增,原 {@code serviceTask.pointLocation})。 * <p> * 新列,不属于"原 DtoConversionUtils 拷贝带入"的那批:它由随访组装器显式 * {@code d.setPointLocation(...)} 带进草稿,再到 {@code service_subtask.point_location}。 */ private String pointLocation; public Long getTaskid() { return taskid; } @@ -276,4 +284,12 @@ public void setCreateBy(String createBy) { this.createBy = createBy; } public String getPointLocation() { return pointLocation; } public void setPointLocation(String pointLocation) { this.pointLocation = pointLocation; } } ruoyi-common/src/main/java/com/ruoyi/common/core/service/dto/VisitSubtaskDraft.java
@@ -125,6 +125,16 @@ */ private String importanceFlagCode; /** * 点位编码(2026-11 新增):来源 {@code service_task.point_location}, * 落库到 {@code service_subtask.point_location}。 * <p> * ⚠️ 与 {@code importanceFlagCode} 的来源不同:那个取自住院登记表, * 这一个取自<b>任务</b>(建任务时前端填的点位),所以只在"任务 → 子任务"的 * 随访建任务链路里填值;门诊/扫码等草稿恒为 {@code null}。 */ private String pointLocation; // ==================== 模板(原 serviceTask) ==================== /** 问卷模板ID */ private Long templateid; @@ -538,6 +548,14 @@ this.importanceFlagCode = importanceFlagCode; } public String getPointLocation() { return pointLocation; } public void setPointLocation(String pointLocation) { this.pointLocation = pointLocation; } public Long getTemplateid() { return templateid; } ruoyi-inhospvisit/src/main/java/com/ruoyi/inhospvisit/domain/row/VisitTaskRow.java
@@ -148,6 +148,14 @@ /** 疾病类型(原 serviceTask.diagType;属 DtoConversionUtils 拷入的 18 个字段之一) */ private String diagType; /** * 点位编码(2026-11 新增,原 {@code serviceTask.pointLocation})。 * <p> * 不属于"靠 DtoConversion 拷入的 18 个字段":它是新列,组装器里显式 * {@code d.setPointLocation(...)} 带走,见 {@code VisitSubtaskFactory} 的两个 boxed 方法。 */ private String pointLocation; public Long getTaskid() { return taskid; } @@ -441,4 +449,12 @@ this.diagType = diagType; } public String getPointLocation() { return pointLocation; } public void setPointLocation(String pointLocation) { this.pointLocation = pointLocation; } } ruoyi-inhospvisit/src/main/java/com/ruoyi/inhospvisit/domain/row/VisitTaskdeptRow.java
@@ -40,7 +40,7 @@ /** 服务类型("2" 出院随访 / "17" 宣教关怀(入院) …) */ private String serviceType; /** 维度类型:"1" = 科室,"2" = 病区 */ /** 维度类型:"1" = 科室,"2" = 病区,"3" = 点位(2026-11 新增;随访侧的科室/病区查询只取 1/2) */ private String deptType; /** 科室ID */ ruoyi-inhospvisit/src/main/java/com/ruoyi/inhospvisit/service/InhospSubtaskWriter.java
@@ -495,6 +495,8 @@ t.setUploadTime(dto.getUploadTime()); t.setUpdateBy(dto.getUpdateBy()); t.setCreateBy(dto.getCreateBy()); // 2026-11:点位编码(任务 → 子任务快照,组装器显式带走) t.setPointLocation(dto.getPointLocation()); return t; } ruoyi-inhospvisit/src/main/java/com/ruoyi/inhospvisit/support/VisitSubtaskFactory.java
@@ -152,6 +152,10 @@ // 子任务落一份,随访列表就能直接按「日间」筛,不用每行回查住院登记表。 // 口径:原样拷贝、不做值域归一;不随 type 4/5 改写(与科室/病区维度无关)。 d.setImportanceFlagCode(inhosp.getImportanceFlagCode()); // ---- 2026-11 新增:点位编码原样带走 ---- // 来源是【任务】而不是住院登记表:service_task.point_location 建任务时由前端填, // 子任务落一份快照,之后任务改点位不回写已有子任务(与 importanceFlagCode 同口径)。 d.setPointLocation(serviceTask.getPointLocation()); // ---- 原 L1128-1134:type 4/5 改用入院维度 ---- if (type != null && (type == 4 || type == 5)) { d.setLeaveldeptcode(inhosp.getDeptcode()); @@ -292,6 +296,9 @@ // ---- 原 L1226:拷贝 ---- applyCarriedOverFieldsFromTask(d, serviceTask); // ---- 2026-11 新增:点位编码原样带走(来源是任务,不是住院登记表) ---- d.setPointLocation(serviceTask.getPointLocation()); // ---- 原 L1227-1231 ---- d.setTaskid(serviceTask.getTaskid()); d.setOrgid(serviceTask.getOrgid()); ruoyi-phone/src/main/java/com/ruoyi/phone/service/impl/PhoneServiceImpl.java
@@ -329,6 +329,8 @@ ServiceSubtask ss = new ServiceSubtask(); ss.setId(serviceSubtask.getId()); ss.setSendstate(6L); //电话已答完(完成):状态与完成时间一起写,避免"已完成却无完成时间" ss.setFinishtime(new Date()); taskSubtaskApi.updateSubtask(ss); String date = simpleDateFormat1.format(new Date()); log.error("没有下一题了,就结束了: {},uuid为:{}", date, phoneCallBackVO.getUuid()); @@ -374,6 +376,8 @@ ServiceSubtask ss = new ServiceSubtask(); ss.setId(serviceSubtask.getId()); ss.setSendstate(6L); //电话已答完(完成):状态与完成时间一起写,避免"已完成却无完成时间" ss.setFinishtime(new Date()); taskSubtaskApi.updateSubtask(ss); String date = simpleDateFormat1.format(new Date()); @@ -607,12 +611,21 @@ //去redis中,把该子任务ID删除 Long id = serviceSubtask.getId(); //先更新一下分数 //⚠️ 静默挂机 = 电话没接通 / 患者一直没应答,属于"未通",不是"完成": // ① 不写 finishtime —— 这条单子接下来要交给补偿任务(setFailPreachForm 置 5 或 3, // 再由 compensateTask 捞起来继续发下一种方式 / 判超时);一旦写了完成时间, // 后面被置成"超时(7)"时就会留下"超时却有完成时间"的脏数据, // 及时率、完成率(ServiceSubtaskMapper#selectTimelyRateBatch 认 finishtime 非空)也会被虚高; // ② 不整行回写 serviceSubtask —— 那会把上面刚写的 sendstate=5(电话静默)覆盖回查库时的旧值, // 所以这里只回写 id + 分数 + 更新时间,状态交给下面的 setFailPreachForm 决定 // (最后一种方式 → 5 发送失败;还有下一种 → 3 已发送,两者都会被补偿任务捞起来)。 double score = redisCache.getCacheObject(phoneCallReqYQVO.getUuid() + "SCORE"); serviceSubtask.setScore(String.valueOf(score)); serviceSubtask.setFinishtime(new Date()); serviceSubtask.setUpdateTime(new Date()); log.error("先更新一下分数,电话的serviceSubtask的值为:{}", serviceSubtask); taskSubtaskApi.updateSubtask(serviceSubtask); ServiceSubtask scoreUpdate = new ServiceSubtask(); scoreUpdate.setId(id); scoreUpdate.setScore(String.valueOf(score)); scoreUpdate.setUpdateTime(new Date()); log.error("先更新一下分数,电话的scoreUpdate的值为:{}", scoreUpdate); taskSubtaskApi.updateSubtask(scoreUpdate); //记录状态 setFailPreachForm(serviceSubtask, "3", "静默次数达到" + num + "次", "4"); @@ -669,6 +682,8 @@ ss.setId(serviceSubtask.getId()); ss.setSendstate(6L); ss.setRemark("电话拨打已完成"); //电话已答完(完成):状态与完成时间一起写,避免"已完成却无完成时间" ss.setFinishtime(new Date()); taskSubtaskApi.updateSubtask(ss); //记录状态 setFailPreachForm(serviceSubtask, "3", "电话拨打已完成", "9"); @@ -854,6 +869,8 @@ ss.setId(serviceSubtask.getId()); ss.setSendstate(6L); ss.setRemark("电话拨打已完成"); //电话已答完(完成):状态与完成时间一起写,避免"已完成却无完成时间" ss.setFinishtime(new Date()); taskSubtaskApi.updateSubtask(ss); //记录状态 setFailPreachForm(serviceSubtask, "3", "电话拨打已完成", "9"); @@ -1566,12 +1583,13 @@ serviceSubtask.setCurrentPreachform(preachform); serviceSubtask.setSendstate(5L); serviceSubtask.setRemark(remark + ";所有执行方式均未获取结果,任务失败"); taskSubtaskApi.updateSubtask(serviceSubtask); //只回写本次要改的列(不再整行回写,避免把库里的旧 finishtime 等一起带回来) updateSubtaskState(serviceSubtask, 5L, preachform, serviceSubtask.getRemark(), null); return true; } else { //加入目前补偿里的 remark serviceSubtask.setRemark(StringUtils.isNotEmpty(serviceSubtask.getRemark()) ? serviceSubtask.getRemark() + "," + remark : remark); taskSubtaskApi.updateSubtask(serviceSubtask); updateSubtaskState(serviceSubtask, null, null, serviceSubtask.getRemark(), null); } } @@ -1581,8 +1599,35 @@ //如果等于9,则说明电话正常结束了 if (failSendstate.equals("9")) serviceSubtask.setSendstate(6L); serviceSubtask.setVisitTime(getNextVisitTime(serviceSubtask.getId(), serviceSubtask.getTaskid(), serviceSubtask.getVisitTime())); taskSubtaskApi.updateSubtask(serviceSubtask); updateSubtaskState(serviceSubtask, serviceSubtask.getSendstate(), preachform, null, serviceSubtask.getVisitTime()); return true; } /** * 只回写本次真正要改的列(替代"整行回写 serviceSubtask")。 * <p> * 为什么必须收窄:mapper 的 updateServiceSubtask 是<if test="xx != null">动态 SET, * 整行回写会把库里已有的 finishtime / visit_time / score 等旧值原样再写一遍 —— 既可能覆盖 * 并发写入,也是"状态 3/5/7 却带着完成时间"这类脏数据的搬运工。 * <p> * 完成时间口径:sendstate=6(完成)时补完成时间;3 已发送 / 5 发送失败 / 7 超时等非完成状态 * 一律不写,Mapper 侧的"完成时间不变量"会顺手把旧值清掉。 */ private void updateSubtaskState(ServiceSubtask source, Long sendstate, String currentPreachform, String remark, Date visitTime) { if (source == null || source.getId() == null) { return; } ServiceSubtask update = new ServiceSubtask(); update.setId(source.getId()); update.setSendstate(sendstate); update.setCurrentPreachform(currentPreachform); update.setRemark(remark); update.setVisitTime(visitTime); if (sendstate != null && sendstate == 6L) { update.setFinishtime(source.getFinishtime() != null ? source.getFinishtime() : new Date()); } taskSubtaskApi.updateSubtask(update); } private Date getNextVisitTime(Long subid, Long taskid, Date visitTime) { @@ -1893,6 +1938,8 @@ ss.setId(serviceSubtask.getId()); ss.setSendstate(6L); ss.setRemark("电话拨打已完成"); //电话已答完(完成):状态与完成时间一起写,避免"已完成却无完成时间" ss.setFinishtime(new Date()); taskSubtaskApi.updateSubtask(ss); //记录状态 setFailPreachForm(serviceSubtask, "3", "电话拨打已完成", "9"); ruoyi-task/src/main/java/com/ruoyi/task/api/contract/VisitTaskCreateApiImpl.java
@@ -240,6 +240,8 @@ dto.setUploadTime(t.getUploadTime()); dto.setUpdateBy(t.getUpdateBy()); dto.setCreateBy(t.getCreateBy()); // 2026-11:点位编码(任务 → 子任务的快照列之一) dto.setPointLocation(t.getPointLocation()); return dto; } @@ -289,6 +291,9 @@ // 只有「出入院随访」链路会填值(VisitSubtaskFactory);门诊/手术等草稿里为 null → // insert 的条件列直接跳过,与改造前这些列不写的效果一致。 e.setImportanceFlagCode(d.getImportanceFlagCode()); // 2026-11:点位编码(建子任务时从 service_task.point_location 同步)。 // 门诊/扫码等草稿里为 null → insert 的条件列直接跳过,与改造前不写该列效果一致。 e.setPointLocation(d.getPointLocation()); e.setTemplateid(d.getTemplateid()); e.setTemplatename(d.getTemplatename()); e.setLibtemplateid(d.getLibtemplateid()); ruoyi-task/src/main/java/com/ruoyi/task/controller/ServiceSubtaskController.java
@@ -1239,7 +1239,12 @@ */ @PostMapping(value = "/scanGenerateSubtask") public AjaxResult scanGenerateSubtask(@RequestBody ServiceTask serviceTask) { return AjaxResult.success(serviceSubtaskService.scanGenerateSubtask(serviceTask)); String url = serviceSubtaskService.scanGenerateSubtask(serviceTask); // data 为空必须回 code=500:前端只看 code,200 + 空串会被误当成「生成成功但没链接」 if (StringUtils.isEmpty(url)) { return AjaxResult.error("扫码生成任务失败:未取到二维码链接"); } return AjaxResult.success(url); } @PostMapping("/syncMedInhospForShiyi") ruoyi-task/src/main/java/com/ruoyi/task/domain/ServiceSubtask.java
@@ -727,6 +727,18 @@ private String importanceFlagCode; /** * 点位编码(2026-11 新增,对应 {@code service_subtask.point_location})。 * <p> * 建子任务时从 {@code service_task.point_location} <b>原样拷贝</b>(快照), * 存点位<b>编码</b>而不是名称;库里是 {@code varchar(64)},不做枚举/外键校验。 * <p> * ⚠️ 之后任务上改了点位<b>不会</b>回写已生成的子任务 —— 与 * {@code importanceFlagCode} 一样是"建任务那一刻的快照"。 */ @ApiModelProperty(value = "点位编码") private String pointLocation; /** * 开始出院日期 */ @JsonFormat(pattern = "yyyy-MM-dd") ruoyi-task/src/main/java/com/ruoyi/task/domain/ServiceSubtaskRes.java
@@ -254,6 +254,13 @@ private String importanceFlagCode; /** * 点位编码(2026-11 新增):建子任务时从 {@code service_task.point_location} 原样拷贝, * 列表接口原样返回(不加 {@code @Excel},不影响导出列)。 */ @ApiModelProperty(value = "点位编码") private String pointLocation; /** * 晚上开始发送时间限制 */ @Excel(name = " 晚上开始发送时间限制 ") ruoyi-task/src/main/java/com/ruoyi/task/domain/ServiceTask.java
@@ -458,4 +458,16 @@ @ApiModelProperty(value = "疾病病种") @Excel(name = " 疾病病种") private String diagType; /** * 点位编码(2026-11 新增,对应 {@code service_task.point_location})。 * <p> * 建任务时由前端传入,存<b>点位编码</b>(不是名称);库里是 {@code varchar(64)}, * 不做枚举/外键校验,传什么存什么。 * <p> * 建子任务时会<b>原样拷贝</b>到 {@code service_subtask.point_location}(快照), * 之后任务上改了点位不会回写已有子任务。 */ @ApiModelProperty(value = "点位编码") private String pointLocation; } ruoyi-task/src/main/java/com/ruoyi/task/domain/ServiceTaskVO.java
@@ -70,9 +70,9 @@ private List<String> leavehospitaldistrictcodes; /** * 出院部门编号集合(点位编号) * 出院部门编号集合 */ @ApiModelProperty(value = "出院部门编号集合(点位编号)") @ApiModelProperty(value = "出院部门编号集合") private List<String> leaveldeptcodes; /** @@ -490,4 +490,13 @@ */ @ApiModelProperty(value = "任务组计划(appltype=5 必填,主计划+明细)") private ServiceTaskScheduleVO serviceTaskScheduleVO; /** * 点位编码(2026-11 新增,对应 {@code service_task.point_location})。 * <p> * 建任务/改任务时由前端传入,落库为 {@code service_task.point_location}; * 建子任务时再按同名拷贝进 {@code service_subtask.point_location}。 */ @ApiModelProperty(value = "点位编码") private String pointLocation; } ruoyi-task/src/main/java/com/ruoyi/task/domain/ServiceTaskdept.java
@@ -52,10 +52,13 @@ private String serviceType; /** * 部门类型:1科室,2病区 * 部门类型:1科室,2病区,3点位 * <p> * 3 = 点位(2026-11 新增,同德「按点位」需求):这类行 * <b>dept_code / dept_name 留空</b>,点位编码只落在 {@link #pointLocation}。 */ @ApiModelProperty("部门类型:1科室,2病区") @Excel(name = "部门类型:1科室,2病区") @ApiModelProperty("部门类型:1科室,2病区,3点位") @Excel(name = "部门类型:1科室,2病区,3点位") private String deptType; /** @@ -84,5 +87,14 @@ @ApiModelProperty("删除标志(0:未删除 1:已删除)") private String delFlag; /** * 点位编码(2026-11 新增,对应 {@code service_taskdept.point_location})。 * <p> * 只在 {@code dept_type='3'}(点位)的行上有值;这类行 {@code dept_code/dept_name} 留空。 * 一个任务一个点位 → 一个任务只会有一条 {@code dept_type='3'} 的行。 */ @ApiModelProperty(value = "点位编码(dept_type=3 时使用)") private String pointLocation; } ruoyi-task/src/main/java/com/ruoyi/task/domain/VO/ServiceSubtaskVO.java
@@ -712,6 +712,24 @@ private String managementDoctor; /** * 重要性标记编码(2026-11 新增,<b>模糊查询</b>):`/patItem`、`/patItemCount`、 * `/patItemExport` 的请求体字段。 * <p> * 对应 {@code service_subtask.importance_flag_code}(建任务时从 * {@code pat_med_inhosp.importance_flag_code} 同步),库里存的是采集拼出的描述串: * 「日间手术」「日间诊疗」「无」「特约人员」「离休人员」,多值用英文逗号拼接(如「无,日间手术」); * 也可能存枚举编码 {@code FH0165.01} / {@code FH0165.02}。 * <p> * 因为是 {@code LIKE '%值%'}:传「日间」可一次命中日间手术 + 日间诊疗 + 多值行, * 传完整值(如「日间手术」或 {@code FH0165.01})则退化为精确匹配。 * <p> * ⚠️ 值经 {@code DtoConversionUtils.sourceToTarget} 同名拷贝到 {@code ServiceSubtaskEntity}, * 所以本类字段名必须与实体一致,否则参数会被静默丢弃。 */ @ApiModelProperty(value = "重要性标记编码(模糊匹配,如 日间 / 日间手术 / FH0165.01)") private String importanceFlagCode; /** * 开始出院日期 */ @JsonFormat(pattern = "yyyy-MM-dd") @@ -843,6 +861,24 @@ @ApiModelProperty(value = "即将超时时间(小时)") private Integer soonTimeoutHours; /** * 点位编码集合(2026-11 新增,<b>点位查询条件的唯一入参</b>)。 * <p> * 前端只传这个字段,<b>单选也传数组</b>: * <ul> * <li>单选:{@code {"pointLocations": ["P001"]}}</li> * <li>多选:{@code {"pointLocations": ["P001","P002"]}}</li> * <li>不筛点位:不传或传 {@code []}</li> * </ul> * → SQL 统一走 {@code point_location IN (...)}(见 {@code serviceSubtaskWhere} / * {@code countByCondition}),列表与 total 用同一条件。 * <p> * ⚠️ 值经 {@code DtoConversionUtils.sourceToTarget} 同名拷贝到 {@code ServiceSubtaskEntity}, * 所以本类字段名必须与实体一致,否则参数会被静默丢弃。 */ @ApiModelProperty(value = "点位编码集合") private List<String> pointLocations; // /** // * 日期限制 目前由前端控制 // */ ruoyi-task/src/main/java/com/ruoyi/task/domain/entity/ServiceSubtaskEntity.java
@@ -11,7 +11,6 @@ import java.util.Date; import java.util.List; import java.util.Map; import com.ruoyi.task.domain.TaskSendTimeVO; /** @@ -757,6 +756,53 @@ private List<String> importanceFlagCodes; /** * 点位编码(2026-11 新增,对应 {@code service_subtask.point_location})。 * <p> * ⚠️ <b>前端不传这个字段</b>(只传 {@link #pointLocations} 数组)。这里保留它有两个原因: * <ol> * <li>它是 {@code service_subtask} 的列语义载体,与其它单值查询字段保持对称;</li> * <li>兜底:直接调 {@code /countByCondition} 之类接口的老调用方,若仍传单值 * (或 {@code "P001,P002"} 逗号串),{@link #getPointLocations()} 会把它拆成集合, * 统一走 {@code IN},不会被静默忽略。</li> * </ol> */ @ApiModelProperty(value = "点位编码(前端不传;仅兼容单值入参的兜底)") private String pointLocation; /** * 点位编码集合(2026-11 新增,<b>点位查询条件的唯一入参</b>):{@code IN} 匹配。 * <p> * 前端单选也传数组:{@code ["P001"]} / 多选 {@code ["P001","P002"]};不筛则传 {@code []} 或不传。 */ @ApiModelProperty(value = "点位编码集合(IN 匹配;单选也传数组)") private List<String> pointLocations; /** * 点位查询条件的<b>唯一入口</b>:SQL 里只有一条 {@code point_location IN (...)}, * 单选/多选都在这里归一,避免出现「只传 pointLocation 时筛选被静默忽略」。 * <p> * 手写这个 getter({@code @Data} 不会重复生成)是为了让 MyBatis 的 OGNL * ({@code pointLocations.size() > 0} 与 {@code foreach collection="pointLocations"}) * 取到合并后的集合。 */ public List<String> getPointLocations() { if (pointLocations != null && !pointLocations.isEmpty()) { return pointLocations; } if (pointLocation != null && !pointLocation.trim().isEmpty()) { // 兼容本仓库「多选=逗号串」的老口径:前端若把多选塞进单值字段(P001,P002)也能命中 List<String> split = new java.util.ArrayList<>(); for (String item : pointLocation.split(",")) { if (!item.trim().isEmpty()) { split.add(item.trim()); } } return split.isEmpty() ? null : split; } return pointLocations; } /** * 开始出院日期 */ @JsonFormat(pattern = "yyyy-MM-dd") ruoyi-task/src/main/java/com/ruoyi/task/service/impl/ServiceSubtaskServiceImpl.java
@@ -1870,6 +1870,25 @@ serviceTaskdeptService.insertServiceTaskdept(serviceTaskdept); } } // 点位行(2026-11,appltype=7):科室 / 病区 / 点位在 service_taskdept 里是 // **各自独立的行**,可以共存也可以不共存 —— 所以点位行是"额外追加", // 不替换上面的科室/病区行。点位行的 dept_code/dept_name 留空,编码只落 point_location。 if ("7".equals(serviceTaskVO.getAppltype())) { String pointLocation = firstNonBlank(serviceTaskVO.getPointLocation(), serviceTask.getPointLocation()); if (StringUtils.isNotEmpty(pointLocation)) { ServiceTaskdept pointDept = new ServiceTaskdept(); pointDept.setTaskId(serviceTask.getTaskid()); pointDept.setTaskName(serviceTask.getTaskName()); pointDept.setOrgid(serviceTask.getOrgid()); pointDept.setLongtask(serviceTask.getLongTask() == null ? null : Long.valueOf(serviceTask.getLongTask())); pointDept.setServiceType(serviceTaskVO.getServiceType()); pointDept.setDeptType("3"); pointDept.setPointLocation(pointLocation); serviceTaskdeptService.insertServiceTaskdept(pointDept); } } } } //长期任务,也允许加人了 @@ -2249,6 +2268,27 @@ scheduleVO.setOplevelCodes(serviceTaskVO.getOplevelcode()); scheduleVO.setCreateBy(serviceTask.getUpdateBy()); serviceTaskScheduleService.saveScheduleWithDetail(scheduleVO); } else if (serviceTaskVO.getAppltype().equals("7")) { // 按点位(同德需求):先软删该任务旧的 dept_type='3' 行,再按入参重建 // (与科室/病区「先删后插」同口径;点位行 dept_code/dept_name 留空) ServiceTaskdept oldPoint = new ServiceTaskdept(); oldPoint.setTaskId(serviceTask.getTaskid()); oldPoint.setDeptType("3"); serviceTaskdeptService.deleteServiceTaskdeptByTaskId(oldPoint); String pointLocation = StringUtils.isNotEmpty(serviceTaskVO.getPointLocation()) ? serviceTaskVO.getPointLocation() : serviceTask.getPointLocation(); if (StringUtils.isNotEmpty(pointLocation)) { ServiceTaskdept pointDept = new ServiceTaskdept(); pointDept.setTaskId(serviceTask.getTaskid()); pointDept.setTaskName(serviceTask.getTaskName()); pointDept.setOrgid(serviceTaskVO.getOrgid()); pointDept.setLongtask(serviceTask.getLongTask() == null ? null : Long.valueOf(serviceTask.getLongTask())); pointDept.setServiceType(serviceTaskVO.getServiceType()); pointDept.setDeptType("3"); pointDept.setPointLocation(pointLocation); serviceTaskdeptService.insertServiceTaskdept(pointDept); } } } if (ObjectUtils.isNotEmpty(serviceTaskVO.getIvrTaskTemplateVO())) { @@ -5456,9 +5496,34 @@ String ip = localIP; ServiceTask serviceTask = new ServiceTask(); serviceTask = serviceTaskService.selectServiceTaskByTaskid(serviceTaskVo.getTaskid()); if (ObjectUtils.isEmpty(serviceTask)) { // 任务不存在 → 不可能有 data,直接抛业务异常(接口层会转成 code=500) throw new BaseException("扫码生成任务失败:任务不存在或已删除(taskid=" + serviceTaskVo.getTaskid() + ")"); } if (ObjectUtils.isNotEmpty(serviceTask)) { try { ServiceTaskdept serviceTaskdept = new ServiceTaskdept(); // 维度(科室/病区/点位)由任务的 appltype 决定:1=科室、2=病区、7=点位。 // 三种维度在 service_taskdept 里是**各自独立的行**(各自的列),可以共存、也可以不共存; // 但查行时必须只带一个维度 —— 否则会用 A 维度的编码去撞 B 维度的行, // 而点位行的 dept_code 本来就是空的。 String appltype = serviceTask.getAppltype(); if ("7".equals(appltype)) { serviceTaskdept.setDeptType("3"); serviceTaskdept.setPointLocation(firstNonBlank(serviceTaskVo.getPointLocation(), serviceTask.getPointLocation())); } else if ("2".equals(appltype)) { serviceTaskdept.setDeptType("2"); // 任务上的科室/病区可能是逗号多选,查 service_taskdept 要用单值(取第一个) serviceTaskdept.setDeptCode(firstNonBlank(serviceTaskVo.getLeavehospitaldistrictcode(), firstValue(serviceTask.getLeavehospitaldistrictcode()))); } else if ("1".equals(appltype)) { serviceTaskdept.setDeptType("1"); serviceTaskdept.setDeptCode(firstNonBlank(serviceTaskVo.getDeptcode(), firstValue(serviceTask.getDeptcode()))); } else { // appltype 为空的历史任务:沿用改造前的口径(科室 → 病区,后写的覆盖先写的), // 点位作为第三维,只在传了点位时补上。 if (ObjectUtils.isNotEmpty(serviceTaskVo.getDeptcode())) { serviceTaskdept.setDeptType("1"); serviceTaskdept.setDeptCode(serviceTaskVo.getDeptcode()); @@ -5466,6 +5531,11 @@ if (ObjectUtils.isNotEmpty(serviceTaskVo.getLeavehospitaldistrictcode())) { serviceTaskdept.setDeptType("2"); serviceTaskdept.setDeptCode(serviceTaskVo.getLeavehospitaldistrictcode()); } if (ObjectUtils.isNotEmpty(serviceTaskVo.getPointLocation())) { serviceTaskdept.setDeptType("3"); serviceTaskdept.setPointLocation(serviceTaskVo.getPointLocation()); } } serviceTaskdept.setServiceType(serviceTask.getServiceType()); serviceTaskdept.setOrgid(serviceTask.getOrgid()); @@ -5502,10 +5572,21 @@ iServiceOutPathService.updateServiceOutPath(serviceOutPath); } } else { // 配置缺失属「业务可预期」情况:带上排查信息抛出去(不吞), // 接口层据此返回 code=500 + 明确 msg,避免「200 + 空 data」。 throw new BaseException("扫码生成任务失败:未匹配到任务维度配置(taskid=" + serviceTaskVo.getTaskid() + ",appltype=" + appltype + ",deptType=" + serviceTaskdept.getDeptType() + ",deptCode=" + serviceTaskdept.getDeptCode() + ",pointLocation=" + serviceTaskdept.getPointLocation() + ")"); } } catch (BaseException be) { // ⚠️ 必须排在 catch (Exception) 之前:BaseException 是 RuntimeException, // 否则刚抛出的业务异常会被下面整段吞掉,接口又变成 200 + 空 data。 throw be; } catch (Exception e) { e.printStackTrace(); log.error("【扫码生成任务】处理异常:{}", e.getMessage()); log.error("【扫码生成任务】处理异常:taskid={},{}", serviceTaskVo.getTaskid(), e.getMessage(), e); } } return content; @@ -5523,6 +5604,9 @@ if (StringUtils.isNotEmpty(serviceTaskdept.getDeptType()) && serviceTaskdept.getDeptType().equals("1")) { serviceSubtask.setLeaveldeptcode(serviceTaskdept.getDeptCode()); serviceSubtask.setLeaveldeptname(serviceTaskdept.getDeptName()); } else if (StringUtils.isNotEmpty(serviceTaskdept.getDeptType()) && serviceTaskdept.getDeptType().equals("3")) { // 点位行(dept_type='3'):dept_code/dept_name 为空,点位只落 point_location serviceSubtask.setPointLocation(serviceTaskdept.getPointLocation()); } else { serviceSubtask.setLeavehospitaldistrictcode(serviceTaskdept.getDeptCode()); serviceSubtask.setLeavehospitaldistrictname(serviceTaskdept.getDeptName()); ruoyi-task/src/main/resources/mapper/task/ServiceSubtaskMapper.xml
@@ -111,6 +111,8 @@ <result property="diagType" column="diag_type"/> <!-- 2026-11:重要性标记编码(日间手术/日间诊疗/无…),建任务时取自 pat_med_inhosp --> <result property="importanceFlagCode" column="importance_flag_code"/> <!-- 2026-11:点位编码,建子任务时原样拷自 service_task.point_location --> <result property="pointLocation" column="point_location"/> </resultMap> <resultMap type="com.ruoyi.task.domain.ServiceSubtaskCount" id="ServiceSubtaskResult2"> @@ -247,6 +249,7 @@ deptcode, deptname, importance_flag_code, point_location, stop_state, leavehospitaldistrictcode, leavehospitaldistrictname, @@ -714,22 +717,37 @@ <if test="hospType != null and hospType != ''">and hosp_type = #{hospType}</if> <!-- 2026-11:重要性标记筛选(对应 service_subtask.importance_flag_code,建任务时取自 pat_med_inhosp.importance_flag_code)。两种写法: ① 精确单值 importanceFlagCode = 'FH0165.01'(或描述口径 '日间手术') ② 精确多值 importanceFlagCodes = ['FH0165.01','FH0165.02'] pat_med_inhosp.importance_flag_code)。 ① 模糊(/patItem 用这个) importanceFlagCode → LIKE '%值%' 传「日间」即可命中「日间手术」「日间诊疗」「无,日间手术」多值行; 传完整值(「日间手术」/「FH0165.01」)退化为精确匹配。 ② 精确多值 importanceFlagCodes → IN 该列采集时就被拼成**描述串**(ImportFlagEnum 的 desc),多值用英文逗号拼接, 如 '无,日间手术'(见 ServiceSLTDHealthcareRecordServiceImpl#hospitalizedApply)—— 等值/IN 只命中单值行;多值行(现场仅 7 条,都在出院记录上)要筛全需自行写 find_in_set。两种口径并存(有库存枚举编码、有库存中文描述),传什么按什么精确比, 所以「日间」这类跨取值查询只能靠模糊;描述与 FH0165.xx 编码两种口径都按原值比, 不做值域归一。 ⚠️ 同一条件必须同步加到 countByCondition(列表 total / 各状态统计走那条 SQL), 否则会出现「列表筛出 N 行、总数还是全量」的错配。 --> <if test="importanceFlagCode != null and importanceFlagCode != ''"> and importance_flag_code = #{importanceFlagCode} and importance_flag_code like concat('%', #{importanceFlagCode}, '%') </if> <if test="importanceFlagCodes != null and importanceFlagCodes.size() > 0"> AND importance_flag_code IN <foreach collection="importanceFlagCodes" item="impFlagItem" open="(" separator="," close=")"> #{impFlagItem} </foreach> </if> <!-- 2026-11:点位筛选(对应 service_subtask.point_location,建子任务时从 service_task 快照过来)。 只有一条 IN 条件:单选/多选都在 ServiceSubtaskEntity#getPointLocations() 里归一 (只传 pointLocation 会自动包成单元素集合),所以不再写等值分支。 ⚠️ 同一条件必须同步加到 countByCondition(列表 total / 各状态统计走那条 SQL), 否则会出现「列表筛出 N 行、总数还是全量」的错配。 --> <if test="pointLocations != null and pointLocations.size() > 0"> AND point_location IN <foreach collection="pointLocations" item="pointLocItem" open="(" separator="," close=")"> #{pointLocItem} </foreach> </if> <if test="libtemplateid != null ">and libtemplateid = #{libtemplateid}</if> @@ -1174,6 +1192,8 @@ <if test="deptname != null and deptname != ''">deptname,</if> <!-- 2026-11:重要性标记编码(日间手术/日间诊疗…),建任务时从 pat_med_inhosp 同步 --> <if test="importanceFlagCode != null and importanceFlagCode != ''">importance_flag_code,</if> <!-- 2026-11:点位编码,建子任务时从 service_task.point_location 同步 --> <if test="pointLocation != null and pointLocation != ''">point_location,</if> <if test="labelstatus != null">labelstatus,</if> <if test="updateBy != null">update_by,</if> <if test="updateTime != null">update_time,</if> @@ -1284,6 +1304,7 @@ <if test="deptcode != null and deptcode != ''">#{deptcode},</if> <if test="deptname != null and deptname != ''">#{deptname},</if> <if test="importanceFlagCode != null and importanceFlagCode != ''">#{importanceFlagCode},</if> <if test="pointLocation != null and pointLocation != ''">#{pointLocation},</if> <if test="labelstatus != null">#{labelstatus},</if> <if test="updateBy != null">#{updateBy},</if> <if test="updateTime != null">#{updateTime},</if> @@ -1431,6 +1452,11 @@ <if test="openid != null ">openid = #{openid},</if> <if test="longSendTime != null ">long_send_time = #{longSendTime},</if> <if test="sendstate != null ">sendstate = #{sendstate},</if> <!-- 完成时间不变量(2026-11):状态一旦不是"完成(6)",就显式清空完成时间与完成时状态。 上面 finishtime 那一段是 if(finishtime != null),传 null 会被整段跳过, 所以"清空"必须在这里用 SQL 的 NULL 写出来;否则 3 已发送 / 5 发送失败 / 7 超时 的行会一直带着历史完成时间("超时却有完成时间、及时率虚高"就是这么来的)。 --> <if test="sendstate != null and sendstate != 6">finishtime = NULL, finish_state = NULL,</if> <if test="sendstate != null and sendstate == 6">finish_state = <include refid="finishStateExpr"/>,</if> <if test="serviceType != null ">service_type = #{serviceType},</if> <if test="score != null ">score = #{score},</if> @@ -1477,6 +1503,8 @@ <if test="scheduleSeq != null ">schedule_seq = #{scheduleSeq},</if> <if test="loopCount != null ">loop_count = #{loopCount},</if> <if test="isManuallyCalled != null ">is_manually_called = #{isManuallyCalled},</if> <!-- 2026-11:点位编码(建子任务时的快照;任务上改点位不会自动回写) --> <if test="pointLocation != null and pointLocation != ''">point_location = #{pointLocation},</if> </trim> where id = #{id} </update> @@ -1556,6 +1584,11 @@ <if test="openid != null ">openid = #{openid},</if> <if test="longSendTime != null ">long_send_time = #{longSendTime},</if> <if test="sendstate != null ">sendstate = #{sendstate},</if> <!-- 完成时间不变量(2026-11):状态一旦不是"完成(6)",就显式清空完成时间与完成时状态。 上面 finishtime 那一段是 if(finishtime != null),传 null 会被整段跳过, 所以"清空"必须在这里用 SQL 的 NULL 写出来;否则 3 已发送 / 5 发送失败 / 7 超时 的行会一直带着历史完成时间("超时却有完成时间、及时率虚高"就是这么来的)。 --> <if test="sendstate != null and sendstate != 6">finishtime = NULL, finish_state = NULL,</if> <if test="sendstate != null and sendstate == 6">finish_state = <include refid="finishStateExpr"/>,</if> <if test="serviceType != null ">service_type = #{serviceType},</if> <if test="score != null ">score = #{score},</if> @@ -1614,6 +1647,11 @@ <if test="sex != null">sex = #{sex},</if> <if test="delFlag != null">del_flag = #{delFlag},</if> <if test="sendstate != null ">sendstate = #{sendstate},</if> <!-- 完成时间不变量(2026-11):状态一旦不是"完成(6)",就显式清空完成时间与完成时状态。 上面 finishtime 那一段是 if(finishtime != null),传 null 会被整段跳过, 所以"清空"必须在这里用 SQL 的 NULL 写出来;否则 3 已发送 / 5 发送失败 / 7 超时 的行会一直带着历史完成时间("超时却有完成时间、及时率虚高"就是这么来的)。 --> <if test="sendstate != null and sendstate != 6">finishtime = NULL, finish_state = NULL,</if> <if test="sendstate != null and sendstate == 6">finish_state = <include refid="finishStateExpr"/>,</if> <if test="age != null">age = #{age},</if> <if test="sfzh != null">sfzh = #{sfzh},</if> @@ -1727,6 +1765,11 @@ <if test="phone != null">phone = #{phone},</if> <if test="sex != null">sex = #{sex},</if> <if test="sendstate != null ">sendstate = #{sendstate},</if> <!-- 完成时间不变量(2026-11):状态一旦不是"完成(6)",就显式清空完成时间与完成时状态。 上面 finishtime 那一段是 if(finishtime != null),传 null 会被整段跳过, 所以"清空"必须在这里用 SQL 的 NULL 写出来;否则 3 已发送 / 5 发送失败 / 7 超时 的行会一直带着历史完成时间("超时却有完成时间、及时率虚高"就是这么来的)。 --> <if test="sendstate != null and sendstate != 6">finishtime = NULL, finish_state = NULL,</if> <if test="sendstate != null and sendstate == 6">finish_state = <include refid="finishStateExpr"/>,</if> <if test="age != null">age = #{age},</if> <if test="sfzh != null">sfzh = #{sfzh},</if> @@ -3669,6 +3712,25 @@ <if test="sendname != null and sendname != ''"> AND sendname LIKE CONCAT('%', #{sendname}, '%') </if> <!-- 2026-11:重要性标记模糊筛选 —— 必须与 serviceSubtaskWhere 里的同名条件【完全一致】, 否则 /patItem 的列表与 total/各状态统计会错配(列表筛了、总数没筛)。 口径见 serviceSubtaskWhere 处的注释。 --> <if test="importanceFlagCode != null and importanceFlagCode != ''"> AND importance_flag_code LIKE CONCAT('%', #{importanceFlagCode}, '%') </if> <!-- 2026-11:点位筛选 —— 必须与 serviceSubtaskWhere 里的同名条件【完全一致】, 否则 /patItem 的列表与 total/各状态统计会错配(列表筛了、总数没筛)。 单选/多选归一在 ServiceSubtaskEntity#getPointLocations(),这里只有一条 IN。 --> <if test="pointLocations != null and pointLocations.size() > 0"> AND point_location IN <foreach collection="pointLocations" item="pointLocItem" open="(" separator="," close=")"> #{pointLocItem} </foreach> </if> <if test="phone != null and phone != ''"> AND phone = #{phone} </if> ruoyi-task/src/main/resources/mapper/task/ServiceTaskMapper.xml
@@ -63,6 +63,8 @@ <result property="appltype" column="appltype"/> <result property="patCycle" column="pat_cycle"/> <result property="diagType" column="diag_type"/> <!-- 2026-11:点位编码(同德「按点位」需求),建子任务时原样拷到 service_subtask --> <result property="pointLocation" column="point_location"/> </resultMap> <sql id="selectServiceTaskVo"> @@ -122,7 +124,8 @@ nexttaskflag, nexttaskid, nexttaskname, diag_type diag_type, point_location from service_task </sql> @@ -259,7 +262,8 @@ service_task.nexttaskid, service_task.nexttaskname, service_task.orgid, service_task.campusid from service_task service_task.campusid, service_task.point_location from service_task where 1=1 and service_task.del_flag = 0 <!-- and taskid in (--> @@ -495,6 +499,7 @@ <if test="appltype != null ">appltype,</if> <if test="patCycle != null ">pat_cycle,</if> <if test="diagType != null and diagType != ''">diag_type,</if> <if test="pointLocation != null and pointLocation != ''">point_location,</if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="taskName != null">#{taskName},</if> @@ -553,6 +558,7 @@ <if test="appltype != null ">#{appltype},</if> <if test="patCycle != null ">#{patCycle},</if> <if test="diagType != null and diagType != ''">#{diagType},</if> <if test="pointLocation != null and pointLocation != ''">#{pointLocation},</if> </trim> </insert> @@ -617,6 +623,7 @@ <if test="appltype != null ">appltype = #{appltype},</if> <if test="patCycle != null ">pat_cycle = #{patCycle},</if> <if test="diagType != null and diagType != ''">diag_type = #{diagType},</if> <if test="pointLocation != null and pointLocation != ''">point_location = #{pointLocation},</if> </trim> where taskid = #{taskid} </update> ruoyi-task/src/main/resources/mapper/task/ServiceTaskdeptMapper.xml
@@ -14,6 +14,8 @@ <result property="deptId" column="dept_id"/> <result property="deptCode" column="dept_code"/> <result property="deptName" column="dept_name"/> <!-- 2026-11:点位编码(dept_type='3' 的行走这一列,dept_code/dept_name 留空) --> <result property="pointLocation" column="point_location"/> <result property="delFlag" column="del_flag"/> <result property="createTime" column="create_time"/> <result property="updateTime" column="update_time"/> @@ -31,6 +33,7 @@ dept_type, dept_id, dept_code, point_location, del_flag, create_time, update_time @@ -65,6 +68,9 @@ </if> <if test="deptName != null and deptCode != ''"> and dept_name = #{deptName} </if> <if test="pointLocation != null and pointLocation != ''"> and point_location = #{pointLocation} </if> <if test="orgid != null and orgid != ''"> and orgid = #{orgid} @@ -103,6 +109,8 @@ </if> <if test="deptName != null">dept_name, </if> <if test="pointLocation != null">point_location, </if> <if test="orgid != null">orgid, </if> </trim> @@ -128,6 +136,8 @@ <if test="taskName != null">#{taskName}, </if> <if test="deptName != null">#{deptName}, </if> <if test="pointLocation != null">#{pointLocation}, </if> <if test="orgid != null">#{orgid}, </if> @@ -168,6 +178,8 @@ </if> <if test="deptName != null">dept_name = #{deptName}, </if> <if test="pointLocation != null">point_location = #{pointLocation}, </if> <if test="orgid != null">orgid = #{orgid}, </if> </trim>