瀏覽代碼

新增
1、报表增加能够根据小数保留配置进行调整
2、账单导出能够根据小数保留配置进行调整
3、导出长者点餐统计导出增加用餐日期列
4、导出长者点餐统计分页筛选增加楼层过滤
修改
1、退住申请通过后删除退住月后的无用账单避免后续报表进行统计
BUGFIX
1、合同状态定时任务解决没有对未开始状态的合同进行遍历处理
2、解决床位对调产生新的费用数据不正确的问题
3、解决应收报表计算退住长者实际费用时会计算到押金等杂费的问题

liangwenxuan 2 周之前
父節點
當前提交
6868c07927
共有 14 個文件被更改,包括 347 次插入78 次删除
  1. 107 15
      yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/controller/admin/goview/GoViewDataController.java
  2. 18 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/sys/BusinessParamsConfigApi.java
  3. 21 6
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/bpm/BpmElderlyExpenseApiImpl.java
  4. 13 22
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/order/OrderApiImpl.java
  5. 42 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/sys/BusinessParamsConfigApiImpl.java
  6. 3 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/restaurant/vo/DishesOrderExportVO.java
  7. 3 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/restaurant/vo/DishesOrderStatisticsPageReqVO.java
  8. 2 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/biz/ElderlyContractMapper.java
  9. 7 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/ElderContractUpdateJob.java
  10. 105 33
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ExpenseOrderServiceImpl.java
  11. 6 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/NurseChangeRecordServiceImpl.java
  12. 10 0
      yudao-module-system/yudao-module-system-biz/src/main/resources/mapper/DishesOrderStatisticsMapper.xml
  13. 9 0
      yudao-module-system/yudao-module-system-biz/src/main/resources/mapper/ElderlyContractMapper.xml
  14. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/resources/mapper/RefundSettlementOrderItemMapper.xml

+ 107 - 15
yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/controller/admin/goview/GoViewDataController.java

@@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataGetBySqlReqVO;
 import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataRespVO;
 import cn.iocoder.yudao.module.system.api.order.OrderApi;
+import cn.iocoder.yudao.module.system.api.sys.BusinessParamsConfigApi;
 import cn.iocoder.yudao.module.system.api.order.VO.OrderItemRespVO;
 import cn.iocoder.yudao.module.system.api.order.VO.OrderItemTotalRespVO;
 import cn.iocoder.yudao.module.system.api.order.VO.OrderRespVO;
@@ -102,11 +103,102 @@ public class GoViewDataController {
     @Resource
     private OrderApi orderApi;
 
+    @Resource
+    private BusinessParamsConfigApi businessParamsConfigApi;
+
     @Resource
     private CacheManager cacheManager;
 
     private static final String ORDER_SUB_CACHE_NAME = "report:goview:orderSubGrouped#30s";
 
+    /**
+     * 账单金额单项取整设置:配置所属标题
+     */
+    private static final String AMOUNT_ROUND_CONFIG_TITLE = "财务模块配置";
+    /**
+     * 账单金额单项取整设置:配置名称
+     */
+    private static final String AMOUNT_ROUND_CONFIG_NAME = "账单金额单项取整设置";
+    /**
+     * 配置值:保留两位小数(四舍五入)
+     */
+    private static final String AMOUNT_ROUND_VALUE_TWO_SCALE = "4";
+    /**
+     * 配置值:四舍五入取整
+     */
+    private static final String AMOUNT_ROUND_VALUE_INTEGER = "2";
+
+    /**
+     * 按照机构的「账单金额单项取整设置」配置,对金额做小数位处理
+     *
+     * @param v           金额
+     * @param configValue 配置值:4-保留两位小数四舍五入;2-四舍五入取整;其它-保持现状
+     */
+    private static BigDecimal roundByConfig(BigDecimal v, String configValue) {
+        if (v == null) {
+            return null;
+        }
+        if (AMOUNT_ROUND_VALUE_TWO_SCALE.equals(configValue)) {
+            return v.setScale(SCALE_TWO, RoundingMode.HALF_UP);
+        }
+        if (AMOUNT_ROUND_VALUE_INTEGER.equals(configValue)) {
+            return v.setScale(0, RoundingMode.HALF_UP);
+        }
+        return v;
+    }
+
+    private static void roundOrderItemByConfig(OrderItemRespVO vo, String configValue) {
+        if (vo == null) {
+            return;
+        }
+        vo.setBedAmount(roundByConfig(vo.getBedAmount(), configValue));
+        vo.setMealAmount(roundByConfig(vo.getMealAmount(), configValue));
+        vo.setNurseAmount(roundByConfig(vo.getNurseAmount(), configValue));
+        vo.setServiceAmount(roundByConfig(vo.getServiceAmount(), configValue));
+        vo.setExtraServiceAmount(roundByConfig(vo.getExtraServiceAmount(), configValue));
+        vo.setTotalAmount(roundByConfig(vo.getTotalAmount(), configValue));
+
+        vo.setAdjustAmount(roundByConfig(vo.getAdjustAmount(), configValue));
+
+        vo.setBedActualAmount(roundByConfig(vo.getBedActualAmount(), configValue));
+        vo.setMealActualAmount(roundByConfig(vo.getMealActualAmount(), configValue));
+        vo.setNurseActualAmount(roundByConfig(vo.getNurseActualAmount(), configValue));
+        vo.setServiceActualAmount(roundByConfig(vo.getServiceActualAmount(), configValue));
+        vo.setTotalActualAmount(roundByConfig(vo.getTotalActualAmount(), configValue));
+    }
+
+    private static void roundOrderTotalByConfig(OrderItemTotalRespVO vo, String configValue) {
+        if (vo == null) {
+            return;
+        }
+        vo.setBedTotalAmount(roundByConfig(vo.getBedTotalAmount(), configValue));
+        vo.setMealTotalAmount(roundByConfig(vo.getMealTotalAmount(), configValue));
+        vo.setNurseTotalAmount(roundByConfig(vo.getNurseTotalAmount(), configValue));
+        vo.setServiceTotalAmount(roundByConfig(vo.getServiceTotalAmount(), configValue));
+        vo.setExtraServiceAmount(roundByConfig(vo.getExtraServiceAmount(), configValue));
+        vo.setTotalAmount(roundByConfig(vo.getTotalAmount(), configValue));
+
+        vo.setAdjustTotalAmount(roundByConfig(vo.getAdjustTotalAmount(), configValue));
+
+        vo.setBedActualTotalAmount(roundByConfig(vo.getBedActualTotalAmount(), configValue));
+        vo.setMealActualTotalAmount(roundByConfig(vo.getMealActualTotalAmount(), configValue));
+        vo.setNurseActualTotalAmount(roundByConfig(vo.getNurseActualTotalAmount(), configValue));
+        vo.setServiceActualTotalAmount(roundByConfig(vo.getServiceActualTotalAmount(), configValue));
+        vo.setTotalActualAmount(roundByConfig(vo.getTotalActualAmount(), configValue));
+    }
+
+    /**
+     * 获取当前机构的「账单金额单项取整设置」配置值
+     */
+    private String getAmountRoundConfigValue(Long tenantId) {
+        try {
+            return businessParamsConfigApi.getConfigValue(tenantId, AMOUNT_ROUND_CONFIG_TITLE, AMOUNT_ROUND_CONFIG_NAME);
+        } catch (Exception e) {
+            // 配置获取失败时,保持现状,不影响报表查询
+            return null;
+        }
+    }
+
     private OrderSubListGroupedRespVO loadOrComputeGrouped(Long tenantId, String billingMonth, Integer payStatus) {
         String cacheKey = tenantId + "|" + billingMonth + "|" + (payStatus == null ? "null" : payStatus);
 
@@ -121,10 +213,13 @@ public class GoViewDataController {
         List<OrderItemRespVO> listByMonth = orderApi.getOrderSubListForReport(null, tenantId, billingMonth, payStatus);
         OrderSubListGroupedRespVO grouped = new OrderSubListGroupedRespVO();
         if (!CollectionUtils.isEmpty(listByMonth)) {
+            // 按当前机构的「账单金额单项取整设置」配置,处理金额小数位
+            String roundConfigValue = getAmountRoundConfigValue(tenantId);
             List<OrderItemRespVO> leave = new ArrayList<>();
             List<OrderItemRespVO> retreat = new ArrayList<>();
             List<OrderItemRespVO> in = new ArrayList<>();
             for (OrderItemRespVO vo : listByMonth) {
+                roundOrderItemByConfig(vo, roundConfigValue);
                 String status = vo.getInStatus();
                 if ("离住".equals(status)) {
                     leave.add(vo);
@@ -212,9 +307,7 @@ public class GoViewDataController {
         String billingMonth = StringUtils.isBlank(params.get("billingMonth")) ? "2025-12" : params.get("billingMonth");
         Integer payStatus = params.get("payStatus") == null ? null : Integer.valueOf(params.get("payStatus"));
         OrderSubListGroupedRespVO grouped = loadOrComputeGrouped(getTenantId(params), billingMonth, payStatus);
-        if (!CollectionUtils.isEmpty(grouped.getInList())) {
-            grouped.getInList().forEach(GoViewDataController::scale2OrderItem);
-        }
+        // 金额小数位已在 loadOrComputeGrouped 中按机构配置处理
         return success(grouped.getInList());
     }
 
@@ -227,9 +320,7 @@ public class GoViewDataController {
         String billingMonth = StringUtils.isBlank(params.get("billingMonth")) ? "2025-12" : params.get("billingMonth");
         Integer payStatus = params.get("payStatus") == null ? null : Integer.valueOf(params.get("payStatus"));
         OrderSubListGroupedRespVO grouped = loadOrComputeGrouped(getTenantId(params), billingMonth, payStatus);
-        if (!CollectionUtils.isEmpty(grouped.getLeaveList())) {
-            grouped.getLeaveList().forEach(GoViewDataController::scale2OrderItem);
-        }
+        // 金额小数位已在 loadOrComputeGrouped 中按机构配置处理
         return success(grouped.getLeaveList());
     }
 
@@ -242,9 +333,7 @@ public class GoViewDataController {
         String billingMonth = StringUtils.isBlank(params.get("billingMonth")) ? "2025-12" : params.get("billingMonth");
         Integer payStatus = params.get("payStatus") == null ? null : Integer.valueOf(params.get("payStatus"));
         OrderSubListGroupedRespVO grouped = loadOrComputeGrouped(getTenantId(params), billingMonth, payStatus);
-        if (!CollectionUtils.isEmpty(grouped.getRetreatList())) {
-            grouped.getRetreatList().forEach(GoViewDataController::scale2OrderItem);
-        }
+        // 金额小数位已在 loadOrComputeGrouped 中按机构配置处理
         return success(grouped.getRetreatList());
     }
 
@@ -302,9 +391,10 @@ public class GoViewDataController {
             @RequestBody(required = false) String body) {
         String billingMonth = params.get("billingMonth");
         Integer payStatus = params.get("payStatus") == null ? null : Integer.valueOf(params.get("payStatus"));
-        OrderSubListGroupedRespVO grouped = loadOrComputeGrouped(getTenantId(params), billingMonth, payStatus);
+        Long tenantId = getTenantId(params);
+        OrderSubListGroupedRespVO grouped = loadOrComputeGrouped(tenantId, billingMonth, payStatus);
         OrderItemTotalRespVO total = aggregate(grouped.getInList(), billingMonth);
-        scale2OrderTotal(total);
+        roundOrderTotalByConfig(total, getAmountRoundConfigValue(tenantId));
         return success(java.util.Collections.singletonList(total));
     }
 
@@ -316,9 +406,10 @@ public class GoViewDataController {
             @RequestBody(required = false) String body) {
         String billingMonth = params.get("billingMonth");
         Integer payStatus = params.get("payStatus") == null ? null : Integer.valueOf(params.get("payStatus"));
-        OrderSubListGroupedRespVO grouped = loadOrComputeGrouped(getTenantId(params), billingMonth, payStatus);
+        Long tenantId = getTenantId(params);
+        OrderSubListGroupedRespVO grouped = loadOrComputeGrouped(tenantId, billingMonth, payStatus);
         OrderItemTotalRespVO total = aggregate(grouped.getLeaveList(), billingMonth);
-        scale2OrderTotal(total);
+        roundOrderTotalByConfig(total, getAmountRoundConfigValue(tenantId));
         return success(java.util.Collections.singletonList(total));
     }
 
@@ -330,9 +421,10 @@ public class GoViewDataController {
             @RequestBody(required = false) String body) {
         String billingMonth = params.get("billingMonth");
         Integer payStatus = params.get("payStatus") == null ? null : Integer.valueOf(params.get("payStatus"));
-        OrderSubListGroupedRespVO grouped = loadOrComputeGrouped(getTenantId(params), billingMonth, payStatus);
+        Long tenantId = getTenantId(params);
+        OrderSubListGroupedRespVO grouped = loadOrComputeGrouped(tenantId, billingMonth, payStatus);
         OrderItemTotalRespVO total = aggregate(grouped.getRetreatList(), billingMonth);
-        scale2OrderTotal(total);
+        roundOrderTotalByConfig(total, getAmountRoundConfigValue(tenantId));
         return success(java.util.Collections.singletonList(total));
     }
 

+ 18 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/sys/BusinessParamsConfigApi.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.system.api.sys;
+
+/**
+ * 系统业务参数配置 API
+ */
+public interface BusinessParamsConfigApi {
+
+    /**
+     * 获取指定机构(租户)下,某标题、某配置名称的参数值
+     *
+     * @param tenantId   租户(机构)编号
+     * @param title      标题,例如:财务模块配置
+     * @param configName 配置名称,例如:账单金额单项取整设置
+     * @return 参数值,不存在时返回 null
+     */
+    String getConfigValue(Long tenantId, String title, String configName);
+
+}

+ 21 - 6
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/bpm/BpmElderlyExpenseApiImpl.java

@@ -109,6 +109,9 @@ public class BpmElderlyExpenseApiImpl implements BpmElderlyExpenseApi {
     @Autowired
     private ElderlyConsumerVouchersMapper consumerVouchersMapper;
 
+    @Autowired
+    private ExpenseOrderService expenseOrderService;
+
     @Override
     @LogRecord(type = EXPENSE_SETTLEMENT_TYPE, subType = EXPENSE_SETTLEMENT_TYPE_SUB_TYPE, bizNo = "{{#elderlyInfo.id}}",
             success = EXPENSE_SETTLEMENT_INSERT_SUCCESS)
@@ -125,11 +128,21 @@ public class BpmElderlyExpenseApiImpl implements BpmElderlyExpenseApi {
         // 更新费用结束时间
         updateExpenseItemEndDate(elderlyInfo.getId(),costDeadlineDate);
         YearMonth costDeadlineYearMonth = YearMonth.from(costDeadlineDate);
-        // 将费用截止日期后的账单更新为不显示
-        expenseOrderMapper.update(new ExpenseOrderDO().setIsShow(Boolean.FALSE),new LambdaQueryWrapperX<ExpenseOrderDO>()
-                .eq(ExpenseOrderDO::getElderId,elderlyInfo.getId())
-                .eq(ExpenseOrderDO::getPayStatus,0)
-                .ge(ExpenseOrderDO::getBillingMonth,costDeadlineYearMonth));
+        ExpenseOrderDO costDeadLineExpenseOrder = null;
+        // 将费用截止日期账单更新为不显示,截止日期后的账单删掉
+        List<ExpenseOrderDO> expenseOrderDOS = expenseOrderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
+                .eq(ExpenseOrderDO::getElderId, elderlyInfo.getId())
+                .eq(ExpenseOrderDO::getPayStatus, 0)
+                .ge(ExpenseOrderDO::getBillingMonth, costDeadlineYearMonth));
+        for (ExpenseOrderDO expenseOrderDO : expenseOrderDOS) {
+            if(expenseOrderDO.getBillingMonth().equals(costDeadlineYearMonth.toString())){
+                costDeadLineExpenseOrder = expenseOrderDO;
+                expenseOrderDO.setIsShow(Boolean.FALSE);
+                expenseOrderMapper.updateById(expenseOrderDO);
+            }else {
+                expenseOrderService.deleteExpenseOrder(expenseOrderDO.getId());
+            }
+        }
         // 解锁退住日期后续月账单,避免推送至金蝶
         expenseOrderMapper.update(new ExpenseOrderDO().setIsLock(Boolean.FALSE),new LambdaQueryWrapperX<ExpenseOrderDO>()
                 .eq(ExpenseOrderDO::getElderId,elderlyInfo.getId())
@@ -152,7 +165,9 @@ public class BpmElderlyExpenseApiImpl implements BpmElderlyExpenseApi {
         order.setOrderNumber("T" + RandomUtil.randomNumbers(15));
         order.setTenantId(startTenantId);
         refundSettlementOrderMapper.insert(order);
-
+        if(costDeadLineExpenseOrder != null){
+            costDeadLineExpenseOrder.setRefundSettlementOrderId(order.getId());
+        }
 //        createRetreatChangeDailyExpenses(elderlyRetreatRecord, startTenantId);
 
         // 生成退款单子项,获取退住结算数据

+ 13 - 22
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/order/OrderApiImpl.java

@@ -666,24 +666,11 @@ public class OrderApiImpl implements OrderApi {
         BigDecimal nurseDeduct = nz(typeMap.get(2));
         BigDecimal mealDeduct = nz(typeMap.get(3));
         BigDecimal serviceDeduct = nz(typeMap.get(7));
-
-        dto.setBedActualAmount(nz(dto.getBedActualAmount()).add(bedDeduct));
-        dto.setMealActualAmount(nz(dto.getMealActualAmount()).add(mealDeduct));
-        dto.setNurseActualAmount(nz(dto.getNurseActualAmount()).add(nurseDeduct));
-        dto.setServiceActualAmount(nz(dto.getServiceActualAmount()).add(serviceDeduct));
-
-        if (dto.getBedActualAmount().compareTo(BigDecimal.ZERO) < 0) {
-            dto.setBedActualAmount(BigDecimal.ZERO);
-        }
-        if (dto.getMealActualAmount().compareTo(BigDecimal.ZERO) < 0) {
-            dto.setMealActualAmount(BigDecimal.ZERO);
-        }
-        if (dto.getNurseActualAmount().compareTo(BigDecimal.ZERO) < 0) {
-            dto.setNurseActualAmount(BigDecimal.ZERO);
-        }
-        if (dto.getServiceActualAmount().compareTo(BigDecimal.ZERO) < 0) {
-            dto.setServiceActualAmount(BigDecimal.ZERO);
-        }
+        // 如果退住了那么就按退住结算单的显示
+        dto.setBedActualAmount(bedDeduct);
+        dto.setMealActualAmount(mealDeduct);
+        dto.setNurseActualAmount(nurseDeduct);
+        dto.setServiceActualAmount(serviceDeduct);
 
         BigDecimal actualTotal = dto.getBedActualAmount()
                 .add(dto.getMealActualAmount())
@@ -1630,16 +1617,20 @@ public class OrderApiImpl implements OrderApi {
                 BigDecimal amt = item.getTotalAmount() == null ? BigDecimal.ZERO : item.getTotalAmount();
                 if (type != null && type == 1) {
                     vo.setBedAmount(vo.getBedAmount().add(amt));
-                    vo.setBedActualAmount(vo.getBedActualAmount().add(amt));
+                    // 整月离住,实际费用不应该产生
+//                    vo.setBedActualAmount(vo.getBedActualAmount().add(amt));
                 } else if (type != null && type == 2) {
                     vo.setNurseAmount(vo.getNurseAmount().add(amt));
-                    vo.setNurseActualAmount(vo.getNurseActualAmount().add(amt));
+                    // 整月离住,实际费用不应该产生
+//                    vo.setNurseActualAmount(vo.getNurseActualAmount().add(amt));
                 } else if (type != null && type == 3) {
                     vo.setMealAmount(vo.getMealAmount().add(amt));
-                    vo.setMealActualAmount(vo.getMealActualAmount().add(amt));
+                    // 整月离住,实际费用不应该产生
+//                    vo.setMealActualAmount(vo.getMealActualAmount().add(amt));
                 } else if (type != null && type == 7) {
                     vo.setServiceAmount(vo.getServiceAmount().add(amt));
-                    vo.setServiceActualAmount(vo.getServiceActualAmount().add(amt));
+                    // 整月离住,实际费用不应该产生
+//                    vo.setServiceActualAmount(vo.getServiceActualAmount().add(amt));
                 }
             }
             BigDecimal standardTotal = vo.getBedAmount().add(vo.getMealAmount()).add(vo.getNurseAmount()).add(vo.getServiceAmount());

+ 42 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/sys/BusinessParamsConfigApiImpl.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.system.api.sys;
+
+import cn.iocoder.yudao.module.system.dal.dataobject.sys.BusinessParamsConfigDO;
+import cn.iocoder.yudao.module.system.dal.mysql.sys.BusinessParamsConfigMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 系统业务参数配置 API 实现类
+ *
+ * @author 系统管理员
+ */
+@Service
+@Slf4j
+public class BusinessParamsConfigApiImpl implements BusinessParamsConfigApi {
+
+    @Resource
+    private BusinessParamsConfigMapper businessParamsConfigMapper;
+
+    @Override
+    public String getConfigValue(Long tenantId, String title, String configName) {
+        if (tenantId == null || configName == null) {
+            return null;
+        }
+        // 复用 Mapper 中带 @TenantIgnore 的查询,避免租户插件自动拼接 tenant_id 导致查不到数据
+        List<BusinessParamsConfigDO> list = businessParamsConfigMapper.selectListByName(tenantId, configName);
+        if (list == null || list.isEmpty()) {
+            return null;
+        }
+        return list.stream()
+                .filter(item -> title == null || Objects.equals(title, item.getTitle()))
+                .map(BusinessParamsConfigDO::getValue)
+                .filter(Objects::nonNull)
+                .findFirst()
+                .orElse(null);
+    }
+
+}

+ 3 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/restaurant/vo/DishesOrderExportVO.java

@@ -18,6 +18,9 @@ public class DishesOrderExportVO {
     @ExcelProperty("餐厅名称")
     private String restaurantName;
 
+    @ExcelProperty("用餐日期")
+    private String orderDate;
+
     @ExcelProperty("点餐时间")
     private Date orderTime;
 

+ 3 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/restaurant/vo/DishesOrderStatisticsPageReqVO.java

@@ -26,4 +26,7 @@ public class DishesOrderStatisticsPageReqVO extends PageParam {
     @Schema(description = "点餐月份")
     private String month;
 
+    @Schema(description = "楼层id")
+    private Long floorId;
+
 }

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

@@ -32,6 +32,8 @@ public interface ElderlyContractMapper extends BaseMapperX<ElderlyContractDO> {
 
     int batchUpdateStatusToExpired(@Param("tenantId") Long tenantId, @Param("nowDate") Date nowDate);
 
+    int batchUpdateStatusToInProgress(@Param("tenantId") Long tenantId, @Param("nowDate") Date nowDate);
+
     List<ElderlyContractDO> selectExpiringWithinOneMonth(@Param("tenantId") Long tenantId,
                                                          @Param("startDate") Date startDate,
                                                          @Param("endDate") Date endDate);

+ 7 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/job/ElderContractUpdateJob.java

@@ -52,6 +52,13 @@ public class ElderContractUpdateJob implements JobHandler {
             logger.info("租户{}合同状态修复:进行中->未开始 {} 条,进行中->过期 {} 条", tenantId, fixToNotStartedCount, fixToExpiredCount);
         }
 
+        // 1.5) 将“今天已开始且未过期”的未开始合同(2)激活为进行中(1)
+        //      例如合同 2026-08-01 开始,在 2026-08-01 执行定时任务时由 2 更新为 1
+        int startToInProgressCount = elderlyContractMapper.batchUpdateStatusToInProgress(tenantId, nowDate);
+        if (startToInProgressCount > 0) {
+            logger.info("租户{}合同状态更新:未开始->进行中 {} 条", tenantId, startToInProgressCount);
+        }
+
         // 2) 原有逻辑:到期的旧合同置为过期(0),并将符合条件的未开始合同置为进行中(1)
         //    这里先重新查询一次进行中合同(避免上一步修复后状态已变化造成遗漏/误判)
         List<ElderlyContractDO> elderlyContractDOList = elderlyContractMapper.selectList(new LambdaQueryWrapperX<ElderlyContractDO>()

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

@@ -171,6 +171,64 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
     @Autowired
     private ElderlyConsumerVouchersMapper elderlyConsumerVouchersMapper;
 
+    @Resource
+    private cn.iocoder.yudao.module.system.api.sys.BusinessParamsConfigApi businessParamsConfigApi;
+
+    /**
+     * 账单金额单项取整设置:配置所属标题
+     */
+    private static final String AMOUNT_ROUND_CONFIG_TITLE = "财务模块配置";
+    /**
+     * 账单金额单项取整设置:配置名称
+     */
+    private static final String AMOUNT_ROUND_CONFIG_NAME = "账单金额单项取整设置";
+    /**
+     * 配置值:保留两位小数(四舍五入)
+     */
+    private static final String AMOUNT_ROUND_VALUE_TWO_SCALE = "4";
+    /**
+     * 配置值:四舍五入取整
+     */
+    private static final String AMOUNT_ROUND_VALUE_INTEGER = "2";
+
+    /**
+     * 获取当前机构的「账单金额单项取整设置」配置值
+     */
+    private String getAmountRoundConfigValue(Long tenantId) {
+        try {
+            return businessParamsConfigApi.getConfigValue(tenantId, AMOUNT_ROUND_CONFIG_TITLE, AMOUNT_ROUND_CONFIG_NAME);
+        } catch (Exception e) {
+            log.warn("[getAmountRoundConfigValue] 获取账单金额单项取整设置失败,tenantId={}", tenantId, e);
+            return null;
+        }
+    }
+
+    /**
+     * 判断「账单金额单项取整设置」是否配置了需要处理的取整方式
+     */
+    private static boolean isAmountRoundConfigured(String configValue) {
+        return AMOUNT_ROUND_VALUE_TWO_SCALE.equals(configValue) || AMOUNT_ROUND_VALUE_INTEGER.equals(configValue);
+    }
+
+    /**
+     * 按照机构的「账单金额单项取整设置」配置,对金额做小数位处理
+     *
+     * @param v           金额
+     * @param configValue 配置值:4-保留两位小数四舍五入;2-四舍五入取整;其它-保持现状
+     */
+    private static BigDecimal roundByConfig(BigDecimal v, String configValue) {
+        if (v == null) {
+            return null;
+        }
+        if (AMOUNT_ROUND_VALUE_TWO_SCALE.equals(configValue)) {
+            return v.setScale(2, RoundingMode.HALF_UP);
+        }
+        if (AMOUNT_ROUND_VALUE_INTEGER.equals(configValue)) {
+            return v.setScale(0, RoundingMode.HALF_UP);
+        }
+        return v;
+    }
+
     @Override
     public PageResult<ExpenseOrderDO> getExpenseBillPage(ExpenseBillPageReqVO pageReqVO) {
         Page<ExpenseOrderDO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
@@ -844,8 +902,8 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
                 actualEnd = LocalDate.parse(collect.get(0).getEndTime());
             }
             List<ExpenseItemRespVO> dailyExpensesCollect = collectDailyExpenseItemsNoBill(elderId, billingMonth);
-            List<ExpenseItemRespVO> subsidyCollect = collectSubsidyItems(elderId, billingMonth, actualStart, actualEnd, false);
-            List<ExpenseItemRespVO> consumerVoucherCollect = collectConsumerVoucherItems(elderId, billingMonth, actualStart, actualEnd, false);
+            List<ExpenseItemRespVO> subsidyCollect = collectSubsidyItems(elderId, billingMonth, actualStart, actualEnd, false,null);
+            List<ExpenseItemRespVO> consumerVoucherCollect = collectConsumerVoucherItems(elderId, billingMonth, actualStart, actualEnd, false,null);
 
             // 优惠抵扣项:按实际区间折算金额 & 展示区间
             List<ExpenseItemRespVO> discountCollect = adjustDiscountByActualRange(elderId, billingMonth, actualStart, actualEnd);
@@ -864,8 +922,8 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
             LocalDate monthStart = billingYm.atDay(1);
             LocalDate monthEnd = billingYm.atEndOfMonth();
             resultList.addAll(collectDailyExpenseItemsNoBill(elderId, billingMonth));
-            resultList.addAll(collectSubsidyItems(elderId, billingMonth, monthStart, monthEnd, false));
-            resultList.addAll(collectConsumerVoucherItems(elderId, billingMonth, monthStart, monthEnd, false));
+            resultList.addAll(collectSubsidyItems(elderId, billingMonth, monthStart, monthEnd, false,null));
+            resultList.addAll(collectConsumerVoucherItems(elderId, billingMonth, monthStart, monthEnd, false,null));
             resultList.addAll(calculateDiscountDeduction(elderId, billingMonth));
             return resultList;
         }
@@ -907,7 +965,7 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
 
     private List<ExpenseItemRespVO> collectSubsidyItems(Long elderId, String billingMonth,
                                                          LocalDate startDate, LocalDate endDate,
-                                                         boolean markUsed) {
+                                                         boolean markUsed,ExpenseOrderDO expenseOrder) {
         List<ExpenseSubsidyDO> unclaimedSubsidies = expenseSubsidyMapper.selectList(new LambdaQueryWrapperX<ExpenseSubsidyDO>()
                 .eq(ExpenseSubsidyDO::getElderId, elderId)
                 .eq(ExpenseSubsidyDO::getDeductionBillMonth, billingMonth)
@@ -928,6 +986,7 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
             respVO.setStartTime(startDate.toString());
             respVO.setEndTime(endDate.toString());
             if (markUsed) {
+                subsidy.setOrderNumber(expenseOrder.getBillOrderNumber());
                 subsidy.setStatus(BooleanEnum.TRUE.getValue());
                 updateList.add(subsidy);
             }
@@ -942,7 +1001,7 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
 
     private List<ExpenseItemRespVO> collectConsumerVoucherItems(Long elderId, String billingMonth,
                                                                  LocalDate startDate, LocalDate endDate,
-                                                                 boolean markUsed) {
+                                                                 boolean markUsed,ExpenseOrderDO expenseOrder) {
         List<ElderlyConsumerVouchersDO> voucherList = elderlyConsumerVouchersMapper.selectList(new LambdaQueryWrapperX<ElderlyConsumerVouchersDO>()
                 .eq(ElderlyConsumerVouchersDO::getElderId, elderId)
                 .eq(ElderlyConsumerVouchersDO::getBillingMonth, billingMonth)
@@ -963,6 +1022,8 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
             respVO.setStartTime(startDate.toString());
             respVO.setEndTime(endDate.toString());
             if (markUsed) {
+                voucher.setOrderNumber(expenseOrder.getBillOrderNumber());
+                voucher.setId(expenseOrder.getId());
                 voucher.setStatus(BooleanEnum.TRUE.getValue());
                 updateList.add(voucher);
             }
@@ -1257,6 +1318,9 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
     public void exportExpenseOrderExcel(HttpServletResponse response, ExpenseBillPageReqVO pageReqVO) throws IOException {
         List<ExpenseOrderDO> list = expenseOrderMapper.getExpenseBillPage(null, pageReqVO);
 
+        // 当前机构的「账单金额单项取整设置」,用于导出金额的小数位处理
+        String roundConfigValue = getAmountRoundConfigValue(TenantContextHolder.getTenantId());
+
         Map<Long, Integer> elderCareTypeMap = new HashMap<>();
         List<Long> elderIds = list.stream()
                 .map(ExpenseOrderDO::getElderId)
@@ -1340,10 +1404,11 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
                 setExpenseOrderType(expenseOrderItemDO, dictDataDOList);
 
                 if (identificationTypeMap.containsKey(expenseOrderItemDO.getType())) {
+                    // 汇总时不做截断,最终统一按「账单金额单项取整设置」处理小数位
                     BigDecimal add = expenseOrderItemDO.getTotalAmount()
-                            .add(identificationTypeMap.get(expenseOrderItemDO.getType()))
-                            .setScale(2, RoundingMode.DOWN);
-                    identificationTypeMap.put(expenseOrderItemDO.getType(), add);
+                            .add(identificationTypeMap.get(expenseOrderItemDO.getType()));
+                    identificationTypeMap.put(expenseOrderItemDO.getType(), isAmountRoundConfigured(roundConfigValue)
+                            ? add : add.setScale(2, RoundingMode.DOWN));
 
                 } else {
                     identificationTypeMap.put(expenseOrderItemDO.getType(), expenseOrderItemDO.getTotalAmount());
@@ -1352,7 +1417,7 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
 
             for (DictDataDO dictDataDO : dictDataDOList) {
                 if (identificationTypeMap.containsKey(Integer.valueOf(dictDataDO.getValue()))) {
-                    row.add(identificationTypeMap.get(Integer.valueOf(dictDataDO.getValue())));
+                    row.add(roundByConfig(identificationTypeMap.get(Integer.valueOf(dictDataDO.getValue())), roundConfigValue));
                 } else {
                     row.add(0);
                 }
@@ -1371,10 +1436,12 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
             if(CollectionUtil.isNotEmpty(elderlyConsumerVouchersDOS)){
                 consumerVouchers = elderlyConsumerVouchersDOS.stream().map(ElderlyConsumerVouchersDO::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
             }
-            row.add(subsidyAmount);
-            row.add(consumerVouchers);
-            row.add(orderActualAmount.add(consumerVouchers).add(subsidyAmount));
-            row.add(order.getInsuranceAmount().setScale(2, RoundingMode.DOWN));
+            row.add(roundByConfig(subsidyAmount, roundConfigValue));
+            row.add(roundByConfig(consumerVouchers, roundConfigValue));
+            row.add(roundByConfig(orderActualAmount.add(consumerVouchers).add(subsidyAmount), roundConfigValue));
+            row.add(isAmountRoundConfigured(roundConfigValue)
+                    ? roundByConfig(order.getInsuranceAmount(), roundConfigValue)
+                    : order.getInsuranceAmount().setScale(2, RoundingMode.DOWN));
 
             List<ExpensePayOrderDO> expensePayOrderList = expensePayOrderMapper.selectList(new LambdaQueryWrapperX<ExpensePayOrderDO>()
                     .eq(ExpensePayOrderDO::getExpenseOrderId, order.getId()));
@@ -1385,7 +1452,10 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
             if (CollectionUtil.isNotEmpty(expensePayOrderList)) {
                 for (ExpensePayOrderDO expensePayOrderDO : expensePayOrderList) {
                     if (expensePayOrderDO.getDiscountAmount() != null) {
-                        discountAmount = discountAmount.add(expensePayOrderDO.getDiscountAmount().setScale(2, RoundingMode.DOWN));
+                        // 汇总时不做截断,最终统一按「账单金额单项取整设置」处理小数位
+                        discountAmount = discountAmount.add(isAmountRoundConfigured(roundConfigValue)
+                                ? expensePayOrderDO.getDiscountAmount()
+                                : expensePayOrderDO.getDiscountAmount().setScale(2, RoundingMode.DOWN));
                     }
                     if (StringUtil.isNotEmptyORNull(expensePayOrderDO.getRemarks())) {
                         remark = remark + expensePayOrderDO.getRemarks() + ";";
@@ -1410,18 +1480,18 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
                 }
 
             }
-            row.add(discountAmount);
+            row.add(roundByConfig(discountAmount, roundConfigValue));
             if (payTypeList.size() > 0) {
                 for (DictDataDO dictDataDO : billPayTypeList) {
                     if (payTypeMap.containsKey(dictDataDO.getValue())) {
-                        row.add(payTypeMap.get(dictDataDO.getValue()));
+                        row.add(roundByConfig(payTypeMap.get(dictDataDO.getValue()), roundConfigValue));
                     } else {
                         row.add(BigDecimal.ZERO);
                     }
                 }
 
             } else {
-                row.add(payAmount);
+                row.add(roundByConfig(payAmount, roundConfigValue));
             }
             row.add(remark);
             data.add(row);
@@ -2438,19 +2508,6 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
             actualEnd = LocalDate.parse(collect.get(0).getEndTime());
         }
 
-        List<ExpenseItemRespVO> dailyExpensesCollect = collectDailyExpenseItemsNoBill(elderId, billingMonth);
-        List<ExpenseItemRespVO> subsidyCollect = collectSubsidyItems(elderId, billingMonth, actualStart, actualEnd, true);
-        List<ExpenseItemRespVO> consumerVoucherCollect = collectConsumerVoucherItems(elderId, billingMonth, actualStart, actualEnd, true);
-
-        // 4、优惠抵扣项:按实际区间折算金额 & 展示区间
-        List<ExpenseItemRespVO> adjustedDiscountCollect = adjustDiscountByActualRange(elderId, billingMonth, actualStart, actualEnd);
-
-        dataList.addAll(collect);
-        dataList.addAll(dailyExpensesCollect);
-        dataList.addAll(adjustedDiscountCollect);
-        dataList.addAll(subsidyCollect);
-        dataList.addAll(consumerVoucherCollect);
-
         // 根据长者的入住类型确定账单类型
         // inStatusType: 1=入住(长住), 2=试住, 3=短住
         // type: 1=入院账单, 2=月度账单, 3=试住账单
@@ -2468,6 +2525,21 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
         expenseOrder.setTenantId(elderlyInfo.getTenantId());
         expenseOrderMapper.insert(expenseOrder);
 
+        List<ExpenseItemRespVO> dailyExpensesCollect = collectDailyExpenseItemsNoBill(elderId, billingMonth);
+        List<ExpenseItemRespVO> subsidyCollect = collectSubsidyItems(elderId, billingMonth, actualStart, actualEnd, true,expenseOrder);
+        List<ExpenseItemRespVO> consumerVoucherCollect = collectConsumerVoucherItems(elderId, billingMonth, actualStart, actualEnd, true,expenseOrder);
+
+        // 4、优惠抵扣项:按实际区间折算金额 & 展示区间
+        List<ExpenseItemRespVO> adjustedDiscountCollect = adjustDiscountByActualRange(elderId, billingMonth, actualStart, actualEnd);
+
+        dataList.addAll(collect);
+        dataList.addAll(dailyExpensesCollect);
+        dataList.addAll(adjustedDiscountCollect);
+        dataList.addAll(subsidyCollect);
+        dataList.addAll(consumerVoucherCollect);
+
+
+
         // 判断是否是当前月份
         BigDecimal totalAmount = BigDecimal.ZERO;
         // 生成账单费用项
@@ -2529,8 +2601,8 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
 
         YearMonth attributionBillTime = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
         List<ExpenseItemRespVO> dataList = new ArrayList<>(collectDailyExpenseItemsNoBill(elderId, billingMonth));
-        dataList.addAll(collectSubsidyItems(elderId, billingMonth, attributionBillTime.atDay(1), attributionBillTime.atEndOfMonth(), true));
-        dataList.addAll(collectConsumerVoucherItems(elderId, billingMonth, attributionBillTime.atDay(1), attributionBillTime.atEndOfMonth(), true));
+        dataList.addAll(collectSubsidyItems(elderId, billingMonth, attributionBillTime.atDay(1), attributionBillTime.atEndOfMonth(), true,expenseOrder));
+        dataList.addAll(collectConsumerVoucherItems(elderId, billingMonth, attributionBillTime.atDay(1), attributionBillTime.atEndOfMonth(), true,expenseOrder));
         dataList.addAll(calculateDiscountDeduction(elderId, billingMonth));
 
         BigDecimal amount = BigDecimal.ZERO;

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

@@ -437,7 +437,11 @@ public class NurseChangeRecordServiceImpl implements ElderlyChangeRecordService
         BigDecimal actualAmount = isElderA ? reqVO.getActualAmountA() : reqVO.getActualAmountB();
         int count = originalItem.getCount() == null ? 1 : originalItem.getCount();
         BigDecimal totalAmount = actualAmount.multiply(BigDecimal.valueOf(count));
-
+        newExpenseItemList = newExpenseItemList.stream().map(e->{
+            e.setChangeStartDate(reqVO.getStartDate());
+            e.setChangeEndDate(null);
+            return e;
+        }).collect(Collectors.toList());
         ExpenseItemDO newItem = new ExpenseItemDO();
         BeanUtils.copyProperties(originalItem, newItem);
         newItem.setId(null);
@@ -460,6 +464,7 @@ public class NurseChangeRecordServiceImpl implements ElderlyChangeRecordService
         newExpenseItemList = newExpenseItemList.stream().map(e->{
             e.setId(null);
             e.setExpenseId(expenseDO.getId());
+            e.setCreateTime(new Date());
             return e;
         }).collect(Collectors.toList());
         // 判断费用是否真的发生变化:比较原费用项实际金额与新费用项实际金额

+ 10 - 0
yudao-module-system/yudao-module-system-biz/src/main/resources/mapper/DishesOrderStatisticsMapper.xml

@@ -36,6 +36,9 @@
             <if test="params.month != null and params.month != ''">
                 and rdos.month = #{params.month}
             </if>
+            <if test="params.floorId != null">
+                and ei.floor_id = #{params.floorId}
+            </if>
             <if test="params.tenantId != null">
                 and rdos.tenant_id = #{params.tenantId}
             </if>
@@ -49,6 +52,7 @@
         SELECT
         o.id,
         r.restaurant_name as restaurantName,
+        o.order_date,
         i.order_time as orderTime,
         e.elder_name as elderName,
         d.food_name as foodName,
@@ -90,6 +94,9 @@
             <if test="elderName != null and elderName != ''">
                 AND e.elder_name = #{elderName}
             </if>
+            <if test="floorId != null">
+                AND e.floor_id = #{floorId}
+            </if>
             <if test="id != null">
                 AND o.id = #{id}
             </if>
@@ -139,6 +146,9 @@
             <if test="params.elderName != null and params.elderName != ''">
                 AND e.elder_name = #{params.elderName}
             </if>
+            <if test="params.floorId != null">
+                AND e.floor_id = #{params.floorId}
+            </if>
             <if test="params.id != null">
                 AND o.id = #{params.id}
             </if>

+ 9 - 0
yudao-module-system/yudao-module-system-biz/src/main/resources/mapper/ElderlyContractMapper.xml

@@ -84,6 +84,15 @@
           AND DATE(expire_time) &lt; DATE(#{nowDate})
     </update>
 
+    <update id="batchUpdateStatusToInProgress">
+        UPDATE elderly_contract
+        SET status = 1
+        WHERE status = 2
+          AND tenant_id = #{tenantId}
+          AND DATE(begin_time) &lt;= DATE(#{nowDate})
+          AND DATE(expire_time) &gt;= DATE(#{nowDate})
+    </update>
+
     <select id="selectExpiringWithinOneMonth" resultType="cn.iocoder.yudao.module.system.dal.dataobject.biz.ElderlyContractDO">
         SELECT ec.*, ei.in_status_type
         FROM elderly_contract ec

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/resources/mapper/RefundSettlementOrderItemMapper.xml

@@ -14,7 +14,7 @@
         JOIN
             elderly_expense_item eei ON rsoi.source_item_id = eei.id
         WHERE
-            rso.tenant_id = #{tenantId}
+            rsoi.type = 1 AND rso.tenant_id = #{tenantId}
             AND rso.elder_id IN
             <foreach item="elderId" collection="elderIds" open="(" separator="," close=")">
                 #{elderId}