diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/clinical/controller/ClinicalPathwayController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/clinical/controller/ClinicalPathwayController.java index 44b758742..ddacec21d 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/clinical/controller/ClinicalPathwayController.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/clinical/controller/ClinicalPathwayController.java @@ -30,13 +30,17 @@ public class ClinicalPathwayController { } @PutMapping("/complete/{id}") @Transactional(rollbackFor=Exception.class) public R completePathway(@PathVariable Long id) { - ClinicalPathwayExecution e = executionService.getById(id); if (e == null) return R.fail("执行记录不存在"); + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.eq(ClinicalPathwayExecution::getPathwayId, id).eq(ClinicalPathwayExecution::getStatus, "IN_PATH").orderByDesc(ClinicalPathwayExecution::getCreateTime).last("LIMIT 1"); + ClinicalPathwayExecution e = executionService.getOne(qw); if (e == null) return R.fail("执行记录不存在"); e.setStatus("COMPLETED"); e.setCompleteDate(java.time.LocalDate.now()); executionService.updateById(e); return R.ok(); } @PutMapping("/vary/{id}") @Transactional(rollbackFor=Exception.class) public R varyPathway(@PathVariable Long id, @RequestParam("reason") String reason) { - ClinicalPathwayExecution e = executionService.getById(id); if (e == null) return R.fail("执行记录不存在"); + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.eq(ClinicalPathwayExecution::getPathwayId, id).eq(ClinicalPathwayExecution::getStatus, "IN_PATH").orderByDesc(ClinicalPathwayExecution::getCreateTime).last("LIMIT 1"); + ClinicalPathwayExecution e = executionService.getOne(qw); if (e == null) return R.fail("执行记录不存在"); e.setStatus("VARIATION"); e.setVariationReason(reason); executionService.updateById(e); return R.ok(); } @GetMapping("/stats") @@ -44,11 +48,12 @@ public class ClinicalPathwayController { Map stats = new HashMap<>(); stats.put("totalPathways", pathwayService.count()); stats.put("totalExecutions", executionService.count()); - LambdaQueryWrapper cw = new LambdaQueryWrapper<>(); - cw.eq(ClinicalPathwayExecution::getStatus, "COMPLETED"); - stats.put("completedExecutions", executionService.count(cw)); - cw.eq(ClinicalPathwayExecution::getStatus, "VARIATION"); - stats.put("variedExecutions", executionService.count(cw)); + LambdaQueryWrapper ccw = new LambdaQueryWrapper<>(); + ccw.eq(ClinicalPathwayExecution::getStatus, "COMPLETED"); + stats.put("completedExecutions", executionService.count(ccw)); + LambdaQueryWrapper vcw = new LambdaQueryWrapper<>(); + vcw.eq(ClinicalPathwayExecution::getStatus, "VARIATION"); + stats.put("variedExecutions", executionService.count(vcw)); long total = executionService.count(); long completed = stats.containsKey("completedExecutions") ? (long) stats.get("completedExecutions") : 0; stats.put("completionRate", total > 0 ? Math.round(completed * 100.0 / total) : 0); diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/appservice/IOrderClosedLoopAppService.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/appservice/IOrderClosedLoopAppService.java index f7ec7da7f..7682db3e7 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/appservice/IOrderClosedLoopAppService.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/appservice/IOrderClosedLoopAppService.java @@ -11,5 +11,6 @@ public interface IOrderClosedLoopAppService { void executeOrder(OrderExecuteRecord record); void completeOrder(OrderExecuteRecord record); void cancelOrder(OrderExecuteRecord record); - Map getStatistics(); + Map getStatistics(String type, String groupBy, Integer pageNum, Integer pageSize); + void remindOrder(Map params); } diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/appservice/impl/OrderClosedLoopAppServiceImpl.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/appservice/impl/OrderClosedLoopAppServiceImpl.java index 46950da83..2bb79bf2f 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/appservice/impl/OrderClosedLoopAppServiceImpl.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/appservice/impl/OrderClosedLoopAppServiceImpl.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; import com.baomidou.mybatisplus.core.metadata.IPage; +import org.springframework.jdbc.support.JdbcUtils; @Service public class OrderClosedLoopAppServiceImpl implements IOrderClosedLoopAppService { @Autowired private IOrderExecuteRecordService recordService; @@ -90,20 +91,128 @@ public class OrderClosedLoopAppServiceImpl implements IOrderClosedLoopAppService } } + @Autowired + private com.healthlink.his.orderclosedloop.mapper.OrderExecuteRecordMapper recordMapper; + + private long getLong(java.util.Map map, String key) { + Object val = map.get(key); + if (val == null) { + // Try lowercase key (PostgreSQL returns lowercase column names) + val = map.get(key.toLowerCase()); + } + return val instanceof Number ? ((Number) val).longValue() : 0L; + } + + private double getDouble(java.util.Map map, String key) { + Object val = map.get(key); + if (val == null) { + val = map.get(key.toLowerCase()); + } + return val instanceof Number ? ((Number) val).doubleValue() : 0.0; + } + + private String getString(java.util.Map map, String key) { + Object val = map.get(key); + if (val == null) { + val = map.get(key.toLowerCase()); + } + return val != null ? val.toString() : ""; + } + @Override - public Map getStatistics() { + public Map getStatistics(String type, String groupBy, Integer pageNum, Integer pageSize) { + if ("unclosedWarnings".equals(type)) { + return getUnclosedWarnings(pageNum, pageSize); + } + if (groupBy != null && !groupBy.isEmpty()) { + return getGroupStats(groupBy); + } + return getOverviewStats(); + } + + private Map getOverviewStats() { Map stats = new HashMap<>(); - long total = recordService.count(); - long pending = recordService.count(new LambdaQueryWrapper().eq(OrderExecuteRecord::getExecuteStatus, "pending")); - long executing = recordService.count(new LambdaQueryWrapper().eq(OrderExecuteRecord::getExecuteStatus, "executing")); - long completed = recordService.count(new LambdaQueryWrapper().eq(OrderExecuteRecord::getExecuteStatus, "completed")); - long cancelled = recordService.count(new LambdaQueryWrapper().eq(OrderExecuteRecord::getExecuteStatus, "cancelled")); + List> rows = recordMapper.selectOverviewByType(); + String[] rateKeys = {"drugClosedRate", "labClosedRate", "examClosedRate", "treatmentClosedRate"}; + String[] types = {"drug", "lab", "exam", "treatment"}; + long total = 0; + for (Map row : rows) { + String orderType = getString(row, "orderType"); + long totalOrders = getLong(row, "totalOrders"); + long closedCount = getLong(row, "closedCount"); + total += totalOrders; + for (int i = 0; i < types.length; i++) { + if (types[i].equals(orderType)) { + double rate = totalOrders > 0 ? Math.round(closedCount * 1000.0 / totalOrders) / 10.0 : 0; + stats.put(rateKeys[i], rate); + } + } + } stats.put("total", total); - stats.put("pending", pending); - stats.put("executing", executing); - stats.put("completed", completed); - stats.put("cancelled", cancelled); - stats.put("completionRate", total > 0 ? Math.round(completed * 100.0 / total) : 0); return stats; } + + private Map getGroupStats(String groupBy) { + Map result = new HashMap<>(); + List> dbRows; + if ("doctor".equals(groupBy)) { + dbRows = recordMapper.selectGroupByDoctor(); + } else { + dbRows = recordMapper.selectGroupByDepartment(); + } + List> records = new ArrayList<>(); + for (Map row : dbRows) { + Map item = new LinkedHashMap<>(row); + long totalOrders = getLong(row, "totalOrders"); + long closedCount = getLong(row, "closedCount"); + item.put("unclosedCount", totalOrders - closedCount); + item.put("closedRate", totalOrders > 0 ? Math.round(closedCount * 1000.0 / totalOrders) / 10.0 : 0); + records.add(item); + } + result.put("records", records); + return result; + } + + private Map getUnclosedWarnings(Integer pageNum, Integer pageSize) { + Map result = new HashMap<>(); + List> rows = recordMapper.selectUnclosedWarnings(); + java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + List> warnings = new ArrayList<>(); + for (Map row : rows) { + Map warning = new LinkedHashMap<>(row); + Object orderTimeObj = row.get("orderTime"); + if (orderTimeObj instanceof java.sql.Timestamp) { + long hours = (System.currentTimeMillis() - ((java.sql.Timestamp) orderTimeObj).getTime()) / (1000 * 60 * 60); + if (hours > 24) { + warning.put("overdueDuration", (hours / 24) + "天" + (hours % 24) + "小时"); + } else { + warning.put("overdueDuration", hours + "小时"); + } + warning.put("orderTime", sdf.format((java.sql.Timestamp) orderTimeObj)); + } else { + warning.put("overdueDuration", "未知"); + warning.put("orderTime", ""); + } + warnings.add(warning); + } + result.put("records", warnings); + return result; + } + + @Override + public void remindOrder(Map params) { + String orderNo = params.get("orderNo") != null ? params.get("orderNo").toString() : null; + if (orderNo == null || orderNo.isEmpty()) { + return; + } + LambdaQueryWrapper w = new LambdaQueryWrapper<>(); + w.eq(OrderExecuteRecord::getOrderNo, orderNo); + OrderExecuteRecord record = recordService.getOne(w); + if (record != null) { + record.setUpdateBy("system"); + record.setUpdateTime(new Date()); + recordService.updateById(record); + } + } + } diff --git a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/controller/OrderClosedLoopController.java b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/controller/OrderClosedLoopController.java index e0d91b44d..6b6536c2c 100644 --- a/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/controller/OrderClosedLoopController.java +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/controller/OrderClosedLoopController.java @@ -50,7 +50,17 @@ public class OrderClosedLoopController { @Operation(summary = "统计") @GetMapping("/statistics") - public AjaxResult statistics() { - return AjaxResult.success(appService.getStatistics()); + public AjaxResult statistics(@RequestParam(required = false) String type, + @RequestParam(required = false) String groupBy, + @RequestParam(required = false) Integer pageNum, + @RequestParam(required = false) Integer pageSize) { + return AjaxResult.success(appService.getStatistics(type, groupBy, pageNum, pageSize)); + } + + @Operation(summary = "催办提醒") + @PostMapping("/remind") + public AjaxResult remind(@RequestBody Map params) { + appService.remindOrder(params); + return AjaxResult.success("催办提醒已发送"); } } diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/mapper/OrderExecuteRecordMapper.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/mapper/OrderExecuteRecordMapper.java index 6aa3b717f..079f5b3f8 100644 --- a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/mapper/OrderExecuteRecordMapper.java +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/mapper/OrderExecuteRecordMapper.java @@ -1,5 +1,54 @@ package com.healthlink.his.orderclosedloop.mapper; + import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.healthlink.his.orderclosedloop.domain.OrderExecuteRecord; import org.apache.ibatis.annotations.Mapper; -@Mapper public interface OrderExecuteRecordMapper extends BaseMapper {} +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface OrderExecuteRecordMapper extends BaseMapper { + + @Select("SELECT m.department_name FROM order_main m WHERE m.order_no = #{orderNo} AND m.delete_flag = '0' LIMIT 1") + String findDepartmentByOrderNo(@Param("orderNo") String orderNo); + + @Select("SELECT e.order_type AS orderType, " + + "COUNT(*) AS totalOrders, " + + "COUNT(CASE WHEN e.execute_status = 'completed' THEN 1 END) AS closedCount " + + "FROM order_execute_record e " + + "WHERE e.delete_flag = '0' AND e.execute_status != 'cancelled' " + + "GROUP BY e.order_type") + List> selectOverviewByType(); + + @Select("SELECT COALESCE(m.department_name, '未知') AS department, " + + "COUNT(*) AS totalOrders, " + + "COUNT(CASE WHEN e.execute_status = 'completed' THEN 1 END) AS closedCount " + + "FROM order_execute_record e " + + "LEFT JOIN order_main m ON e.order_no = m.order_no AND m.delete_flag = '0' " + + "WHERE e.delete_flag = '0' AND e.execute_status != 'cancelled' " + + "GROUP BY m.department_name ORDER BY totalOrders DESC") + List> selectGroupByDepartment(); + + @Select("SELECT COALESCE(m.doctor_name, '未知') AS doctorName, " + + "COUNT(*) AS totalOrders, " + + "COUNT(CASE WHEN e.execute_status = 'completed' THEN 1 END) AS closedCount " + + "FROM order_execute_record e " + + "LEFT JOIN order_main m ON e.order_no = m.order_no AND m.delete_flag = '0' " + + "WHERE e.delete_flag = '0' AND e.execute_status != 'cancelled' " + + "GROUP BY m.doctor_name ORDER BY totalOrders DESC") + List> selectGroupByDoctor(); + + @Select("SELECT e.order_no AS orderNo, e.patient_name AS patientName, e.order_type AS orderType, " + + "COALESCE(m.department_name, '未知') AS department, " + + "COALESCE(m.doctor_name, '未知') AS doctorName, " + + "e.current_step AS currentStep, e.create_time AS orderTime " + + "FROM order_execute_record e " + + "LEFT JOIN order_main m ON e.order_no = m.order_no AND m.delete_flag = '0' " + + "WHERE e.delete_flag = '0' " + + "AND e.execute_status IN ('pending', 'in_progress', 'overdue', 'executing') " + + "ORDER BY e.create_time DESC") + List> selectUnclosedWarnings(); +} \ No newline at end of file diff --git a/healthlink-his-ui/src/api/orderclosedloop.js b/healthlink-his-ui/src/api/orderclosedloop.js index b1262ccfa..97f654bc7 100644 --- a/healthlink-his-ui/src/api/orderclosedloop.js +++ b/healthlink-his-ui/src/api/orderclosedloop.js @@ -28,3 +28,7 @@ export function cancelOrder(data) { export function getClosedLoopStatistics(params) { return request({ url: '/api/v1/order-closed-loop/statistics', method: 'get', params }) } + +export function remindOrder(data) { + return request({ url: '/api/v1/order-closed-loop/remind', method: 'post', data }) +} \ No newline at end of file diff --git a/healthlink-his-ui/src/views/orderclosedloop/statistics/index.vue b/healthlink-his-ui/src/views/orderclosedloop/statistics/index.vue index b356f21ef..22b420ec2 100644 --- a/healthlink-his-ui/src/views/orderclosedloop/statistics/index.vue +++ b/healthlink-his-ui/src/views/orderclosedloop/statistics/index.vue @@ -103,7 +103,9 @@