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 new file mode 100644 index 000000000..f7ec7da7f --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/appservice/IOrderClosedLoopAppService.java @@ -0,0 +1,15 @@ +package com.healthlink.his.web.orderclosedloop.appservice; +import com.healthlink.his.orderclosedloop.domain.OrderExecuteRecord; +import com.healthlink.his.orderclosedloop.domain.OrderExecuteStep; +import java.util.Map; +import java.util.List; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.metadata.IPage; +public interface IOrderClosedLoopAppService { + IPage listRecords(String orderNo, String orderType, String executeStatus, Integer pageNum, Integer pageSize); + Map getOrderStatus(Long orderId); + void executeOrder(OrderExecuteRecord record); + void completeOrder(OrderExecuteRecord record); + void cancelOrder(OrderExecuteRecord record); + Map getStatistics(); +} 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 new file mode 100644 index 000000000..46950da83 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/appservice/impl/OrderClosedLoopAppServiceImpl.java @@ -0,0 +1,109 @@ +package com.healthlink.his.web.orderclosedloop.appservice.impl; +import com.healthlink.his.orderclosedloop.domain.*; +import com.healthlink.his.orderclosedloop.service.*; +import com.healthlink.his.web.orderclosedloop.appservice.IOrderClosedLoopAppService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.*; +import com.baomidou.mybatisplus.core.metadata.IPage; +@Service +public class OrderClosedLoopAppServiceImpl implements IOrderClosedLoopAppService { + @Autowired private IOrderExecuteRecordService recordService; + @Autowired private IOrderExecuteStepService stepService; + + @Override + public IPage listRecords(String orderNo, String orderType, String executeStatus, Integer pageNum, Integer pageSize) { + LambdaQueryWrapper w = new LambdaQueryWrapper<>(); + if (orderNo != null && !orderNo.isEmpty()) w.like(OrderExecuteRecord::getOrderNo, orderNo); + if (orderType != null && !orderType.isEmpty()) w.eq(OrderExecuteRecord::getOrderType, orderType); + if (executeStatus != null && !executeStatus.isEmpty()) w.eq(OrderExecuteRecord::getExecuteStatus, executeStatus); + w.orderByDesc(OrderExecuteRecord::getCreateTime); + return recordService.page(new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNum, pageSize), w); + } + + @Override + public Map getOrderStatus(Long orderId) { + Map result = new HashMap<>(); + OrderExecuteRecord record = recordService.getById(orderId); + if (record == null) { + result.put("error", "记录不存在"); + return result; + } + result.put("record", record); + List steps = stepService.list(new LambdaQueryWrapper().eq(OrderExecuteStep::getOrderNo, record.getOrderNo()).orderByAsc(OrderExecuteStep::getStepOrder)); + result.put("steps", steps); + return result; + } + + @Override + public void executeOrder(OrderExecuteRecord record) { + record.setExecuteStatus("executing"); + record.setExecuteTime(new Date()); + record.setCurrentStep("1"); + recordService.save(record); + // Create default steps + String[] stepNames = {"医师开立", "药师审核", "护士执行", "执行确认"}; + for (int i = 0; i < stepNames.length; i++) { + OrderExecuteStep step = new OrderExecuteStep(); + step.setOrderNo(record.getOrderNo()); + step.setStepName(stepNames[i]); + step.setStepOrder(i + 1); + step.setCompleted(false); + step.setTenantId(record.getTenantId() != null ? record.getTenantId() : 0); + stepService.save(step); + } + } + + @Override + public void completeOrder(OrderExecuteRecord record) { + OrderExecuteRecord existing = recordService.getById(record.getId()); + if (existing != null) { + existing.setExecuteStatus("completed"); + existing.setCurrentStep("4"); + existing.setExecutorId(record.getExecutorId()); + existing.setExecutorName(record.getExecutorName()); + existing.setExecuteTime(new Date()); + recordService.updateById(existing); + // Complete all steps + List steps = stepService.list(new LambdaQueryWrapper().eq(OrderExecuteStep::getOrderNo, existing.getOrderNo())); + for (OrderExecuteStep step : steps) { + step.setCompleted(true); + step.setExecuteTime(new Date()); + step.setExecutorId(record.getExecutorId()); + step.setExecutorName(record.getExecutorName()); + } + stepService.updateBatchById(steps); + } + } + + @Override + public void cancelOrder(OrderExecuteRecord record) { + OrderExecuteRecord existing = recordService.getById(record.getId()); + if (existing != null) { + existing.setExecuteStatus("cancelled"); + existing.setCancelReason(record.getCancelReason()); + existing.setExecutorId(record.getExecutorId()); + existing.setExecutorName(record.getExecutorName()); + existing.setExecuteTime(new Date()); + recordService.updateById(existing); + } + } + + @Override + public Map getStatistics() { + 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")); + 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; + } +} 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 new file mode 100644 index 000000000..e0d91b44d --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/java/com/healthlink/his/web/orderclosedloop/controller/OrderClosedLoopController.java @@ -0,0 +1,56 @@ +package com.healthlink.his.web.orderclosedloop.controller; +import com.core.common.core.domain.AjaxResult; +import com.healthlink.his.orderclosedloop.domain.OrderExecuteRecord; +import com.healthlink.his.web.orderclosedloop.appservice.IOrderClosedLoopAppService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import java.util.Map; +@Tag(name = "医嘱闭环") @RestController @RequestMapping("/api/v1/order-closed-loop") +public class OrderClosedLoopController { + @Autowired private IOrderClosedLoopAppService appService; + + @Operation(summary = "医嘱执行记录列表") + @GetMapping("/list") + public AjaxResult list(@RequestParam(required = false) String orderNo, + @RequestParam(required = false) String orderType, + @RequestParam(required = false) String executeStatus, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + return AjaxResult.success(appService.listRecords(orderNo, orderType, executeStatus, pageNum, pageSize)); + } + + @Operation(summary = "获取医嘱执行状态") + @GetMapping("/status/{orderId}") + public AjaxResult status(@PathVariable Long orderId) { + return AjaxResult.success(appService.getOrderStatus(orderId)); + } + + @Operation(summary = "执行医嘱") + @PostMapping("/execute") + public AjaxResult execute(@RequestBody OrderExecuteRecord record) { + appService.executeOrder(record); + return AjaxResult.success(); + } + + @Operation(summary = "完成医嘱") + @PostMapping("/complete") + public AjaxResult complete(@RequestBody OrderExecuteRecord record) { + appService.completeOrder(record); + return AjaxResult.success(); + } + + @Operation(summary = "取消医嘱") + @PostMapping("/cancel") + public AjaxResult cancel(@RequestBody OrderExecuteRecord record) { + appService.cancelOrder(record); + return AjaxResult.success(); + } + + @Operation(summary = "统计") + @GetMapping("/statistics") + public AjaxResult statistics() { + return AjaxResult.success(appService.getStatistics()); + } +} diff --git a/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V38__order_closed_loop.sql b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V38__order_closed_loop.sql new file mode 100644 index 000000000..01cc51ca3 --- /dev/null +++ b/healthlink-his-server/healthlink-his-application/src/main/resources/db/migration/V38__order_closed_loop.sql @@ -0,0 +1,41 @@ +-- 医嘱执行记录表 +CREATE TABLE IF NOT EXISTS healthlink_his.order_execute_record ( + id BIGSERIAL PRIMARY KEY, + order_no VARCHAR(50) NOT NULL, + encounter_id BIGINT, + patient_id BIGINT, + patient_name VARCHAR(100), + order_type VARCHAR(20) NOT NULL, + order_content TEXT, + current_step VARCHAR(50), + execute_status VARCHAR(20) DEFAULT 'pending', + executor_id BIGINT, + executor_name VARCHAR(100), + execute_time TIMESTAMP, + cancel_reason TEXT, + tenant_id INTEGER DEFAULT 0, + delete_flag CHAR(1) DEFAULT '0', + create_by VARCHAR(64), + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by VARCHAR(64), + update_time TIMESTAMP +); + +-- 医嘱执行步骤表(闭环流程) +CREATE TABLE IF NOT EXISTS healthlink_his.order_execute_step ( + id BIGSERIAL PRIMARY KEY, + order_no VARCHAR(50) NOT NULL, + step_name VARCHAR(100) NOT NULL, + step_order INTEGER NOT NULL, + completed BOOLEAN DEFAULT FALSE, + executor_id BIGINT, + executor_name VARCHAR(100), + execute_time TIMESTAMP, + remark TEXT, + tenant_id INTEGER DEFAULT 0, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_order_exec_order_no ON healthlink_his.order_execute_record(order_no); +CREATE INDEX IF NOT EXISTS idx_order_exec_status ON healthlink_his.order_execute_record(execute_status); +CREATE INDEX IF NOT EXISTS idx_order_step_order_no ON healthlink_his.order_execute_step(order_no); diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/domain/OrderExecuteRecord.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/domain/OrderExecuteRecord.java new file mode 100644 index 000000000..fc3c4fb3c --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/domain/OrderExecuteRecord.java @@ -0,0 +1,32 @@ +package com.healthlink.his.orderclosedloop.domain; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.core.common.core.domain.HisBaseEntity; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Date; +import java.util.List; + +@Data +@TableName("order_execute_record") +@EqualsAndHashCode(callSuper = true) +public class OrderExecuteRecord extends HisBaseEntity { + @TableId(type = IdType.ASSIGN_ID) + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + private String orderNo; + private Long encounterId; + private Long patientId; + private String patientName; + private String orderType; + private String orderContent; + private String currentStep; + private String executeStatus; + private Long executorId; + private String executorName; + private Date executeTime; + private String cancelReason; +} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/domain/OrderExecuteStep.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/domain/OrderExecuteStep.java new file mode 100644 index 000000000..64a804cdf --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/domain/OrderExecuteStep.java @@ -0,0 +1,26 @@ +package com.healthlink.his.orderclosedloop.domain; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import lombok.Data; +import java.util.Date; + +@Data +@TableName("order_execute_step") +public class OrderExecuteStep { + @TableId(type = IdType.ASSIGN_ID) + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + private String orderNo; + private String stepName; + private Integer stepOrder; + private Boolean completed; + private Long executorId; + private String executorName; + private Date executeTime; + private String remark; + private Integer tenantId; + private Date createTime; +} 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 new file mode 100644 index 000000000..6aa3b717f --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/mapper/OrderExecuteRecordMapper.java @@ -0,0 +1,5 @@ +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 {} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/mapper/OrderExecuteStepMapper.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/mapper/OrderExecuteStepMapper.java new file mode 100644 index 000000000..3018b03ff --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/mapper/OrderExecuteStepMapper.java @@ -0,0 +1,5 @@ +package com.healthlink.his.orderclosedloop.mapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.healthlink.his.orderclosedloop.domain.OrderExecuteStep; +import org.apache.ibatis.annotations.Mapper; +@Mapper public interface OrderExecuteStepMapper extends BaseMapper {} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/service/IOrderExecuteRecordService.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/service/IOrderExecuteRecordService.java new file mode 100644 index 000000000..6cdd83b03 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/service/IOrderExecuteRecordService.java @@ -0,0 +1,4 @@ +package com.healthlink.his.orderclosedloop.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.healthlink.his.orderclosedloop.domain.OrderExecuteRecord; +public interface IOrderExecuteRecordService extends IService {} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/service/IOrderExecuteStepService.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/service/IOrderExecuteStepService.java new file mode 100644 index 000000000..b7d7c0949 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/service/IOrderExecuteStepService.java @@ -0,0 +1,4 @@ +package com.healthlink.his.orderclosedloop.service; +import com.baomidou.mybatisplus.extension.service.IService; +import com.healthlink.his.orderclosedloop.domain.OrderExecuteStep; +public interface IOrderExecuteStepService extends IService {} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/service/impl/OrderExecuteRecordServiceImpl.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/service/impl/OrderExecuteRecordServiceImpl.java new file mode 100644 index 000000000..3c9367197 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/service/impl/OrderExecuteRecordServiceImpl.java @@ -0,0 +1,8 @@ +package com.healthlink.his.orderclosedloop.service.impl; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.healthlink.his.orderclosedloop.domain.OrderExecuteRecord; +import com.healthlink.his.orderclosedloop.mapper.OrderExecuteRecordMapper; +import com.healthlink.his.orderclosedloop.service.IOrderExecuteRecordService; +import org.springframework.stereotype.Service; +@Service +public class OrderExecuteRecordServiceImpl extends ServiceImpl implements IOrderExecuteRecordService {} diff --git a/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/service/impl/OrderExecuteStepServiceImpl.java b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/service/impl/OrderExecuteStepServiceImpl.java new file mode 100644 index 000000000..a993f7be1 --- /dev/null +++ b/healthlink-his-server/healthlink-his-domain/src/main/java/com/healthlink/his/orderclosedloop/service/impl/OrderExecuteStepServiceImpl.java @@ -0,0 +1,8 @@ +package com.healthlink.his.orderclosedloop.service.impl; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.healthlink.his.orderclosedloop.domain.OrderExecuteStep; +import com.healthlink.his.orderclosedloop.mapper.OrderExecuteStepMapper; +import com.healthlink.his.orderclosedloop.service.IOrderExecuteStepService; +import org.springframework.stereotype.Service; +@Service +public class OrderExecuteStepServiceImpl extends ServiceImpl implements IOrderExecuteStepService {} diff --git a/healthlink-his-ui/src/api/orderclosedloop.js b/healthlink-his-ui/src/api/orderclosedloop.js new file mode 100644 index 000000000..b1262ccfa --- /dev/null +++ b/healthlink-his-ui/src/api/orderclosedloop.js @@ -0,0 +1,30 @@ +import request from '@/utils/request' + +// ==================== 医嘱闭环 ==================== +export function listOrderExecuteRecord(params) { + return request({ url: '/api/v1/order-closed-loop/list', method: 'get', params }) +} + +export function getOrderClosedLoopStatus(orderId) { + return request({ url: `/api/v1/order-closed-loop/status/${orderId}`, method: 'get' }) +} + +export function getOrderStatistics() { + return request({ url: '/api/v1/order-closed-loop/statistics', method: 'get' }) +} + +export function executeOrder(data) { + return request({ url: '/api/v1/order-closed-loop/execute', method: 'post', data }) +} + +export function completeOrder(data) { + return request({ url: '/api/v1/order-closed-loop/complete', method: 'post', data }) +} + +export function cancelOrder(data) { + return request({ url: '/api/v1/order-closed-loop/cancel', method: 'post', data }) +} + +export function getClosedLoopStatistics(params) { + return request({ url: '/api/v1/order-closed-loop/statistics', method: 'get', params }) +}