Merge branch 'dev'

This commit is contained in:
tianyongbao
2026-03-21 22:29:20 +08:00
47 changed files with 3815 additions and 0 deletions

View File

@@ -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<ChronicDiseaseRecordVo> 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<ChronicDiseaseRecordVo> 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<ChronicDiseaseRecordVo> list = chronicDiseaseRecordService.selectChronicDiseaseRecordList(chronicDiseaseRecordDto);
ExcelUtil<ChronicDiseaseRecordVo> util = new ExcelUtil<ChronicDiseaseRecordVo>(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));
}
}

View File

@@ -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();
}
}

View File

@@ -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<DailyAdherenceVo> 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<DailyAdherenceVo> 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<String, MedicationAdherenceVo> 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<String, Object> 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);
}
}

View File

@@ -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<MedicationPlanVo> 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));
}
}

View File

@@ -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<MedicationTaskVo> 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));
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<ChronicDiseaseRecordVo> selectChronicDiseaseRecordList(ChronicDiseaseRecordDto chronicDiseaseRecordDto);
/**
* 根据成员ID查询慢性疾病档案列表
*
* @param memberId 成员ID
* @return 慢性疾病档案集合
*/
public List<ChronicDiseaseRecordVo> 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);
}

View File

@@ -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<MedicationPlanVo> selectList(MedicationPlanDto dto);
/**
* 查询执行中的用药计划列表(用于生成任务)
*/
List<MedicationPlanVo> 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);
}

View File

@@ -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<MedicationTaskVo> selectList(MedicationTaskDto dto);
/**
* 新增服药任务
*/
int insert(MedicationTask task);
/**
* 批量新增服药任务
*/
int batchInsert(List<MedicationTask> 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<MedicationTaskVo> selectPendingRemind(@Param("startTime") LocalDateTime startTime, @Param("endTime") LocalDateTime endTime);
// ========== 依从性统计 ==========
/**
* 获取总体统计
*/
java.util.Map<String, Object> getOverallStats(@Param("personId") Long personId,
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
/**
* 获取每日统计
*/
List<java.util.Map<String, Object>> getDailyStats(@Param("personId") Long personId,
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
/**
* 获取时段统计
*/
List<java.util.Map<String, Object>> getTimeSlotStats(@Param("personId") Long personId,
@Param("startDate") LocalDate startDate,
@Param("endDate") LocalDate endDate);
}

View File

@@ -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<ChronicDiseaseRecordVo> selectChronicDiseaseRecordList(ChronicDiseaseRecordDto chronicDiseaseRecordDto);
/**
* 根据成员ID查询慢性疾病档案列表
*
* @param memberId 成员ID
* @return 慢性疾病档案集合
*/
public List<ChronicDiseaseRecordVo> 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);
}

View File

@@ -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<DailyAdherenceVo> getDailyAdherenceList(Long memberId, LocalDate startDate, LocalDate endDate);
/**
* 获取日历视图数据
*/
List<DailyAdherenceVo> getCalendarData(Long memberId, String yearMonth);
/**
* 获取各时段服药统计
*/
Map<String, MedicationAdherenceVo> getTimeSlotAdherence(Long memberId, LocalDate startDate, LocalDate endDate);
}

View File

@@ -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<MedicationPlanVo> 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);
}

View File

@@ -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<MedicationTaskVo> 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);
}

View File

@@ -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<ChronicDiseaseRecordVo> selectChronicDiseaseRecordList(ChronicDiseaseRecordDto chronicDiseaseRecordDto)
{
return chronicDiseaseRecordMapper.selectChronicDiseaseRecordList(chronicDiseaseRecordDto);
}
/**
* 根据成员ID查询慢性疾病档案列表
*
* @param memberId 成员ID
* @return 慢性疾病档案集合
*/
@Override
public List<ChronicDiseaseRecordVo> 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);
}
}

View File

@@ -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<String, Object> 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<DailyAdherenceVo> getDailyAdherenceList(Long memberId, LocalDate startDate, LocalDate endDate) {
List<Map<String, Object>> dailyStats = taskMapper.getDailyStats(memberId, startDate, endDate);
List<DailyAdherenceVo> result = new ArrayList<>();
for (Map<String, Object> 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<DailyAdherenceVo> getCalendarData(Long memberId, String yearMonth) {
YearMonth ym = YearMonth.parse(yearMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
LocalDate startDate = ym.atDay(1);
LocalDate endDate = ym.atEndOfMonth();
List<DailyAdherenceVo> dailyList = getDailyAdherenceList(memberId, startDate, endDate);
// 补全缺失日期
List<DailyAdherenceVo> result = new ArrayList<>();
Map<String, DailyAdherenceVo> 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<String, MedicationAdherenceVo> getTimeSlotAdherence(Long memberId, LocalDate startDate, LocalDate endDate) {
List<Map<String, Object>> slotStats = taskMapper.getTimeSlotStats(memberId, startDate, endDate);
Map<String, MedicationAdherenceVo> 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<String, Object> 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<String, MedicationAdherenceVo> 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<String, Object> 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<String, Object> map, String key) {
if (map == null || map.get(key) == null) return null;
return map.get(key).toString();
}
}

View File

@@ -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<MedicationPlanVo> 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);
}
}

View File

@@ -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<MedicationTaskVo> 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);
}
}

View File

@@ -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<MedicationTaskVo> 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<MedicationTaskVo> 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. 如果配置了家属通知,通知家属
}
}

View File

@@ -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<MedicationPlanVo> activePlans = planMapper.selectActiveList();
if (activePlans == null || activePlans.isEmpty()) {
log.info("没有执行中的用药计划");
return 0;
}
for (MedicationPlanVo plan : activePlans) {
try {
// 检查该计划当天是否需要服药
if (!shouldTakeMedicineOnDate(plan, date)) {
continue;
}
// 先计算本次应生成的任务列表
List<MedicationTask> 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<MedicationTask> generateTasksForPlan(MedicationPlanVo plan, LocalDate date) {
List<MedicationTask> 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);
}
}

View File

@@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.intc.health.mapper.ChronicDiseaseRecordMapper">
<resultMap type="ChronicDiseaseRecordVo" id="ChronicDiseaseRecordResult">
<result property="id" column="id"/>
<result property="personId" column="person_id"/>
<result property="personName" column="person_name"/>
<result property="diseaseId" column="disease_id"/>
<result property="diseaseName" column="disease_name"/>
<result property="diseaseCode" column="disease_code"/>
<result property="diagnosisDate" column="diagnosis_date"/>
<result property="diseaseStatus" column="disease_status"/>
<result property="mainDoctor" column="main_doctor"/>
<result property="hospital" column="hospital"/>
<result property="department" column="department"/>
<result property="notes" column="notes"/>
<result property="isActive" column="is_active"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="delFlag" column="del_flag"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectChronicDiseaseRecordVo">
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
</sql>
<select id="selectChronicDiseaseRecordList" parameterType="ChronicDiseaseRecordDto" resultMap="ChronicDiseaseRecordResult">
<include refid="selectChronicDiseaseRecordVo"/>
<where>
a.del_flag = '0'
<if test="personId != null">
and a.person_id = #{personId}
</if>
<if test="diseaseName != null and diseaseName != ''">
and a.disease_name like '%' || #{diseaseName} || '%'
</if>
<if test="diseaseCode != null and diseaseCode != ''">
and a.disease_code = #{diseaseCode}
</if>
<if test="diseaseStatus != null">
and a.disease_status = #{diseaseStatus}
</if>
<if test="isActive != null">
and a.is_active = #{isActive}
</if>
<if test="diagnosisDateStart != null">
and a.diagnosis_date &gt;= #{diagnosisDateStart}
</if>
<if test="diagnosisDateEnd != null">
and a.diagnosis_date &lt;= #{diagnosisDateEnd}
</if>
</where>
<!-- 数据范围过滤 -->
${params.dataScope}
order by a.create_time desc
</select>
<select id="selectChronicDiseaseRecordById" parameterType="Long" resultMap="ChronicDiseaseRecordResult">
<include refid="selectChronicDiseaseRecordVo"/>
where a.id = #{id}
</select>
<select id="selectChronicDiseaseRecordByMemberId" parameterType="Long" resultMap="ChronicDiseaseRecordResult">
<include refid="selectChronicDiseaseRecordVo"/>
where a.del_flag = '0' and a.is_active = 1 and a.person_id = #{personId}
order by a.disease_status desc, a.create_time desc
</select>
<insert id="insertChronicDiseaseRecord" parameterType="ChronicDiseaseRecord">
insert into chronic_disease_record
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="personId != null">person_id,</if>
<if test="diseaseId != null">disease_id,</if>
<if test="diseaseName != null and diseaseName != ''">disease_name,</if>
<if test="diseaseCode != null">disease_code,</if>
<if test="diagnosisDate != null">diagnosis_date,</if>
<if test="diseaseStatus != null">disease_status,</if>
<if test="mainDoctor != null">main_doctor,</if>
<if test="hospital != null">hospital,</if>
<if test="department != null">department,</if>
<if test="notes != null">notes,</if>
<if test="isActive != null">is_active,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="delFlag != null">del_flag,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="personId != null">#{personId},</if>
<if test="diseaseId != null">#{diseaseId},</if>
<if test="diseaseName != null and diseaseName != ''">#{diseaseName},</if>
<if test="diseaseCode != null">#{diseaseCode},</if>
<if test="diagnosisDate != null">#{diagnosisDate},</if>
<if test="diseaseStatus != null">#{diseaseStatus},</if>
<if test="mainDoctor != null">#{mainDoctor},</if>
<if test="hospital != null">#{hospital},</if>
<if test="department != null">#{department},</if>
<if test="notes != null">#{notes},</if>
<if test="isActive != null">#{isActive},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="delFlag != null">#{delFlag},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateChronicDiseaseRecord" parameterType="ChronicDiseaseRecord">
update chronic_disease_record
<trim prefix="SET" suffixOverrides=",">
<if test="personId != null">person_id = #{personId},</if>
<if test="diseaseId != null">disease_id = #{diseaseId},</if>
<if test="diseaseName != null and diseaseName != ''">disease_name = #{diseaseName},</if>
<if test="diseaseCode != null">disease_code = #{diseaseCode},</if>
<if test="diagnosisDate != null">diagnosis_date = #{diagnosisDate},</if>
<if test="diseaseStatus != null">disease_status = #{diseaseStatus},</if>
<if test="mainDoctor != null">main_doctor = #{mainDoctor},</if>
<if test="hospital != null">hospital = #{hospital},</if>
<if test="department != null">department = #{department},</if>
<if test="notes != null">notes = #{notes},</if>
<if test="isActive != null">is_active = #{isActive},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteChronicDiseaseRecordById" parameterType="Long">
delete from chronic_disease_record where id = #{id}
</delete>
<delete id="deleteChronicDiseaseRecordByIds" parameterType="String">
delete from chronic_disease_record where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<update id="removeChronicDiseaseRecordById" parameterType="Long">
update chronic_disease_record set del_flag = '1' where id = #{id}
</update>
<update id="removeChronicDiseaseRecordByIds" parameterType="String">
update chronic_disease_record set del_flag = '1' where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</update>
</mapper>

View File

@@ -0,0 +1,176 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.intc.health.mapper.MedicationPlanMapper">
<resultMap type="com.intc.health.domain.vo.MedicationPlanVo" id="MedicationPlanResult">
<id property="id" column="id"/>
<result property="personId" column="person_id"/>
<result property="personName" column="person_name"/>
<result property="chronicDiseaseId" column="chronic_disease_id"/>
<result property="chronicDiseaseName" column="chronic_disease_name"/>
<result property="planName" column="plan_name"/>
<result property="medicineId" column="medicine_id"/>
<result property="medicineName" column="medicine_name"/>
<result property="specification" column="specification"/>
<result property="dosagePerTime" column="dosage_per_time"/>
<result property="dosageUnit" column="dosage_unit"/>
<result property="frequencyType" column="frequency_type"/>
<result property="frequencyConfig" column="frequency_config" typeHandler="com.intc.common.core.handler.JsonNodeTypeHandler"/>
<result property="timeSlots" column="time_slots" typeHandler="com.intc.common.core.handler.JsonNodeTypeHandler"/>
<result property="startDate" column="start_date"/>
<result property="endDate" column="end_date"/>
<result property="remindEnabled" column="remind_enabled"/>
<result property="remindAdvanceMin" column="remind_advance_min"/>
<result property="notifyFamily" column="notify_family"/>
<result property="familyMemberIds" column="family_member_ids" typeHandler="com.intc.common.core.handler.JsonNodeTypeHandler"/>
<result property="status" column="status"/>
<result property="todayTotal" column="today_total"/>
<result property="todayCompleted" column="today_completed"/>
<result property="createTime" column="create_time"/>
</resultMap>
<sql id="selectVo">
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
</sql>
<select id="selectById" parameterType="Long" resultMap="MedicationPlanResult">
<include refid="selectVo"/>
where a.id = #{id} and a.del_flag = 0
</select>
<select id="selectList" parameterType="com.intc.health.domain.dto.MedicationPlanDto" resultMap="MedicationPlanResult">
<include refid="selectVo"/>
<where>
a.del_flag = 0
<if test="personId != null"> and a.person_id = #{personId}</if>
<if test="chronicDiseaseId != null"> and a.chronic_disease_id = #{chronicDiseaseId}</if>
<if test="planName != null and planName != ''"> and a.plan_name like '%' || #{planName} || '%'</if>
<if test="medicineId != null"> and a.medicine_id = #{medicineId}</if>
<if test="medicineName != null and medicineName != ''"> and a.medicine_name like '%' || #{medicineName} || '%'</if>
<if test="status != null"> and a.status = #{status}</if>
<if test="startDateBegin != null"> and a.start_date &gt;= #{startDateBegin}</if>
<if test="startDateEnd != null"> and a.start_date &lt;= #{startDateEnd}</if>
<if test="keyword != null and keyword != ''">
and (a.plan_name like '%' || #{keyword} || '%' or a.medicine_name like '%' || #{keyword} || '%')
</if>
</where>
<!-- 数据范围过滤 -->
${params.dataScope}
order by a.create_time desc
</select>
<select id="selectActiveList" resultMap="MedicationPlanResult">
<include refid="selectVo"/>
where a.del_flag = 0 and a.status = 1
and a.start_date &lt;= current_date
and (a.end_date is null or a.end_date &gt;= current_date)
</select>
<insert id="insert" parameterType="com.intc.health.domain.MedicationPlan" useGeneratedKeys="true" keyProperty="id">
insert into medication_plan
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="personId != null">person_id,</if>
<if test="chronicDiseaseId != null">chronic_disease_id,</if>
<if test="planName != null and planName != ''">plan_name,</if>
<if test="medicineId != null">medicine_id,</if>
<if test="medicineName != null">medicine_name,</if>
<if test="specification != null">specification,</if>
<if test="dosagePerTime != null">dosage_per_time,</if>
<if test="dosageUnit != null">dosage_unit,</if>
<if test="frequencyType != null">frequency_type,</if>
<if test="frequencyConfig != null">frequency_config,</if>
<if test="timeSlots != null">time_slots,</if>
<if test="startDate != null">start_date,</if>
<if test="endDate != null">end_date,</if>
<if test="remindEnabled != null">remind_enabled,</if>
<if test="remindAdvanceMin != null">remind_advance_min,</if>
<if test="notifyFamily != null">notify_family,</if>
<if test="familyMemberIds != null">family_member_ids,</if>
<if test="status != null">status,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="remark != null">remark,</if>
del_flag,
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="personId != null">#{personId},</if>
<if test="chronicDiseaseId != null">#{chronicDiseaseId},</if>
<if test="planName != null and planName != ''">#{planName},</if>
<if test="medicineId != null">#{medicineId},</if>
<if test="medicineName != null">#{medicineName},</if>
<if test="specification != null">#{specification},</if>
<if test="dosagePerTime != null">#{dosagePerTime},</if>
<if test="dosageUnit != null">#{dosageUnit},</if>
<if test="frequencyType != null">#{frequencyType},</if>
<if test="frequencyConfig != null">#{frequencyConfig, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler},</if>
<if test="timeSlots != null">#{timeSlots, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler},</if>
<if test="startDate != null">#{startDate},</if>
<if test="endDate != null">#{endDate},</if>
<if test="remindEnabled != null">#{remindEnabled},</if>
<if test="remindAdvanceMin != null">#{remindAdvanceMin},</if>
<if test="notifyFamily != null">#{notifyFamily},</if>
<if test="familyMemberIds != null">#{familyMemberIds, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler},</if>
<if test="status != null">#{status},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="remark != null">#{remark},</if>
0,
</trim>
</insert>
<update id="update" parameterType="com.intc.health.domain.MedicationPlan">
update medication_plan
<set>
<if test="personId != null">person_id = #{personId},</if>
<if test="chronicDiseaseId != null">chronic_disease_id = #{chronicDiseaseId},</if>
<if test="planName != null">plan_name = #{planName},</if>
<if test="medicineId != null">medicine_id = #{medicineId},</if>
<if test="medicineName != null">medicine_name = #{medicineName},</if>
<if test="specification != null">specification = #{specification},</if>
<if test="dosagePerTime != null">dosage_per_time = #{dosagePerTime},</if>
<if test="dosageUnit != null">dosage_unit = #{dosageUnit},</if>
<if test="frequencyType != null">frequency_type = #{frequencyType},</if>
<if test="frequencyConfig != null">frequency_config = #{frequencyConfig, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler},</if>
<if test="timeSlots != null">time_slots = #{timeSlots, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler},</if>
<if test="startDate != null">start_date = #{startDate},</if>
<if test="endDate != null">end_date = #{endDate},</if>
<if test="remindEnabled != null">remind_enabled = #{remindEnabled},</if>
<if test="remindAdvanceMin != null">remind_advance_min = #{remindAdvanceMin},</if>
<if test="notifyFamily != null">notify_family = #{notifyFamily},</if>
<if test="familyMemberIds != null">family_member_ids = #{familyMemberIds, typeHandler=com.intc.common.core.handler.JsonNodeTypeHandler},</if>
<if test="status != null">status = #{status},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</set>
where id = #{id}
</update>
<delete id="deleteById" parameterType="Long">
delete from medication_plan where id = #{id}
</delete>
<delete id="deleteByIds" parameterType="Long">
delete from medication_plan where id in
<foreach item="id" collection="array" open="(" separator="," close=")">#{id}</foreach>
</delete>
<update id="removeById" parameterType="Long">
update medication_plan set del_flag = 1 where id = #{id}
</update>
<update id="removeByIds" parameterType="Long">
update medication_plan set del_flag = 1 where id in
<foreach item="id" collection="array" open="(" separator="," close=")">#{id}</foreach>
</update>
</mapper>

View File

@@ -0,0 +1,203 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.intc.health.mapper.MedicationTaskMapper">
<resultMap type="com.intc.health.domain.vo.MedicationTaskVo" id="MedicationTaskResult">
<id property="id" column="id"/>
<result property="planId" column="plan_id"/>
<result property="planName" column="plan_name"/>
<result property="personId" column="person_id"/>
<result property="personName" column="person_name"/>
<result property="medicineName" column="medicine_name"/>
<result property="plannedDate" column="planned_date"/>
<result property="plannedTime" column="planned_time"/>
<result property="plannedDosage" column="planned_dosage"/>
<result property="actualTime" column="actual_time"/>
<result property="actualDosage" column="actual_dosage"/>
<result property="status" column="status"/>
<result property="confirmType" column="confirm_type"/>
<result property="notes" column="notes"/>
<result property="remindStatus" column="remind_status"/>
<result property="remindTime" column="remind_time"/>
<result property="createTime" column="create_time"/>
</resultMap>
<sql id="selectVo">
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
</sql>
<select id="selectById" parameterType="Long" resultMap="MedicationTaskResult">
<include refid="selectVo"/>
where a.id = #{id} and a.del_flag = 0
</select>
<select id="selectList" parameterType="com.intc.health.domain.dto.MedicationTaskDto" resultMap="MedicationTaskResult">
<include refid="selectVo"/>
<where>
a.del_flag = 0
<if test="planId != null"> and a.plan_id = #{planId}</if>
<if test="personId != null"> and a.person_id = #{personId}</if>
<if test="medicineName != null and medicineName != ''"> and a.medicine_name like '%' || #{medicineName} || '%'</if>
<if test="status != null"> and a.status = #{status}</if>
<if test="plannedDateBegin != null"> and a.planned_date &gt;= #{plannedDateBegin}</if>
<if test="plannedDateEnd != null"> and a.planned_date &lt;= #{plannedDateEnd}</if>
<if test="queryDate != null"> and a.planned_date = #{queryDate}</if>
<if test="keyword != null and keyword != ''">
and a.medicine_name like '%' || #{keyword} || '%'
</if>
</where>
<!-- 数据范围过滤 -->
${params.dataScope}
order by a.planned_date desc, a.planned_time desc
</select>
<insert id="insert" parameterType="com.intc.health.domain.MedicationTask" useGeneratedKeys="true" keyProperty="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>
<insert id="batchInsert" parameterType="java.util.List">
insert into medication_task (
plan_id, person_id, medicine_name, planned_date, planned_time,
planned_dosage, status, remind_status, create_time, del_flag
) values
<foreach collection="list" item="item" separator=",">
(#{item.planId}, #{item.personId}, #{item.medicineName}, #{item.plannedDate}, #{item.plannedTime},
#{item.plannedDosage}, #{item.status}, #{item.remindStatus}, #{item.createTime}, 0)
</foreach>
</insert>
<update id="update" parameterType="com.intc.health.domain.MedicationTask">
update medication_task
<set>
<if test="actualTime != null">actual_time = #{actualTime},</if>
<if test="actualDosage != null">actual_dosage = #{actualDosage},</if>
<if test="status != null">status = #{status},</if>
<if test="confirmType != null">confirm_type = #{confirmType},</if>
<if test="notes != null">notes = #{notes},</if>
<if test="remindStatus != null">remind_status = #{remindStatus},</if>
<if test="remindTime != null">remind_time = #{remindTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</set>
where id = #{id}
</update>
<delete id="deleteById" parameterType="Long">
delete from medication_task where id = #{id}
</delete>
<delete id="deleteByIds" parameterType="Long">
delete from medication_task where id in
<foreach item="id" collection="array" open="(" separator="," close=")">#{id}</foreach>
</delete>
<update id="removeById" parameterType="Long">
update medication_task set del_flag = 1 where id = #{id}
</update>
<update id="removeByIds" parameterType="Long">
update medication_task set del_flag = 1 where id in
<foreach item="id" collection="array" open="(" separator="," close=")">#{id}</foreach>
</update>
<select id="countByPlanAndDate" resultType="int">
select count(1) from medication_task
where del_flag = 0 and plan_id = #{planId} and planned_date = #{date}
</select>
<delete id="deleteByPlanAndDate">
delete from medication_task
where plan_id = #{planId} and planned_date = #{date} and status = 0
</delete>
<select id="countCompletedByPlanAndDate" resultType="int">
select count(1) from medication_task
where del_flag = 0 and plan_id = #{planId} and planned_date = #{date} and status = 1
</select>
<update id="markExpired">
update medication_task
set status = 2, update_time = current_timestamp
where del_flag = 0 and status = 0
and (planned_date || ' ' || planned_time)::timestamp &lt; #{expireThreshold}
</update>
<select id="selectPendingRemind" resultMap="MedicationTaskResult">
<include refid="selectVo"/>
where a.del_flag = 0 and a.status = 0 and a.remind_status = 0
and (a.planned_date || ' ' || a.planned_time)::timestamp &gt;= #{startTime}
and (a.planned_date || ' ' || a.planned_time)::timestamp &lt;= #{endTime}
order by a.planned_date, a.planned_time
</select>
<!-- ========== 依从性统计 ========== -->
<select id="getOverallStats" resultType="java.util.Map">
select
count(1) as total_tasks,
sum(case when status = 1 then 1 else 0 end) as completed_tasks,
sum(case when status = 2 then 1 else 0 end) as missed_tasks,
sum(case when status = 3 then 1 else 0 end) as skipped_tasks,
sum(case when status = 0 then 1 else 0 end) as pending_tasks,
sum(case when status = 1 and actual_time is not null
and extract(epoch from (actual_time - (planned_date || ' ' || planned_time)::timestamp))/3600 &lt;= 1
then 1 else 0 end) as ontime_tasks
from medication_task
where del_flag = 0
<if test="personId != null"> and person_id = #{personId}</if>
<if test="startDate != null"> and planned_date &gt;= #{startDate}</if>
<if test="endDate != null"> and planned_date &lt;= #{endDate}</if>
</select>
<select id="getDailyStats" resultType="java.util.Map">
select
planned_date as stat_date,
count(1) as total_tasks,
sum(case when status = 1 then 1 else 0 end) as completed_tasks,
sum(case when status = 2 then 1 else 0 end) as missed_tasks,
sum(case when status = 3 then 1 else 0 end) as skipped_tasks,
sum(case when status = 1 and actual_time is not null
and extract(epoch from (actual_time - (planned_date || ' ' || planned_time)::timestamp))/3600 &lt;= 1
then 1 else 0 end) as ontime_tasks
from medication_task
where del_flag = 0
<if test="personId != null"> and person_id = #{personId}</if>
<if test="startDate != null"> and planned_date &gt;= #{startDate}</if>
<if test="endDate != null"> and planned_date &lt;= #{endDate}</if>
group by planned_date
order by planned_date
</select>
<select id="getTimeSlotStats" resultType="java.util.Map">
select
extract(hour from planned_time) as hour,
count(1) as total_tasks,
sum(case when status = 1 then 1 else 0 end) as completed_tasks,
sum(case when status = 2 then 1 else 0 end) as missed_tasks,
sum(case when status = 1 and actual_time is not null
and extract(epoch from (actual_time - (planned_date || ' ' || planned_time)::timestamp))/3600 &lt;= 1
then 1 else 0 end) as ontime_tasks
from medication_task
where del_flag = 0
<if test="personId != null"> and person_id = #{personId}</if>
<if test="startDate != null"> and planned_date &gt;= #{startDate}</if>
<if test="endDate != null"> and planned_date &lt;= #{endDate}</if>
group by extract(hour from planned_time)
order by hour
</select>
</mapper>