liangwenxuan 3 месяцев назад
Родитель
Сommit
6dbb9bbc11

+ 19 - 5
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ElderlyTempOutServiceImpl.java

@@ -141,7 +141,7 @@ public class ElderlyTempOutServiceImpl implements ElderlyTempOutService {
         ExpenseOrderDO monthOrder = expenseOrderMapper.selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                 .eq(ExpenseOrderDO::getElderId, elderId)
                 .eq(ExpenseOrderDO::getBillingMonth, billingMonth)
-                .eq(ExpenseOrderDO::getType, 2));
+                .in(ExpenseOrderDO::getType, 1,2));
         if (monthOrder == null) {
             return;
         }
@@ -244,16 +244,30 @@ public class ElderlyTempOutServiceImpl implements ElderlyTempOutService {
 
         // 账单不存在:新建月度账单,并将固定费用(按返院日至月末折算)加入账单
         if (monthOrder == null) {
+            int addDays = monthEnd.getDayOfMonth() - comeDate.getDayOfMonth() + 1;
+            int daysInMonth = billYm.lengthOfMonth();
+
             List<ExpenseItemRespVO> items = fixedItems.stream().map(item -> {
                 ExpenseItemRespVO respVO = new ExpenseItemRespVO();
                 respVO.setExpenseType(0);
                 respVO.setExpenseSource(BusinessConstants.EXPENSE_ITEM);
                 respVO.setSourceExpenseItemId(item.getId());
-                respVO.setPrice(item.getActualAmount());
-                respVO.setTotalAmount(item.getTotalAmount());
+
+                BigDecimal monthAmount = item.getTotalAmount() == null ? BigDecimal.ZERO : item.getTotalAmount();
+                BigDecimal dayPrice = BigDecimal.ZERO;
+                BigDecimal addAmount = BigDecimal.ZERO;
+                if (monthAmount.compareTo(BigDecimal.ZERO) != 0 && daysInMonth > 0 && addDays > 0) {
+                    dayPrice = monthAmount.divide(BigDecimal.valueOf(daysInMonth), 8, RoundingMode.DOWN);
+                    addAmount = dayPrice.multiply(BigDecimal.valueOf(addDays)).setScale(2, RoundingMode.HALF_UP);
+                }
+
+                // 返院当月:按“返院日~月末(含当天)”折算总金额;price 按日单价回传,便于与账单已存在分支一致
+                respVO.setPrice(dayPrice.setScale(2, RoundingMode.HALF_UP));
+                respVO.setTotalAmount(addAmount);
+
                 respVO.setItemName(item.getItemName());
                 respVO.setItemCategory(item.getItemCategoryName());
-                respVO.setCount(item.getCount() == null ? 1 : item.getCount());
+                respVO.setCount(addDays);
                 respVO.setStartTime(comeDate.toString());
                 respVO.setEndTime(monthEnd.toString());
                 respVO.setType(item.getType());
@@ -313,7 +327,7 @@ public class ElderlyTempOutServiceImpl implements ElderlyTempOutService {
             orderItem.setTenantId(tenantId);
             orderItem.setStartDate(comeDate);
             orderItem.setEndDate(monthEnd);
-            orderItem.setDescription("返院补收固定费:" + comeDate + "至" + monthEnd + "共" + addDays + "天," + item.getItemName());
+            orderItem.setDescription("返院恢复固定费:" + comeDate + "至" + monthEnd + "共" + addDays + "天," + item.getItemName());
             orderItem.setType(item.getType());
             orderItem.setItemId(item.getItemId());
             // 标注月度费用(该字段为非持久化扩展字段,用于返回前端展示)

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

@@ -1061,7 +1061,7 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
                             orderItem.setCount(item.getCount());
                             orderItem.setItemName(item.getItemName());
                             orderItem.setDescription(StringUtils.isBlank(item.getDescription()) ?
-                                    LocalDateTimeUtil.format(expireDate.with(TemporalAdjusters.firstDayOfMonth()), DatePattern.NORM_DATE_PATTERN) + "至" + LocalDateTimeUtil.format(expireDate, DatePattern.NORM_DATE_PATTERN) + "的" + item.getItemName() :
+                                    item.getStartTime() + "至" + item.getEndTime() + "的" + item.getItemName() :
                                     item.getDescription());
                             orderItem.setContractId(item.getContractId());
                             if (item.getExpenseSource().equals(BusinessConstants.EXPENSE_ITEM)) {
@@ -1127,8 +1127,8 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
                             orderItem.setExpenseSource(item.getExpenseSource());
                             orderItem.setCount(item.getCount());
                             orderItem.setItemName(item.getItemName());
-                            orderItem.setDescription(StringUtils.isBlank(item.getDescription()) ? LocalDateTimeUtil.format(billYearMonth.atDay(1), DatePattern.NORM_DATE_PATTERN)
-                                    + "至" + LocalDateTimeUtil.format(billYearMonth.atEndOfMonth(), DatePattern.NORM_DATE_PATTERN) + "的" + item.getItemName() :
+                            orderItem.setDescription(StringUtils.isBlank(item.getDescription()) ?
+                                    item.getStartTime() + "至" + item.getEndTime() + "的" + item.getItemName() :
                                     item.getDescription());
                             orderItem.setPrice(item.getPrice());  //单价
                             orderItem.setActualPrice(item.getPrice()); //实际单价
@@ -3746,34 +3746,61 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
     }
 
     private List<ExpenseItemRespVO> handlerCollectMonthlyExpense(String billingMonth, Long elderId){
-        ElderlyTempOutDO elderlyTempOutDO = elderlyTempOutMapper.selectOne(new LambdaQueryWrapperX<ElderlyTempOutDO>()
+        // 离住/返院记录:你方约定为“只存在一条记录”,返院时会把该记录 outStatus 更新为 2 并写入 comeTime
+        ElderlyTempOutDO tempOutRecord = elderlyTempOutMapper.selectOne(new LambdaQueryWrapperX<ElderlyTempOutDO>()
                 .eq(ElderlyTempOutDO::getElderId, elderId)
-                .eq(ElderlyTempOutDO::getOutStatus, 1)
-                .le(ElderlyTempOutDO::getOutMonth, billingMonth));
+                .orderByDesc(ElderlyTempOutDO::getOutTime)
+                .last("LIMIT 1"));
+
         List<ExpenseItemRespVO> collect = new ArrayList<>();
         YearMonth yearMonth = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
-        LocalDate yearMonthStartDay = yearMonth.atDay(1);
-        // 规则:一旦开始离住(outStatus=1),离住当月按 1 号~离院前一天计固定费;离住后的月份直到返院前均不计固定费
-        // 因此:
-        // - billingMonth < outMonth:正常整月固定费
-        // - billingMonth == outMonth:若离院日>1号,则计 1号~离院前一天;若离院日=1号则当月不计固定费
-        // - billingMonth > outMonth:离住期内后续月份不计固定费
-        LocalDate fixedEnd = yearMonth.atEndOfMonth();
-        if (elderlyTempOutDO != null && elderlyTempOutDO.getOutTime() != null) {
-            YearMonth outYm = YearMonth.parse(elderlyTempOutDO.getOutMonth(), DateTimeFormatter.ofPattern("yyyy-MM"));
-            if (yearMonth.isAfter(outYm)) {
-                return collect;
-            }
-            if (yearMonth.equals(outYm)) {
-                LocalDate outDate = elderlyTempOutDO.getOutTime().toLocalDate();
-                if (outDate.isAfter(yearMonthStartDay)) {
-                    fixedEnd = outDate.minusDays(1);
+        LocalDate monthStart = yearMonth.atDay(1);
+        LocalDate monthEnd = yearMonth.atEndOfMonth();
+
+        // 计费核心:仅当目标月份与“在院区间”有交集才生成固定费用。
+        // 这里的“在院区间”按离住/返院时间截断,避免出现:离住后未返院的月份不应计费,但后续补录返院导致回算。
+        LocalDate actualStart = monthStart;
+        LocalDate actualEnd = monthEnd;
+
+        if (tempOutRecord != null && tempOutRecord.getOutTime() != null) {
+            LocalDate outDate = tempOutRecord.getOutTime().toLocalDate();
+            LocalDate comeDate = tempOutRecord.getComeTime() == null ? null : tempOutRecord.getComeTime().toLocalDate();
+
+            // 规则:
+            // 1) 离住月:计费区间为 月初 ~ 离院前一天(不包含离院当天)
+            // 2) 离住后、返院前:不计固定费
+            // 3) 返院月:计费区间为 返院当天 ~ 月末(包含返院当天)
+            // 4) 返院后月份:整月计费
+
+            // 离住发生在本月内:本月仅计 1号~离住前一天
+            if (!outDate.isBefore(monthStart) && !outDate.isAfter(monthEnd)) {
+                if (outDate.isAfter(monthStart)) {
+                    actualEnd = outDate.minusDays(1);
                 } else {
                     return collect;
                 }
             }
+
+            // 离住后的月份(本月月初在离院日之后)
+            if (monthStart.isAfter(outDate)) {
+                if (comeDate == null) {
+                    // 仍处于离住中:离院后月份不计费
+                    return collect;
+                }
+                YearMonth comeYm = YearMonth.from(comeDate);
+                if (yearMonth.isBefore(comeYm)) {
+                    // 返院发生在未来月份:本月仍不计费
+                    return collect;
+                }
+                if (yearMonth.equals(comeYm)) {
+                    // 返院当月:从返院日开始计费(包含当天)
+                    actualStart = comeDate.isAfter(monthStart) ? comeDate : monthStart;
+                }
+                // 返院后的月份:整月计费(保持 monthStart)
+            }
         }
-        if (!fixedEnd.isBefore(yearMonthStartDay)) {
+
+        if (!actualEnd.isBefore(actualStart)) {
             //如果没有当月订单查询当月未缴费的日常费用和月度费用
             Optional<ExpenseDO> optional = expenseMapper.selectList(new LambdaQueryWrapperX<ExpenseDO>()
                             .eq(ExpenseDO::getElderId, elderId)
@@ -3797,7 +3824,8 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
                     .le(ExpenseItemDO::getFreeStartTime, queryTime.atDay(1))
                     .ge(ExpenseItemDO::getFreeEndTime, queryTime.atEndOfMonth()));
             expenseItems.addAll(freeGiftItemList);
-
+            LocalDate finalActualStart = actualStart;
+            LocalDate finalActualEnd = actualEnd;
             collect = expenseItems.stream().map(item -> {
                 ExpenseItemRespVO respVO = new ExpenseItemRespVO();
                 if (item.getIsFreeGift().equals(BooleanEnum.TRUE.getValue())) {
@@ -3807,13 +3835,25 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
                 }
                 respVO.setExpenseSource(BusinessConstants.EXPENSE_ITEM);
                 respVO.setSourceExpenseItemId(item.getId());
-                respVO.setPrice(item.getActualAmount());
-                respVO.setTotalAmount(item.getTotalAmount());
+                BigDecimal originAmount = item.getActualAmount();
+                BigDecimal originTotal = item.getTotalAmount();
+                if (finalActualStart.isAfter(monthStart) || finalActualEnd.isBefore(monthEnd)) {
+                    long monthDays = ChronoUnit.DAYS.between(monthStart, monthEnd) + 1;
+                    long actualDays = ChronoUnit.DAYS.between(finalActualStart, finalActualEnd) + 1;
+                    if (monthDays > 0 && actualDays > 0) {
+                        BigDecimal ratio = BigDecimal.valueOf(actualDays)
+                                .divide(BigDecimal.valueOf(monthDays), 8, RoundingMode.HALF_UP);
+                        originAmount = originAmount.multiply(ratio).setScale(2, RoundingMode.HALF_UP);
+                        originTotal = originTotal.multiply(ratio).setScale(2, RoundingMode.HALF_UP);
+                    }
+                }
+                respVO.setPrice(originAmount);
+                respVO.setTotalAmount(originTotal);
                 respVO.setItemName(item.getItemName());
                 respVO.setItemCategory(item.getItemCategoryName());
                 respVO.setCount(item.getCount() == null ? 1 : item.getCount());
-                respVO.setStartTime(yearMonthStartDay.toString());
-                respVO.setEndTime(yearMonth.atEndOfMonth().toString());
+                respVO.setStartTime(finalActualStart.toString());
+                respVO.setEndTime(finalActualEnd.toString());
                 respVO.setType(item.getType());
                 respVO.setItemCategory(item.getItemCategoryName());
                 respVO.setContractId(expense.getContractId());