package cn.iocoder.yudao.module.system.service.biz;

import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.AskLeavePageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.ElderlyAskLeaveExportExcelVO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.*;
import cn.iocoder.yudao.module.system.dal.mysql.biz.*;
import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper;
import cn.iocoder.yudao.module.system.enums.change.BusinessConstants;
import cn.iocoder.yudao.module.system.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.system.enums.common.BooleanEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mzt.logapi.starter.annotation.LogRecord;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.enums.LogRecordConstants.*;

@Service
@Slf4j
public class ElderlyAskLeaveServiceImpl implements ElderlyAskLeaveService {

    @Resource
    private ElderlyAskLeaveMapper elderlyAskLeaveMapper;
    @Resource
    private ElderlyInfoMapper elderlyInfoMapper;

    @Resource
    private OutboundRefundConfigMapper outboundRefundConfigMapper;
    @Resource
    private OutboundRefundItemMapper outboundRefundItemMapper;

    @Resource
    private SysChargeCategoryMapper sysChargeCategoryMapper;

    @Resource
    private SysOverheadChargeMapper sysOverheadChargeMapper;

    @Resource
    private DailyExpensesMapper dailyExpensesMapper;

    @Resource
    private ExpenseMapper expenseMapper;

    @Resource
    private ExpenseItemMapper expenseItemMapper;

    @Resource
    private ExpenseOrderMapper expenseOrderMapper;

    @Resource
    private ExpenseOrderItemMapper expenseOrderItemMapper;

    @Resource
    private BuildService buildService;

    @Resource
    private TenantMapper tenantMapper;

    @Resource
    private ElderlyContractMapper elderlyContractMapper;


    @Override
    @LogRecord(type = ELDERLY_ASK_LEAVE_TYPE, subType = ELDERLY_ASK_LEAVE_CREATE_SUB_TYPE, bizNo = "{{#createReqVO.id}}",
            success = ELDERLY_ASK_LEAVE_CREATE_SUCCESS)
    public void createAskLeave(ElderlyAskLeaveDO createReqVO) {
        createReqVO.setCreateDate(new Date());
        elderlyAskLeaveMapper.insert(createReqVO);
    }

    @Override
    @Transactional
    public void editAskLeave(ElderlyAskLeaveDO elderlyAskLeaveDO) {
        checkLeaveUpdateDate(elderlyAskLeaveDO);
        elderlyAskLeaveMapper.updateById(elderlyAskLeaveDO);

        if (elderlyAskLeaveDO.getUpdateDate() != null && elderlyAskLeaveDO.getOutboundRefundConfigId() != null) {
            Date endDate = elderlyAskLeaveDO.getUpdateDate();
            Date outDate = elderlyAskLeaveDO.getOutDate();

            LocalDate endLocalDate = endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            LocalDate outLocalDate = outDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            int totalDays = (int) ChronoUnit.DAYS.between(outLocalDate, endLocalDate) + 1;

            OutboundRefundConfigDO config = outboundRefundConfigMapper.selectById(elderlyAskLeaveDO.getOutboundRefundConfigId());
            if (config != null && config.getIsRefund().equals(BooleanEnum.TRUE.getValue())) {
                List<OutboundRefundItemDO> items = outboundRefundItemMapper.selectList(
                        new LambdaQueryWrapper<OutboundRefundItemDO>()
                                .eq(OutboundRefundItemDO::getOutboundRefundConfigId, config.getId()));

                for (OutboundRefundItemDO item : items) {
                    processRefundItem(elderlyAskLeaveDO, item, outLocalDate, endLocalDate, totalDays, config);
                }
            }
        }
    }

    private void processRefundItem(ElderlyAskLeaveDO elderlyAskLeaveDO, OutboundRefundItemDO item,
                                   LocalDate outLocalDate, LocalDate endLocalDate, int totalDays,
                                   OutboundRefundConfigDO config) {
        String configName = config.getName();
        // 获取退费规则
        RefundRule rule = getRefundRule(config, item);

        // 检查是否满足最少外出天数
        // 退费生效条件：外出天数必须“超过”最少外出天数
        if (totalDays <= rule.getMinimumDays()) {
            log.debug("项目{}不退费，外出天数{}未超过最少外出天数{}",
                    item.getItemId(), totalDays, rule.getMinimumDays());
            return;
        }

        // 检查开始退费天数是否有效
        if (rule.getStartRefundDay() > totalDays) {
            log.debug("项目{}不退费，开始退费天数{}大于外出总天数{}",
                    item.getItemId(), rule.getStartRefundDay(), totalDays);
            return;
        }

        // 查询费用项
        List<ExpenseDO> expenseDOList = expenseMapper.selectList(
                new LambdaQueryWrapperX<ExpenseDO>()
                        .eq(ExpenseDO::getElderId, elderlyAskLeaveDO.getElderId())
                        .orderByDesc(ExpenseDO::getCreatedTime));

        if (CollectionUtils.isEmpty(expenseDOList)) {
            return;
        }

        // 查询费用项
        List<ExpenseItemDO> expenseItems = expenseItemMapper.selectList(
                new LambdaQueryWrapperX<ExpenseItemDO>()
                        .in(ExpenseItemDO::getExpenseId,
                                expenseDOList.stream().map(ExpenseDO::getId).collect(Collectors.toList()))
                        .le(ExpenseItemDO::getChangeStartDate, elderlyAskLeaveDO.getUpdateDate())
                        .and(w -> w
                                .ge(ExpenseItemDO::getChangeEndDate, elderlyAskLeaveDO.getOutDate())
                                .or()
                                .isNull(ExpenseItemDO::getChangeEndDate)
                        ));

        // 匹配对应的费用项
        List<ExpenseItemDO> expenseItemDOList = expenseItems.stream()
                .filter(i -> i.getItemId() != null && i.getItemId().equals(item.getItemId()))
                .collect(Collectors.toList());

        expenseItemDOList.sort(Comparator.comparing(ExpenseItemDO::getChangeStartDate));

        Set<LocalDate> processedDates = new HashSet<>();

        for (ExpenseItemDO expenseItemDO : expenseItemDOList) {
            // 计算退费开始日期（从第startRefundDay天开始）
            // 注意：如果是第5天开始退费，那么实际日期是 outLocalDate + 4
            LocalDate refundStartDate = outLocalDate.plusDays(rule.getMinimumDays() - 1);
            LocalDate refundEndDate = endLocalDate;

            // 计算费用项与退费时间段的实际重叠部分
            LocalDate itemStartDate = expenseItemDO.getChangeStartDate();
            LocalDate itemEndDate = expenseItemDO.getChangeEndDate();

            // 计算符合退费时间段
            LocalDate overlapStart = refundStartDate.isAfter(itemStartDate) ? refundStartDate : itemStartDate;
            LocalDate overlapEnd = (itemEndDate == null || itemEndDate.isAfter(refundEndDate)) ? refundEndDate : itemEndDate;

            // 如果无重叠，跳过
            if (overlapStart.isAfter(overlapEnd)) {
                continue;
            }

            // 检查是否已被处理
            if (isAlreadyProcessed(processedDates, overlapStart, overlapEnd)) {
                continue;
            }

            // 计算是否包含结束当天
            boolean includeEndDay = !overlapEnd.equals(endLocalDate) || rule.isSameDayRefund();

            // 计算重叠天数
            int overlapDays = calculateOverlapDays(overlapStart, overlapEnd, includeEndDay);

            if (overlapDays <= 0) {
                continue;
            }

            // 标记为已处理
            markDatesAsProcessed(processedDates, overlapStart, overlapEnd);

            // 根据退费类型处理（传入实际退费开始的日期，用于计算归属月份）
            processRefundByType(elderlyAskLeaveDO, item, expenseItemDO,
                    overlapStart, overlapEnd, overlapDays, includeEndDay, configName, overlapStart);
        }
    }

    /**
     * 获取退费规则
     */
    private RefundRule getRefundRule(OutboundRefundConfigDO config, OutboundRefundItemDO item) {
        RefundRule rule = new RefundRule();

        // 判断配置类型
        Integer configType = config.getConfigType();
        if (configType == null || configType == 0) {
            // 全局统一配置模式
            rule.setMinimumDays(config.getMinimumDays());
            rule.setStartRefundDay(config.getMaximumDays());
            rule.setSameDayRefund(BooleanEnum.TRUE.getValue().equals(config.getIsSameDayRefund()));
        } else {
            // 项目独立配置模式
            // 项目必须有独立配置，否则视为不退费
            if (item.getMinimumDays() == null || item.getMaximumDays() == null) {
                rule.setRefund(false);
                return rule;
            }
            rule.setMinimumDays(item.getMinimumDays());
            rule.setStartRefundDay(item.getMaximumDays());
            rule.setSameDayRefund(item.getIsSameDayRefund() != null
                    && BooleanEnum.TRUE.getValue().equals(item.getIsSameDayRefund()));
        }

        rule.setRefund(true);
        return rule;
    }

    /**
     * 检查时间段是否已被处理
     */
    private boolean isAlreadyProcessed(Set<LocalDate> processedDates, LocalDate start, LocalDate end) {
        LocalDate checkDate = start;
        while (!checkDate.isAfter(end)) {
            if (processedDates.contains(checkDate)) {
                return true;
            }
            checkDate = checkDate.plusDays(1);
        }
        return false;
    }

    /**
     * 计算重叠天数
     */
    private int calculateOverlapDays(LocalDate start, LocalDate end, boolean includeEndDay) {
        int overlapDays = (int) ChronoUnit.DAYS.between(start, end);
        if (includeEndDay) {
            overlapDays += 1;
        }
        return overlapDays;
    }

    /**
     * 标记日期为已处理
     */
    private void markDatesAsProcessed(Set<LocalDate> processedDates, LocalDate start, LocalDate end) {
        LocalDate markDate = start;
        while (!markDate.isAfter(end)) {
            processedDates.add(markDate);
            markDate = markDate.plusDays(1);
        }
    }

    /**
     * 根据退费类型处理
     */
    private void processRefundByType(ElderlyAskLeaveDO elderlyAskLeaveDO, OutboundRefundItemDO outboundRefundItem,
                                     ExpenseItemDO expenseItemDO, LocalDate startDate, LocalDate endDate,
                                     int refundDays, boolean includeEndDay, String configName, LocalDate actualRefundStartDate) {

        if (Objects.equals(outboundRefundItem.getType(), 1)) {
            // 固定金额退费
            processFixedAmountRefund(elderlyAskLeaveDO, outboundRefundItem, startDate, endDate, refundDays, includeEndDay, configName, actualRefundStartDate);
        } else if (outboundRefundItem.getType() == 2) {
            // 比例退费
            processProportionalRefund(elderlyAskLeaveDO, outboundRefundItem, startDate, endDate, refundDays, includeEndDay, configName, actualRefundStartDate);
        } else {
            // 按月份天数计算
            processMonthlyRefunds(elderlyAskLeaveDO, outboundRefundItem, expenseItemDO,
                    startDate, endDate, refundDays, includeEndDay, configName, actualRefundStartDate);
        }
    }

    private void processFixedAmountRefund(ElderlyAskLeaveDO elderlyAskLeaveDO, OutboundRefundItemDO outboundRefundItem,
                                          LocalDate startDate, LocalDate endDate, int refundDays, boolean includeEndDay, String configName, LocalDate actualRefundStartDate) {
        BigDecimal dailyAmount = resolveCustomAmount(outboundRefundItem, outboundRefundItem.getAmount());

        // 判断是否跨月
        YearMonth startMonth = YearMonth.from(startDate);
        YearMonth endMonth = YearMonth.from(endDate);
        
        if (startMonth.equals(endMonth)) {
            // 不跨月，按原逻辑处理
            DailyExpensesDO dailyExpenses = createBaseExpenseRecord(elderlyAskLeaveDO, outboundRefundItem, startDate, endDate);
            BigDecimal totalAmount = dailyAmount.multiply(BigDecimal.valueOf(refundDays)).setScale(8, RoundingMode.DOWN);
            
            dailyExpenses.setPrice(dailyAmount);
            dailyExpenses.setAmount(totalAmount.negate());
            dailyExpenses.setCount(refundDays);
            
            // 设置归属月份
            String attributionBillTime = calculateAttributionBillTime(elderlyAskLeaveDO, startMonth, actualRefundStartDate);
            dailyExpenses.setAttributionBillTime(attributionBillTime);
            String customMark = BooleanEnum.TRUE.getValue().equals(outboundRefundItem.getIsCustom()) ? "使用自定义金额,退费计算金额:" + dailyAmount + ",": "";
            String endDayInfo = includeEndDay ? "包含最后一天" : "不包含最后一天";
            dailyExpenses.setRemarks(customMark+ configName + "退费:长者外出,退" + startDate + "到" + endDate + "的费用(共" + refundDays + "天," + endDayInfo + ")");
            
            saveDailyExpenseAndAttachBill(elderlyAskLeaveDO, dailyExpenses);
        } else {
            // 跨月，按月份拆分处理
            LocalDate currentDate = startDate;
            while (!currentDate.isAfter(endDate)) {
                YearMonth currentYearMonth = YearMonth.from(currentDate);
                LocalDate monthEnd = currentYearMonth.atEndOfMonth();
                LocalDate periodEnd = endDate.isBefore(monthEnd) ? endDate : monthEnd;
                
                // 计算这个月的天数
                int daysInMonth = calculateOverlapDays(currentDate, periodEnd, true);
                
                // 特殊处理：如果是最后一个月且不包含结束当天，需要减1天
                if (!includeEndDay && periodEnd.equals(endDate)) {
                    daysInMonth -= 1;
                }
                
                if (daysInMonth > 0) {
                    DailyExpensesDO dailyExpenses = createBaseExpenseRecord(elderlyAskLeaveDO, outboundRefundItem, currentDate, periodEnd);
                    BigDecimal monthAmount = dailyAmount.multiply(BigDecimal.valueOf(daysInMonth)).setScale(8, RoundingMode.DOWN);
                    
                    dailyExpenses.setPrice(dailyAmount);
                    dailyExpenses.setAmount(monthAmount.negate());
                    dailyExpenses.setCount(daysInMonth);
                    
                    // 设置归属月份（基于实际退费开始日期所在月份计算）
                    String attributionBillTime = calculateAttributionBillTime(elderlyAskLeaveDO, currentYearMonth, actualRefundStartDate);
                    dailyExpenses.setAttributionBillTime(attributionBillTime);
                    String customMark = BooleanEnum.TRUE.getValue().equals(outboundRefundItem.getIsCustom()) ? "使用自定义金额,退费计算金额:" + dailyAmount + ",": "";
                    String endDayInfo = (!includeEndDay && periodEnd.equals(endDate)) ? "不包含最后一天" : "包含最后一天";
                    dailyExpenses.setRemarks(customMark+ configName + "退费:长者外出,退" + currentDate + "到" + periodEnd + "的费用(" + currentYearMonth + "月份共" + daysInMonth + "天," + endDayInfo + ")");
                    
                    saveDailyExpenseAndAttachBill(elderlyAskLeaveDO, dailyExpenses);
                }
                
                // 移动到下个月
                currentDate = periodEnd.plusDays(1);
            }
        }
    }

    private void processProportionalRefund(ElderlyAskLeaveDO elderlyAskLeaveDO, OutboundRefundItemDO outboundRefundItem,
                                           LocalDate startDate, LocalDate endDate, int refundDays, boolean includeEndDay, String configName, LocalDate actualRefundStartDate) {
        BigDecimal proportion = outboundRefundItem.getProportion();

        // 获取费用项原价
        OverheadChargeDO overheadChargeDO = sysOverheadChargeMapper.selectById(outboundRefundItem.getItemId());
        BigDecimal basePrice;
        if (BooleanEnum.TRUE.getValue().equals(outboundRefundItem.getIsCustom())) {
            basePrice = resolveCustomAmount(outboundRefundItem, BigDecimal.ZERO);
        } else {
            double itemPrice = overheadChargeDO.getPrice();
            basePrice = BigDecimal.valueOf(itemPrice);
        }

        // 每日金额
        BigDecimal dailyPrice = basePrice.multiply(proportion).setScale(8, RoundingMode.DOWN);

        // 判断是否跨月
        YearMonth startMonth = YearMonth.from(startDate);
        YearMonth endMonth = YearMonth.from(endDate);
        
        if (startMonth.equals(endMonth)) {
            // 不跨月，按原逻辑处理
            DailyExpensesDO dailyExpenses = createBaseExpenseRecord(elderlyAskLeaveDO, outboundRefundItem, startDate, endDate);
            BigDecimal totalAmount = dailyPrice.multiply(BigDecimal.valueOf(refundDays)).setScale(8, RoundingMode.DOWN);
            
            dailyExpenses.setPrice(dailyPrice);
            dailyExpenses.setAmount(totalAmount.negate());
            dailyExpenses.setCount(refundDays);
            dailyExpenses.setRoundAmount(dailyExpenses.getAmount().setScale(0,RoundingMode.HALF_UP));
            
            // 设置归属月份
            String attributionBillTime = calculateAttributionBillTime(elderlyAskLeaveDO, startMonth, actualRefundStartDate);
            dailyExpenses.setAttributionBillTime(attributionBillTime);
            String customMark = BooleanEnum.TRUE.getValue().equals(outboundRefundItem.getIsCustom()) ? "使用自定义金额,退费计算金额:" + basePrice.setScale(2,RoundingMode.HALF_UP) + ",": "";
            String endDayInfo = includeEndDay ? "包含最后一天" : "不包含最后一天";
            dailyExpenses.setRemarks(customMark + configName + "退费:长者外出,退" + startDate + "到" + endDate + "的费用(共" + refundDays + "天," + endDayInfo + ")");
            
            saveDailyExpenseAndAttachBill(elderlyAskLeaveDO, dailyExpenses);
        } else {
            // 跨月，按月份拆分处理
            LocalDate currentDate = startDate;
            while (!currentDate.isAfter(endDate)) {
                YearMonth currentYearMonth = YearMonth.from(currentDate);
                LocalDate monthEnd = currentYearMonth.atEndOfMonth();
                LocalDate periodEnd = endDate.isBefore(monthEnd) ? endDate : monthEnd;
                
                // 计算这个月的天数
                int daysInMonth = calculateOverlapDays(currentDate, periodEnd, true);
                
                // 特殊处理：如果是最后一个月且不包含结束当天，需要减1天
                if (!includeEndDay && periodEnd.equals(endDate)) {
                    daysInMonth -= 1;
                }
                
                if (daysInMonth > 0) {
                    DailyExpensesDO dailyExpenses = createBaseExpenseRecord(elderlyAskLeaveDO, outboundRefundItem, currentDate, periodEnd);
                    BigDecimal monthAmount = dailyPrice.multiply(BigDecimal.valueOf(daysInMonth)).setScale(8, RoundingMode.DOWN);
                    
                    dailyExpenses.setPrice(dailyPrice);
                    dailyExpenses.setAmount(monthAmount.negate());
                    dailyExpenses.setCount(daysInMonth);
                    dailyExpenses.setRoundAmount(dailyExpenses.getAmount().setScale(0,RoundingMode.HALF_UP));
                    
                    // 设置归属月份（基于实际退费开始日期所在月份计算）
                    String attributionBillTime = calculateAttributionBillTime(elderlyAskLeaveDO, currentYearMonth, actualRefundStartDate);
                    dailyExpenses.setAttributionBillTime(attributionBillTime);
                    String customMark = BooleanEnum.TRUE.getValue().equals(outboundRefundItem.getIsCustom()) ? "使用自定义金额,退费计算金额:" + basePrice.setScale(2,RoundingMode.HALF_UP) + ",": "";
                    String endDayInfo = (!includeEndDay && periodEnd.equals(endDate)) ? "不包含最后一天" : "包含最后一天";
                    dailyExpenses.setRemarks(customMark + configName + "退费:长者外出,退" + currentDate + "到" + periodEnd + "的费用(" + currentYearMonth + "月份共" + daysInMonth + "天," + endDayInfo + ")");
                    
                    saveDailyExpenseAndAttachBill(elderlyAskLeaveDO, dailyExpenses);
                }
                
                // 移动到下个月
                currentDate = periodEnd.plusDays(1);
            }
        }
    }

    private void processMonthlyRefunds(ElderlyAskLeaveDO elderlyAskLeaveDO, OutboundRefundItemDO outboundRefundItem,
                                       ExpenseItemDO expenseItemDO, LocalDate startDate, LocalDate endDate,
                                       int totalDays, boolean includeEndDay, String configName, LocalDate actualRefundStartDate) {
        // 按月份循环处理
        LocalDate currentDate = startDate;
        while (!currentDate.isAfter(endDate)) {
            YearMonth currentYearMonth = YearMonth.from(currentDate);
            LocalDate monthEnd = currentYearMonth.atEndOfMonth();
            LocalDate periodEnd = endDate.isBefore(monthEnd) ? endDate : monthEnd;

            // 计算这个月退费的天数
            int daysInMonth = calculateOverlapDays(currentDate, periodEnd, true);

            // 特殊处理：如果是最后一个月且不包含结束当天，需要减1天
            if (!includeEndDay && periodEnd.equals(endDate)) {
                daysInMonth -= 1;
            }

            if (daysInMonth > 0) {
                createMonthlyExpenseRecord(elderlyAskLeaveDO, outboundRefundItem, expenseItemDO,
                        currentYearMonth, currentDate, periodEnd, daysInMonth, !includeEndDay && periodEnd.equals(endDate), configName, actualRefundStartDate);
            }

            // 移动到下个月
            currentDate = periodEnd.plusDays(1);
        }
    }

    private DailyExpensesDO createBaseExpenseRecord(ElderlyAskLeaveDO elderlyAskLeaveDO, OutboundRefundItemDO outboundRefundItem,
                                                    LocalDate startDate, LocalDate endDate) {
        DailyExpensesDO dailyExpenses = new DailyExpensesDO();
        dailyExpenses.setElderId(elderlyAskLeaveDO.getElderId());
        dailyExpenses.setItemId(outboundRefundItem.getItemId());

        SysChargeCategoryDO sysChargeCategory = sysChargeCategoryMapper.selectById(outboundRefundItem.getCategoryId());
        dailyExpenses.setItemCategory(sysChargeCategory == null ? null : sysChargeCategory.getName());

        OverheadChargeDO overheadChargeDO = sysOverheadChargeMapper.selectById(outboundRefundItem.getItemId());
        dailyExpenses.setItemName(overheadChargeDO.getChargeName());

        dailyExpenses.setType(3);  // 外出类型
        dailyExpenses.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
        dailyExpenses.setCreatedTime(new Date());
        dailyExpenses.setAccountingTime(new Date());
        dailyExpenses.setStartDate(startDate);
        dailyExpenses.setEndDate(endDate);
        dailyExpenses.setTenantId(elderlyAskLeaveDO.getTenantId());

        return dailyExpenses;
    }

    private void createMonthlyExpenseRecord(ElderlyAskLeaveDO elderlyAskLeaveDO, OutboundRefundItemDO outboundRefundItem,
                                            ExpenseItemDO expenseItemDO, YearMonth yearMonth, LocalDate startDate,
                                            LocalDate endDate, int daysInMonth, boolean includeEndDay, String configName, LocalDate actualRefundStartDate) {
        DailyExpensesDO dailyExpenses = createBaseExpenseRecord(elderlyAskLeaveDO, outboundRefundItem, startDate, endDate);

        // 计算金额
        BigDecimal actualAmount = resolveCustomAmount(outboundRefundItem, expenseItemDO.getActualAmount());
        int monthTotalDays = yearMonth.lengthOfMonth();
        BigDecimal dayPrice = expenseItemDO.getIsDayCalculate() == 1 ? expenseItemDO.getActualAmount() :actualAmount.divide(BigDecimal.valueOf(monthTotalDays), 5, RoundingMode.HALF_UP);
        BigDecimal monthAmount = dayPrice.multiply(BigDecimal.valueOf(daysInMonth)).setScale(2, RoundingMode.HALF_UP);

        dailyExpenses.setPrice(dayPrice);
        dailyExpenses.setAmount(monthAmount.negate());
        dailyExpenses.setCount(daysInMonth);

        // 设置归属月份（基于实际退费开始日期所在月份计算）
        String attributionBillTime = calculateAttributionBillTime(elderlyAskLeaveDO, yearMonth, actualRefundStartDate);
        dailyExpenses.setAttributionBillTime(attributionBillTime);

        // 设置备注
        String customMark = BooleanEnum.TRUE.getValue().equals(outboundRefundItem.getIsCustom()) ? "使用自定义金额,退费计算金额:" + actualAmount.setScale(2,RoundingMode.HALF_UP) + ",": "";
        String endDayInfo = !includeEndDay ? "包含最后一天" : "不包含最后一天";
        dailyExpenses.setRemarks(customMark + configName + "退费:长者外出,退" + startDate + "到" + endDate + "的费用(" + yearMonth + "月份共" + daysInMonth + "天," + endDayInfo + ")");

        saveDailyExpenseAndAttachBill(elderlyAskLeaveDO, dailyExpenses);
    }

    private String calculateAttributionBillTime(ElderlyAskLeaveDO elderlyAskLeaveDO, YearMonth yearMonth, LocalDate startDate) {
        if (elderlyAskLeaveDO.getBelongMonth() != null) {
            try {
                YearMonth belongYearMonth = YearMonth.parse(elderlyAskLeaveDO.getBelongMonth());
                long monthsBetween = ChronoUnit.MONTHS.between(startDate.withDayOfMonth(1), yearMonth.atDay(1));
                return belongYearMonth.plusMonths(monthsBetween).toString();
            } catch (Exception e) {
                log.warn("解析归属月份失败: {}", elderlyAskLeaveDO.getBelongMonth(), e);
            }
        }
        return yearMonth.toString();
    }

    /**
     * 退费规则实体类
     */
    @Data
    private static class RefundRule {
        private Integer minimumDays = 0;      // 最少外出天数
        private Integer startRefundDay = 1;   // 开始退费的天数（第几天）
        private boolean isRefund = false;     // 是否退费
        private boolean isSameDayRefund = false; // 返院当天是否退费
    }

    /**
     * 计算使用的金额（支持配置自定义金额）
     */
    private BigDecimal resolveCustomAmount(OutboundRefundItemDO outboundRefundItem, BigDecimal defaultAmount) {
        if (BooleanEnum.TRUE.getValue().equals(outboundRefundItem.getIsCustom())
                && outboundRefundItem.getCustomAmount() != null) {
            return outboundRefundItem.getCustomAmount();
        }
        return defaultAmount == null ? BigDecimal.ZERO : defaultAmount;
    }

    /**
     * 保存外出日常费用，并在账单未缴费/部分缴费时自动写入对应归属月账单
     */
    private void saveDailyExpenseAndAttachBill(ElderlyAskLeaveDO askLeaveDO, DailyExpensesDO dailyExpenses) {
        if(dailyExpenses.getAmount().compareTo(BigDecimal.ZERO) == 0){
            return;
        }
        // 默认标记为未写入账单
        dailyExpenses.setIsGenerateBill(BooleanEnum.FALSE.getValue());

        // 查找对应归属月的月度账单（type=2）
        ExpenseOrderDO expenseOrder = expenseOrderMapper.selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                .eq(ExpenseOrderDO::getElderId, askLeaveDO.getElderId())
                .eq(ExpenseOrderDO::getBillingMonth, dailyExpenses.getAttributionBillTime())
                .eq(ExpenseOrderDO::getType, 2));

        // 先保存日常费用，获取ID
        dailyExpensesMapper.insert(dailyExpenses);

        // 账单不存在、已缴费或已确认则不带入账单
        if (expenseOrder == null || Objects.equals(expenseOrder.getPayStatus(), 1) || expenseOrder.getIsLock()) {
            return;
        }

        // 标记已生成账单并写入账单号
        dailyExpenses.setIsGenerateBill(BooleanEnum.TRUE.getValue());
        dailyExpenses.setExpenseOrderNumber(expenseOrder.getBillOrderNumber());
        dailyExpensesMapper.updateById(dailyExpenses);

        // 写入账单明细
        ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
        orderItem.setExpenseOrderId(expenseOrder.getId());
        orderItem.setSourceExpenseItemId(dailyExpenses.getId());
        orderItem.setExpenseSource(BusinessConstants.DAILY_EXPENSES);
        orderItem.setCount(dailyExpenses.getCount());
        orderItem.setItemName(dailyExpenses.getItemName());
        orderItem.setItemCategoryName(dailyExpenses.getItemCategory());
        orderItem.setPrice(dailyExpenses.getPrice());
        orderItem.setActualPrice(dailyExpenses.getPrice());
        orderItem.setTotalAmount(dailyExpenses.getAmount());
        orderItem.setRoundAmount(orderItem.getTotalAmount().setScale(0, RoundingMode.HALF_UP));
        orderItem.setRoundTwoDecimalAmount(orderItem.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
        orderItem.setCreatedTime(new Date());
        orderItem.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
        orderItem.setTenantId(askLeaveDO.getTenantId());
        orderItem.setStartDate(dailyExpenses.getStartDate());
        orderItem.setEndDate(dailyExpenses.getEndDate());
        orderItem.setDescription(dailyExpenses.getRemarks());
        expenseOrderItemMapper.insert(orderItem);

        // 更新账单金额（退费为负数会自动冲减）
        BigDecimal newAmount = expenseOrder.getActualAmount() == null ? BigDecimal.ZERO : expenseOrder.getActualAmount();
        newAmount = newAmount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));
        expenseOrder.setActualAmount(newAmount);
        expenseOrderMapper.updateById(expenseOrder);
    }

    @Override
    public void delete(Long id) {
        elderlyAskLeaveMapper.deleteById(id);
    }

    @Override
    public PageResult<ElderlyAskLeaveDO> findPage(AskLeavePageReqVO reqVO) {
        PageResult<ElderlyAskLeaveDO> elderlyAskLeaveDOPageResult = elderlyAskLeaveMapper.selectPage(reqVO);
        for (ElderlyAskLeaveDO elderlyAskLeaveDO : elderlyAskLeaveDOPageResult.getList()) {
            ElderlyInfoDO elderlyInfoDO = elderlyInfoMapper.selectById(elderlyAskLeaveDO.getElderId());
            if (elderlyInfoDO != null){
                elderlyInfoDO.setBedName(buildService.getFullBedName(elderlyInfoDO.getId()));
            }
            elderlyAskLeaveDO.setElderlyInfo(elderlyInfoDO);
            elderlyAskLeaveDO.setTenantName(tenantMapper.selectById(elderlyAskLeaveDO.getTenantId()).getName());
        }
        return elderlyAskLeaveDOPageResult;
    }

    @Override
    public ElderlyAskLeaveDO getDetail(Long id) {
        ElderlyAskLeaveDO elderlyAskLeaveDO = elderlyAskLeaveMapper.selectById(id);
        if (elderlyAskLeaveDO != null) {
            ElderlyInfoDO elderlyInfoDO = elderlyInfoMapper.selectById(elderlyAskLeaveDO.getElderId());
            if (elderlyInfoDO != null){
                elderlyInfoDO.setBedName(buildService.getFullBedName(elderlyInfoDO.getId()));
            }
            elderlyAskLeaveDO.setElderlyInfo(elderlyInfoDO);
        }
        return elderlyAskLeaveDO;
    }

    @Override
    public List<ElderlyAskLeaveExportExcelVO> getExportList(AskLeavePageReqVO reqVO) {
        // 设置不分页，导出全部数据
        reqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        PageResult<ElderlyAskLeaveDO> pageResult = elderlyAskLeaveMapper.selectPage(reqVO);

        List<ElderlyAskLeaveDO> list = pageResult.getList();
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }

        // 填充老人信息和租户名称
        for (ElderlyAskLeaveDO elderlyAskLeaveDO : list) {
            ElderlyInfoDO elderlyInfoDO = elderlyInfoMapper.selectById(elderlyAskLeaveDO.getElderId());
            if (elderlyInfoDO != null) {
                elderlyInfoDO.setBedName(buildService.getFullBedName(elderlyInfoDO.getId()));
            }
            elderlyAskLeaveDO.setElderlyInfo(elderlyInfoDO);
        }

        // 转换为导出VO
        return list.stream().map(this::convertToExportVO).collect(Collectors.toList());
    }

    /**
     * 将 DO 转换为导出 VO
     */
    private ElderlyAskLeaveExportExcelVO convertToExportVO(ElderlyAskLeaveDO elderlyAskLeaveDO) {
        ElderlyAskLeaveExportExcelVO exportVO = new ElderlyAskLeaveExportExcelVO();

        // 基础字段
        exportVO.setElderName(elderlyAskLeaveDO.getElderName());
        exportVO.setAccompanyPerson(elderlyAskLeaveDO.getAccompanyPerson());
        exportVO.setOutDate(ElderlyAskLeaveExportExcelVO.convertToLocalDate(elderlyAskLeaveDO.getOutDate()));
        exportVO.setComeDate(ElderlyAskLeaveExportExcelVO.convertToLocalDate(elderlyAskLeaveDO.getComeDate()));
        exportVO.setOutPerson(elderlyAskLeaveDO.getOutPerson());
        exportVO.setOutPhone(elderlyAskLeaveDO.getOutPhone());
        exportVO.setRemarks(elderlyAskLeaveDO.getRemarks());
        exportVO.setType(elderlyAskLeaveDO.getType());
        exportVO.setLeaveStatus(elderlyAskLeaveDO.getLeaveStatus());
        exportVO.setNumberDays(elderlyAskLeaveDO.getNumberDays());
        exportVO.setTenantName(elderlyAskLeaveDO.getTenantName());
        exportVO.setBelongMonth(elderlyAskLeaveDO.getBelongMonth());

        // 老人信息（床位等）
        ElderlyInfoDO elderlyInfo = elderlyAskLeaveDO.getElderlyInfo();
        if (elderlyInfo != null) {
            exportVO.setBuildName(elderlyInfo.getBuildName());
            exportVO.setFloorName(elderlyInfo.getFloorName());
            exportVO.setRoomName(elderlyInfo.getRoomName());
            exportVO.setBedName(elderlyInfo.getBedName());
        }

        return exportVO;
    }

    /**
     * 检查返院日期当天是否仍在合同有效期内
     */
    private void checkLeaveUpdateDate(ElderlyAskLeaveDO elderlyAskLeaveDO){
        LocalDate updateLocalDate = elderlyAskLeaveDO.getUpdateDate().toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDate();
        List<ElderlyContractDO> elderlyContractDOS = elderlyContractMapper.selectList(new LambdaQueryWrapperX<ElderlyContractDO>()
                .eq(ElderlyContractDO::getElderId, elderlyAskLeaveDO.getElderId())
                .apply("date_format(expire_time, '%Y-%m-%d') >= {0}", updateLocalDate)
                .apply("date_format(begin_time, '%Y-%m-%d') <= {0}", updateLocalDate));
        if(CollectionUtil.isEmpty(elderlyContractDOS)){
            throw exception(ErrorCodeConstants.ELDERLY_LEAVE_UPDATE_DATE_OVER_EXPIRE);
        }
    }
}
