浏览代码

新增
1、新增床位对调接口
修改
1、修改获取机构看板统计(院内动态、护理情况)接口对请假的判断逻辑
BUGFIX
1、修复获得集团租户和用户关联接口获取重复数据

liangwenxuan 3 月之前
父节点
当前提交
a08b21b607
共有 14 个文件被更改,包括 316 次插入66 次删除
  1. 13 11
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/BedChangeRecordController.java
  2. 61 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/BedSwapReqVO.java
  3. 1 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/elderlyactivityimage/ElderlyBuildFloorActivityImageRespVO.java
  4. 1 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/elderlyactivityimage/ElderlyBuildFloorActivityImageSaveReqVO.java
  5. 4 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/elderly/ElderlyNursingLogController.java
  6. 2 5
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/biz/ElderlyBuildFloorActivityImageDO.java
  7. 1 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/elderly/ElderlyNursingLogDO.java
  8. 3 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/biz/ElderlyBuildFloorActivityImageMapper.java
  9. 10 3
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ElderlyChangeRecordService.java
  10. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ElderlyInfoServiceImpl.java
  11. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ExpenseOrderServiceImpl.java
  12. 165 7
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/NurseChangeRecordServiceImpl.java
  13. 32 13
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/UserGroupTenantServiceImpl.java
  14. 21 17
      yudao-module-system/yudao-module-system-biz/src/main/resources/mapper/ExpenseItemMapper.xml

+ 13 - 11
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/BedChangeRecordController.java

@@ -18,13 +18,6 @@ import javax.validation.Valid;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
-/**
- * @Author: jinyd
- * @CreateTime: 2024/8/19  11:17
- * @Description:
- * @Version: 4.0
- */
-
 @Tag(name = "管理后台 - 床位变更记录")
 @RestController
 @RequestMapping("/elderly/bedChangeRecord")
@@ -33,19 +26,21 @@ public class BedChangeRecordController {
 
     @Resource
     private ElderlyChangeRecordService changeRecordService;
+
     @GetMapping("/detail")
     @Operation(summary = "获得床位变更记录详情")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('bed-change:detail')")
     @TenantIgnore
-    public CommonResult<BedChangeRecordRespVO> getBedChangeRecord(@RequestParam("id") Long id, @RequestParam("status") Integer status,@RequestParam("isDetail")Boolean isDetail) {
-        BedChangeRecordRespVO bedChangeRecord = changeRecordService.detail(id, status,isDetail);
+    public CommonResult<BedChangeRecordRespVO> getBedChangeRecord(@RequestParam("id") Long id,
+                                                                  @RequestParam("status") Integer status,
+                                                                  @RequestParam("isDetail") Boolean isDetail) {
+        BedChangeRecordRespVO bedChangeRecord = changeRecordService.detail(id, status, isDetail);
         return success(bedChangeRecord);
     }
 
     @GetMapping("/findElderlyPage")
     @Operation(summary = "长者信息列表分页")
-    //@PreAuthorize("@ss.hasPermission('elderly:bed-change-record:findElderlyPage')")
     @TenantIgnore
     public CommonResult<PageResult<BedChangeElderlyRespVO>> findElderlyPage(@Valid ElderlyInfoPageReqVO pageVO) {
         pageVO.setTenantIds(pageVO.getTenantIds() == null ? new Long[]{TenantContextHolder.getTenantId()} : pageVO.getTenantIds());
@@ -57,11 +52,18 @@ public class BedChangeRecordController {
     @Operation(summary = "校验是否可以包房")
     @Parameter(name = "bedId", description = "床位id", required = true, example = "1024")
     @Parameter(name = "elderId", description = "申请该床位包房的长者id", required = true, example = "1024")
-    //@PreAuthorize("@ss.hasPermission('elderly:bed-change-record:query')")
     public CommonResult<Boolean> checkPrivateRoom(@RequestParam("bedId") Long bedId,
                                                   @RequestParam("elderId") Long elderId) {
         Boolean result = changeRecordService.checkPrivateRoom(bedId, elderId);
         return success(result);
     }
 
+    @PostMapping("/swap")
+    @Operation(summary = "床位对调")
+    @TenantIgnore
+    public CommonResult<Boolean> swapBed(@Valid @RequestBody BedSwapReqVO reqVO) {
+        changeRecordService.swapBed(reqVO);
+        return success(true);
+    }
+
 }

+ 61 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/BedSwapReqVO.java

@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.system.controller.admin.biz.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@Schema(description = "管理后台 - 床位对调 Request VO")
+@Data
+public class BedSwapReqVO {
+
+    @Schema(description = "开始日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-10-01")
+    @NotNull(message = "开始日期不能为空")
+    private LocalDate startDate;
+
+    @Schema(description = "长者A ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
+    @NotNull(message = "长者A ID不能为空")
+    private Long elderIdA;
+
+    @Schema(description = "长者B ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1002")
+    @NotNull(message = "长者B ID不能为空")
+    private Long elderIdB;
+
+    @Schema(description = "长者A床位ID(用于校验/对调目标)", example = "2001")
+    private Long bedIdA;
+
+    @Schema(description = "长者B床位ID(用于校验/对调目标)", example = "2002")
+    private Long bedIdB;
+
+    @Schema(description = "长者A原费用项目ID", example = "3001")
+    private Long originalExpenseItemIdA;
+
+    @Schema(description = "长者B原费用项目ID", example = "3002")
+    private Long originalExpenseItemIdB;
+
+    @Schema(description = "长者A新费用项目ID", example = "4001")
+    private Long newExpenseItemIdA;
+
+    @Schema(description = "长者B新费用项目ID", example = "4002")
+    private Long newExpenseItemIdB;
+
+    @Schema(description = "长者A是否打折", example = "1")
+    private Integer isDiscountA;
+
+    @Schema(description = "长者B是否打折", example = "1")
+    private Integer isDiscountB;
+
+    @Schema(description = "长者A折扣金额", example = "100")
+    private BigDecimal discountAmountA;
+
+    @Schema(description = "长者B折扣金额", example = "100")
+    private BigDecimal discountAmountB;
+
+    @Schema(description = "长者A实际费用金额", example = "900")
+    private BigDecimal actualAmountA;
+
+    @Schema(description = "长者B实际费用金额", example = "900")
+    private BigDecimal actualAmountB;
+}

+ 1 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/elderlyactivityimage/ElderlyBuildFloorActivityImageRespVO.java

@@ -46,9 +46,7 @@ public class ElderlyBuildFloorActivityImageRespVO {
     private String belongFloor;
 
     @Schema(description = "活动日期")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = "GMT+8")
-    private Date activityDate;
+    private String activityDate;
 
     @Schema(description = "活动内容")
     private String activityContent;

+ 1 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/elderlyactivityimage/ElderlyBuildFloorActivityImageSaveReqVO.java

@@ -52,9 +52,7 @@ public class ElderlyBuildFloorActivityImageSaveReqVO {
     private String belongFloor;
 
     @Schema(description = "活动日期")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = "GMT+8")
-    private Date activityDate;
+    private String activityDate;
 
     @Schema(description = "活动内容")
     private String activityContent;

+ 4 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/elderly/ElderlyNursingLogController.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.controller.admin.elderly;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 import cn.iocoder.yudao.module.system.controller.admin.elderly.vo.ElderlyNursingLogDetailRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.elderly.vo.ElderlyNursingLogPageReqVO;
@@ -15,6 +16,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import javax.annotation.security.PermitAll;
 import javax.validation.Valid;
 import java.util.Objects;
 
@@ -31,6 +33,8 @@ public class ElderlyNursingLogController {
 
     @PostMapping("/create")
     @Operation(summary = "创建长者护理记录")
+    @PermitAll
+    @TenantIgnore
     public CommonResult<Long> create(@Valid @RequestBody ElderlyNursingLogSaveReqVO createReqVO) {
         createReqVO.setTenantId(Objects.isNull(createReqVO.getTenantId())
                 ? TenantContextHolder.getTenantId() : createReqVO.getTenantId());

+ 2 - 5
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/biz/ElderlyBuildFloorActivityImageDO.java

@@ -20,12 +20,11 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
  */
 @TableName("elderly_build_floor_activity_image")
 @Data
-@EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class ElderlyBuildFloorActivityImageDO extends BaseNoDeleteDO {
+public class ElderlyBuildFloorActivityImageDO{
 
     /**
      * 主键ID
@@ -81,9 +80,7 @@ public class ElderlyBuildFloorActivityImageDO extends BaseNoDeleteDO {
      * 活动日期
      */
     @Schema(description = "活动日期")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = "GMT+8")
-    private Date activityDate;
+    private String activityDate;
 
     /**
      * 活动内容

+ 1 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/elderly/ElderlyNursingLogDO.java

@@ -15,11 +15,10 @@ import java.time.LocalDate;
 @TableName("elderly_nursing_log")
 @KeySequence("elderly_nursing_log_seq")
 @Data
-@ToString(callSuper = true)
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
-public class ElderlyNursingLogDO extends BaseNoDeleteDO {
+public class ElderlyNursingLogDO {
 
     @TableId(type = IdType.AUTO)
     private Long id;

+ 3 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/biz/ElderlyBuildFloorActivityImageMapper.java

@@ -31,3 +31,6 @@ public interface ElderlyBuildFloorActivityImageMapper extends BaseMapperX<Elderl
 
 
 
+
+
+

+ 10 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ElderlyChangeRecordService.java

@@ -53,7 +53,7 @@ public interface ElderlyChangeRecordService {
     PageResult<ElderlyChangeRecordDO> getBedChangeRecordPage(BedChangeRecordPageReqVO pageReqVO);
 
 
-    BedChangeRecordRespVO detail(Long id, Integer status,Boolean isDetail);
+    BedChangeRecordRespVO detail(Long id, Integer status, Boolean isDetail);
 
     PageResult<BedChangeElderlyRespVO> findElderlyPage(ElderlyInfoPageReqVO pageVO);
 
@@ -61,7 +61,14 @@ public interface ElderlyChangeRecordService {
 
     List<NurseChangeyEvaluationRespVO> getEvaluationByElderId(Long elderId);
 
-    NurseChangeDetailRespVO getNurseDetail(Long id,Boolean isDetail);
+    NurseChangeDetailRespVO getNurseDetail(Long id, Boolean isDetail);
 
-    CateringChangeDetailRespVO getCateringDetail(Long id,Boolean isDetail);
+    CateringChangeDetailRespVO getCateringDetail(Long id, Boolean isDetail);
+
+    /**
+     * 床位对调
+     *
+     * @param reqVO 请求参数
+     */
+    void swapBed(BedSwapReqVO reqVO);
 }

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ElderlyInfoServiceImpl.java

@@ -1578,7 +1578,7 @@ public class ElderlyInfoServiceImpl implements ElderlyInfoService {
         long askLeaveCount = Optional.ofNullable(elderlyAskLeaveMapper.selectCount(new LambdaQueryWrapperX<ElderlyAskLeaveDO>()
                 .in(ElderlyAskLeaveDO::getTenantId, tenantId)
                 .le(ElderlyAskLeaveDO::getOutDate, todayEndDate)
-                .and(w -> w.ge(ElderlyAskLeaveDO::getComeDate, todayStartDate).or().isNull(ElderlyAskLeaveDO::getComeDate)))).orElse(0L);
+                .and(w -> w.ge(ElderlyAskLeaveDO::getUpdateDate, todayStartDate).or().isNull(ElderlyAskLeaveDO::getUpdateDate)))).orElse(0L);
 
         long todayAskLeaveCount = Optional.ofNullable(elderlyAskLeaveMapper.selectCount(new LambdaQueryWrapperX<ElderlyAskLeaveDO>()
                 .in(ElderlyAskLeaveDO::getTenantId, tenantId)

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ExpenseOrderServiceImpl.java

@@ -3802,7 +3802,7 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
             ExpenseDO expense = optional.get();
             // 查询账单生成月的原金额并赋值返回前端,因为存在扣减的变更日常消费记录,返回变更后的金额会双重扣减金额导致总金额不对
             List<ExpenseItemDO> expenseItems = expenseItemMapper.generateMonthlyBill(elderId, billingMonth, BooleanEnum.FALSE.getValue());
-            if (expenseItems.size() == 0) {
+            if (expenseItems.isEmpty()) {
                 expenseItems = expenseItemMapper.generateMonthlyBill(elderId, billingMonth, BooleanEnum.TRUE.getValue());
             }
             YearMonth queryTime = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));

+ 165 - 7
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/NurseChangeRecordServiceImpl.java

@@ -12,24 +12,30 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.*;
 import cn.iocoder.yudao.module.system.dal.dataobject.biz.*;
-import cn.iocoder.yudao.module.system.dal.mysql.biz.ElderlyChangeRecordMapper;
-import cn.iocoder.yudao.module.system.dal.mysql.biz.BuildBedMapper;
-import cn.iocoder.yudao.module.system.dal.mysql.biz.ElderlyInfoMapper;
+import cn.iocoder.yudao.module.system.dal.mysql.biz.*;
 import cn.iocoder.yudao.module.system.enums.common.BooleanEnum;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exceptionCustomMsg;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.BED_CHANGE_RECORD_NOT_EXISTS;
+import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.COMMON_ERROR;
 
 /**
  * 床位变更记录 Service 实现类
@@ -52,6 +58,14 @@ public class NurseChangeRecordServiceImpl implements ElderlyChangeRecordService
     @Autowired
     private ElderlyInfoMapper elderlyInfoMapper;
 
+    @Resource
+    private ExpenseItemMapper expenseItemMapper;
+
+    private ExpenseMapper expenseMapper;
+
+    @Resource
+    private SysOverheadChargeMapper sysOverheadChargeMapper;
+
     @Override
     public Long createBedChangeRecord(BedChangeRecordSaveReqVO createReqVO) {
         // 插入
@@ -96,8 +110,8 @@ public class NurseChangeRecordServiceImpl implements ElderlyChangeRecordService
 
     @Override
     @TenantIgnore
-    public BedChangeRecordRespVO detail(Long id, Integer status,Boolean isDetail) {
-        Integer currentFlag  = 0;
+    public BedChangeRecordRespVO detail(Long id, Integer status, Boolean isDetail) {
+        Integer currentFlag = 0;
         if (null != status && status == 4) {
             Integer count = bedChangeRecordMapper.checkBpmBusinessFormById(id);
             if (null == count || count == 0) {
@@ -191,7 +205,7 @@ public class NurseChangeRecordServiceImpl implements ElderlyChangeRecordService
 
     @Override
     @TenantIgnore
-    public NurseChangeDetailRespVO getNurseDetail(Long id,Boolean isDetail) {
+    public NurseChangeDetailRespVO getNurseDetail(Long id, Boolean isDetail) {
         NurseChangeDetailRespVO detail = isDetail ? bedChangeRecordMapper.getNurseDetail(id) : bedChangeRecordMapper.createNurseChangeGetDetail(id);
         detail.setBedName(buildService.getFullBedName(detail.getElderlyId()));
         return detail;
@@ -199,9 +213,153 @@ public class NurseChangeRecordServiceImpl implements ElderlyChangeRecordService
 
     @Override
     @TenantIgnore
-    public CateringChangeDetailRespVO getCateringDetail(Long id,Boolean isDetail) {
+    public CateringChangeDetailRespVO getCateringDetail(Long id, Boolean isDetail) {
         CateringChangeDetailRespVO detail = isDetail ? bedChangeRecordMapper.getCateringDetail(id) : bedChangeRecordMapper.createCateringChangeGetDetail(id);
         detail.setBedName(buildService.getFullBedName(detail.getElderlyId()));
         return detail;
     }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void swapBed(BedSwapReqVO reqVO) {
+        if (reqVO.getElderIdA() == null || reqVO.getElderIdB() == null) {
+            throw exceptionCustomMsg(COMMON_ERROR, "长者ID不能为空");
+        }
+        if (reqVO.getElderIdA().equals(reqVO.getElderIdB())) {
+            throw exceptionCustomMsg(COMMON_ERROR, "两位长者不能相同");
+        }
+        LocalDate startDate = reqVO.getStartDate();
+        if (startDate == null) {
+            throw exceptionCustomMsg(COMMON_ERROR, "开始日期不能为空");
+        }
+
+        ElderlyInfoDO elderA = elderlyInfoMapper.selectById(reqVO.getElderIdA());
+        ElderlyInfoDO elderB = elderlyInfoMapper.selectById(reqVO.getElderIdB());
+        if (elderA == null || elderB == null) {
+            throw exceptionCustomMsg(COMMON_ERROR, "长者信息不存在");
+        }
+        if (reqVO.getBedIdA() != null && !reqVO.getBedIdA().equals(elderA.getBedId())) {
+            throw exceptionCustomMsg(COMMON_ERROR, "长者A床位ID与当前床位不匹配");
+        }
+        if (reqVO.getBedIdB() != null && !reqVO.getBedIdB().equals(elderB.getBedId())) {
+            throw exceptionCustomMsg(COMMON_ERROR, "长者B床位ID与当前床位不匹配");
+        }
+
+        validateExpenseParams(reqVO);
+
+        BuildBedDO bedA = buildBedMapper.selectById(elderA.getBedId());
+        BuildBedDO bedB = buildBedMapper.selectById(elderB.getBedId());
+        if (bedA == null || bedB == null) {
+            throw exceptionCustomMsg(COMMON_ERROR, "床位信息不存在");
+        }
+
+        elderA.setBedId(bedB.getId());
+        elderA.setBedName(bedB.getBedName());
+        elderA.setRoomId(bedB.getRoomId());
+        elderA.setFloorId(bedB.getFloorId());
+        elderA.setBuildId(bedB.getBuildId());
+
+        elderB.setBedId(bedA.getId());
+        elderB.setBedName(bedA.getBedName());
+        elderB.setRoomId(bedA.getRoomId());
+        elderB.setFloorId(bedA.getFloorId());
+        elderB.setBuildId(bedA.getBuildId());
+
+        elderlyInfoMapper.updateById(elderA);
+        elderlyInfoMapper.updateById(elderB);
+
+        if (reqVO.getNewExpenseItemIdA() != null) {
+            handleExpenseItemSwap(reqVO, true);
+        }
+        if (reqVO.getNewExpenseItemIdB() != null) {
+            handleExpenseItemSwap(reqVO, false);
+        }
+    }
+
+    private void validateExpenseParams(BedSwapReqVO reqVO) {
+        boolean changeA = reqVO.getNewExpenseItemIdA() != null;
+        boolean changeB = reqVO.getNewExpenseItemIdB() != null;
+        if (changeA != changeB) {
+            throw exceptionCustomMsg(COMMON_ERROR, "费用变更需同时提交A/B两位长者的信息");
+        }
+        if (!changeA) {
+            return;
+        }
+        validateExpenseParamsForElder(reqVO, true);
+        validateExpenseParamsForElder(reqVO, false);
+    }
+
+    private void validateExpenseParamsForElder(BedSwapReqVO reqVO, boolean isElderA) {
+        String label = isElderA ? "长者A" : "长者B";
+        Long originalExpenseItemId = isElderA ? reqVO.getOriginalExpenseItemIdA() : reqVO.getOriginalExpenseItemIdB();
+        if (originalExpenseItemId == null) {
+            throw exceptionCustomMsg(COMMON_ERROR, label + "原费用项目ID不能为空");
+        }
+        Integer isDiscount = isElderA ? reqVO.getIsDiscountA() : reqVO.getIsDiscountB();
+        BigDecimal discountAmount = isElderA ? reqVO.getDiscountAmountA() : reqVO.getDiscountAmountB();
+        BigDecimal actualAmount = isElderA ? reqVO.getActualAmountA() : reqVO.getActualAmountB();
+        if (actualAmount != null) {
+            return;
+        }
+        if (isDiscount != null && isDiscount == 1 && discountAmount == null) {
+            throw exceptionCustomMsg(COMMON_ERROR, label + "打折时折扣金额不能为空");
+        }
+    }
+
+    private void handleExpenseItemSwap(BedSwapReqVO reqVO, boolean isElderA) {
+        Long originalExpenseItemId = isElderA ? reqVO.getOriginalExpenseItemIdA() : reqVO.getOriginalExpenseItemIdB();
+        Long elderId = isElderA ? reqVO.getElderIdA() : reqVO.getElderIdB();
+        if (originalExpenseItemId == null) {
+            throw exceptionCustomMsg(COMMON_ERROR, (isElderA ? "长者A" : "长者B") + "原费用项目ID不能为空");
+        }
+        ExpenseItemDO originalItem = expenseItemMapper.selectById(originalExpenseItemId);
+        if (originalItem == null) {
+            throw exceptionCustomMsg(COMMON_ERROR, (isElderA ? "长者A" : "长者B") + "原费用项目不存在");
+        }
+        List<ExpenseItemDO> expenseItemDOS = expenseItemMapper.selectList(new LambdaQueryWrapperX<ExpenseItemDO>()
+                .eq(ExpenseItemDO::getExpenseId, originalItem.getExpenseId()));
+        List<ExpenseItemDO> newExpenseItemList = new ArrayList<>();
+        for (ExpenseItemDO expenseItemDO : expenseItemDOS) {
+            expenseItemDO.setChangeEndDate(reqVO.getStartDate().plusDays(-1));
+            if(!Objects.equals(expenseItemDO.getId(), originalItem.getId())){
+                newExpenseItemList.add(expenseItemDO);
+            }
+        }
+        expenseItemMapper.updateBatch(expenseItemDOS);
+
+        Long newExpenseItemId = isElderA ? reqVO.getNewExpenseItemIdA() : reqVO.getNewExpenseItemIdB();
+        OverheadChargeDO newCharge = sysOverheadChargeMapper.selectById(newExpenseItemId);
+        if (newCharge == null) {
+            throw exceptionCustomMsg(COMMON_ERROR, (isElderA ? "长者A" : "长者B") + "新费用项目不存在");
+        }
+
+        BigDecimal amount = BigDecimal.valueOf(newCharge.getPrice());
+        Integer isDiscount = isElderA ? reqVO.getIsDiscountA() : reqVO.getIsDiscountB();
+        BigDecimal discountAmount = isElderA ? reqVO.getDiscountAmountA() : reqVO.getDiscountAmountB();
+        BigDecimal actualAmount = isElderA ? reqVO.getActualAmountA() : reqVO.getActualAmountB();
+        int count = originalItem.getCount() == null ? 1 : originalItem.getCount();
+        BigDecimal totalAmount = actualAmount.multiply(BigDecimal.valueOf(count));
+
+        ExpenseItemDO newItem = new ExpenseItemDO();
+        BeanUtils.copyProperties(originalItem, newItem);
+        newItem.setId(null);
+        newItem.setItemId(newCharge.getId());
+        newItem.setItemName(newCharge.getChargeName());
+        newItem.setAmount(amount);
+        newItem.setActualAmount(actualAmount);
+        newItem.setTotalAmount(totalAmount);
+        newItem.setIsDiscount(isDiscount);
+        newItem.setDiscountAmount(discountAmount);
+        newItem.setChangeStartDate(reqVO.getStartDate());
+        newItem.setChangeEndDate(null);
+        newItem.setTenantId(originalItem.getTenantId() == null ? TenantContextHolder.getTenantId() : originalItem.getTenantId());
+        newExpenseItemList.add(newItem);
+        BigDecimal total = newExpenseItemList.stream().map(ExpenseItemDO::getActualAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
+        ExpenseDO expenseDO = new ExpenseDO();
+        expenseDO.setElderId(elderId);
+        expenseDO.setTotalAmount(total);
+        expenseMapper.insert(expenseDO);
+        newExpenseItemList = newExpenseItemList.stream().map(e->e.setExpenseId(expenseDO.getId())).collect(Collectors.toList());
+        expenseItemMapper.insertBatch(newExpenseItemList);
+    }
 }

+ 32 - 13
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/UserGroupTenantServiceImpl.java

@@ -58,22 +58,41 @@ public class UserGroupTenantServiceImpl implements UserGroupTenantService {
         }
     }
 
-    @Override                                                                                                       
+    @Override
     public List<UserGroupTenantRespVO> getUserGroupTenant(Long userId, String province, Long groupTenantId) {
-        List<UserGroupTenantDO> userGroupTenants = userGroupTenantMapper.selectListByUserIdAndProvince(userId,province,groupTenantId);
-        return userGroupTenants.stream().map(userGroupTenantDO -> {
-            TenantDO tenantDO = tenantMapper.selectById(userGroupTenantDO.getOrgTenantId());
-            UserGroupTenantRespVO respVO = new UserGroupTenantRespVO();
-            BeanUtils.copyProperties(userGroupTenantDO, respVO);
-            respVO.setOrgTenantName(tenantDO.getName());
+        // 1. 查询原始数据
+        List<UserGroupTenantDO> userGroupTenants = userGroupTenantMapper.selectListByUserIdAndProvince(userId, province, groupTenantId);
+
+        // 2. 按租户ID分组
+        Map<Long, List<UserGroupTenantDO>> groupedMap = userGroupTenants.stream()
+                .collect(Collectors.groupingBy(UserGroupTenantDO::getOrgTenantId));
+
+        List<UserGroupTenantRespVO> result = new ArrayList<>();
+
+        // 3. 遍历每个租户分组
+        groupedMap.forEach((orgTenantId, doList) -> {
+            // 3.1 取该租户下的第一条记录(可根据业务需要调整,例如按某个字段排序后取第一条)
+            UserGroupTenantDO first = doList.get(0);
+
+            // 3.2 查询租户信息(每个租户只查一次)
+            TenantDO tenantDO = tenantMapper.selectById(orgTenantId);
+            String orgTenantName = tenantDO != null ? tenantDO.getName() : null;
 
-            //查询企业信息
-            EnterpriseInfoDO enterpriseInfoDO = enterpriseInfoMapper
-                    .selectOne(new LambdaQueryWrapperX<EnterpriseInfoDO>()
-                            .eq(EnterpriseInfoDO::getTenantId, userGroupTenantDO.getOrgTenantId()));
+            // 3.3 查询企业信息(每个租户只查一次)
+            EnterpriseInfoDO enterpriseInfoDO = enterpriseInfoMapper.selectOne(
+                    new LambdaQueryWrapperX<EnterpriseInfoDO>()
+                            .eq(EnterpriseInfoDO::getTenantId, orgTenantId));
+
+            // 3.4 组装 VO
+            UserGroupTenantRespVO respVO = new UserGroupTenantRespVO();
+            BeanUtils.copyProperties(first, respVO);
+            respVO.setOrgTenantName(orgTenantName);
             respVO.setEnterpriseInfo(enterpriseInfoDO);
-            return respVO;
-        }).collect(Collectors.toList());
+
+            result.add(respVO);
+        });
+
+        return result;
     }
 
     @Override

+ 21 - 17
yudao-module-system/yudao-module-system-biz/src/main/resources/mapper/ExpenseItemMapper.xml

@@ -191,25 +191,29 @@
         WHERE eei.is_monthly_expense = 1
         AND ee.elder_id = #{elderId}
         AND (
-            <!-- 情况1:有明确的开始和结束日期,且查询月份在这个区间内 -->
-            (DATE_FORMAT(eei.change_start_date, '%Y-%m') &lt;= #{billingMonth}
-            AND DATE_FORMAT(eei.change_end_date, '%Y-%m') &gt;= #{billingMonth})
-            <!-- 情况2:开始日期 &lt;= 查询月份,且结束日期为NULL -->
-            OR (DATE_FORMAT(eei.change_start_date, '%Y-%m') &lt;= #{billingMonth}
-            AND eei.change_end_date IS NULL)
+        -- 情况1:有明确的开始和结束日期,且查询月份在这个区间内
+        (DATE_FORMAT(eei.change_start_date, '%Y-%m') &lt;= #{billingMonth}
+        AND DATE_FORMAT(eei.change_end_date, '%Y-%m') &gt;= #{billingMonth})
+        -- 情况2:开始日期 &lt;= 查询月份,且结束日期为NULL
+        OR (DATE_FORMAT(eei.change_start_date, '%Y-%m') &lt;= #{billingMonth}
+        AND eei.change_end_date IS NULL)
         )
-        <!-- 关键:排除在查询月份开始的新费用(防止月中变更时新旧费用都出现) -->
+        -- 关键修复:排除在账单月开始的新费用(当存在同一老人、同一费用项的旧版本时)
         AND NOT (
-            DATE_FORMAT(eei.change_start_date, '%Y-%m') = #{billingMonth}
-            AND eei.change_start_date > STR_TO_DATE(CONCAT(#{billingMonth}, '-01'), '%Y-%m-%d')
-            AND EXISTS (
-                SELECT 1 FROM elderly_expense_item eei2
-                WHERE eei2.expense_id = ee.id
-                AND eei2.item_id = eei.item_id
-                AND DATE_FORMAT(eei2.change_start_date, '%Y-%m') &lt; #{billingMonth}
-                AND (eei2.change_end_date IS NULL
-                OR DATE_FORMAT(eei2.change_end_date, '%Y-%m') &gt;= #{billingMonth})
-            )
+        DATE_FORMAT(eei.change_start_date, '%Y-%m') = #{billingMonth}
+        AND eei.change_start_date &gt; STR_TO_DATE(CONCAT(#{billingMonth}, '-01'), '%Y-%m-%d')
+        AND EXISTS (
+        SELECT 1
+        FROM elderly_expense_item eei2
+        INNER JOIN elderly_expense ee2 ON eei2.expense_id = ee2.id
+        WHERE ee2.elder_id = ee.elder_id          -- 同一老人
+        AND eei2.item_id = eei.item_id          -- 同一费用项
+        AND eei2.id != eei.id                    -- 排除自身
+        AND eei2.is_monthly_expense = 1          -- 同样只考虑月费用
+        AND DATE_FORMAT(eei2.change_start_date, '%Y-%m') &lt; #{billingMonth}
+        AND (eei2.change_end_date IS NULL
+        OR DATE_FORMAT(eei2.change_end_date, '%Y-%m') &gt;= #{billingMonth})
+        )
         )
         ) expense_data
     </select>