diff --git a/intc-api/intc-api-health/pom.xml b/intc-api/intc-api-health/pom.xml new file mode 100644 index 0000000..fd7279c --- /dev/null +++ b/intc-api/intc-api-health/pom.xml @@ -0,0 +1,28 @@ + + + + com.intc + intc-api + 3.6.3 + + 4.0.0 + + intc-api-health + + + intc-api-health 健康模块远程调用接口 + + + + + + + com.intc + intc-common-core + + + + + diff --git a/intc-api/intc-api-health/src/main/java/com/intc/api/health/RemoteHealthService.java b/intc-api/intc-api-health/src/main/java/com/intc/api/health/RemoteHealthService.java new file mode 100644 index 0000000..817b03a --- /dev/null +++ b/intc-api/intc-api-health/src/main/java/com/intc/api/health/RemoteHealthService.java @@ -0,0 +1,40 @@ +package com.intc.api.health; + +import com.intc.common.core.constant.ServiceNameConstants; +import com.intc.common.core.domain.R; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * 健康模块远程调用服务 + * + * @author intc + */ +@FeignClient(contextId = "remoteHealthService", value = ServiceNameConstants.INTC_HEALTH, fallbackFactory = com.intc.api.health.factory.RemoteHealthFallbackFactory.class) +public interface RemoteHealthService { + + /** + * 每日生成服药任务 + * + * @return 结果 + */ + @RequestMapping(value = "/job/generateDailyTasks", method = RequestMethod.POST) + R generateDailyTasks(); + + /** + * 扫描并发送服药提醒 + * + * @return 结果 + */ + @RequestMapping(value = "/job/scanAndSendReminders", method = RequestMethod.POST) + R scanAndSendReminders(); + + /** + * 扫描超时未服药任务 + * + * @return 结果 + */ + @RequestMapping(value = "/job/scanOverdueRecords", method = RequestMethod.POST) + R scanOverdueRecords(); +} diff --git a/intc-api/intc-api-health/src/main/java/com/intc/api/health/factory/RemoteHealthFallbackFactory.java b/intc-api/intc-api-health/src/main/java/com/intc/api/health/factory/RemoteHealthFallbackFactory.java new file mode 100644 index 0000000..935c7c4 --- /dev/null +++ b/intc-api/intc-api-health/src/main/java/com/intc/api/health/factory/RemoteHealthFallbackFactory.java @@ -0,0 +1,41 @@ +package com.intc.api.health.factory; + +import com.intc.api.health.RemoteHealthService; +import com.intc.common.core.domain.R; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.openfeign.FallbackFactory; +import org.springframework.stereotype.Component; + +/** + * 健康服务降级处理 + * + * @author intc + */ +@Component +public class RemoteHealthFallbackFactory implements FallbackFactory { + + private static final Logger log = LoggerFactory.getLogger(RemoteHealthFallbackFactory.class); + + @Override + public RemoteHealthService create(Throwable throwable) { + log.error("健康服务调用失败:{}", throwable.getMessage()); + return new RemoteHealthService() { + + @Override + public R generateDailyTasks() { + return R.fail("健康服务调用每日生成服药任务失败"); + } + + @Override + public R scanAndSendReminders() { + return R.fail("健康服务调用扫描服药提醒失败"); + } + + @Override + public R scanOverdueRecords() { + return R.fail("健康服务调用扫描超时未服药记录失败"); + } + }; + } +} diff --git a/intc-api/intc-api-health/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/intc-api/intc-api-health/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..4389ae5 --- /dev/null +++ b/intc-api/intc-api-health/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.intc.api.health.factory.RemoteHealthFallbackFactory \ No newline at end of file diff --git a/intc-api/pom.xml b/intc-api/pom.xml index 3395678..db3a536 100644 --- a/intc-api/pom.xml +++ b/intc-api/pom.xml @@ -11,6 +11,7 @@ intc-api-system intc-api-invest + intc-api-health intc-api pom diff --git a/intc-common/intc-common-core/pom.xml b/intc-common/intc-common-core/pom.xml index 7ae2228..33431b0 100644 --- a/intc-common/intc-common-core/pom.xml +++ b/intc-common/intc-common-core/pom.xml @@ -145,6 +145,12 @@ mybatis-plus-extension 3.5.5 + + + org.postgresql + postgresql + provided + diff --git a/intc-common/intc-common-core/src/main/java/com/intc/common/core/constant/ServiceNameConstants.java b/intc-common/intc-common-core/src/main/java/com/intc/common/core/constant/ServiceNameConstants.java index da07691..55d79d9 100644 --- a/intc-common/intc-common-core/src/main/java/com/intc/common/core/constant/ServiceNameConstants.java +++ b/intc-common/intc-common-core/src/main/java/com/intc/common/core/constant/ServiceNameConstants.java @@ -65,4 +65,9 @@ public class ServiceNameConstants * INTC_INVEST_PROD的serviceId */ public static final String INTC_INVEST_PROD ="intc-invest-prod"; + + /** + * INTC_HEALTH的serviceId + */ + public static final String INTC_HEALTH = "intc-health"; } diff --git a/intc-common/intc-common-core/src/main/java/com/intc/common/core/handler/JsonNodeTypeHandler.java b/intc-common/intc-common-core/src/main/java/com/intc/common/core/handler/JsonNodeTypeHandler.java new file mode 100644 index 0000000..cdb6ee5 --- /dev/null +++ b/intc-common/intc-common-core/src/main/java/com/intc/common/core/handler/JsonNodeTypeHandler.java @@ -0,0 +1,63 @@ +package com.intc.common.core.handler; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedTypes; +import org.postgresql.util.PGobject; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * PostgreSQL JSONB 类型处理器 + * 用于 MyBatis 映射 JsonNode 类型 + * + * @author bot5 + * @date 2026-03-19 + */ +@MappedTypes(JsonNode.class) +public class JsonNodeTypeHandler extends BaseTypeHandler { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, JsonNode parameter, JdbcType jdbcType) throws SQLException { + PGobject pgObject = new PGobject(); + pgObject.setType("jsonb"); + pgObject.setValue(parameter.toString()); + ps.setObject(i, pgObject); + } + + @Override + public JsonNode getNullableResult(ResultSet rs, String columnName) throws SQLException { + String value = rs.getString(columnName); + return parseJson(value); + } + + @Override + public JsonNode getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + String value = rs.getString(columnIndex); + return parseJson(value); + } + + @Override + public JsonNode getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + String value = cs.getString(columnIndex); + return parseJson(value); + } + + private JsonNode parseJson(String value) { + if (value == null || value.isEmpty()) { + return null; + } + try { + return objectMapper.readTree(value); + } catch (Exception e) { + return null; + } + } +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/controller/ChronicDiseaseRecordController.java b/intc-modules/intc-health/src/main/java/com/intc/health/controller/ChronicDiseaseRecordController.java new file mode 100644 index 0000000..44e7e5b --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/controller/ChronicDiseaseRecordController.java @@ -0,0 +1,128 @@ +package com.intc.health.controller; + +import java.util.List; +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; +import javax.annotation.Resource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.intc.common.log.annotation.Log; +import com.intc.common.log.enums.BusinessType; +import com.intc.common.security.annotation.RequiresPermissions; +import com.intc.health.domain.ChronicDiseaseRecord; +import com.intc.health.domain.vo.ChronicDiseaseRecordVo; +import com.intc.health.domain.dto.ChronicDiseaseRecordDto; +import com.intc.health.service.IChronicDiseaseRecordService; +import com.intc.common.core.web.controller.BaseController; +import com.intc.common.core.web.domain.AjaxResult; +import com.intc.common.core.utils.poi.ExcelUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import com.intc.common.core.web.page.TableDataInfo; + +/** + * 慢性疾病档案Controller + * + * @author bot5 + * @date 2026-03-17 + */ +@Api(tags = "慢性疾病档案") +@RestController +@RequestMapping("/chronicDisease") +public class ChronicDiseaseRecordController extends BaseController +{ + @Resource + private IChronicDiseaseRecordService chronicDiseaseRecordService; + + /** + * 查询慢性疾病档案列表 + */ + @ApiOperation(value = "查询慢性疾病档案列表", response = ChronicDiseaseRecordVo.class) + @RequiresPermissions("health:chronicDisease:list") + @GetMapping("/list") + public TableDataInfo list(ChronicDiseaseRecordDto chronicDiseaseRecordDto) + { + startPage(); + List list = chronicDiseaseRecordService.selectChronicDiseaseRecordList(chronicDiseaseRecordDto); + return getDataTable(list); + } + + /** + * 根据成员ID查询慢性疾病档案列表 + */ + @ApiOperation(value = "根据成员ID查询慢性疾病档案列表", response = ChronicDiseaseRecordVo.class) + @RequiresPermissions("health:chronicDisease:list") + @GetMapping({"/listByPerson/{personId}", "/listByMember/{personId}"}) + public AjaxResult listByMember(@PathVariable("personId") Long personId) + { + List list = chronicDiseaseRecordService.selectChronicDiseaseRecordByMemberId(personId); + return success(list); + } + + /** + * 导出慢性疾病档案列表 + */ + @ApiOperation(value = "导出慢性疾病档案列表") + @RequiresPermissions("health:chronicDisease:export") + @Log(title = "慢性疾病档案", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, ChronicDiseaseRecordDto chronicDiseaseRecordDto) + { + List list = chronicDiseaseRecordService.selectChronicDiseaseRecordList(chronicDiseaseRecordDto); + ExcelUtil util = new ExcelUtil(ChronicDiseaseRecordVo.class); + util.exportExcel(response, list, "慢性疾病档案数据"); + } + + /** + * 获取慢性疾病档案详细信息 + */ + @ApiOperation(value = "获取慢性疾病档案详细信息") + @RequiresPermissions("health:chronicDisease:query") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) + { + return success(chronicDiseaseRecordService.selectChronicDiseaseRecordById(id)); + } + + /** + * 新增慢性疾病档案 + */ + @ApiOperation(value = "新增慢性疾病档案") + @RequiresPermissions("health:chronicDisease:add") + @Log(title = "慢性疾病档案", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody ChronicDiseaseRecord chronicDiseaseRecord) + { + return toAjax(chronicDiseaseRecordService.insertChronicDiseaseRecord(chronicDiseaseRecord)); + } + + /** + * 修改慢性疾病档案 + */ + @ApiOperation(value = "修改慢性疾病档案") + @RequiresPermissions("health:chronicDisease:edit") + @Log(title = "慢性疾病档案", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody ChronicDiseaseRecord chronicDiseaseRecord) + { + return toAjax(chronicDiseaseRecordService.updateChronicDiseaseRecord(chronicDiseaseRecord)); + } + + /** + * 删除慢性疾病档案 + */ + @ApiOperation(value = "删除慢性疾病档案") + @RequiresPermissions("health:chronicDisease:remove") + @Log(title = "慢性疾病档案", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) + { + return toAjax(chronicDiseaseRecordService.deleteChronicDiseaseRecordByIds(ids)); + } +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/controller/HealthJobController.java b/intc-modules/intc-health/src/main/java/com/intc/health/controller/HealthJobController.java new file mode 100644 index 0000000..b5c8270 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/controller/HealthJobController.java @@ -0,0 +1,59 @@ +package com.intc.health.controller; + +import com.intc.common.core.web.domain.AjaxResult; +import com.intc.health.task.MedicationRemindJob; +import com.intc.health.task.MedicationTaskJob; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +/** + * 健康模块定时任务触发接口(供 intc-job 远程调用) + * + * @author intc + */ +@RestController +@RequestMapping("/job") +@Api(tags = "健康模块定时任务接口") +public class HealthJobController { + + @Resource + private MedicationTaskJob medicationTaskJob; + + @Resource + private MedicationRemindJob medicationRemindJob; + + /** + * 每日生成服药任务 + */ + @PostMapping("/generateDailyTasks") + @ApiOperation("每日生成服药任务") + public AjaxResult generateDailyTasks() { + medicationTaskJob.generateDailyTasks(); + return AjaxResult.success(); + } + + /** + * 扫描并发送服药提醒 + */ + @PostMapping("/scanAndSendReminders") + @ApiOperation("扫描并发送服药提醒") + public AjaxResult scanAndSendReminders() { + medicationRemindJob.scanAndSendReminders(); + return AjaxResult.success(); + } + + /** + * 扫描超时未服药任务 + */ + @PostMapping("/scanOverdueRecords") + @ApiOperation("扫描超时未服药任务") + public AjaxResult scanOverdueRecords() { + medicationRemindJob.scanOverdueRecords(); + return AjaxResult.success(); + } +} diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/controller/MedicationAdherenceController.java b/intc-modules/intc-health/src/main/java/com/intc/health/controller/MedicationAdherenceController.java new file mode 100644 index 0000000..692df09 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/controller/MedicationAdherenceController.java @@ -0,0 +1,83 @@ +package com.intc.health.controller; + +import com.intc.common.core.web.domain.AjaxResult; +import com.intc.health.domain.vo.DailyAdherenceVo; +import com.intc.health.domain.vo.MedicationAdherenceVo; +import com.intc.health.service.IMedicationAdherenceService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +/** + * 依从性统计Controller + * + * @author bot5 + * @date 2026-03-19 + */ +@RestController +@RequestMapping("/adherence") +@Api(tags = "依从性统计") +public class MedicationAdherenceController { + + @Resource + private IMedicationAdherenceService adherenceService; + + @GetMapping("/overall") + @ApiOperation("获取总体依从性统计") + public AjaxResult getOverallAdherence( + @RequestParam(required = false) Long personId, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) { + MedicationAdherenceVo vo = adherenceService.getOverallAdherence(personId, startDate, endDate); + return AjaxResult.success(vo); + } + + @GetMapping("/daily") + @ApiOperation("获取每日依从性统计列表") + public AjaxResult getDailyAdherenceList( + @RequestParam(required = false) Long personId, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) { + List list = adherenceService.getDailyAdherenceList(personId, startDate, endDate); + return AjaxResult.success(list); + } + + @GetMapping("/calendar") + @ApiOperation("获取日历视图数据") + public AjaxResult getCalendarData( + @RequestParam(required = false) Long personId, + @RequestParam String yearMonth) { + List list = adherenceService.getCalendarData(personId, yearMonth); + return AjaxResult.success(list); + } + + @GetMapping("/timeSlot") + @ApiOperation("获取各时段服药统计") + public AjaxResult getTimeSlotAdherence( + @RequestParam(required = false) Long personId, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) { + Map map = adherenceService.getTimeSlotAdherence(personId, startDate, endDate); + return AjaxResult.success(map); + } + + @GetMapping("/dashboard") + @ApiOperation("获取仪表盘概览数据") + public AjaxResult getDashboard(@RequestParam(required = false) Long personId) { + LocalDate today = LocalDate.now(); + LocalDate weekAgo = today.minusDays(7); + + Map result = new java.util.HashMap<>(); + result.put("overall", adherenceService.getOverallAdherence(personId, weekAgo, today)); + result.put("dailyTrend", adherenceService.getDailyAdherenceList(personId, weekAgo, today)); + result.put("timeSlot", adherenceService.getTimeSlotAdherence(personId, weekAgo, today)); + + return AjaxResult.success(result); + } +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/controller/MedicationPlanController.java b/intc-modules/intc-health/src/main/java/com/intc/health/controller/MedicationPlanController.java new file mode 100644 index 0000000..4f3ff56 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/controller/MedicationPlanController.java @@ -0,0 +1,97 @@ +package com.intc.health.controller; + +import com.intc.common.core.web.controller.BaseController; +import com.intc.common.core.web.domain.AjaxResult; +import com.intc.common.core.web.page.TableDataInfo; +import com.intc.common.log.annotation.Log; +import com.intc.common.log.enums.BusinessType; +import com.intc.common.security.annotation.RequiresPermissions; +import com.intc.health.domain.MedicationPlan; +import com.intc.health.domain.dto.MedicationPlanDto; +import com.intc.health.domain.vo.MedicationPlanVo; +import com.intc.health.service.IMedicationPlanService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 用药计划Controller + * + * @author bot5 + * @date 2026-03-19 + */ +@RestController +@RequestMapping("/medicationPlan") +@Api(tags = "用药计划管理") +public class MedicationPlanController extends BaseController { + + @Resource + private IMedicationPlanService planService; + + @GetMapping("/list") + @ApiOperation("查询用药计划列表") + @RequiresPermissions("health:medicationPlan:list") + public TableDataInfo list(MedicationPlanDto dto) { + startPage(); + List list = planService.selectList(dto); + return getDataTable(list); + } + + @GetMapping("/{id}") + @ApiOperation("查询用药计划详情") + @RequiresPermissions("health:medicationPlan:query") + public AjaxResult getInfo(@PathVariable Long id) { + return success(planService.selectById(id)); + } + + @PostMapping + @ApiOperation("新增用药计划") + @RequiresPermissions("health:medicationPlan:add") + @Log(title = "用药计划", businessType = BusinessType.INSERT) + public AjaxResult add(@RequestBody MedicationPlan plan) { + return toAjax(planService.insert(plan)); + } + + @PutMapping + @ApiOperation("修改用药计划") + @RequiresPermissions("health:medicationPlan:edit") + @Log(title = "用药计划", businessType = BusinessType.UPDATE) + public AjaxResult edit(@RequestBody MedicationPlan plan) { + return toAjax(planService.update(plan)); + } + + @DeleteMapping("/{ids}") + @ApiOperation("删除用药计划") + @RequiresPermissions("health:medicationPlan:remove") + @Log(title = "用药计划", businessType = BusinessType.DELETE) + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(planService.deleteByIds(ids)); + } + + @PutMapping("/pause/{id}") + @ApiOperation("暂停用药计划") + @RequiresPermissions("health:medicationPlan:edit") + @Log(title = "用药计划", businessType = BusinessType.UPDATE) + public AjaxResult pause(@PathVariable Long id) { + return toAjax(planService.pause(id)); + } + + @PutMapping("/resume/{id}") + @ApiOperation("恢复用药计划") + @RequiresPermissions("health:medicationPlan:edit") + @Log(title = "用药计划", businessType = BusinessType.UPDATE) + public AjaxResult resume(@PathVariable Long id) { + return toAjax(planService.resume(id)); + } + + @PutMapping("/end/{id}") + @ApiOperation("结束用药计划") + @RequiresPermissions("health:medicationPlan:edit") + @Log(title = "用药计划", businessType = BusinessType.UPDATE) + public AjaxResult end(@PathVariable Long id) { + return toAjax(planService.end(id)); + } +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/controller/MedicationTaskController.java b/intc-modules/intc-health/src/main/java/com/intc/health/controller/MedicationTaskController.java new file mode 100644 index 0000000..15c8f8b --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/controller/MedicationTaskController.java @@ -0,0 +1,82 @@ +package com.intc.health.controller; + +import com.intc.common.core.web.controller.BaseController; +import com.intc.common.core.web.domain.AjaxResult; +import com.intc.common.core.web.page.TableDataInfo; +import com.intc.common.log.annotation.Log; +import com.intc.common.log.enums.BusinessType; +import com.intc.common.security.annotation.RequiresPermissions; +import com.intc.health.domain.dto.MedicationTaskDto; +import com.intc.health.domain.vo.MedicationTaskVo; +import com.intc.health.service.IMedicationTaskService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 服药任务Controller + * + * @author bot5 + * @date 2026-03-19 + */ +@RestController +@RequestMapping("/medicationTask") +@Api(tags = "服药任务管理") +public class MedicationTaskController extends BaseController { + + @Resource + private IMedicationTaskService taskService; + + @GetMapping("/list") + @ApiOperation("查询服药任务列表") + @RequiresPermissions("health:medicationTask:list") + public TableDataInfo list(MedicationTaskDto dto) { + startPage(); + List list = taskService.selectList(dto); + return getDataTable(list); + } + + @GetMapping("/{id}") + @ApiOperation("查询服药任务详情") + @RequiresPermissions("health:medicationTask:query") + public AjaxResult getInfo(@PathVariable Long id) { + return success(taskService.selectById(id)); + } + + @DeleteMapping("/{ids}") + @ApiOperation("删除服药任务") + @RequiresPermissions("health:medicationTask:remove") + @Log(title = "服药任务", businessType = BusinessType.DELETE) + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(taskService.deleteByIds(ids)); + } + + @PutMapping("/confirm/{id}") + @ApiOperation("确认服药") + @RequiresPermissions("health:medicationTask:edit") + @Log(title = "服药任务", businessType = BusinessType.UPDATE) + public AjaxResult confirm(@PathVariable Long id, + @RequestParam(required = false) Integer confirmType, + @RequestParam(required = false) String confirmTime) { + return toAjax(taskService.confirm(id, confirmType, confirmTime)); + } + + @PutMapping("/skip/{id}") + @ApiOperation("跳过服药") + @RequiresPermissions("health:medicationTask:edit") + @Log(title = "服药任务", businessType = BusinessType.UPDATE) + public AjaxResult skip(@PathVariable Long id, @RequestParam(required = false) String notes) { + return toAjax(taskService.skip(id, notes)); + } + + @PutMapping("/missed/{id}") + @ApiOperation("标记漏服") + @RequiresPermissions("health:medicationTask:edit") + @Log(title = "服药任务", businessType = BusinessType.UPDATE) + public AjaxResult missed(@PathVariable Long id, @RequestParam(required = false) String notes) { + return toAjax(taskService.markMissed(id, notes)); + } +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/ChronicDiseaseRecord.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/ChronicDiseaseRecord.java new file mode 100644 index 0000000..a9ec46d --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/ChronicDiseaseRecord.java @@ -0,0 +1,58 @@ +package com.intc.health.domain; + +import com.intc.common.core.web.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 慢性疾病档案对象 chronic_disease_record + * + * @author bot5 + * @date 2026-03-19 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("慢性疾病档案") +public class ChronicDiseaseRecord extends BaseEntity { + + @ApiModelProperty("主键ID") + private Long id; + + @ApiModelProperty("关联成员ID") + private Long personId; + + @ApiModelProperty("关联疾病ID") + private Long diseaseId; + + @ApiModelProperty("疾病名称") + private String diseaseName; + + @ApiModelProperty("疾病编码(ICD-10标准)") + private String diseaseCode; + + @ApiModelProperty("确诊日期") + private String diagnosisDate; + + @ApiModelProperty("状态:1-稳定 2-需关注 3-急性期") + private Integer diseaseStatus; + + @ApiModelProperty("主治医生") + private String mainDoctor; + + @ApiModelProperty("就诊医院") + private String hospital; + + @ApiModelProperty("科室") + private String department; + + @ApiModelProperty("备注说明") + private String notes; + + @ApiModelProperty("是否启用:1-启用 0-禁用") + private Integer isActive; + + @ApiModelProperty("删除标记:0-正常 1-删除") + private Integer delFlag; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/MedicationPlan.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/MedicationPlan.java new file mode 100644 index 0000000..76460b3 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/MedicationPlan.java @@ -0,0 +1,83 @@ +package com.intc.health.domain; + +import com.fasterxml.jackson.databind.JsonNode; +import com.intc.common.core.web.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.time.LocalDate; + +/** + * 用药计划对象 medication_plan + * + * @author bot5 + * @date 2026-03-19 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("用药计划") +public class MedicationPlan extends BaseEntity { + + @ApiModelProperty("主键ID") + private Long id; + + @ApiModelProperty("关联成员ID") + private Long personId; + + @ApiModelProperty("关联慢性疾病ID") + private Long chronicDiseaseId; + + @ApiModelProperty("计划名称") + private String planName; + + @ApiModelProperty("关联药品ID") + private Long medicineId; + + @ApiModelProperty("药品名称") + private String medicineName; + + @ApiModelProperty("药品规格") + private String specification; + + @ApiModelProperty("单次剂量") + private BigDecimal dosagePerTime; + + @ApiModelProperty("剂量单位") + private String dosageUnit; + + @ApiModelProperty("频次类型:1-每天 2-每周 3-隔天 4-自定义") + private Integer frequencyType; + + @ApiModelProperty("频次配置(JSON格式)") + private JsonNode frequencyConfig; + + @ApiModelProperty("服药时段配置") + private JsonNode timeSlots; + + @ApiModelProperty("开始日期") + private LocalDate startDate; + + @ApiModelProperty("结束日期") + private LocalDate endDate; + + @ApiModelProperty("是否开启提醒:1-是 0-否") + private Integer remindEnabled; + + @ApiModelProperty("提前提醒分钟数") + private Integer remindAdvanceMin; + + @ApiModelProperty("漏服是否通知家属:1-是 0-否") + private Integer notifyFamily; + + @ApiModelProperty("通知家属成员ID列表") + private JsonNode familyMemberIds; + + @ApiModelProperty("状态:1-执行中 2-已暂停 3-已结束") + private Integer status; + + @ApiModelProperty("删除标记:0-正常 1-删除") + private Integer delFlag; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/MedicationTask.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/MedicationTask.java new file mode 100644 index 0000000..235e3cb --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/MedicationTask.java @@ -0,0 +1,69 @@ +package com.intc.health.domain; + +import com.intc.common.core.web.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; + +/** + * 服药任务对象 medication_task + * + * @author bot5 + * @date 2026-03-19 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel("服药任务") +public class MedicationTask extends BaseEntity { + + @ApiModelProperty("主键ID") + private Long id; + + @ApiModelProperty("关联用药计划ID") + private Long planId; + + @ApiModelProperty("关联成员ID") + private Long personId; + + @ApiModelProperty("药品名称") + private String medicineName; + + @ApiModelProperty("计划服药日期") + private LocalDate plannedDate; + + @ApiModelProperty("计划服药时间") + private LocalTime plannedTime; + + @ApiModelProperty("计划剂量") + private BigDecimal plannedDosage; + + @ApiModelProperty("实际服药时间") + private LocalDateTime actualTime; + + @ApiModelProperty("实际剂量") + private BigDecimal actualDosage; + + @ApiModelProperty("状态:0-待服药 1-已服药 2-漏服 3-跳过") + private Integer status; + + @ApiModelProperty("确认方式:1-用户确认 2-家属代确认 3-系统自动") + private Integer confirmType; + + @ApiModelProperty("备注(如漏服原因)") + private String notes; + + @ApiModelProperty("提醒状态:0-未提醒 1-已提醒 2-已超时提醒") + private Integer remindStatus; + + @ApiModelProperty("提醒时间") + private LocalDateTime remindTime; + + @ApiModelProperty("删除标记:0-正常 1-删除") + private Integer delFlag; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/ChronicDiseaseRecordDto.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/ChronicDiseaseRecordDto.java new file mode 100644 index 0000000..e2d8b76 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/ChronicDiseaseRecordDto.java @@ -0,0 +1,51 @@ +package com.intc.health.domain.dto; + +import lombok.Data; +import java.io.Serializable; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.util.Date; + +/** + * 慢性疾病档案Dto对象 chronic_disease_record + * + * @author bot5 + * @date 2026-03-17 + */ +@ApiModel("慢性疾病档案Dto对象") +@Data +public class ChronicDiseaseRecordDto implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 关联成员ID */ + @ApiModelProperty(value = "关联成员ID") + private Long personId; + + /** 疾病名称 */ + @ApiModelProperty(value = "疾病名称") + private String diseaseName; + + /** 疾病编码 */ + @ApiModelProperty(value = "疾病编码") + private String diseaseCode; + + /** 状态:1-稳定 2-需关注 3-急性期 */ + @ApiModelProperty(value = "状态") + private Integer diseaseStatus; + + /** 是否启用 */ + @ApiModelProperty(value = "是否启用") + private Integer isActive; + + /** 确诊日期-开始 */ + @ApiModelProperty(value = "确诊日期-开始") + @JsonFormat(pattern = "yyyy-MM-dd") + private Date diagnosisDateStart; + + /** 确诊日期-结束 */ + @ApiModelProperty(value = "确诊日期-结束") + @JsonFormat(pattern = "yyyy-MM-dd") + private Date diagnosisDateEnd; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/MedicationAdherenceDto.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/MedicationAdherenceDto.java new file mode 100644 index 0000000..422edae --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/MedicationAdherenceDto.java @@ -0,0 +1,34 @@ +package com.intc.health.domain.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 依从性统计查询DTO + * + * @author bot5 + * @date 2026-03-19 + */ +@Data +@ApiModel("依从性统计查询DTO") +public class MedicationAdherenceDto { + + @ApiModelProperty("人员ID") + private Long personId; + + @ApiModelProperty("计划ID") + private Long planId; + + @ApiModelProperty("开始日期(yyyy-MM-dd)") + private String startDate; + + @ApiModelProperty("结束日期(yyyy-MM-dd)") + private String endDate; + + @ApiModelProperty("统计类型:day-日,week-周,month-月,quarter-季度") + private String statType; + + @ApiModelProperty("药品ID") + private Long medicineId; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/MedicationPlanDto.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/MedicationPlanDto.java new file mode 100644 index 0000000..bb49c58 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/MedicationPlanDto.java @@ -0,0 +1,56 @@ +package com.intc.health.domain.dto; + +import com.fasterxml.jackson.databind.JsonNode; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; + +/** + * 用药计划查询DTO + * + * @author bot5 + * @date 2026-03-19 + */ +@Data +@ApiModel("用药计划查询DTO") +public class MedicationPlanDto { + + @ApiModelProperty("主键ID") + private Long id; + + @ApiModelProperty("关联成员ID") + private Long personId; + + @ApiModelProperty("关联慢性疾病ID") + private Long chronicDiseaseId; + + @ApiModelProperty("计划名称") + private String planName; + + @ApiModelProperty("关联药品ID") + private Long medicineId; + + @ApiModelProperty("药品名称") + private String medicineName; + + @ApiModelProperty("状态:1-执行中 2-已暂停 3-已结束") + private Integer status; + + @ApiModelProperty("开始日期-起") + private String startDateBegin; + + @ApiModelProperty("开始日期-止") + private String startDateEnd; + + @ApiModelProperty("结束日期-起") + private String endDateBegin; + + @ApiModelProperty("结束日期-止") + private String endDateEnd; + + @ApiModelProperty("关键字搜索") + private String keyword; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/MedicationTaskDto.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/MedicationTaskDto.java new file mode 100644 index 0000000..13a5a4d --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/dto/MedicationTaskDto.java @@ -0,0 +1,42 @@ +package com.intc.health.domain.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.time.LocalDate; + +/** + * 服药任务查询DTO + * + * @author bot5 + * @date 2026-03-19 + */ +@Data +@ApiModel("服药任务查询DTO") +public class MedicationTaskDto { + + @ApiModelProperty("关联用药计划ID") + private Long planId; + + @ApiModelProperty("关联成员ID") + private Long personId; + + @ApiModelProperty("药品名称") + private String medicineName; + + @ApiModelProperty("计划服药日期-起") + private String plannedDateBegin; + + @ApiModelProperty("计划服药日期-止") + private String plannedDateEnd; + + @ApiModelProperty("状态:0-待服药 1-已服药 2-漏服 3-跳过") + private Integer status; + + @ApiModelProperty("查询日期(yyyy-MM-dd)") + private String queryDate; + + @ApiModelProperty("关键字搜索") + private String keyword; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/ChronicDiseaseRecordVo.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/ChronicDiseaseRecordVo.java new file mode 100644 index 0000000..dda41d7 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/ChronicDiseaseRecordVo.java @@ -0,0 +1,22 @@ +package com.intc.health.domain.vo; + +import com.intc.health.domain.ChronicDiseaseRecord; +import lombok.Data; +import io.swagger.annotations.ApiModel; + +/** + * 慢性疾病档案Vo对象 chronic_disease_record + * + * @author bot5 + * @date 2026-03-17 + */ +@ApiModel("慢性疾病档案Vo对象") +@Data +public class ChronicDiseaseRecordVo extends ChronicDiseaseRecord +{ + private static final long serialVersionUID = 1L; + + /** 成员姓名(来自health_person表) */ + private String personName; + +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/DailyAdherenceVo.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/DailyAdherenceVo.java new file mode 100644 index 0000000..94598c5 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/DailyAdherenceVo.java @@ -0,0 +1,42 @@ +package com.intc.health.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 每日依从性统计VO + * + * @author bot5 + * @date 2026-03-19 + */ +@Data +@ApiModel("每日依从性统计VO") +public class DailyAdherenceVo { + + @ApiModelProperty("日期") + private String date; + + @ApiModelProperty("总任务数") + private Integer totalTasks; + + @ApiModelProperty("已服药数") + private Integer completedTasks; + + @ApiModelProperty("漏服数") + private Integer missedTasks; + + @ApiModelProperty("跳过数") + private Integer skippedTasks; + + @ApiModelProperty("准时服药数") + private Integer ontimeTasks; + + @ApiModelProperty("服药率(%)") + private BigDecimal adherenceRate; + + @ApiModelProperty("准时率(%)") + private BigDecimal ontimeRate; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/MedicationAdherenceVo.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/MedicationAdherenceVo.java new file mode 100644 index 0000000..88db068 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/MedicationAdherenceVo.java @@ -0,0 +1,60 @@ +package com.intc.health.domain.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 依从性统计VO + * + * @author bot5 + * @date 2026-03-19 + */ +@Data +@ApiModel("依从性统计VO") +public class MedicationAdherenceVo { + + @ApiModelProperty("总任务数") + private Integer totalTasks; + + @ApiModelProperty("已服药数") + private Integer completedTasks; + + @ApiModelProperty("漏服数") + private Integer missedTasks; + + @ApiModelProperty("跳过数") + private Integer skippedTasks; + + @ApiModelProperty("待服用数") + private Integer pendingTasks; + + @ApiModelProperty("准时服药数") + private Integer ontimeTasks; + + @ApiModelProperty("服药率(%)") + private BigDecimal adherenceRate; + + @ApiModelProperty("准时率(%)") + private BigDecimal ontimeRate; + + @ApiModelProperty("统计开始日期") + private String startDate; + + @ApiModelProperty("统计结束日期") + private String endDate; + + @ApiModelProperty("人员ID") + private Long personId; + + @ApiModelProperty("人员名称") + private String personName; + + @ApiModelProperty("计划ID") + private Long planId; + + @ApiModelProperty("计划名称") + private String planName; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/MedicationPlanVo.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/MedicationPlanVo.java new file mode 100644 index 0000000..11615f8 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/MedicationPlanVo.java @@ -0,0 +1,96 @@ +package com.intc.health.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.JsonNode; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 用药计划VO + * + * @author bot5 + * @date 2026-03-19 + */ +@Data +@ApiModel("用药计划VO") +public class MedicationPlanVo { + + @ApiModelProperty("主键ID") + private Long id; + + @ApiModelProperty("关联成员ID") + private Long personId; + + @ApiModelProperty("成员名称") + private String personName; + + @ApiModelProperty("关联慢性疾病ID") + private Long chronicDiseaseId; + + @ApiModelProperty("慢性疾病名称") + private String chronicDiseaseName; + + @ApiModelProperty("计划名称") + private String planName; + + @ApiModelProperty("关联药品ID") + private Long medicineId; + + @ApiModelProperty("药品名称") + private String medicineName; + + @ApiModelProperty("药品规格") + private String specification; + + @ApiModelProperty("单次剂量") + private BigDecimal dosagePerTime; + + @ApiModelProperty("剂量单位") + private String dosageUnit; + + @ApiModelProperty("频次类型:1-每天 2-每周 3-隔天 4-自定义") + private Integer frequencyType; + + @ApiModelProperty("频次配置(JSON格式)") + private JsonNode frequencyConfig; + + @ApiModelProperty("服药时段配置") + private JsonNode timeSlots; + + @ApiModelProperty("开始日期") + @JsonFormat(pattern = "yyyy-MM-dd") + private Date startDate; + + @ApiModelProperty("结束日期") + @JsonFormat(pattern = "yyyy-MM-dd") + private Date endDate; + + @ApiModelProperty("是否开启提醒:1-是 0-否") + private Integer remindEnabled; + + @ApiModelProperty("提前提醒分钟数") + private Integer remindAdvanceMin; + + @ApiModelProperty("漏服是否通知家属:1-是 0-否") + private Integer notifyFamily; + + @ApiModelProperty("通知家属成员ID列表") + private JsonNode familyMemberIds; + + @ApiModelProperty("状态:1-执行中 2-已暂停 3-已结束") + private Integer status; + + @ApiModelProperty("今日任务统计") + private Integer todayTotal; + + @ApiModelProperty("今日已完成") + private Integer todayCompleted; + + @ApiModelProperty("创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/MedicationTaskVo.java b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/MedicationTaskVo.java new file mode 100644 index 0000000..9cb2312 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/domain/vo/MedicationTaskVo.java @@ -0,0 +1,79 @@ +package com.intc.health.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 服药任务VO + * + * @author bot5 + * @date 2026-03-19 + */ +@Data +@ApiModel("服药任务VO") +public class MedicationTaskVo { + + @ApiModelProperty("主键ID") + private Long id; + + @ApiModelProperty("关联用药计划ID") + private Long planId; + + @ApiModelProperty("计划名称") + private String planName; + + @ApiModelProperty("关联成员ID") + private Long personId; + + @ApiModelProperty("成员名称") + private String personName; + + @ApiModelProperty("药品名称") + private String medicineName; + + @ApiModelProperty("计划服药日期") + @JsonFormat(pattern = "yyyy-MM-dd") + private Date plannedDate; + + @ApiModelProperty("计划服药时间") + @JsonFormat(pattern = "HH:mm:ss") + private Date plannedTime; + + @ApiModelProperty("计划剂量") + private BigDecimal plannedDosage; + + @ApiModelProperty("实际服药时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date actualTime; + + @ApiModelProperty("实际剂量") + private BigDecimal actualDosage; + + @ApiModelProperty("状态:0-待服药 1-已服药 2-漏服 3-跳过") + private Integer status; + + @ApiModelProperty("状态名称") + private String statusName; + + @ApiModelProperty("确认方式:1-用户确认 2-家属代确认 3-系统自动") + private Integer confirmType; + + @ApiModelProperty("备注(如漏服原因)") + private String notes; + + @ApiModelProperty("提醒状态:0-未提醒 1-已提醒 2-已超时提醒") + private Integer remindStatus; + + @ApiModelProperty("提醒时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date remindTime; + + @ApiModelProperty("创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/mapper/ChronicDiseaseRecordMapper.java b/intc-modules/intc-health/src/main/java/com/intc/health/mapper/ChronicDiseaseRecordMapper.java new file mode 100644 index 0000000..8680d1b --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/mapper/ChronicDiseaseRecordMapper.java @@ -0,0 +1,89 @@ +package com.intc.health.mapper; + +import com.intc.common.datascope.annotation.DataScope; +import java.util.List; +import com.intc.health.domain.vo.ChronicDiseaseRecordVo; +import com.intc.health.domain.dto.ChronicDiseaseRecordDto; +import org.apache.ibatis.annotations.Param; + +/** + * 慢性疾病档案Mapper接口 + * + * @author bot5 + * @date 2026-03-17 + */ +public interface ChronicDiseaseRecordMapper +{ + /** + * 查询慢性疾病档案 + * + * @param id 慢性疾病档案主键 + * @return 慢性疾病档案 + */ + public ChronicDiseaseRecordVo selectChronicDiseaseRecordById(Long id); + + /** + * 查询慢性疾病档案列表 + * + * @param chronicDiseaseRecordDto 慢性疾病档案 + * @return 慢性疾病档案集合 + */ + @DataScope(businessAlias = "a") + public List selectChronicDiseaseRecordList(ChronicDiseaseRecordDto chronicDiseaseRecordDto); + + /** + * 根据成员ID查询慢性疾病档案列表 + * + * @param memberId 成员ID + * @return 慢性疾病档案集合 + */ + public List selectChronicDiseaseRecordByMemberId(Long memberId); + + /** + * 新增慢性疾病档案 + * + * @param chronicDiseaseRecord 慢性疾病档案 + * @return 结果 + */ + public int insertChronicDiseaseRecord(com.intc.health.domain.ChronicDiseaseRecord chronicDiseaseRecord); + + /** + * 修改慢性疾病档案 + * + * @param chronicDiseaseRecord 慢性疾病档案 + * @return 结果 + */ + public int updateChronicDiseaseRecord(com.intc.health.domain.ChronicDiseaseRecord chronicDiseaseRecord); + + /** + * 删除慢性疾病档案 + * + * @param id 慢性疾病档案主键 + * @return 结果 + */ + public int deleteChronicDiseaseRecordById(Long id); + + /** + * 批量删除慢性疾病档案 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteChronicDiseaseRecordByIds(Long[] ids); + + /** + * 逻辑删除慢性疾病档案 + * + * @param id 慢性疾病档案主键 + * @return 结果 + */ + public int removeChronicDiseaseRecordById(Long id); + + /** + * 批量逻辑删除慢性疾病档案 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int removeChronicDiseaseRecordByIds(Long[] ids); +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/mapper/MedicationPlanMapper.java b/intc-modules/intc-health/src/main/java/com/intc/health/mapper/MedicationPlanMapper.java new file mode 100644 index 0000000..44c56b3 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/mapper/MedicationPlanMapper.java @@ -0,0 +1,64 @@ +package com.intc.health.mapper; + +import com.intc.common.datascope.annotation.DataScope; +import com.intc.health.domain.MedicationPlan; +import com.intc.health.domain.dto.MedicationPlanDto; +import com.intc.health.domain.vo.MedicationPlanVo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 用药计划Mapper接口 + * + * @author bot5 + * @date 2026-03-19 + */ +public interface MedicationPlanMapper { + + /** + * 查询用药计划 + */ + MedicationPlanVo selectById(Long id); + + /** + * 查询用药计划列表 + */ + @DataScope(businessAlias = "a") + List selectList(MedicationPlanDto dto); + + /** + * 查询执行中的用药计划列表(用于生成任务) + */ + List selectActiveList(); + + /** + * 新增用药计划 + */ + int insert(MedicationPlan plan); + + /** + * 修改用药计划 + */ + int update(MedicationPlan plan); + + /** + * 删除用药计划 + */ + int deleteById(Long id); + + /** + * 批量删除用药计划 + */ + int deleteByIds(Long[] ids); + + /** + * 逻辑删除用药计划 + */ + int removeById(Long id); + + /** + * 批量逻辑删除用药计划 + */ + int removeByIds(Long[] ids); +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/mapper/MedicationTaskMapper.java b/intc-modules/intc-health/src/main/java/com/intc/health/mapper/MedicationTaskMapper.java new file mode 100644 index 0000000..993cdfd --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/mapper/MedicationTaskMapper.java @@ -0,0 +1,114 @@ +package com.intc.health.mapper; + +import com.intc.common.datascope.annotation.DataScope; +import com.intc.health.domain.MedicationTask; +import com.intc.health.domain.dto.MedicationTaskDto; +import com.intc.health.domain.vo.MedicationTaskVo; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 服药任务Mapper接口 + * + * @author bot5 + * @date 2026-03-19 + */ +public interface MedicationTaskMapper { + + /** + * 查询服药任务 + */ + MedicationTaskVo selectById(Long id); + + /** + * 查询服药任务列表 + */ + @DataScope(businessAlias = "a") + List selectList(MedicationTaskDto dto); + + /** + * 新增服药任务 + */ + int insert(MedicationTask task); + + /** + * 批量新增服药任务 + */ + int batchInsert(List tasks); + + /** + * 修改服药任务 + */ + int update(MedicationTask task); + + /** + * 删除服药任务 + */ + int deleteById(Long id); + + /** + * 批量删除服药任务 + */ + int deleteByIds(Long[] ids); + + /** + * 逻辑删除服药任务 + */ + int removeById(Long id); + + /** + * 批量逻辑删除服药任务 + */ + int removeByIds(Long[] ids); + + /** + * 统计某计划某天的任务数 + */ + int countByPlanAndDate(@Param("planId") Long planId, @Param("date") LocalDate date); + + /** + * 删除某计划某天的所有任务(用于重新生成时清理旧数据) + */ + int deleteByPlanAndDate(@Param("planId") Long planId, @Param("date") LocalDate date); + + /** + * 统计某计划某天的已服药数 + */ + int countCompletedByPlanAndDate(@Param("planId") Long planId, @Param("date") LocalDate date); + + /** + * 标记过期任务(超过24小时未服药) + */ + int markExpired(@Param("expireThreshold") LocalDateTime threshold); + + /** + * 查询待提醒的任务 + */ + List selectPendingRemind(@Param("startTime") LocalDateTime startTime, @Param("endTime") LocalDateTime endTime); + + // ========== 依从性统计 ========== + + /** + * 获取总体统计 + */ + java.util.Map getOverallStats(@Param("personId") Long personId, + @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate); + + /** + * 获取每日统计 + */ + List> getDailyStats(@Param("personId") Long personId, + @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate); + + /** + * 获取时段统计 + */ + List> getTimeSlotStats(@Param("personId") Long personId, + @Param("startDate") LocalDate startDate, + @Param("endDate") LocalDate endDate); +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/service/IChronicDiseaseRecordService.java b/intc-modules/intc-health/src/main/java/com/intc/health/service/IChronicDiseaseRecordService.java new file mode 100644 index 0000000..8a07b49 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/service/IChronicDiseaseRecordService.java @@ -0,0 +1,71 @@ +package com.intc.health.service; + +import java.util.List; +import com.intc.health.domain.ChronicDiseaseRecord; +import com.intc.health.domain.dto.ChronicDiseaseRecordDto; +import com.intc.health.domain.vo.ChronicDiseaseRecordVo; + +/** + * 慢性疾病档案Service接口 + * + * @author bot5 + * @date 2026-03-17 + */ +public interface IChronicDiseaseRecordService +{ + /** + * 查询慢性疾病档案 + * + * @param id 慢性疾病档案主键 + * @return 慢性疾病档案 + */ + public ChronicDiseaseRecordVo selectChronicDiseaseRecordById(Long id); + + /** + * 查询慢性疾病档案列表 + * + * @param chronicDiseaseRecordDto 慢性疾病档案 + * @return 慢性疾病档案集合 + */ + public List selectChronicDiseaseRecordList(ChronicDiseaseRecordDto chronicDiseaseRecordDto); + + /** + * 根据成员ID查询慢性疾病档案列表 + * + * @param memberId 成员ID + * @return 慢性疾病档案集合 + */ + public List selectChronicDiseaseRecordByMemberId(Long memberId); + + /** + * 新增慢性疾病档案 + * + * @param chronicDiseaseRecord 慢性疾病档案 + * @return 结果 + */ + public int insertChronicDiseaseRecord(ChronicDiseaseRecord chronicDiseaseRecord); + + /** + * 修改慢性疾病档案 + * + * @param chronicDiseaseRecord 慢性疾病档案 + * @return 结果 + */ + public int updateChronicDiseaseRecord(ChronicDiseaseRecord chronicDiseaseRecord); + + /** + * 批量删除慢性疾病档案 + * + * @param ids 需要删除的慢性疾病档案主键集合 + * @return 结果 + */ + public int deleteChronicDiseaseRecordByIds(Long[] ids); + + /** + * 删除慢性疾病档案信息 + * + * @param id 慢性疾病档案主键 + * @return 结果 + */ + public int deleteChronicDiseaseRecordById(Long id); +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/service/IMedicationAdherenceService.java b/intc-modules/intc-health/src/main/java/com/intc/health/service/IMedicationAdherenceService.java new file mode 100644 index 0000000..8d4b5e4 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/service/IMedicationAdherenceService.java @@ -0,0 +1,37 @@ +package com.intc.health.service; + +import com.intc.health.domain.vo.DailyAdherenceVo; +import com.intc.health.domain.vo.MedicationAdherenceVo; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +/** + * 依从性统计Service接口 + * + * @author bot5 + * @date 2026-03-19 + */ +public interface IMedicationAdherenceService { + + /** + * 获取总体依从性统计 + */ + MedicationAdherenceVo getOverallAdherence(Long memberId, LocalDate startDate, LocalDate endDate); + + /** + * 获取每日依从性统计列表 + */ + List getDailyAdherenceList(Long memberId, LocalDate startDate, LocalDate endDate); + + /** + * 获取日历视图数据 + */ + List getCalendarData(Long memberId, String yearMonth); + + /** + * 获取各时段服药统计 + */ + Map getTimeSlotAdherence(Long memberId, LocalDate startDate, LocalDate endDate); +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/service/IMedicationPlanService.java b/intc-modules/intc-health/src/main/java/com/intc/health/service/IMedicationPlanService.java new file mode 100644 index 0000000..0a5df55 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/service/IMedicationPlanService.java @@ -0,0 +1,56 @@ +package com.intc.health.service; + +import com.intc.health.domain.MedicationPlan; +import com.intc.health.domain.dto.MedicationPlanDto; +import com.intc.health.domain.vo.MedicationPlanVo; + +import java.util.List; + +/** + * 用药计划Service接口 + * + * @author bot5 + * @date 2026-03-19 + */ +public interface IMedicationPlanService { + + /** + * 查询用药计划 + */ + MedicationPlanVo selectById(Long id); + + /** + * 查询用药计划列表 + */ + List selectList(MedicationPlanDto dto); + + /** + * 新增用药计划 + */ + int insert(MedicationPlan plan); + + /** + * 修改用药计划 + */ + int update(MedicationPlan plan); + + /** + * 删除用药计划 + */ + int deleteByIds(Long[] ids); + + /** + * 暂停用药计划 + */ + int pause(Long id); + + /** + * 恢复用药计划 + */ + int resume(Long id); + + /** + * 结束用药计划 + */ + int end(Long id); +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/service/IMedicationTaskService.java b/intc-modules/intc-health/src/main/java/com/intc/health/service/IMedicationTaskService.java new file mode 100644 index 0000000..af445b9 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/service/IMedicationTaskService.java @@ -0,0 +1,56 @@ +package com.intc.health.service; + +import com.intc.health.domain.MedicationTask; +import com.intc.health.domain.dto.MedicationTaskDto; +import com.intc.health.domain.vo.MedicationTaskVo; + +import java.util.List; + +/** + * 服药任务Service接口 + * + * @author bot5 + * @date 2026-03-19 + */ +public interface IMedicationTaskService { + + /** + * 查询服药任务 + */ + MedicationTaskVo selectById(Long id); + + /** + * 查询服药任务列表 + */ + List selectList(MedicationTaskDto dto); + + /** + * 新增服药任务 + */ + int insert(MedicationTask task); + + /** + * 修改服药任务 + */ + int update(MedicationTask task); + + /** + * 删除服药任务 + */ + int deleteByIds(Long[] ids); + + /** + * 确认服药 + */ + int confirm(Long id, Integer confirmType, String confirmTime); + + /** + * 跳过服药 + */ + int skip(Long id, String notes); + + /** + * 标记漏服 + */ + int markMissed(Long id, String notes); +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/ChronicDiseaseRecordServiceImpl.java b/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/ChronicDiseaseRecordServiceImpl.java new file mode 100644 index 0000000..8c6de32 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/ChronicDiseaseRecordServiceImpl.java @@ -0,0 +1,124 @@ +package com.intc.health.service.impl; + +import com.intc.common.core.utils.DateUtils; +import com.intc.common.core.utils.IdWorker; +import com.intc.common.security.utils.SecurityUtils; +import com.intc.health.domain.ChronicDiseaseRecord; +import com.intc.health.domain.dto.ChronicDiseaseRecordDto; +import com.intc.health.domain.vo.ChronicDiseaseRecordVo; +import com.intc.health.mapper.ChronicDiseaseRecordMapper; +import com.intc.health.service.IChronicDiseaseRecordService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 慢性疾病档案Service业务层处理 + * + * @author bot5 + * @date 2026-03-17 + */ +@Service +public class ChronicDiseaseRecordServiceImpl implements IChronicDiseaseRecordService +{ + @Resource + private ChronicDiseaseRecordMapper chronicDiseaseRecordMapper; + + /** + * 查询慢性疾病档案 + * + * @param id 慢性疾病档案主键 + * @return 慢性疾病档案 + */ + @Override + public ChronicDiseaseRecordVo selectChronicDiseaseRecordById(Long id) + { + return chronicDiseaseRecordMapper.selectChronicDiseaseRecordById(id); + } + + /** + * 查询慢性疾病档案列表 + * + * @param chronicDiseaseRecordDto 慢性疾病档案 + * @return 慢性疾病档案 + */ + @Override + public List selectChronicDiseaseRecordList(ChronicDiseaseRecordDto chronicDiseaseRecordDto) + { + return chronicDiseaseRecordMapper.selectChronicDiseaseRecordList(chronicDiseaseRecordDto); + } + + /** + * 根据成员ID查询慢性疾病档案列表 + * + * @param memberId 成员ID + * @return 慢性疾病档案集合 + */ + @Override + public List selectChronicDiseaseRecordByMemberId(Long memberId) + { + return chronicDiseaseRecordMapper.selectChronicDiseaseRecordByMemberId(memberId); + } + + /** + * 新增慢性疾病档案 + * + * @param chronicDiseaseRecord 慢性疾病档案 + * @return 结果 + */ + @Override + public int insertChronicDiseaseRecord(ChronicDiseaseRecord chronicDiseaseRecord) + { + chronicDiseaseRecord.setCreateBy(SecurityUtils.getUsername()); + chronicDiseaseRecord.setCreateTime(DateUtils.getNowDate()); + chronicDiseaseRecord.setId(IdWorker.getId()); + // 默认启用 + if (chronicDiseaseRecord.getIsActive() == null) { + chronicDiseaseRecord.setIsActive(1); + } + // 默认状态为稳定 + if (chronicDiseaseRecord.getDiseaseStatus() == null) { + chronicDiseaseRecord.setDiseaseStatus(1); + } + return chronicDiseaseRecordMapper.insertChronicDiseaseRecord(chronicDiseaseRecord); + } + + /** + * 修改慢性疾病档案 + * + * @param chronicDiseaseRecord 慢性疾病档案 + * @return 结果 + */ + @Override + public int updateChronicDiseaseRecord(ChronicDiseaseRecord chronicDiseaseRecord) + { + chronicDiseaseRecord.setUpdateBy(SecurityUtils.getUsername()); + chronicDiseaseRecord.setUpdateTime(DateUtils.getNowDate()); + return chronicDiseaseRecordMapper.updateChronicDiseaseRecord(chronicDiseaseRecord); + } + + /** + * 批量删除慢性疾病档案 + * + * @param ids 需要删除的慢性疾病档案主键 + * @return 结果 + */ + @Override + public int deleteChronicDiseaseRecordByIds(Long[] ids) + { + return chronicDiseaseRecordMapper.removeChronicDiseaseRecordByIds(ids); + } + + /** + * 删除慢性疾病档案信息 + * + * @param id 慢性疾病档案主键 + * @return 结果 + */ + @Override + public int deleteChronicDiseaseRecordById(Long id) + { + return chronicDiseaseRecordMapper.removeChronicDiseaseRecordById(id); + } +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/MedicationAdherenceServiceImpl.java b/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/MedicationAdherenceServiceImpl.java new file mode 100644 index 0000000..e16a7d6 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/MedicationAdherenceServiceImpl.java @@ -0,0 +1,203 @@ +package com.intc.health.service.impl; + +import com.intc.health.mapper.MedicationTaskMapper; +import com.intc.health.service.IMedicationAdherenceService; +import com.intc.health.domain.vo.MedicationAdherenceVo; +import com.intc.health.domain.vo.DailyAdherenceVo; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.*; + +/** + * 依从性统计Service实现 + * + * @author bot5 + * @date 2026-03-19 + */ +@Service +public class MedicationAdherenceServiceImpl implements IMedicationAdherenceService { + + @Resource + private MedicationTaskMapper taskMapper; + + @Override + public MedicationAdherenceVo getOverallAdherence(Long memberId, LocalDate startDate, LocalDate endDate) { + Map stats = taskMapper.getOverallStats(memberId, startDate, endDate); + + MedicationAdherenceVo vo = new MedicationAdherenceVo(); + vo.setTotalTasks(getIntValue(stats, "total_tasks")); + vo.setCompletedTasks(getIntValue(stats, "completed_tasks")); + vo.setMissedTasks(getIntValue(stats, "missed_tasks")); + vo.setSkippedTasks(getIntValue(stats, "skipped_tasks")); + vo.setPendingTasks(getIntValue(stats, "pending_tasks")); + vo.setOntimeTasks(getIntValue(stats, "ontime_tasks")); + + // 计算服药率 + if (vo.getTotalTasks() > 0) { + BigDecimal rate = BigDecimal.valueOf(vo.getCompletedTasks()) + .multiply(BigDecimal.valueOf(100)) + .divide(BigDecimal.valueOf(vo.getTotalTasks()), 2, RoundingMode.HALF_UP); + vo.setAdherenceRate(rate); + } else { + vo.setAdherenceRate(BigDecimal.ZERO); + } + + // 计算准时率 + if (vo.getCompletedTasks() > 0) { + BigDecimal rate = BigDecimal.valueOf(vo.getOntimeTasks()) + .multiply(BigDecimal.valueOf(100)) + .divide(BigDecimal.valueOf(vo.getCompletedTasks()), 2, RoundingMode.HALF_UP); + vo.setOntimeRate(rate); + } else { + vo.setOntimeRate(BigDecimal.ZERO); + } + + return vo; + } + + @Override + public List getDailyAdherenceList(Long memberId, LocalDate startDate, LocalDate endDate) { + List> dailyStats = taskMapper.getDailyStats(memberId, startDate, endDate); + + List result = new ArrayList<>(); + for (Map stat : dailyStats) { + DailyAdherenceVo vo = new DailyAdherenceVo(); + vo.setDate(getStringValue(stat, "stat_date")); + vo.setTotalTasks(getIntValue(stat, "total_tasks")); + vo.setCompletedTasks(getIntValue(stat, "completed_tasks")); + vo.setMissedTasks(getIntValue(stat, "missed_tasks")); + vo.setSkippedTasks(getIntValue(stat, "skipped_tasks")); + vo.setOntimeTasks(getIntValue(stat, "ontime_tasks")); + + if (vo.getTotalTasks() > 0) { + BigDecimal rate = BigDecimal.valueOf(vo.getCompletedTasks()) + .multiply(BigDecimal.valueOf(100)) + .divide(BigDecimal.valueOf(vo.getTotalTasks()), 2, RoundingMode.HALF_UP); + vo.setAdherenceRate(rate); + } else { + vo.setAdherenceRate(BigDecimal.ZERO); + } + + if (vo.getCompletedTasks() > 0) { + BigDecimal rate = BigDecimal.valueOf(vo.getOntimeTasks()) + .multiply(BigDecimal.valueOf(100)) + .divide(BigDecimal.valueOf(vo.getCompletedTasks()), 2, RoundingMode.HALF_UP); + vo.setOntimeRate(rate); + } else { + vo.setOntimeRate(BigDecimal.ZERO); + } + + result.add(vo); + } + + return result; + } + + @Override + public List getCalendarData(Long memberId, String yearMonth) { + YearMonth ym = YearMonth.parse(yearMonth, DateTimeFormatter.ofPattern("yyyy-MM")); + LocalDate startDate = ym.atDay(1); + LocalDate endDate = ym.atEndOfMonth(); + + List dailyList = getDailyAdherenceList(memberId, startDate, endDate); + + // 补全缺失日期 + List result = new ArrayList<>(); + Map dateMap = new HashMap<>(); + for (DailyAdherenceVo vo : dailyList) { + dateMap.put(vo.getDate(), vo); + } + + long days = ChronoUnit.DAYS.between(startDate, endDate) + 1; + for (int i = 0; i < days; i++) { + LocalDate date = startDate.plusDays(i); + String dateStr = date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + + if (dateMap.containsKey(dateStr)) { + result.add(dateMap.get(dateStr)); + } else { + DailyAdherenceVo vo = new DailyAdherenceVo(); + vo.setDate(dateStr); + vo.setTotalTasks(0); + vo.setCompletedTasks(0); + vo.setMissedTasks(0); + vo.setSkippedTasks(0); + vo.setOntimeTasks(0); + vo.setAdherenceRate(BigDecimal.ZERO); + vo.setOntimeRate(BigDecimal.ZERO); + result.add(vo); + } + } + + return result; + } + + @Override + public Map getTimeSlotAdherence(Long memberId, LocalDate startDate, LocalDate endDate) { + List> slotStats = taskMapper.getTimeSlotStats(memberId, startDate, endDate); + + Map result = new LinkedHashMap<>(); + result.put("morning", new MedicationAdherenceVo()); + result.put("afternoon", new MedicationAdherenceVo()); + result.put("evening", new MedicationAdherenceVo()); + result.put("night", new MedicationAdherenceVo()); + + for (Map stat : slotStats) { + int hour = getIntValue(stat, "hour"); + String slotKey = getTimeSlot(hour); + + MedicationAdherenceVo vo = result.get(slotKey); + if (vo.getTotalTasks() == null) { + vo.setTotalTasks(0); + vo.setCompletedTasks(0); + vo.setMissedTasks(0); + vo.setOntimeTasks(0); + } + + vo.setTotalTasks(vo.getTotalTasks() + getIntValue(stat, "total_tasks")); + vo.setCompletedTasks(vo.getCompletedTasks() + getIntValue(stat, "completed_tasks")); + vo.setMissedTasks(vo.getMissedTasks() + getIntValue(stat, "missed_tasks")); + vo.setOntimeTasks(vo.getOntimeTasks() + getIntValue(stat, "ontime_tasks")); + } + + for (Map.Entry entry : result.entrySet()) { + MedicationAdherenceVo vo = entry.getValue(); + if (vo.getTotalTasks() != null && vo.getTotalTasks() > 0) { + BigDecimal rate = BigDecimal.valueOf(vo.getCompletedTasks()) + .multiply(BigDecimal.valueOf(100)) + .divide(BigDecimal.valueOf(vo.getTotalTasks()), 2, RoundingMode.HALF_UP); + vo.setAdherenceRate(rate); + } else { + vo.setAdherenceRate(BigDecimal.ZERO); + } + } + + return result; + } + + private String getTimeSlot(int hour) { + if (hour >= 6 && hour < 12) return "morning"; + if (hour >= 12 && hour < 18) return "afternoon"; + if (hour >= 18 && hour < 24) return "evening"; + return "night"; + } + + private Integer getIntValue(Map map, String key) { + if (map == null || map.get(key) == null) return 0; + Object value = map.get(key); + if (value instanceof Number) return ((Number) value).intValue(); + return 0; + } + + private String getStringValue(Map map, String key) { + if (map == null || map.get(key) == null) return null; + return map.get(key).toString(); + } +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/MedicationPlanServiceImpl.java b/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/MedicationPlanServiceImpl.java new file mode 100644 index 0000000..8188bdb --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/MedicationPlanServiceImpl.java @@ -0,0 +1,100 @@ +package com.intc.health.service.impl; + +import com.intc.common.core.exception.ServiceException; +import com.intc.common.core.utils.DateUtils; +import com.intc.common.security.utils.SecurityUtils; +import com.intc.health.domain.MedicationPlan; +import com.intc.health.domain.dto.MedicationPlanDto; +import com.intc.health.domain.vo.MedicationPlanVo; +import com.intc.health.mapper.MedicationPlanMapper; +import com.intc.health.service.IMedicationPlanService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 用药计划Service实现 + * + * @author bot5 + * @date 2026-03-19 + */ +@Service +public class MedicationPlanServiceImpl implements IMedicationPlanService { + + @Resource + private MedicationPlanMapper planMapper; + + @Override + public MedicationPlanVo selectById(Long id) { + return planMapper.selectById(id); + } + + @Override + public List selectList(MedicationPlanDto dto) { + return planMapper.selectList(dto); + } + + @Override + public int insert(MedicationPlan plan) { + if (plan.getPersonId() == null) { + throw new ServiceException("关联成员ID不能为空"); + } + if (plan.getMedicineName() == null || plan.getMedicineName().isEmpty()) { + throw new ServiceException("药品名称不能为空"); + } + if (plan.getFrequencyType() == null) { + throw new ServiceException("频次类型不能为空"); + } + if (plan.getStartDate() == null) { + throw new ServiceException("开始日期不能为空"); + } + plan.setStatus(1); // 默认执行中 + plan.setDelFlag(0); + plan.setCreateBy(SecurityUtils.getUsername()); + plan.setCreateTime(DateUtils.getNowDate()); + return planMapper.insert(plan); + } + + @Override + public int update(MedicationPlan plan) { + plan.setUpdateBy(SecurityUtils.getUsername()); + plan.setUpdateTime(DateUtils.getNowDate()); + return planMapper.update(plan); + } + + @Override + public int deleteByIds(Long[] ids) { + return planMapper.removeByIds(ids); + } + + @Override + public int pause(Long id) { + MedicationPlan plan = new MedicationPlan(); + plan.setId(id); + plan.setStatus(2); // 已暂停 + plan.setUpdateBy(SecurityUtils.getUsername()); + plan.setUpdateTime(DateUtils.getNowDate()); + return planMapper.update(plan); + } + + @Override + public int resume(Long id) { + MedicationPlan plan = new MedicationPlan(); + plan.setId(id); + plan.setStatus(1); // 执行中 + plan.setUpdateBy(SecurityUtils.getUsername()); + plan.setUpdateTime(DateUtils.getNowDate()); + return planMapper.update(plan); + } + + @Override + public int end(Long id) { + MedicationPlan plan = new MedicationPlan(); + plan.setId(id); + plan.setStatus(3); // 已结束 + plan.setUpdateBy(SecurityUtils.getUsername()); + plan.setUpdateTime(DateUtils.getNowDate()); + return planMapper.update(plan); + } +} diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/MedicationTaskServiceImpl.java b/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/MedicationTaskServiceImpl.java new file mode 100644 index 0000000..db3bd6a --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/service/impl/MedicationTaskServiceImpl.java @@ -0,0 +1,110 @@ +package com.intc.health.service.impl; + +import com.intc.common.core.utils.DateUtils; +import com.intc.common.security.utils.SecurityUtils; +import com.intc.health.domain.MedicationTask; +import com.intc.health.domain.dto.MedicationTaskDto; +import com.intc.health.domain.vo.MedicationTaskVo; +import com.intc.health.mapper.MedicationTaskMapper; +import com.intc.health.service.IMedicationTaskService; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +/** + * 服药任务Service实现 + * + * @author bot5 + * @date 2026-03-19 + */ +@Service +public class MedicationTaskServiceImpl implements IMedicationTaskService { + + @Resource + private MedicationTaskMapper taskMapper; + + @Override + public MedicationTaskVo selectById(Long id) { + return taskMapper.selectById(id); + } + + @Override + public List selectList(MedicationTaskDto dto) { + return taskMapper.selectList(dto); + } + + @Override + public int insert(MedicationTask task) { + task.setStatus(0); // 待服药 + task.setRemindStatus(0); // 未提醒 + task.setDelFlag(0); + task.setCreateBy(SecurityUtils.getUsername()); + task.setCreateTime(DateUtils.getNowDate()); + return taskMapper.insert(task); + } + + @Override + public int update(MedicationTask task) { + task.setUpdateBy(SecurityUtils.getUsername()); + task.setUpdateTime(DateUtils.getNowDate()); + return taskMapper.update(task); + } + + @Override + public int deleteByIds(Long[] ids) { + return taskMapper.removeByIds(ids); + } + + @Override + public int confirm(Long id, Integer confirmType, String confirmTime) { + MedicationTask task = new MedicationTask(); + task.setId(id); + task.setStatus(1); // 已服药 + task.setConfirmType(confirmType != null ? confirmType : 1); + // 优先使用前端传入的实际服药时间,没有则用当前时间 + if (confirmTime != null && !confirmTime.isEmpty()) { + try { + LocalTime time = LocalTime.parse(confirmTime, java.time.format.DateTimeFormatter.ofPattern("HH:mm")); + task.setActualTime(LocalDateTime.of(LocalDate.now(), time)); + } catch (Exception e1) { + try { + LocalTime time = LocalTime.parse(confirmTime, java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss")); + task.setActualTime(LocalDateTime.of(LocalDate.now(), time)); + } catch (Exception e2) { + task.setActualTime(LocalDateTime.now()); + } + } + } else { + task.setActualTime(LocalDateTime.now()); + } + task.setUpdateBy(SecurityUtils.getUsername()); + task.setUpdateTime(DateUtils.getNowDate()); + return taskMapper.update(task); + } + + @Override + public int skip(Long id, String notes) { + MedicationTask task = new MedicationTask(); + task.setId(id); + task.setStatus(3); // 跳过 + task.setNotes(notes); + task.setUpdateBy(SecurityUtils.getUsername()); + task.setUpdateTime(DateUtils.getNowDate()); + return taskMapper.update(task); + } + + @Override + public int markMissed(Long id, String notes) { + MedicationTask task = new MedicationTask(); + task.setId(id); + task.setStatus(2); // 漏服 + task.setNotes(notes); + task.setUpdateBy(SecurityUtils.getUsername()); + task.setUpdateTime(DateUtils.getNowDate()); + return taskMapper.update(task); + } +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/task/MedicationRemindJob.java b/intc-modules/intc-health/src/main/java/com/intc/health/task/MedicationRemindJob.java new file mode 100644 index 0000000..cd8f689 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/task/MedicationRemindJob.java @@ -0,0 +1,116 @@ +package com.intc.health.task; + +import com.intc.health.domain.vo.MedicationTaskVo; +import com.intc.health.mapper.MedicationTaskMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +/** + * 用药提醒定时器 + * + * @author bot5 + * @date 2026-03-19 + */ +@Component("medicationRemindJob") +public class MedicationRemindJob { + + private static final Logger log = LoggerFactory.getLogger(MedicationRemindJob.class); + + @Resource + private MedicationTaskMapper taskMapper; + + /** + * 扫描并发送提醒 + * 定时任务配置:每分钟执行 + */ + public void scanAndSendReminders() { + log.debug("========== 开始扫描服药提醒 =========="); + + LocalDateTime now = LocalDateTime.now(); + // 扫描未来1分钟内的待服药任务 + LocalDateTime startTime = now.minusMinutes(1); + LocalDateTime endTime = now.plusMinutes(1); + + List pendingTasks = taskMapper.selectPendingRemind(startTime, endTime); + + if (pendingTasks == null || pendingTasks.isEmpty()) { + return; + } + + log.info("发现 {} 条待提醒的服药任务", pendingTasks.size()); + + for (MedicationTaskVo task : pendingTasks) { + try { + sendReminder(task); + } catch (Exception e) { + log.error("发送提醒失败,任务ID: {}", task.getId(), e); + } + } + } + + /** + * 发送提醒 + */ + private void sendReminder(MedicationTaskVo task) { + log.info("发送提醒: 成员={}, 药品={}, 计划时间={} {}", + task.getPersonName(), task.getMedicineName(), + task.getPlannedDate(), task.getPlannedTime()); + + // TODO: 实现消息推送 + // 1. 查询用户的提醒设置 + // 2. 根据设置选择推送渠道 + // 3. 发送推送消息 + // 4. 记录提醒日志 + } + + /** + * 扫描超时未服药任务 + * 定时任务配置:每5分钟执行 + */ + public void scanOverdueRecords() { + log.debug("========== 开始扫描超时未服药任务 =========="); + + LocalDateTime now = LocalDateTime.now(); + // 扫描超过计划时间30分钟仍未服药的任务 + LocalDateTime overdueThreshold = now.minusMinutes(30); + + LocalDateTime startTime = overdueThreshold.minusMinutes(5); + LocalDateTime endTime = overdueThreshold; + + List overdueTasks = taskMapper.selectPendingRemind(startTime, endTime); + + if (overdueTasks == null || overdueTasks.isEmpty()) { + return; + } + + log.info("发现 {} 条超时未服药任务", overdueTasks.size()); + + for (MedicationTaskVo task : overdueTasks) { + try { + sendOverdueReminder(task); + } catch (Exception e) { + log.error("发送超时提醒失败,任务ID: {}", task.getId(), e); + } + } + } + + /** + * 发送超时提醒 + */ + private void sendOverdueReminder(MedicationTaskVo task) { + log.warn("超时未服药: 成员={}, 药品={}, 计划时间={} {}", + task.getPersonName(), task.getMedicineName(), + task.getPlannedDate(), task.getPlannedTime()); + + // TODO: 实现超时提醒逻辑 + // 1. 发送二次提醒 + // 2. 如果配置了家属通知,通知家属 + } +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/java/com/intc/health/task/MedicationTaskJob.java b/intc-modules/intc-health/src/main/java/com/intc/health/task/MedicationTaskJob.java new file mode 100644 index 0000000..2dd3168 --- /dev/null +++ b/intc-modules/intc-health/src/main/java/com/intc/health/task/MedicationTaskJob.java @@ -0,0 +1,271 @@ +package com.intc.health.task; + +import com.fasterxml.jackson.databind.JsonNode; +import com.intc.common.core.utils.DateUtils; +import com.intc.health.domain.MedicationTask; +import com.intc.health.domain.vo.MedicationPlanVo; +import com.intc.health.mapper.MedicationPlanMapper; +import com.intc.health.mapper.MedicationTaskMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 服药任务生成定时器 + * 按文档规范实现 + * + * @author bot5 + * @date 2026-03-19 + */ +@Component("medicationTaskJob") +public class MedicationTaskJob { + + private static final Logger log = LoggerFactory.getLogger(MedicationTaskJob.class); + + @Resource + private MedicationPlanMapper planMapper; + + @Resource + private MedicationTaskMapper taskMapper; + + /** + * 每日生成服药任务 + * 定时任务配置:每日 00:05 执行 + */ + public void generateDailyTasks() { + log.info("========== 开始生成每日服药任务 =========="); + LocalDate today = LocalDate.now(); + int generatedCount = generateTasksForDate(today); + log.info("========== 每日服药任务生成完成,共生成 {} 条记录 ==========", generatedCount); + } + + /** + * 生成指定日期的服药任务 + */ + public int generateTasksForDate(LocalDate date) { + int generatedCount = 0; + + // 查询所有执行中的计划 + List activePlans = planMapper.selectActiveList(); + if (activePlans == null || activePlans.isEmpty()) { + log.info("没有执行中的用药计划"); + return 0; + } + + for (MedicationPlanVo plan : activePlans) { + try { + // 检查该计划当天是否需要服药 + if (!shouldTakeMedicineOnDate(plan, date)) { + continue; + } + + // 先计算本次应生成的任务列表 + List tasks = generateTasksForPlan(plan, date); + if (tasks.isEmpty()) { + continue; + } + + // 检查已生成的任务数,只补充未生成的部分(防止重复) + int existCount = taskMapper.countByPlanAndDate(plan.getId(), date); + if (existCount >= tasks.size()) { + // 已全量生成,跳过 + continue; + } + + // 未全量生成时,删除旧的再重新全量插入(保证幂等) + if (existCount > 0) { + taskMapper.deleteByPlanAndDate(plan.getId(), date); + } + taskMapper.batchInsert(tasks); + generatedCount += tasks.size(); + log.info("计划 {} 在 {} 生成了 {} 条任务", plan.getPlanName(), date, tasks.size()); + } catch (Exception e) { + log.error("生成计划 {} 的任务失败: {}", plan.getId(), e.getMessage(), e); + } + } + + return generatedCount; + } + + /** + * 判断某计划在指定日期是否需要服药 + */ + private boolean shouldTakeMedicineOnDate(MedicationPlanVo plan, LocalDate date) { + // 检查日期范围 + if (plan.getStartDate() != null && date.isBefore(toLocalDate(plan.getStartDate()))) { + return false; + } + if (plan.getEndDate() != null && date.isAfter(toLocalDate(plan.getEndDate()))) { + return false; + } + + // 根据频次类型判断 + Integer frequencyType = plan.getFrequencyType(); + if (frequencyType == null) { + frequencyType = 1; + } + + switch (frequencyType) { + case 1: // 每天 + return true; + case 2: // 每周 + return shouldTakeOnWeekly(plan, date); + case 3: // 隔天 + return shouldTakeOnAlternate(plan, date); + case 4: // 自定义 + return shouldTakeOnCustom(plan, date); + default: + return true; + } + } + + /** + * 每周服药判断 + */ + private boolean shouldTakeOnWeekly(MedicationPlanVo plan, LocalDate date) { + JsonNode config = plan.getFrequencyConfig(); + if (config == null || !config.has("days")) { + return true; + } + + JsonNode daysNode = config.get("days"); + if (!daysNode.isArray()) { + return true; + } + + DayOfWeek dayOfWeek = date.getDayOfWeek(); + int dayValue = dayOfWeek.getValue(); // 1=周一, 7=周日 + + for (JsonNode day : daysNode) { + if (day.asInt() == dayValue) { + return true; + } + } + return false; + } + + /** + * 隔天服药判断 + */ + private boolean shouldTakeOnAlternate(MedicationPlanVo plan, LocalDate date) { + LocalDate startDate = plan.getStartDate() != null ? toLocalDate(plan.getStartDate()) : null; + if (startDate == null) { + startDate = date; + } + + JsonNode config = plan.getFrequencyConfig(); + int intervalDays = 2; // 默认隔天 + if (config != null && config.has("interval_days")) { + intervalDays = config.get("interval_days").asInt(2); + } + + long daysBetween = ChronoUnit.DAYS.between(startDate, date); + return daysBetween % intervalDays == 0; + } + + /** + * 自定义频次判断 + */ + private boolean shouldTakeOnCustom(MedicationPlanVo plan, LocalDate date) { + // 自定义频次,暂时默认返回 true + return true; + } + + /** + * 为某个计划生成指定日期的任务 + */ + private List generateTasksForPlan(MedicationPlanVo plan, LocalDate date) { + List tasks = new ArrayList<>(); + + JsonNode timeSlots = plan.getTimeSlots(); + log.info("计划 {} timeSlots原始值={}, isArray={}, nodeType={}", + plan.getId(), timeSlots, timeSlots != null && timeSlots.isArray(), timeSlots != null ? timeSlots.getNodeType() : null); + // TextNode说明存储时被二次序列化,需要再解析一次 + if (timeSlots != null && timeSlots.isTextual()) { + try { + timeSlots = new com.fasterxml.jackson.databind.ObjectMapper().readTree(timeSlots.asText()); + } catch (Exception e) { + timeSlots = null; + } + } + if (timeSlots == null || !timeSlots.isArray()) { + // 如果没有配置时段,使用默认时段 + timeSlots = createDefaultTimeSlots(plan); + } + + for (JsonNode slot : timeSlots) { + String timeStr = slot.asText(); + BigDecimal dosage = plan.getDosagePerTime(); + LocalTime time = parseTime(timeStr); + + MedicationTask task = new MedicationTask(); + task.setPlanId(plan.getId()); + task.setPersonId(plan.getPersonId()); + task.setMedicineName(plan.getMedicineName()); + task.setPlannedDate(date); + task.setPlannedTime(time); + task.setPlannedDosage(dosage); + task.setStatus(0); // 待服药 + task.setRemindStatus(0); // 未提醒 + task.setCreateTime(DateUtils.getNowDate()); + + tasks.add(task); + } + + return tasks; + } + + /** + * 创建默认时段配置 + */ + private JsonNode createDefaultTimeSlots(MedicationPlanVo plan) { + // 根据剂量单位推测服药时段 + String json = "[{\"slot_name\":\"早\",\"time\":\"08:00\",\"dosage\":1,\"enabled\":true}]"; + try { + return new com.fasterxml.jackson.databind.ObjectMapper().readTree(json); + } catch (Exception e) { + return null; + } + } + + /** + * Date 转 LocalDate + */ + private LocalDate toLocalDate(Date date) { + return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); + } + + /** + * 解析时间字符串 + */ + private LocalTime parseTime(String timeStr) { + try { + return LocalTime.parse(timeStr, DateTimeFormatter.ofPattern("HH:mm")); + } catch (Exception e) { + return LocalTime.of(8, 0); + } + } + + /** + * 标记过期任务 + */ + public void markExpiredRecords() { + log.info("========== 开始标记过期服药任务 =========="); + LocalDateTime expireThreshold = LocalDateTime.now().minusHours(24); + int count = taskMapper.markExpired(expireThreshold); + log.info("========== 标记过期任务完成,共 {} 条 ==========", count); + } +} \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/resources/mapper/health/ChronicDiseaseRecordMapper.xml b/intc-modules/intc-health/src/main/resources/mapper/health/ChronicDiseaseRecordMapper.xml new file mode 100644 index 0000000..b2aac63 --- /dev/null +++ b/intc-modules/intc-health/src/main/resources/mapper/health/ChronicDiseaseRecordMapper.xml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select a.id, a.person_id,a.disease_id, a.disease_name, a.disease_code, a.diagnosis_date, + a.disease_status, a.main_doctor, a.hospital, a.department, a.notes, a.is_active, + a.create_by, a.create_time, a.update_by, a.update_time, a.del_flag, a.remark, + p.name as person_name + from chronic_disease_record a + left join health_person p on a.person_id = p.id + + + + + + + + + + insert into chronic_disease_record + + id, + person_id, + disease_id, + disease_name, + disease_code, + diagnosis_date, + disease_status, + main_doctor, + hospital, + department, + notes, + is_active, + create_by, + create_time, + update_by, + update_time, + del_flag, + remark, + + + #{id}, + #{personId}, + #{diseaseId}, + #{diseaseName}, + #{diseaseCode}, + #{diagnosisDate}, + #{diseaseStatus}, + #{mainDoctor}, + #{hospital}, + #{department}, + #{notes}, + #{isActive}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{delFlag}, + #{remark}, + + + + + update chronic_disease_record + + person_id = #{personId}, + disease_id = #{diseaseId}, + disease_name = #{diseaseName}, + disease_code = #{diseaseCode}, + diagnosis_date = #{diagnosisDate}, + disease_status = #{diseaseStatus}, + main_doctor = #{mainDoctor}, + hospital = #{hospital}, + department = #{department}, + notes = #{notes}, + is_active = #{isActive}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + delete from chronic_disease_record where id = #{id} + + + + delete from chronic_disease_record where id in + + #{id} + + + + + update chronic_disease_record set del_flag = '1' where id = #{id} + + + + update chronic_disease_record set del_flag = '1' where id in + + #{id} + + + \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/resources/mapper/health/MedicationPlanMapper.xml b/intc-modules/intc-health/src/main/resources/mapper/health/MedicationPlanMapper.xml new file mode 100644 index 0000000..29bb0e1 --- /dev/null +++ b/intc-modules/intc-health/src/main/resources/mapper/health/MedicationPlanMapper.xml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select a.id, a.person_id, p.name as person_name, a.chronic_disease_id, + c.disease_name as chronic_disease_name, a.plan_name, a.medicine_id, + a.medicine_name, a.specification, a.dosage_per_time, a.dosage_unit, + a.frequency_type, a.frequency_config, a.time_slots, a.start_date, a.end_date, + a.remind_enabled, a.remind_advance_min, a.notify_family, a.family_member_ids, + a.status, a.create_time, + (select count(1) from medication_task t where t.plan_id = a.id and t.del_flag = 0 and t.planned_date = current_date) as today_total, + (select count(1) from medication_task t where t.plan_id = a.id and t.del_flag = 0 and t.planned_date = current_date and t.status = 1) as today_completed + from medication_plan a + left join health_person p on a.person_id = p.id + left join chronic_disease_record c on a.chronic_disease_id = c.id + + + + + + + + + + insert into medication_plan + + person_id, + chronic_disease_id, + plan_name, + medicine_id, + medicine_name, + specification, + dosage_per_time, + dosage_unit, + frequency_type, + frequency_config, + time_slots, + start_date, + end_date, + remind_enabled, + remind_advance_min, + notify_family, + family_member_ids, + status, + create_by, + create_time, + remark, + del_flag, + + + #{personId}, + #{chronicDiseaseId}, + #{planName}, + #{medicineId}, + #{medicineName}, + #{specification}, + #{dosagePerTime}, + #{dosageUnit}, + #{frequencyType}, + #{frequencyConfig, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler}, + #{timeSlots, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler}, + #{startDate}, + #{endDate}, + #{remindEnabled}, + #{remindAdvanceMin}, + #{notifyFamily}, + #{familyMemberIds, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler}, + #{status}, + #{createBy}, + #{createTime}, + #{remark}, + 0, + + + + + update medication_plan + + person_id = #{personId}, + chronic_disease_id = #{chronicDiseaseId}, + plan_name = #{planName}, + medicine_id = #{medicineId}, + medicine_name = #{medicineName}, + specification = #{specification}, + dosage_per_time = #{dosagePerTime}, + dosage_unit = #{dosageUnit}, + frequency_type = #{frequencyType}, + frequency_config = #{frequencyConfig, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler}, + time_slots = #{timeSlots, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler}, + start_date = #{startDate}, + end_date = #{endDate}, + remind_enabled = #{remindEnabled}, + remind_advance_min = #{remindAdvanceMin}, + notify_family = #{notifyFamily}, + family_member_ids = #{familyMemberIds, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler}, + status = #{status}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + delete from medication_plan where id = #{id} + + + + delete from medication_plan where id in + #{id} + + + + update medication_plan set del_flag = 1 where id = #{id} + + + + update medication_plan set del_flag = 1 where id in + #{id} + + \ No newline at end of file diff --git a/intc-modules/intc-health/src/main/resources/mapper/health/MedicationTaskMapper.xml b/intc-modules/intc-health/src/main/resources/mapper/health/MedicationTaskMapper.xml new file mode 100644 index 0000000..bffa40a --- /dev/null +++ b/intc-modules/intc-health/src/main/resources/mapper/health/MedicationTaskMapper.xml @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select a.id, a.plan_id, p.plan_name, a.person_id, m.name as person_name, + a.medicine_name, a.planned_date, a.planned_time, a.planned_dosage, + a.actual_time, a.actual_dosage, a.status, a.confirm_type, a.notes, + a.remind_status, a.remind_time, a.create_time + from medication_task a + left join medication_plan p on a.plan_id = p.id + left join health_person m on a.person_id = m.id + + + + + + + + insert into medication_task ( + plan_id, person_id, medicine_name, planned_date, planned_time, + planned_dosage, actual_time, actual_dosage, status, confirm_type, + notes, remind_status, remind_time, create_by, create_time, remark, del_flag + ) values ( + #{planId}, #{personId}, #{medicineName}, #{plannedDate}, #{plannedTime}, + #{plannedDosage}, #{actualTime}, #{actualDosage}, #{status}, #{confirmType}, + #{notes}, #{remindStatus}, #{remindTime}, #{createBy}, #{createTime}, #{remark}, 0 + ) + + + + insert into medication_task ( + plan_id, person_id, medicine_name, planned_date, planned_time, + planned_dosage, status, remind_status, create_time, del_flag + ) values + + (#{item.planId}, #{item.personId}, #{item.medicineName}, #{item.plannedDate}, #{item.plannedTime}, + #{item.plannedDosage}, #{item.status}, #{item.remindStatus}, #{item.createTime}, 0) + + + + + update medication_task + + actual_time = #{actualTime}, + actual_dosage = #{actualDosage}, + status = #{status}, + confirm_type = #{confirmType}, + notes = #{notes}, + remind_status = #{remindStatus}, + remind_time = #{remindTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + delete from medication_task where id = #{id} + + + + delete from medication_task where id in + #{id} + + + + update medication_task set del_flag = 1 where id = #{id} + + + + update medication_task set del_flag = 1 where id in + #{id} + + + + + + delete from medication_task + where plan_id = #{planId} and planned_date = #{date} and status = 0 + + + + + + update medication_task + set status = 2, update_time = current_timestamp + where del_flag = 0 and status = 0 + and (planned_date || ' ' || planned_time)::timestamp < #{expireThreshold} + + + + + + + + + + + + \ No newline at end of file diff --git a/intc-modules/intc-job/pom.xml b/intc-modules/intc-job/pom.xml index 23e1355..ff885e0 100644 --- a/intc-modules/intc-job/pom.xml +++ b/intc-modules/intc-job/pom.xml @@ -92,6 +92,12 @@ 3.6.3 compile + + com.intc + intc-api-health + 3.6.3 + compile + diff --git a/intc-modules/intc-job/src/main/java/com/intc/job/task/MedicationRemindJobTask.java b/intc-modules/intc-job/src/main/java/com/intc/job/task/MedicationRemindJobTask.java new file mode 100644 index 0000000..9464d4e --- /dev/null +++ b/intc-modules/intc-job/src/main/java/com/intc/job/task/MedicationRemindJobTask.java @@ -0,0 +1,36 @@ +package com.intc.job.task; + +import com.intc.api.health.RemoteHealthService; +import com.intc.common.core.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 用药提醒定时任务(通过 Feign 调用 intc-health 服务) + * + * @author intc + */ +@Component("medicationRemindJob") +public class MedicationRemindJobTask { + + @Autowired + private RemoteHealthService remoteHealthService; + + /** + * 扫描并发送服药提醒(每分钟执行) + */ + public void scanAndSendReminders() { + System.out.println("================================开始扫描服药提醒(" + DateUtils.getTime() + ")================================="); + remoteHealthService.scanAndSendReminders(); + System.out.println("================================结束扫描服药提醒(" + DateUtils.getTime() + ")================================="); + } + + /** + * 扫描超时未服药任务(每5分钟执行) + */ + public void scanOverdueRecords() { + System.out.println("================================开始扫描超时未服药任务(" + DateUtils.getTime() + ")================================="); + remoteHealthService.scanOverdueRecords(); + System.out.println("================================结束扫描超时未服药任务(" + DateUtils.getTime() + ")================================="); + } +} diff --git a/intc-modules/intc-job/src/main/java/com/intc/job/task/MedicationTaskJobTask.java b/intc-modules/intc-job/src/main/java/com/intc/job/task/MedicationTaskJobTask.java new file mode 100644 index 0000000..a58fdd1 --- /dev/null +++ b/intc-modules/intc-job/src/main/java/com/intc/job/task/MedicationTaskJobTask.java @@ -0,0 +1,27 @@ +package com.intc.job.task; + +import com.intc.api.health.RemoteHealthService; +import com.intc.common.core.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 健康模块定时任务(通过 Feign 调用 intc-health 服务) + * + * @author intc + */ +@Component("medicationTaskJob") +public class MedicationTaskJobTask { + + @Autowired + private RemoteHealthService remoteHealthService; + + /** + * 每日生成服药任务(每日 00:05 执行) + */ + public void generateDailyTasks() { + System.out.println("================================开始每日生成服药任务(" + DateUtils.getTime() + ")================================="); + remoteHealthService.generateDailyTasks(); + System.out.println("================================结束每日生成服药任务(" + DateUtils.getTime() + ")================================="); + } +} diff --git a/sql/medication_job_pg.sql b/sql/medication_job_pg.sql new file mode 100644 index 0000000..44d7b56 --- /dev/null +++ b/sql/medication_job_pg.sql @@ -0,0 +1,64 @@ +-- ======================================== +-- 用药管理模块 - 定时任务配置 +-- PostgreSQL 版本 +-- ======================================== + +-- 1. 每日生成服药任务(每日 00:05 执行) +INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark) +VALUES ( + '每日生成服药任务', + 'DEFAULT', + 'medicationTaskJob.generateDailyTasks', + '0 5 0 * * ?', + '2', + '1', + '0', + 'admin', + NOW(), + '每日凌晨00:05自动生成当天的服药任务' +); + +-- 2. 扫描并发送服药提醒(每分钟执行) +INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark) +VALUES ( + '扫描服药提醒', + 'DEFAULT', + 'medicationRemindJob.scanAndSendReminders', + '0 * * * * ?', + '2', + '1', + '0', + 'admin', + NOW(), + '每分钟扫描待提醒的服药任务并发送提醒' +); + +-- 3. 扫描超时未服药记录(每5分钟执行) +INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark) +VALUES ( + '扫描超时未服药', + 'DEFAULT', + 'medicationRemindJob.scanOverdueRecords', + '0 */5 * * * ?', + '2', + '1', + '0', + 'admin', + NOW(), + '每5分钟扫描超时未服药的记录并发送提醒' +); + +-- 4. 标记过期服药记录(每小时执行) +INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark) +VALUES ( + '标记过期服药记录', + 'DEFAULT', + 'medicationTaskJob.markExpiredRecords', + '0 0 * * * ?', + '2', + '1', + '0', + 'admin', + NOW(), + '每小时检查并标记过期的服药记录' +); \ No newline at end of file diff --git a/sql/medication_menu_pg.sql b/sql/medication_menu_pg.sql new file mode 100644 index 0000000..3a49f92 --- /dev/null +++ b/sql/medication_menu_pg.sql @@ -0,0 +1,106 @@ +-- ======================================== +-- 用药管理模块 - 菜单权限配置 +-- PostgreSQL 版本 +-- ======================================== + +-- 用药计划菜单 +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) +SELECT '用药计划', menu_id, 10, 'medicationPlan', 'health/medicationPlan/index', 1, 0, 'C', '0', '0', 'health:medicationPlan:list', 'calendar', 'admin', NOW(), '用药计划菜单' +FROM sys_menu WHERE menu_name = '健康管理' LIMIT 1; + +-- 用药计划按钮权限 +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) +SELECT '用药计划查询', m.menu_id, 1, '', NULL, 1, 0, 'F', '0', '0', 'health:medicationPlan:query', '#', 'admin', NOW(), '' +FROM sys_menu m WHERE m.perms = 'health:medicationPlan:list' +UNION ALL +SELECT '用药计划新增', m.menu_id, 2, '', NULL, 1, 0, 'F', '0', '0', 'health:medicationPlan:add', '#', 'admin', NOW(), '' +FROM sys_menu m WHERE m.perms = 'health:medicationPlan:list' +UNION ALL +SELECT '用药计划修改', m.menu_id, 3, '', NULL, 1, 0, 'F', '0', '0', 'health:medicationPlan:edit', '#', 'admin', NOW(), '' +FROM sys_menu m WHERE m.perms = 'health:medicationPlan:list' +UNION ALL +SELECT '用药计划删除', m.menu_id, 4, '', NULL, 1, 0, 'F', '0', '0', 'health:medicationPlan:remove', '#', 'admin', NOW(), '' +FROM sys_menu m WHERE m.perms = 'health:medicationPlan:list'; + +-- 用药任务菜单 +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) +SELECT '用药任务', menu_id, 11, 'medicationTask', 'health/medicationTask/index', 1, 0, 'C', '0', '0', 'health:medicationTask:list', 'log', 'admin', NOW(), '用药任务菜单' +FROM sys_menu WHERE menu_name = '健康管理' LIMIT 1; + +-- 用药任务按钮权限 +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) +SELECT '用药任务查询', m.menu_id, 1, '', NULL, 1, 0, 'F', '0', '0', 'health:medicationTask:query', '#', 'admin', NOW(), '' +FROM sys_menu m WHERE m.perms = 'health:medicationTask:list' +UNION ALL +SELECT '用药任务修改', m.menu_id, 2, '', NULL, 1, 0, 'F', '0', '0', 'health:medicationTask:edit', '#', 'admin', NOW(), '' +FROM sys_menu m WHERE m.perms = 'health:medicationTask:list' +UNION ALL +SELECT '用药任务删除', m.menu_id, 3, '', NULL, 1, 0, 'F', '0', '0', 'health:medicationTask:remove', '#', 'admin', NOW(), '' +FROM sys_menu m WHERE m.perms = 'health:medicationTask:list'; + +-- 用药统计菜单 +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) +SELECT '用药统计', menu_id, 12, 'medicationStatistic', 'health/medicationStatistic/index', 1, 0, 'C', '0', '0', 'health:medicationStatistic:query', 'chart', 'admin', NOW(), '用药统计菜单' +FROM sys_menu WHERE menu_name = '健康管理' LIMIT 1; + +-- 慢性疾病档案菜单 +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) +SELECT '慢性疾病档案', menu_id, 9, 'chronicDisease', 'health/chronicDisease/index', 1, 0, 'C', '0', '0', 'health:chronicDisease:list', 'folder', 'admin', NOW(), '慢性疾病档案菜单' +FROM sys_menu WHERE menu_name = '健康管理' LIMIT 1; + +-- 慢性疾病档案按钮权限 +INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) +SELECT '慢性疾病档案查询', m.menu_id, 1, '', NULL, 1, 0, 'F', '0', '0', 'health:chronicDisease:query', '#', 'admin', NOW(), '' +FROM sys_menu m WHERE m.perms = 'health:chronicDisease:list' +UNION ALL +SELECT '慢性疾病档案新增', m.menu_id, 2, '', NULL, 1, 0, 'F', '0', '0', 'health:chronicDisease:add', '#', 'admin', NOW(), '' +FROM sys_menu m WHERE m.perms = 'health:chronicDisease:list' +UNION ALL +SELECT '慢性疾病档案修改', m.menu_id, 3, '', NULL, 1, 0, 'F', '0', '0', 'health:chronicDisease:edit', '#', 'admin', NOW(), '' +FROM sys_menu m WHERE m.perms = 'health:chronicDisease:list' +UNION ALL +SELECT '慢性疾病档案删除', m.menu_id, 4, '', NULL, 1, 0, 'F', '0', '0', 'health:chronicDisease:remove', '#', 'admin', NOW(), '' +FROM sys_menu m WHERE m.perms = 'health:chronicDisease:list'; + +-- ======================================== +-- 字典类型 +-- ======================================== +INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark) +VALUES +('用药频率类型', 'medication_frequency_type', '0', 'admin', NOW(), '用药频率类型字典'), +('用药计划状态', 'medication_plan_status', '0', 'admin', NOW(), '用药计划状态字典'), +('服药任务状态', 'medication_task_status', '0', 'admin', NOW(), '服药任务状态字典'), +('疾病状态', 'disease_status', '0', 'admin', NOW(), '疾病状态字典'); + +-- ======================================== +-- 字典数据 +-- ======================================== +-- 用药频率类型 +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time) +VALUES +(1, '每天', '1', 'medication_frequency_type', '', 'primary', 'Y', '0', 'admin', NOW()), +(2, '每周', '2', 'medication_frequency_type', '', 'success', 'N', '0', 'admin', NOW()), +(3, '隔天', '3', 'medication_frequency_type', '', 'info', 'N', '0', 'admin', NOW()), +(4, '自定义', '4', 'medication_frequency_type', '', 'warning', 'N', '0', 'admin', NOW()); + +-- 用药计划状态 +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time) +VALUES +(1, '执行中', '1', 'medication_plan_status', '', 'primary', 'Y', '0', 'admin', NOW()), +(2, '已暂停', '2', 'medication_plan_status', '', 'warning', 'N', '0', 'admin', NOW()), +(3, '已结束', '3', 'medication_plan_status', '', 'info', 'N', '0', 'admin', NOW()); + +-- 服药任务状态 +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time) +VALUES +(0, '待服药', '0', 'medication_task_status', '', 'warning', 'Y', '0', 'admin', NOW()), +(1, '已服药', '1', 'medication_task_status', '', 'success', 'N', '0', 'admin', NOW()), +(2, '漏服', '2', 'medication_task_status', '', 'danger', 'N', '0', 'admin', NOW()), +(3, '跳过', '3', 'medication_task_status', '', 'info', 'N', '0', 'admin', NOW()); + +-- 疾病状态 +INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time) +VALUES +(1, '稳定', '1', 'disease_status', '', 'success', 'Y', '0', 'admin', NOW()), +(2, '需关注', '2', 'disease_status', '', 'warning', 'N', '0', 'admin', NOW()), +(3, '急性期', '3', 'disease_status', '', 'danger', 'N', '0', 'admin', NOW()); \ No newline at end of file diff --git a/sql/medication_module_pg.sql b/sql/medication_module_pg.sql new file mode 100644 index 0000000..28dfe74 --- /dev/null +++ b/sql/medication_module_pg.sql @@ -0,0 +1,293 @@ +-- ======================================== +-- 用药管理模块 - PostgreSQL 建表语句 +-- 依据:慢性病用药管理模块-功能规划文档 +-- ======================================== + +-- ======================================== +-- 1. 慢性疾病档案表 +-- ======================================== +DROP TABLE IF EXISTS chronic_disease_record; +CREATE TABLE chronic_disease_record ( + id BIGSERIAL PRIMARY KEY, + member_id BIGINT NOT NULL, + disease_name VARCHAR(100) NOT NULL, + disease_code VARCHAR(20), + diagnosis_date DATE, + disease_status SMALLINT NOT NULL DEFAULT 1, + main_doctor VARCHAR(50), + hospital VARCHAR(100), + department VARCHAR(50), + notes TEXT, + -- 若依标准字段 + create_by VARCHAR(64) DEFAULT '', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by VARCHAR(64) DEFAULT '', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + remark VARCHAR(500) DEFAULT '', + del_flag SMALLINT DEFAULT 0 +); + +COMMENT ON TABLE chronic_disease_record IS '慢性疾病档案表'; +COMMENT ON COLUMN chronic_disease_record.id IS '主键ID'; +COMMENT ON COLUMN chronic_disease_record.member_id IS '关联成员ID'; +COMMENT ON COLUMN chronic_disease_record.disease_name IS '疾病名称(高血压/糖尿病/冠心病等)'; +COMMENT ON COLUMN chronic_disease_record.disease_code IS '疾病编码(ICD-10标准)'; +COMMENT ON COLUMN chronic_disease_record.diagnosis_date IS '确诊日期'; +COMMENT ON COLUMN chronic_disease_record.disease_status IS '状态:1-稳定 2-需关注 3-急性期'; +COMMENT ON COLUMN chronic_disease_record.main_doctor IS '主治医生'; +COMMENT ON COLUMN chronic_disease_record.hospital IS '就诊医院'; +COMMENT ON COLUMN chronic_disease_record.department IS '科室'; +COMMENT ON COLUMN chronic_disease_record.notes IS '备注说明'; +COMMENT ON COLUMN chronic_disease_record.create_by IS '创建者'; +COMMENT ON COLUMN chronic_disease_record.create_time IS '创建时间'; +COMMENT ON COLUMN chronic_disease_record.update_by IS '更新者'; +COMMENT ON COLUMN chronic_disease_record.update_time IS '更新时间'; +COMMENT ON COLUMN chronic_disease_record.remark IS '备注'; +COMMENT ON COLUMN chronic_disease_record.del_flag IS '删除标记:0-正常 1-删除'; + +CREATE INDEX idx_cdr_member_id ON chronic_disease_record(member_id); +CREATE INDEX idx_cdr_disease_status ON chronic_disease_record(disease_status); + +-- ======================================== +-- 2. 用药计划表 +-- ======================================== +DROP TABLE IF EXISTS medication_plan; +CREATE TABLE medication_plan ( + id BIGSERIAL PRIMARY KEY, + member_id BIGINT NOT NULL, + chronic_disease_id BIGINT, + plan_name VARCHAR(100) NOT NULL, + medicine_id BIGINT NOT NULL, + medicine_name VARCHAR(100) NOT NULL, + specification VARCHAR(50), + dosage_per_time DECIMAL(10,2) NOT NULL, + dosage_unit VARCHAR(20) NOT NULL, + frequency_type SMALLINT NOT NULL, + frequency_config JSONB NOT NULL, + time_slots JSONB NOT NULL, + start_date DATE NOT NULL, + end_date DATE, + remind_enabled SMALLINT NOT NULL DEFAULT 1, + remind_advance_min INTEGER, + notify_family SMALLINT DEFAULT 0, + family_member_ids JSONB, + status SMALLINT NOT NULL DEFAULT 1, + -- 若依标准字段 + create_by VARCHAR(64) DEFAULT '', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by VARCHAR(64) DEFAULT '', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + remark VARCHAR(500) DEFAULT '', + del_flag SMALLINT DEFAULT 0 +); + +COMMENT ON TABLE medication_plan IS '用药计划表'; +COMMENT ON COLUMN medication_plan.id IS '主键ID'; +COMMENT ON COLUMN medication_plan.member_id IS '关联成员ID'; +COMMENT ON COLUMN medication_plan.chronic_disease_id IS '关联慢性疾病ID'; +COMMENT ON COLUMN medication_plan.plan_name IS '计划名称(如"降压药计划")'; +COMMENT ON COLUMN medication_plan.medicine_id IS '关联药品ID'; +COMMENT ON COLUMN medication_plan.medicine_name IS '药品名称(冗余字段)'; +COMMENT ON COLUMN medication_plan.specification IS '药品规格(如"10mg/片")'; +COMMENT ON COLUMN medication_plan.dosage_per_time IS '单次剂量'; +COMMENT ON COLUMN medication_plan.dosage_unit IS '剂量单位(片/粒/毫升/喷)'; +COMMENT ON COLUMN medication_plan.frequency_type IS '频次类型:1-每天 2-每周 3-隔天 4-自定义'; +COMMENT ON COLUMN medication_plan.frequency_config IS '频次配置(JSON格式)'; +COMMENT ON COLUMN medication_plan.time_slots IS '服药时段配置'; +COMMENT ON COLUMN medication_plan.start_date IS '开始日期'; +COMMENT ON COLUMN medication_plan.end_date IS '结束日期(空表示长期)'; +COMMENT ON COLUMN medication_plan.remind_enabled IS '是否开启提醒:1-是 0-否'; +COMMENT ON COLUMN medication_plan.remind_advance_min IS '提前提醒分钟数'; +COMMENT ON COLUMN medication_plan.notify_family IS '漏服是否通知家属:1-是 0-否'; +COMMENT ON COLUMN medication_plan.family_member_ids IS '通知家属成员ID列表'; +COMMENT ON COLUMN medication_plan.status IS '状态:1-执行中 2-已暂停 3-已结束'; +COMMENT ON COLUMN medication_plan.create_by IS '创建者'; +COMMENT ON COLUMN medication_plan.create_time IS '创建时间'; +COMMENT ON COLUMN medication_plan.update_by IS '更新者'; +COMMENT ON COLUMN medication_plan.update_time IS '更新时间'; +COMMENT ON COLUMN medication_plan.remark IS '备注'; +COMMENT ON COLUMN medication_plan.del_flag IS '删除标记:0-正常 1-删除'; + +CREATE INDEX idx_mp_member_id ON medication_plan(member_id); +CREATE INDEX idx_mp_status ON medication_plan(status); +CREATE INDEX idx_mp_medicine_id ON medication_plan(medicine_id); + +-- ======================================== +-- 3. 服药任务表 +-- ======================================== +DROP TABLE IF EXISTS medication_task; +CREATE TABLE medication_task ( + id BIGSERIAL PRIMARY KEY, + plan_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + medicine_name VARCHAR(100) NOT NULL, + planned_date DATE NOT NULL, + planned_time TIME NOT NULL, + planned_dosage DECIMAL(10,2) NOT NULL, + actual_time TIMESTAMP, + actual_dosage DECIMAL(10,2), + status SMALLINT NOT NULL DEFAULT 0, + confirm_type SMALLINT, + notes VARCHAR(255), + remind_status SMALLINT DEFAULT 0, + remind_time TIMESTAMP, + -- 若依标准字段 + create_by VARCHAR(64) DEFAULT '', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by VARCHAR(64) DEFAULT '', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + remark VARCHAR(500) DEFAULT '', + del_flag SMALLINT DEFAULT 0 +); + +COMMENT ON TABLE medication_task IS '服药任务表'; +COMMENT ON COLUMN medication_task.id IS '主键ID'; +COMMENT ON COLUMN medication_task.plan_id IS '关联用药计划ID'; +COMMENT ON COLUMN medication_task.member_id IS '关联成员ID'; +COMMENT ON COLUMN medication_task.medicine_name IS '药品名称'; +COMMENT ON COLUMN medication_task.planned_date IS '计划服药日期'; +COMMENT ON COLUMN medication_task.planned_time IS '计划服药时间'; +COMMENT ON COLUMN medication_task.planned_dosage IS '计划剂量'; +COMMENT ON COLUMN medication_task.actual_time IS '实际服药时间'; +COMMENT ON COLUMN medication_task.actual_dosage IS '实际剂量'; +COMMENT ON COLUMN medication_task.status IS '状态:0-待服药 1-已服药 2-漏服 3-跳过'; +COMMENT ON COLUMN medication_task.confirm_type IS '确认方式:1-用户确认 2-家属代确认 3-系统自动'; +COMMENT ON COLUMN medication_task.notes IS '备注(如漏服原因)'; +COMMENT ON COLUMN medication_task.remind_status IS '提醒状态:0-未提醒 1-已提醒 2-已超时提醒'; +COMMENT ON COLUMN medication_task.remind_time IS '提醒时间'; +COMMENT ON COLUMN medication_task.create_by IS '创建者'; +COMMENT ON COLUMN medication_task.create_time IS '创建时间'; +COMMENT ON COLUMN medication_task.update_by IS '更新者'; +COMMENT ON COLUMN medication_task.update_time IS '更新时间'; +COMMENT ON COLUMN medication_task.remark IS '备注'; +COMMENT ON COLUMN medication_task.del_flag IS '删除标记:0-正常 1-删除'; + +CREATE INDEX idx_mt_plan_id ON medication_task(plan_id); +CREATE INDEX idx_mt_member_id ON medication_task(member_id); +CREATE INDEX idx_mt_planned_date ON medication_task(planned_date); +CREATE INDEX idx_mt_status ON medication_task(status); +CREATE INDEX idx_mt_planned_datetime ON medication_task(planned_date, planned_time); + +-- ======================================== +-- 4. 用药提醒日志表 +-- ======================================== +DROP TABLE IF EXISTS medication_remind_log; +CREATE TABLE medication_remind_log ( + id BIGSERIAL PRIMARY KEY, + task_id BIGINT NOT NULL, + remind_type SMALLINT NOT NULL, + remind_time TIMESTAMP NOT NULL, + remind_channel VARCHAR(20) NOT NULL, + remind_result SMALLINT NOT NULL, + error_msg VARCHAR(255), + -- 若依标准字段 + create_by VARCHAR(64) DEFAULT '', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by VARCHAR(64) DEFAULT '', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + remark VARCHAR(500) DEFAULT '', + del_flag SMALLINT DEFAULT 0 +); + +COMMENT ON TABLE medication_remind_log IS '用药提醒日志表'; +COMMENT ON COLUMN medication_remind_log.id IS '主键ID'; +COMMENT ON COLUMN medication_remind_log.task_id IS '关联服药任务ID'; +COMMENT ON COLUMN medication_remind_log.remind_type IS '提醒类型:1-按时提醒 2-超时提醒 3-漏服通知家属'; +COMMENT ON COLUMN medication_remind_log.remind_time IS '提醒时间'; +COMMENT ON COLUMN medication_remind_log.remind_channel IS '提醒渠道:app/sms/wechat'; +COMMENT ON COLUMN medication_remind_log.remind_result IS '结果:1-成功 2-失败'; +COMMENT ON COLUMN medication_remind_log.error_msg IS '错误信息'; +COMMENT ON COLUMN medication_remind_log.create_by IS '创建者'; +COMMENT ON COLUMN medication_remind_log.create_time IS '创建时间'; +COMMENT ON COLUMN medication_remind_log.update_by IS '更新者'; +COMMENT ON COLUMN medication_remind_log.update_time IS '更新时间'; +COMMENT ON COLUMN medication_remind_log.remark IS '备注'; +COMMENT ON COLUMN medication_remind_log.del_flag IS '删除标记:0-正常 1-删除'; + +CREATE INDEX idx_mrl_task_id ON medication_remind_log(task_id); +CREATE INDEX idx_mrl_remind_time ON medication_remind_log(remind_time); + +-- ======================================== +-- 5. 药品库存变动表 +-- ======================================== +DROP TABLE IF EXISTS medicine_stock_log; +CREATE TABLE medicine_stock_log ( + id BIGSERIAL PRIMARY KEY, + medicine_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + change_type SMALLINT NOT NULL, + change_quantity DECIMAL(10,2) NOT NULL, + before_quantity DECIMAL(10,2) NOT NULL, + after_quantity DECIMAL(10,2) NOT NULL, + relate_id BIGINT, + notes VARCHAR(255), + -- 若依标准字段 + create_by VARCHAR(64) DEFAULT '', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by VARCHAR(64) DEFAULT '', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + remark VARCHAR(500) DEFAULT '', + del_flag SMALLINT DEFAULT 0 +); + +COMMENT ON TABLE medicine_stock_log IS '药品库存变动表'; +COMMENT ON COLUMN medicine_stock_log.id IS '主键ID'; +COMMENT ON COLUMN medicine_stock_log.medicine_id IS '关联药品ID'; +COMMENT ON COLUMN medicine_stock_log.member_id IS '关联成员ID'; +COMMENT ON COLUMN medicine_stock_log.change_type IS '变动类型:1-入库 2-服药消耗 3-手动调整 4-过期报废'; +COMMENT ON COLUMN medicine_stock_log.change_quantity IS '变动数量(正数为增,负数为减)'; +COMMENT ON COLUMN medicine_stock_log.before_quantity IS '变动前库存'; +COMMENT ON COLUMN medicine_stock_log.after_quantity IS '变动后库存'; +COMMENT ON COLUMN medicine_stock_log.relate_id IS '关联ID(入库ID/任务ID等)'; +COMMENT ON COLUMN medicine_stock_log.notes IS '备注'; +COMMENT ON COLUMN medicine_stock_log.create_by IS '创建者'; +COMMENT ON COLUMN medicine_stock_log.create_time IS '创建时间'; +COMMENT ON COLUMN medicine_stock_log.update_by IS '更新者'; +COMMENT ON COLUMN medicine_stock_log.update_time IS '更新时间'; +COMMENT ON COLUMN medicine_stock_log.remark IS '备注'; +COMMENT ON COLUMN medicine_stock_log.del_flag IS '删除标记:0-正常 1-删除'; + +CREATE INDEX idx_msl_medicine_id ON medicine_stock_log(medicine_id); +CREATE INDEX idx_msl_member_id ON medicine_stock_log(member_id); +CREATE INDEX idx_msl_create_time ON medicine_stock_log(create_time); + +-- ======================================== +-- 6. 依从性统计汇总表 +-- ======================================== +DROP TABLE IF EXISTS medication_adherence_stats; +CREATE TABLE medication_adherence_stats ( + id BIGSERIAL PRIMARY KEY, + member_id BIGINT NOT NULL, + plan_id BIGINT, + stat_date DATE NOT NULL, + total_tasks INTEGER NOT NULL DEFAULT 0, + completed_tasks INTEGER NOT NULL DEFAULT 0, + missed_tasks INTEGER NOT NULL DEFAULT 0, + skipped_tasks INTEGER NOT NULL DEFAULT 0, + ontime_tasks INTEGER NOT NULL DEFAULT 0, + adherence_rate DECIMAL(5,2) NOT NULL DEFAULT 0.00, + ontime_rate DECIMAL(5,2) NOT NULL DEFAULT 0.00, + -- 若依标准字段 + create_by VARCHAR(64) DEFAULT '', + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by VARCHAR(64) DEFAULT '', + update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + remark VARCHAR(500) DEFAULT '', + del_flag SMALLINT DEFAULT 0 +); + +COMMENT ON TABLE medication_adherence_stats IS '依从性统计汇总表'; +COMMENT ON COLUMN medication_adherence_stats.id IS '主键ID'; +COMMENT ON COLUMN medication_adherence_stats.member_id IS '关联成员ID'; +COMMENT ON COLUMN medication_adherence_stats.plan_id IS '关联用药计划ID(为空表示该成员汇总)'; +COMMENT ON COLUMN medication_adherence_stats.stat_date IS '统计日期'; +COMMENT ON COLUMN medication_adherence_stats.total_tasks IS '总任务数'; +COMMENT ON COLUMN medication_adherence_stats.completed_tasks IS '已完成任务数'; +COMMENT ON COLUMN medication_adherence_stats.missed_tasks IS '漏服任务数'; +COMMENT ON COLUMN medication_adherence_stats.skipped_tasks IS '跳过任务数'; +COMMENT ON COLUMN medication_adherence_stats.ontime_tasks IS '准时服药任务数'; +COMMENT ON COLUMN medication_adherence_stats.adherence_rate IS '服药率(%)'; +COMMENT ON COLUMN medication_adherence_stats.ontime_rate IS '准时率(%)'; + +CREATE UNIQUE INDEX idx_mas_member_date ON medication_adherence_stats(member_id, stat_date, plan_id); +CREATE INDEX idx_mas_stat_date ON medication_adherence_stats(stat_date); \ No newline at end of file