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

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.string.SerialCodeUtils;
import cn.iocoder.yudao.framework.common.util.string.StringUtil;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.bpm.enums.message.BpmMessageEnum;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
import cn.iocoder.yudao.module.system.controller.admin.biz.task.TaskStatusManager;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.*;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.excel.ElderlyExpenseOrderExportExcelVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.excel.ExpenseOrderBatchPayExcelVO;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.excel.OneTimeExpenseImportExcelVO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.*;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
import cn.iocoder.yudao.module.system.dal.dataobject.discount.DiscountRecordDO;
import cn.iocoder.yudao.module.system.dal.dataobject.discount.DiscountRuleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
import cn.iocoder.yudao.module.system.dal.mysql.biz.*;
import cn.iocoder.yudao.module.system.dal.mysql.dict.DictDataMapper;
import cn.iocoder.yudao.module.system.dal.mysql.discount.DiscountRecordMapper;
import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper;
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.system.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.system.enums.change.BusinessConstants;
import cn.iocoder.yudao.module.system.enums.common.BooleanEnum;
import cn.iocoder.yudao.module.system.framework.sms.util.AliyunSmsUtils;
import cn.iocoder.yudao.module.system.service.discount.DiscountRuleService;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.starter.annotation.LogRecord;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;

import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.client.RestTemplate;

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

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.Period;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.stream.Collectors;


/**
 * 长者账单 Service 实现类
 *
 * @author 系统管理员
 */
@Service
@Validated
@Slf4j
public class ExpenseOrderServiceImpl implements ExpenseOrderService {

    @Resource
    private ExpenseOrderMapper expenseOrderMapper;
    @Resource
    private ExpenseOrderItemMapper expenseOrderItemMapper;
    @Resource
    private ExpensePayOrderMapper expensePayOrderMapper;
    @Resource
    private ExpensePayOrderItemMapper expensePayOrderItemMapper;
    @Resource
    private ExpenseSubsidyMapper expenseSubsidyMapper;
    @Resource
    private ExpenseSubsidyAccountMapper expenseSubsidyAccountMapper;
    @Resource
    private BalanceMapper balanceMapper;
    @Resource
    private BalanceRecordMapper balanceRecordMapper;
    @Resource
    private ElderlyInfoMapper elderlyInfoMapper;
    @Resource
    private BuildService buildService;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private DailyExpensesMapper dailyExpensesMapper;
    @Resource
    private ExpenseItemMapper expenseItemMapper;

    @Resource
    private ExpenseMapper expenseMapper;
    @Resource
    private DictDataMapper dictDataMapper;

    @Resource
    private DepositMapper depositMapper;
    @Resource
    private DepositRecordMapper depositRecordMapper;
    @Resource
    private MemberUserApi memberUserApi;

    @Resource
    private TenantMapper tenantMapper;

    @Resource
    private SysOverheadChargeMapper sysOverheadChargeMapper;


    @Resource
    private ElderlyContractMapper elderlyContractMapper;

    @Autowired
    private NotifyMessageSendApi notifyMessageSendApi;

    @Resource
    private DiscountRuleService discountRuleService;

    @Resource
    private RelativesMapper relativesMapper;

    @Resource
    private DiscountRecordMapper discountRecordMapper;

    @Resource
    private TaskStatusManager taskStatusManager;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private ElderlyTempOutMapper elderlyTempOutMapper;

    @Autowired
    private ElderlyConsumerVouchersMapper elderlyConsumerVouchersMapper;

    @Override
    public PageResult<ExpenseOrderDO> getExpenseBillPage(ExpenseBillPageReqVO pageReqVO) {
        Page<ExpenseOrderDO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
        List<ExpenseOrderDO> list = expenseOrderMapper.getExpenseBillPage(page, pageReqVO);
        for (ExpenseOrderDO expenseOrderDO : list) {
            Map<String,BigDecimal> categoryAmountMap = new HashMap<>();
            Map<Integer,String> typeCategoryMap = new HashMap<>();
            BigDecimal otherAmount = BigDecimal.ZERO;
            BigDecimal otherRoundAmount = BigDecimal.ZERO;
            BigDecimal payAmountTotal = BigDecimal.ZERO;

            String fullBedName = buildService.getFullBedName(expenseOrderDO.getElderId());
            expenseOrderDO.setBedName(fullBedName);
            TenantDO tenantDO = tenantMapper.selectById(expenseOrderDO.getTenantId());
            expenseOrderDO.setTenantName(tenantDO == null ? null : tenantDO.getName());

            List<ExpenseOrderItemDO> orderItems = expenseOrderItemMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderItemDO>()
                    .eq(ExpenseOrderItemDO::getExpenseOrderId, expenseOrderDO.getId()));

            List<ExpensePayOrderDO> payInfos = expensePayOrderMapper.selectList(new LambdaQueryWrapperX<ExpensePayOrderDO>()
                    .eq(ExpensePayOrderDO::getExpenseOrderId, expenseOrderDO.getId()));
            for (ExpensePayOrderDO payInfo : payInfos) {
                payAmountTotal = payAmountTotal.add(payInfo.getRoundPayTwoDecimalAmount() == null ? BigDecimal.ZERO : payInfo.getRoundPayTwoDecimalAmount());
            }

            for (ExpenseOrderItemDO orderItem : orderItems) {
                String categoryKey = StringUtils.isBlank(orderItem.getItemCategoryName()) ? "" :orderItem.getItemCategoryName().replaceAll("-", "").replace("/", "");
                BigDecimal totalAmount = orderItem.getTotalAmount().setScale(2, RoundingMode.HALF_UP);
                if(orderItem.getExpenseSource().equals(BusinessConstants.EXPENSE_ITEM)){
                    if (orderItem.getType() != null && orderItem.getType() == 1) {
                        typeCategoryMap.put(orderItem.getType(), categoryKey);
                        categoryAmountMap.compute(categoryKey, (k,v)->{
                            if(v == null){
                                v = BigDecimal.ZERO;
                            }
                            return v.add(totalAmount);
                        });
                    } else if (orderItem.getType() != null && orderItem.getType() == 2) {
                        typeCategoryMap.put(orderItem.getType(), categoryKey);
                        categoryAmountMap.compute(categoryKey, (k,v)->{
                            if(v == null){
                                v = BigDecimal.ZERO;
                            }
                            return v.add(totalAmount);
                        });
                    } else if (orderItem.getType() != null && orderItem.getType() == 3) {
                        typeCategoryMap.put(orderItem.getType(), categoryKey);
                        categoryAmountMap.compute(categoryKey, (k,v)->{
                            if(v == null){
                                v = BigDecimal.ZERO;
                            }
                            return v.add(totalAmount);
                        });
                    }else {
                        otherAmount = otherAmount.add(totalAmount);
                        otherRoundAmount = otherRoundAmount.add(totalAmount);
                    }
                } else {
                    BigDecimal categoryAmount = categoryAmountMap.get(categoryKey);
                    if(categoryAmount != null){
                        categoryAmountMap.put(categoryKey,categoryAmount.add(totalAmount));
                        continue;
                    }
                    otherAmount = otherAmount.add(totalAmount);
                    otherRoundAmount = otherRoundAmount.add(totalAmount);
                }
            }
            expenseOrderDO.setBedAmount(categoryAmountMap.getOrDefault(typeCategoryMap.get(1),BigDecimal.ZERO));
            expenseOrderDO.setNurseAmount(categoryAmountMap.getOrDefault(typeCategoryMap.get(2),BigDecimal.ZERO));
            expenseOrderDO.setMealAmount(categoryAmountMap.getOrDefault(typeCategoryMap.get(3),BigDecimal.ZERO));
            expenseOrderDO.setOtherAmount(otherAmount);
            expenseOrderDO.setOtherRoundAmount(otherRoundAmount);
            expenseOrderDO.setPaidAmount(payAmountTotal.setScale(2, RoundingMode.HALF_UP));
            expenseOrderDO.setActualAmount(expenseOrderDO.getBedAmount()
                    .add(expenseOrderDO.getNurseAmount())
                    .add(expenseOrderDO.getMealAmount())
                    .add(expenseOrderDO.getOtherAmount())
                    .setScale(2, RoundingMode.HALF_UP));
        }
        return new PageResult<>(list, page.getTotal());
    }

    @Override
    public void arrearsNote(Long id, String arrearsNote) {
        ExpenseOrderDO expenseOrderDO = expenseOrderMapper.selectById(id);
        if (expenseOrderDO != null) {
            expenseOrderDO.setArrearsNote(arrearsNote);
            expenseOrderMapper.updateById(expenseOrderDO);
        }
    }

    @Override
    public ExpenseBillRespVO getExpenseBill(Long id) {
        ExpenseBillRespVO respVO = new ExpenseBillRespVO();
        ExpenseOrderDO expenseOrder = expenseOrderMapper.selectById(id);
        BeanUtils.copyProperties(expenseOrder, respVO);
        respVO.setBedName(buildService.getFullBedName(expenseOrder.getElderId()));
        ElderlyInfoDO elderlyInfo = elderlyInfoMapper.selectById(expenseOrder.getElderId());
        respVO.setElderName(elderlyInfo == null ? null : elderlyInfo.getElderName());
        respVO.setElderSex(elderlyInfo == null ? null : elderlyInfo.getElderSex());
        List<ExpenseOrderItemDO> items = expenseOrderItemMapper.selectList(new LambdaQueryWrapper<ExpenseOrderItemDO>().eq(ExpenseOrderItemDO::getExpenseOrderId, id));
        if (expenseOrder.getType() == 1) {
            Date checkInTime = elderlyInfo.getCheckInTime();
            LocalDate startDay = checkInTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            LocalDate lastDayOfMonth = startDay.with(TemporalAdjusters.lastDayOfMonth());
            Period period = Period.between(startDay, lastDayOfMonth);
            int daysBetween = period.getDays() + 1;
            respVO.setDescription(startDay + "至" + lastDayOfMonth + "  共" + daysBetween + "天");

            ExpenseOrderItemDO orderItemDO = items.stream().findFirst().get();
            if (orderItemDO.getExpenseSource().equals(BusinessConstants.EXPENSE_ITEM)) {
                ExpenseItemDO expenseItem = expenseItemMapper.selectById(orderItemDO.getSourceExpenseItemId());
                ExpenseDO expenseDO = expenseMapper.selectById(expenseItem.getExpenseId());
                respVO.setExpenseRemarks(expenseDO.getRemark());
                respVO.setExpenseRemarksCreator(expenseDO.getCreatedBy());
            }
        } else {
            List<ElderlyContractDO> elderlyContractList = elderlyContractMapper.selectList(new LambdaQueryWrapperX<ElderlyContractDO>()
                    .eq(ElderlyContractDO::getElderId, elderlyInfo.getId())
                    .orderByDesc(ElderlyContractDO::getCreatedTime));
            ElderlyContractDO elderlyContractDO = elderlyContractList.stream().findFirst().get();
            respVO.setContractNumber(elderlyContractDO.getContractNumber());
            // 加一段逻辑，如果长者当前月份账单过期，就取月头和过期时间生成备注描述，合同未过期就取月头和月尾生成备注描述
            LocalDate startDay = YearMonth.parse(expenseOrder.getBillingMonth(), DateTimeFormatter.ofPattern("yyyy-MM")).atDay(1);
            LocalDate lastDayOfMonth = startDay.with(TemporalAdjusters.lastDayOfMonth());

            // 判断合同是否在账单月份过期
            if (elderlyContractDO.getExpireTime() != null) {
                LocalDate expireDate = elderlyContractDO.getExpireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                YearMonth expireYearMonth = YearMonth.from(expireDate);
                YearMonth billYearMonth = YearMonth.parse(expenseOrder.getBillingMonth(), DateTimeFormatter.ofPattern("yyyy-MM"));

                // 如果合同在当前账单月过期，则使用月头到过期日期作为描述
                if (expireYearMonth.equals(billYearMonth)) {
                    lastDayOfMonth = expireDate;
                }
            }
            Period period = Period.between(startDay, lastDayOfMonth);
            int daysBetween = period.getDays() + 1;
            respVO.setDescription(startDay + "至" + lastDayOfMonth + "  共" + daysBetween + "天");
        }
        List<ExpenseSubsidyDO> expenseSubsidyList = new ArrayList<>();
        //查询账单明细
        for (ExpenseOrderItemDO item : items) {
            if (item.getExpenseSource().equals(BusinessConstants.EXPENSE_ITEM)) {
                ExpenseItemDO expenseItem = expenseItemMapper.selectById(item.getSourceExpenseItemId());
                if (expenseItem != null) {
                    item.setIsMonthlyExpense(expenseItem.getIsMonthlyExpense());
                    item.setIsDeposit(expenseItem.getIsDeposit());
                    item.setIsOneTimeFee(expenseItem.getIsOneTimeFee());
                    item.setIsFreeGift(expenseItem.getIsFreeGift());
                    if (expenseItem.getIsFreeGift().equals(BooleanEnum.TRUE.getValue())) {
                        StringBuilder sb = new StringBuilder();
                        int month = Period.between(expenseItem.getFreeStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), expenseItem.getFreeEndTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate()).getMonths() + 1;
                        sb.append("赠送" + month + "个月");
                        sb.append(",");
                        sb.append("从" + DateUtils.formatDate(expenseItem.getFreeStartTime(), "yyyy-MM-dd") + "至" + DateUtils.formatDate(expenseItem.getFreeEndTime(), "yyyy-MM-dd"));
                        item.setDescription(sb.toString());
                    }
                }
            } else {
                item.setIsMonthlyExpense(BooleanEnum.FALSE.getValue());
                item.setIsFreeGift(0);
                if (item.getExpenseSource().equals(BusinessConstants.DAILY_EXPENSES)) {
                    DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(item.getSourceExpenseItemId());
                    if (dailyExpenses != null) {
                        item.setStartDate(dailyExpenses.getStartDate());
                        item.setEndDate(dailyExpenses.getEndDate());
                        item.setDescription(StringUtils.isBlank(item.getDescription()) ? dailyExpenses.getRemarks() : item.getDescription());
                        ExpenseItemDO dbExpenseItem = expenseItemMapper.selectById(dailyExpenses.getItemId());
                        if (dbExpenseItem != null && dbExpenseItem.getIsHirePurchase().equals(BooleanEnum.TRUE.getValue())) {
                            item.setIsHirePurchase(BooleanEnum.TRUE.getValue());
                            StringBuilder sb = new StringBuilder();
                            sb.append("分" + dbExpenseItem.getHirePurchaseNumber() + "期  总金额" + item.getPrice().setScale(2, RoundingMode.DOWN) + ",");
                            List<DailyExpensesDO> dailyExpensesList = dailyExpensesMapper.selectList(new LambdaQueryWrapperX<DailyExpensesDO>()
                                    .eq(DailyExpensesDO::getElderId, dailyExpenses.getElderId())
                                    .eq(DailyExpensesDO::getType, 5)
                                    .eq(DailyExpensesDO::getItemId, dbExpenseItem.getId()));

                            StringJoiner sj = new StringJoiner(",");
                            for (int i = 0; i < dailyExpensesList.size(); i++) {
                                String result = DateUtils.formatDate(dailyExpensesList.get(i).getCreatedTime(), "yyyy-MM") + "月第" + (i + 1) + "期  " + item.getTotalAmount().setScale(2, RoundingMode.DOWN);
                                sj.add(result);
                            }
                            item.setDescription(sb.append(sj).toString());
                        }
                    }
                } else if (item.getExpenseSource().equals(BusinessConstants.LONG_TERM_CARE_INSURANCE)) {
                    ExpenseSubsidyDO expenseSubsidyDO = expenseSubsidyMapper.selectById(item.getSourceExpenseItemId());
                    if (expenseSubsidyDO != null) {
                        YearMonth yearMonth = YearMonth.parse(expenseSubsidyDO.getMonth(), DateTimeFormatter.ofPattern("yyyy-MM"));
                        item.setStartDate(yearMonth.atDay(1));
                        item.setEndDate(yearMonth.atEndOfMonth());
                    }
                    expenseSubsidyList.add(expenseSubsidyDO);
                }

            }
        }
        respVO.setItems(items);

        //查询支付信息
        List<ExpensePayOrderDO> payInfos = expensePayOrderMapper.selectList(new LambdaQueryWrapperX<ExpensePayOrderDO>().eq(ExpensePayOrderDO::getExpenseOrderId, expenseOrder.getId()));
        for (ExpensePayOrderDO payInfo : payInfos) {
            List<ExpensePayOrderItemDO> expensePayOrderItems = expensePayOrderItemMapper.selectList(new LambdaQueryWrapperX<ExpensePayOrderItemDO>()
                    .eq(ExpensePayOrderItemDO::getPayOrderId, payInfo.getId()));

            List<String> collect = expensePayOrderItems.stream().map(ExpensePayOrderItemDO::getPayType).collect(Collectors.toList());
            payInfo.setPayType(String.join(",", collect));
            payInfo.setItems(expensePayOrderItems);
        }
        respVO.setPayInfos(payInfos);
        respVO.setExpenseSubsidyDO(expenseSubsidyList);
        return respVO;
    }

    @Override
    @LogRecord(type = EXPENSE_ORDER_PAY_TYPE, subType = EXPENSE_ORDER_PAY_CREATE_SUB_TYPE, bizNo = "{{#expenseOrder.id}}",
            success = EXPENSE_ORDER_PAY_CREATE_SUCCESS)
    @Transactional
    @TenantIgnore
    public void payOrder(ExpensePayOrderSaveReqVO saveReqVO) {
        List<DepositRecordVO> depositRecordVOList = saveReqVO.getDepositRecordVOList();
        ExpenseOrderDO expenseOrder = expenseOrderMapper.selectById(saveReqVO.getExpenseOrderId());
        expenseOrder.setPayTime(saveReqVO.getPayTime());
        LogRecordContext.putVariable("expenseOrder", expenseOrder);
        //缴费信息
        ExpensePayOrderDO payOrder = new ExpensePayOrderDO();
        BeanUtils.copyProperties(saveReqVO, payOrder);
        payOrder.setRoundPayAmount(saveReqVO.getPayAmount().setScale(0, RoundingMode.HALF_UP));
        payOrder.setRoundPayTwoDecimalAmount(saveReqVO.getPayAmount().setScale(2, RoundingMode.HALF_UP));
        payOrder.setExpenseOrderId(expenseOrder.getId());
        payOrder.setCreatedTime(saveReqVO.getPayTime());
        payOrder.setType(BusinessConstants.EXPENSE_PAY_ORDER_MONTH);
        payOrder.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
        payOrder.setRemarks(saveReqVO.getRemarks());
        payOrder.setTenantId(expenseOrder.getTenantId());
        expensePayOrderMapper.insert(payOrder);

        List<ExpensePayOrderItemDO> items = saveReqVO.getItems();
        List<String> payTypeList = items.stream().map(ExpensePayOrderItemDO::getPayType).collect(Collectors.toList());
        for (ExpensePayOrderItemDO item : items) {
            //判断是否使用了长护险或者余额，进行扣减
            if (item.getPayType().equals(BusinessConstants.LONG_TERM_CARE_INSURANCE)) {
                // 查询对应的月份账长护险
                List<ExpenseSubsidyDO> expenseSubsidyList = expenseSubsidyMapper.selectList(new LambdaQueryWrapperX<ExpenseSubsidyDO>()
                        .eq(ExpenseSubsidyDO::getDeductionBillMonth, expenseOrder.getBillingMonth())
                        .eq(ExpenseSubsidyDO::getElderId, expenseOrder.getElderId())
                        .eq(ExpenseSubsidyDO::getStatus, BooleanEnum.FALSE.getValue()));
                if (CollectionUtil.isNotEmpty(expenseSubsidyList)) {
                    for (ExpenseSubsidyDO expenseSubsidyDO : expenseSubsidyList) {
                        expenseSubsidyDO.setStatus(BooleanEnum.TRUE.getValue());
                        expenseSubsidyMapper.updateById(expenseSubsidyDO);
                    }
                }
                //扣减总额
                ExpenseSubsidyAccountDO expenseSubsidyAccountDO = expenseSubsidyAccountMapper.selectOne(new LambdaQueryWrapperX<ExpenseSubsidyAccountDO>().eq(ExpenseSubsidyAccountDO::getElderId, expenseOrder.getElderId()));
                if (expenseSubsidyAccountDO != null) {
                    expenseSubsidyAccountDO.setAmount(expenseSubsidyAccountDO.getAmount().subtract(item.getPayAmount()));
                    expenseSubsidyAccountMapper.updateById(expenseSubsidyAccountDO);
                }
            }

            //判断是否使用了账户会员卡进行支付
            if (item.getPayType().equals(BusinessConstants.MEMBER_CARD)) {
                //会员卡余额进行扣减
                memberUserApi.handleMemberCardPay(item.getMemberCardNumber(), item.getPayAmount());
            }


            //余额支付
            if (item.getPayType().equals(BusinessConstants.BALANCE)) {
                BalanceDO balance = balanceMapper.selectOne(new LambdaQueryWrapperX<BalanceDO>().eq(BalanceDO::getElderId, expenseOrder.getElderId()));
                if (balance != null) {
                    balance.setAmount(balance.getAmount().subtract(item.getPayAmount()));
                    balanceMapper.updateById(balance);

                    //创建变动记录
                    BalanceRecordDO balanceRecord = new BalanceRecordDO();
                    balanceRecord.setRemarks("账单抵扣,抵扣月份：" + expenseOrder.getBillingMonth() + ",抵扣金额：" + item.getPayAmount());
                    balanceRecord.setPayType(item.getPayType());

                    balanceRecord.setChangeAmount(item.getPayAmount());
                    balanceRecord.setType(3); //消费
                    balanceRecord.setElderId(balance.getElderId());
                    balanceRecord.setCreatedTime(new Date());
                    balanceRecord.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());

                    // 记录账单号
                    balanceRecord.setOrderNumber(expenseOrder.getBillOrderNumber());
                    balanceRecordMapper.insert(balanceRecord);
                }
            }

            item.setPayOrderId(payOrder.getId());
            item.setCreatedTime(saveReqVO.getPayTime());
            item.setRoundPayAmount(item.getPayAmount().setScale(0, RoundingMode.HALF_UP));
            item.setRoundPayTwoDecimalAmount(item.getPayAmount().setScale(2, RoundingMode.HALF_UP));
            item.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
            item.setTenantId(expenseOrder.getTenantId());
            expensePayOrderItemMapper.insert(item);
        }
        boolean isPartialPay = false;
        if (items.size() == 1 && items.size() == saveReqVO.getExpenseOrderItems().size()) {
            ExpenseOrderItemDO expenseOrderItemDO = saveReqVO.getExpenseOrderItems().get(0);
            BigDecimal needPayAmount = expenseOrderItemDO.getTotalAmount().subtract(expenseOrderItemDO.getPayAmount() == null ? BigDecimal.ZERO : expenseOrderItemDO.getPayAmount());
            isPartialPay = !items.get(0).getPayAmount().equals(needPayAmount);
        }
        //修改勾选的收费项状态
        for (ExpenseOrderItemDO expenseOrderItem : saveReqVO.getExpenseOrderItems()) {
            expenseOrderItem.setPayTime(saveReqVO.getPayTime());
            expenseOrderItem.setPayStatus(isPartialPay ? 2 :BooleanEnum.TRUE.getValue());
            expenseOrderItem.setPayOrderId(payOrder.getId());
            // 某个项目部分缴费，只能是一个
            if(isPartialPay){
                expenseOrderItem.setPayAmount(items.get(0).getPayAmount().add(expenseOrderItem.getPayAmount() == null ? BigDecimal.ZERO : expenseOrderItem.getPayAmount()));
            }else {
                expenseOrderItem.setPayAmount(expenseOrderItem.getTotalAmount());
            }
            expenseOrderItemMapper.updateById(expenseOrderItem);


            //修改日常费用缴费状态
            DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(expenseOrderItem.getSourceExpenseItemId());
            if (dailyExpenses != null) {
                dailyExpenses.setStatus(BooleanEnum.TRUE.getValue());
                dailyExpenses.setExpenseOrderNumber(expenseOrder.getBillOrderNumber());
                dailyExpensesMapper.updateById(dailyExpenses);

                //如果是押金类型的直接录入押金表
                //如果是押金类型的直接录入押金表
                if ((dailyExpenses.getItemName() != null &&
                        (dailyExpenses.getItemName().contains("押金") ||
                                dailyExpenses.getItemName().contains("风险金") ||
                                dailyExpenses.getItemName().contains("预备金") ||
                                dailyExpenses.getItemName().contains("风险预备金"))) ||
                        (dailyExpenses.getItemCategory() != null &&
                                (dailyExpenses.getItemCategory().contains("押金") ||
                                        dailyExpenses.getItemCategory().contains("风险金") ||
                                        dailyExpenses.getItemCategory().contains("预备金") ||
                                        dailyExpenses.getItemCategory().contains("风险预备金")))) {
                    DepositDO dbDeposit = depositMapper.selectOne(new LambdaQueryWrapperX<DepositDO>()
                            .eq(DepositDO::getElderId, expenseOrder.getElderId())
                            .eq(DepositDO::getTenantId, expenseOrder.getTenantId()));
                    if (dbDeposit != null) {
                        DepositRecordDO depositRecordDO = new DepositRecordDO();
                        for (DepositRecordVO depositRecordVO : depositRecordVOList) {
                            if (expenseOrderItem.getId().equals(depositRecordVO.getExpenseItemId())) {
                                depositRecordDO.setReceiptNumber(depositRecordVO.getReceiptNumber());
                            }
                        }
                        depositRecordDO.setType(1);
                        depositRecordDO.setItemName(dailyExpenses.getItemName());
                        depositRecordDO.setPayType(String.join(",", payTypeList));
                        depositRecordDO.setItemId(dailyExpenses.getId());
                        depositRecordDO.setCreatedTime(saveReqVO.getPayTime());
                        depositRecordDO.setRemarks("日常费用押金缴纳");
                        depositRecordDO.setElderId(expenseOrder.getElderId());
                        depositRecordDO.setAmount(dailyExpenses.getAmount());
                        depositRecordDO.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                        depositRecordDO.setTenantId(dbDeposit.getTenantId());
                        depositRecordMapper.insert(depositRecordDO);

                        dbDeposit.setAmount(dbDeposit.getAmount().add(dailyExpenses.getAmount()));
                        depositMapper.updateById(dbDeposit);
                    }
                }

            }

            if (expenseOrderItem.getExpenseSource().equals(BusinessConstants.LONG_TERM_CARE_INSURANCE)) {
                //修改长期护理保险缴费状态
                ExpenseSubsidyDO expenseSubsidyDO = expenseSubsidyMapper.selectById(expenseOrderItem.getSourceExpenseItemId());
                if (expenseSubsidyDO != null) {
                    expenseSubsidyDO.setStatus(BooleanEnum.TRUE.getValue());
                    expenseSubsidyMapper.updateById(expenseSubsidyDO);
                }
            }
            if (expenseOrderItem.getExpenseSource().equals(BusinessConstants.CONSUMER_VOUCHER)) {
                //修改消费券缴费状态
                ElderlyConsumerVouchersDO consumerVouchersDO = elderlyConsumerVouchersMapper.selectById(expenseOrderItem.getSourceExpenseItemId());
                if (consumerVouchersDO != null) {
                    consumerVouchersDO.setReturnStatus(BooleanEnum.TRUE.getValue());
                    elderlyConsumerVouchersMapper.updateById(consumerVouchersDO);
                }
            }

            //生成押金缴费记录
            ExpenseItemDO expenseItemDO = expenseItemMapper.selectById(expenseOrderItem.getSourceExpenseItemId());
            if (expenseItemDO != null && Objects.equals(expenseItemDO.getIsDeposit(), BooleanEnum.TRUE.getValue())) {
                DepositDO dbDeposit = depositMapper.selectOne(new LambdaQueryWrapperX<DepositDO>()
                        .eq(DepositDO::getElderId, expenseOrder.getElderId()));
                if (dbDeposit != null) {
                    DepositRecordDO depositRecordDO = new DepositRecordDO();
                    for (DepositRecordVO depositRecordVO : depositRecordVOList) {
                        if (expenseOrderItem.getId().equals(depositRecordVO.getExpenseItemId())) {
                            depositRecordDO.setReceiptNumber(depositRecordVO.getReceiptNumber());
                        }
                    }
                    depositRecordDO.setType(1);
                    depositRecordDO.setItemName(expenseItemDO.getItemName());
                    depositRecordDO.setPayType(String.join(",", payTypeList));
                    depositRecordDO.setItemId(expenseItemDO.getItemId());
                    depositRecordDO.setCreatedTime(saveReqVO.getPayTime());
                    depositRecordDO.setRemarks("入院押金缴纳");
                    depositRecordDO.setElderId(expenseOrder.getElderId());
                    depositRecordDO.setAmount(expenseOrderItem.getTotalAmount());
                    depositRecordDO.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                    depositRecordDO.setTenantId(dbDeposit.getTenantId());
                    depositRecordMapper.insert(depositRecordDO);

                    dbDeposit.setAmount(dbDeposit.getAmount().add(expenseOrderItem.getTotalAmount()));
                    depositMapper.updateById(dbDeposit);
                }
            }
        }
        List<ExpenseOrderItemDO> expenseOrderItems = expenseOrderItemMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderItemDO>()
                .eq(ExpenseOrderItemDO::getExpenseOrderId, expenseOrder.getId())
                .eq(ExpenseOrderItemDO::getIsShow, BooleanEnum.TRUE.getValue()));
        List<ExpenseOrderItemDO> paySuccessCollect = expenseOrderItems.stream().filter(e -> Objects.equals(e.getPayStatus(), BooleanEnum.TRUE.getValue())).collect(Collectors.toList());
        if (expenseOrder.getPayableAmount() == null) {
            expenseOrder.setPayableAmount(BigDecimal.ZERO);
        }
        expenseOrder.setPayableAmount(expenseOrder.getPayableAmount().add(saveReqVO.getPayAmount()));
        expenseOrder.setPayStatus(2); //部分缴费状态
        //修改订单结算状态
        if (expenseOrderItems.size() == paySuccessCollect.size()) {

            notifyMessageSendApi.updateMessageByCodeAndPkMessage(BpmMessageEnum.EXPENSE_ORDER_HANDLE.getTemplateCode(), expenseOrder.getElderId());


//            expenseOrder.setPayTime(new Date());
            expenseOrder.setPayStatus(BooleanEnum.TRUE.getValue());
            expenseOrder.setPayeeName(SecurityFrameworkUtils.getLoginUserNickname());

            //重新查询所有支付方式
            List<ExpensePayOrderDO> expensePayOrderList = expensePayOrderMapper.selectList(new LambdaQueryWrapperX<ExpensePayOrderDO>()
                    .eq(ExpensePayOrderDO::getExpenseOrderId, expenseOrder.getId()));


            List<ExpensePayOrderItemDO> expensePayOrderItems = new ArrayList<>();
            for (ExpensePayOrderDO expensePayOrderDO : expensePayOrderList) {
                expensePayOrderItems.addAll(expensePayOrderItemMapper
                        .selectList(new LambdaQueryWrapperX<ExpensePayOrderItemDO>()
                                .eq(ExpensePayOrderItemDO::getPayOrderId, expensePayOrderDO.getId())));
            }
            expenseOrder.setPayType(String.join(",", expensePayOrderItems.stream().map(ExpensePayOrderItemDO::getPayType).collect(Collectors.toList())));
        }
        expenseOrder.setInvoiceNumber(saveReqVO.getInvoiceNumber());
        expenseOrderMapper.updateById(expenseOrder);
    }

    @Override
    @Transactional
    @TenantIgnore
    public List<ExpenseOrderBatchPayExcelVO> importBatchPay(List<ExpenseOrderBatchPayExcelVO> list, Long tenantId) {

        if (null == list || list.size() == 0) return null;

        Date date = new Date();

        list.forEach(item -> {
            if (1 != item.getStatus()) {
                item.setRemark("非正确数据，不进行缴费");
                return;
            }

            ExpenseOrderDO expenseOrder = expenseOrderMapper.selectById(item.getId());
            if (null == expenseOrder) {
                item.setRemark("非正确数据，账单id找不到");
                return;
            }

            ExpenseBillRespVO expenseBill = getExpenseBill(item.getId());
            List<ExpenseOrderItemDO> orderItems = expenseBill.getItems();

            //筛选出未缴费的
            orderItems = orderItems.stream().filter(e -> Objects.equals(e.getPayStatus(), BooleanEnum.FALSE.getValue())).collect(Collectors.toList());

            ExpensePayOrderSaveReqVO saveReqVO = new ExpensePayOrderSaveReqVO();
            saveReqVO.setExpenseOrderId(item.getId());
            saveReqVO.setPayAmount(item.getActualAmount());
            saveReqVO.setPayTime(date);
            List<ExpensePayOrderItemDO> items = new ArrayList<>();
            ExpensePayOrderItemDO itemDO = new ExpensePayOrderItemDO();
            itemDO.setPayAmount(item.getActualAmount());
            itemDO.setPayType("2");
            items.add(itemDO);
            saveReqVO.setItems(items);
            saveReqVO.setExpenseOrderItems(orderItems);
            payOrder(saveReqVO);
            item.setRemark("缴费成功");
        });

        return list;
    }

    @Override
    @Transactional
    @TenantIgnore
    public List<ExpenseOrderBatchPayExcelVO> importBatchAddDeposit(List<ExpenseOrderBatchPayExcelVO> list, Long tenantId) {
        if (null == list || list.size() == 0) return null;

        list.forEach(item -> {
            if (1 != item.getStatus()) {
                item.setRemark("非正确数据，无法修复");
                return;
            }

            ExpenseOrderDO expenseOrder = expenseOrderMapper.selectById(item.getId());
            if (null == expenseOrder) {
                item.setRemark("非正确数据，账单id找不到");
                return;
            }

            ExpenseBillRespVO expenseBill = getExpenseBill(item.getId());
            List<ExpenseOrderItemDO> orderItems = expenseBill.getItems();
            List<ExpensePayOrderItemDO> items = new ArrayList<>();
            ExpensePayOrderItemDO itemDO = new ExpensePayOrderItemDO();
            itemDO.setPayAmount(item.getActualAmount());
            itemDO.setPayType("2");
            items.add(itemDO);
            List<String> payTypeList = items.stream().map(ExpensePayOrderItemDO::getPayType).collect(Collectors.toList());

            //筛选出未缴费的
            //orderItems = orderItems.stream().filter(e -> Objects.equals(e.getPayStatus(), BooleanEnum.FALSE.getValue())).collect(Collectors.toList());

            for (ExpenseOrderItemDO expenseOrderItem : orderItems) {

                //生成押金缴费记录
                ExpenseItemDO expenseItemDO = expenseItemMapper.selectById(expenseOrderItem.getSourceExpenseItemId());
                if (expenseItemDO != null && expenseItemDO.getIsDeposit() == BooleanEnum.TRUE.getValue()) {
                    DepositDO dbDeposit = depositMapper.selectOne(new LambdaQueryWrapperX<DepositDO>()
                            .eq(DepositDO::getElderId, expenseOrder.getElderId()));
                    if (dbDeposit != null) {
                        DepositRecordDO depositRecordDO = new DepositRecordDO();
                        depositRecordDO.setType(1);
                        depositRecordDO.setItemName(expenseItemDO.getItemName());
                        depositRecordDO.setPayType(String.join(",", payTypeList));
                        depositRecordDO.setItemId(expenseItemDO.getItemId());
                        depositRecordDO.setCreatedTime(new Date());
                        depositRecordDO.setRemarks("入院押金缴纳");
                        depositRecordDO.setElderId(expenseOrder.getElderId());
                        depositRecordDO.setAmount(expenseOrderItem.getTotalAmount());
                        depositRecordDO.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                        depositRecordDO.setTenantId(dbDeposit.getTenantId());
                        depositRecordMapper.insert(depositRecordDO);

                        dbDeposit.setAmount(dbDeposit.getAmount().add(expenseOrderItem.getTotalAmount()));
                        depositMapper.updateById(dbDeposit);
                        item.setRemark("修复成功");
                    }
                }

            }

        });

        return list;
    }

    @Override
    public List<ExpenseOrderItemRespVO> exportExpenseOrderItemExcel(Long expenseOrderId) {
        ExpenseOrderDO expenseOrder = expenseOrderMapper.selectById(expenseOrderId);
        ElderlyInfoDO elderlyInfo = elderlyInfoMapper.selectById(expenseOrder.getElderId());

        //查询账单明细
        List<ExpenseOrderItemDO> expenseOrderItems = expenseOrderItemMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderItemDO>().eq(ExpenseOrderItemDO::getExpenseOrderId, expenseOrderId).eq(ExpenseOrderItemDO::getExpenseSource, BusinessConstants.DAILY_EXPENSES));

        List<ExpenseOrderItemRespVO> collect = expenseOrderItems.stream().map(expenseOrderItem -> {

            ExpenseOrderItemRespVO respVO = new ExpenseOrderItemRespVO();
            respVO.setBedName(buildService.getFullBedName(expenseOrder.getElderId()));
            respVO.setElderName(elderlyInfo.getElderName());
            respVO.setOrderNumber(expenseOrder.getBillOrderNumber());
            respVO.setBillingMonth(expenseOrder.getBillingMonth());

            String[] split = expenseOrder.getPayType().split(",");
            List<String> payTypeList = new ArrayList<>();
            List<DictDataDO> billPayType = dictDataMapper.selectList(new LambdaQueryWrapperX<DictDataDO>().eq(DictDataDO::getDictType, "bill_pay_type"));
            billPayType.forEach(e -> {
                for (String value : split) {
                    if (e.getValue().equals(value)) {
                        payTypeList.add(e.getLabel());
                    }
                }
            });
            respVO.setPayTypeStr(String.join(",", payTypeList));
            respVO.setItemName(expenseOrderItem.getItemName());
            respVO.setPrice(expenseOrderItem.getPrice());
            respVO.setCount(expenseOrderItem.getCount() == null ? 1 : expenseOrderItem.getCount());

            if (expenseOrderItem.getExpenseSource().equals(BusinessConstants.DAILY_EXPENSES)) {
                DailyExpensesDO dailyExpensesDO = dailyExpensesMapper.selectById(expenseOrderItem.getSourceExpenseItemId());
                if (dailyExpensesDO != null) {
                    respVO.setRemarks(StringUtils.isBlank(dailyExpensesDO.getRemarks()) ? null : dailyExpensesDO.getRemarks());
                    respVO.setItemCategory(dailyExpensesDO.getItemCategory());
                    respVO.setAccountingTime(dailyExpensesDO.getAccountingTime());
                    respVO.setCreatedTime(dailyExpensesDO.getCreatedTime());
                }
            } else {
                ExpenseItemDO expenseItemDO = expenseItemMapper.selectById(expenseOrderItem.getSourceExpenseItemId());
                if (expenseItemDO != null) {
                    respVO.setRemarks(null);
                    respVO.setItemCategory(expenseItemDO.getItemCategoryName());
                    respVO.setAccountingTime(expenseOrderItem.getCreatedTime());
                    respVO.setCreatedTime(expenseOrderItem.getCreatedTime());
                }
            }
            return respVO;
        }).collect(Collectors.toList());

        return collect;
    }

    @Override
    public List<ExpenseItemRespVO> queryExpenseBillItem(String billingMonth, Long elderId) {
        //查询是否有当前月份的的账单
        ExpenseOrderDO dbExpenseOrder = expenseOrderMapper.selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                .eq(ExpenseOrderDO::getBillingMonth, billingMonth)
                .eq(ExpenseOrderDO::getElderId, elderId)
                .in(ExpenseOrderDO::getType,1,2));
        if (dbExpenseOrder == null) {
            YearMonth yearMonth = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
            LocalDate yearMonthStartDay = yearMonth.atDay(1);
            List<ExpenseItemRespVO> collect = handlerCollectMonthlyExpense(billingMonth, elderId);
            LocalDate actualStart = yearMonthStartDay;
            LocalDate actualEnd = yearMonth.atEndOfMonth();
            if (CollectionUtil.isNotEmpty(collect)) {
                // 固定费用的区间在 handlerCollectMonthlyExpense 里已按离住规则处理（1号~离院前一天），这里复用其区间给抵扣项
                actualStart = LocalDate.parse(collect.get(0).getStartTime());
                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> discountCollect = adjustDiscountByActualRange(elderId, billingMonth, actualStart, actualEnd);
            collect.addAll(discountCollect);
            collect.addAll(subsidyCollect);
            collect.addAll(consumerVoucherCollect);
            collect.addAll(dailyExpensesCollect);
            return collect;

        } else {
            List<ExpenseItemRespVO> resultList = new ArrayList<>();
            YearMonth billingYm = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
            LocalDate monthStart = billingYm.atDay(1);
            LocalDate monthEnd = billingYm.atEndOfMonth();
            resultList.addAll(collectDailyExpenseItemsHasBill(elderId, billingMonth));
            resultList.addAll(collectSubsidyItems(elderId, billingMonth, monthStart, monthEnd, false));
            resultList.addAll(collectConsumerVoucherItems(elderId, billingMonth, monthStart, monthEnd, false));
            resultList.addAll(calculateDiscountDeduction(elderId, billingMonth));
            return resultList;
        }
    }

    private List<ExpenseItemRespVO> collectDailyExpenseItemsNoBill(Long elderId, String billingMonth) {
        YearMonth attributionBillTime = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
        List<DailyExpensesDO> dailyExpensesList = dailyExpensesMapper.selectList(new LambdaQueryWrapperX<DailyExpensesDO>()
                .eq(DailyExpensesDO::getElderId, elderId)
                .le(DailyExpensesDO::getAttributionBillTime, attributionBillTime.toString())
                .eq(DailyExpensesDO::getStatus, BooleanEnum.FALSE.getValue())
                .eq(DailyExpensesDO::getIsGenerateBill, BooleanEnum.FALSE.getValue()));

        return dailyExpensesList.stream().map(item -> {
            ExpenseItemRespVO respVO = new ExpenseItemRespVO();
            respVO.setExpenseType(item.getType());
            respVO.setExpenseSource(BusinessConstants.DAILY_EXPENSES);
            respVO.setSourceExpenseItemId(item.getId());
            respVO.setPrice(item.getPrice());
            respVO.setTotalAmount(item.getAmount());
            respVO.setItemName(item.getItemName());
            respVO.setItemCategory(item.getItemCategory());
            respVO.setCount(item.getCount());
            respVO.setDescription(item.getRemarks());
            LocalDate localDate = item.getCreatedTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            if (item.getType() == 2 || item.getType() == 3) {
                respVO.setStartTime(item.getStartDate() == null ? null : item.getStartDate().toString());
                respVO.setEndTime(item.getEndDate() == null ? null : item.getEndDate().toString());
            } else if (item.getType() == 5) {
                respVO.setStartTime(localDate.toString());
                respVO.setEndTime(localDate.with(TemporalAdjusters.lastDayOfMonth()).toString());
            } else {
                respVO.setStartTime(item.getStartDate() == null ? null : item.getStartDate().toString());
                respVO.setEndTime(item.getEndDate() == null ? null : item.getEndDate().toString());
            }
            return respVO;
        }).collect(Collectors.toList());
    }

    private List<ExpenseItemRespVO> collectDailyExpenseItemsHasBill(Long elderId, String billingMonth) {
        YearMonth attributionBillTime = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
        List<DailyExpensesDO> dailyExpensesList = dailyExpensesMapper.selectList(new LambdaQueryWrapperX<DailyExpensesDO>()
                .eq(DailyExpensesDO::getElderId, elderId)
                .like(DailyExpensesDO::getAttributionBillTime, attributionBillTime)
                .eq(DailyExpensesDO::getStatus, BooleanEnum.FALSE.getValue())
                .eq(DailyExpensesDO::getIsGenerateBill, BooleanEnum.FALSE.getValue()));

        return dailyExpensesList.stream().map(item -> {
            ExpenseItemRespVO respVO = new ExpenseItemRespVO();
            respVO.setExpenseType(item.getType());
            respVO.setExpenseSource(BusinessConstants.DAILY_EXPENSES);
            respVO.setSourceExpenseItemId(item.getId());
            respVO.setPrice(item.getPrice());
            respVO.setTotalAmount(item.getAmount());
            respVO.setItemName(item.getItemName());
            respVO.setItemCategory(item.getItemCategory());
            respVO.setCount(item.getCount() == null ? 1 : item.getCount());
            if (item.getStartDate() != null) {
                respVO.setStartTime(item.getStartDate().toString());
            }
            if (item.getEndDate() != null) {
                respVO.setEndTime(item.getEndDate().toString());
            }
            respVO.setDescription(item.getRemarks());
            return respVO;
        }).collect(Collectors.toList());
    }

    private List<ExpenseItemRespVO> collectSubsidyItems(Long elderId, String billingMonth,
                                                         LocalDate startDate, LocalDate endDate,
                                                         boolean markUsed) {
        List<ExpenseSubsidyDO> unclaimedSubsidies = expenseSubsidyMapper.selectList(new LambdaQueryWrapperX<ExpenseSubsidyDO>()
                .eq(ExpenseSubsidyDO::getElderId, elderId)
                .eq(ExpenseSubsidyDO::getDeductionBillMonth, billingMonth)
                .eq(ExpenseSubsidyDO::getStatus, BooleanEnum.FALSE.getValue()));

        List<ExpenseSubsidyDO> updateList = new ArrayList<>();
        List<ExpenseItemRespVO> result = unclaimedSubsidies.stream().map(subsidy -> {
            ExpenseItemRespVO respVO = new ExpenseItemRespVO();
            respVO.setExpenseType(7);
            respVO.setExpenseSource(BusinessConstants.LONG_TERM_CARE_INSURANCE);
            respVO.setSourceExpenseItemId(subsidy.getId());
            respVO.setPrice(subsidy.getAmount().negate());
            respVO.setTotalAmount(subsidy.getAmount().negate());
            respVO.setItemName("长护险抵扣");
            respVO.setItemCategory("长护险");
            respVO.setCount(1);
            respVO.setDescription(subsidy.getRemarks());
            respVO.setStartTime(startDate.toString());
            respVO.setEndTime(endDate.toString());
            if (markUsed) {
                subsidy.setStatus(BooleanEnum.TRUE.getValue());
                updateList.add(subsidy);
            }
            return respVO;
        }).collect(Collectors.toList());

        if (markUsed && CollectionUtil.isNotEmpty(updateList)) {
            expenseSubsidyMapper.updateBatch(updateList);
        }
        return result;
    }

    private List<ExpenseItemRespVO> collectConsumerVoucherItems(Long elderId, String billingMonth,
                                                                 LocalDate startDate, LocalDate endDate,
                                                                 boolean markUsed) {
        List<ElderlyConsumerVouchersDO> voucherList = elderlyConsumerVouchersMapper.selectList(new LambdaQueryWrapperX<ElderlyConsumerVouchersDO>()
                .eq(ElderlyConsumerVouchersDO::getElderId, elderId)
                .eq(ElderlyConsumerVouchersDO::getBillingMonth, billingMonth)
                .eq(ElderlyConsumerVouchersDO::getStatus, BooleanEnum.FALSE.getValue()));

        List<ElderlyConsumerVouchersDO> updateList = new ArrayList<>();
        List<ExpenseItemRespVO> result = voucherList.stream().map(voucher -> {
            ExpenseItemRespVO respVO = new ExpenseItemRespVO();
            respVO.setExpenseType(7);
            respVO.setExpenseSource(BusinessConstants.CONSUMER_VOUCHER);
            respVO.setSourceExpenseItemId(voucher.getId());
            respVO.setPrice(voucher.getAmount().negate());
            respVO.setTotalAmount(voucher.getAmount().negate());
            respVO.setItemName("消费券抵扣");
            respVO.setItemCategory("消费券");
            respVO.setCount(1);
            respVO.setDescription(voucher.getRemarks());
            respVO.setStartTime(startDate.toString());
            respVO.setEndTime(endDate.toString());
            if (markUsed) {
                voucher.setStatus(BooleanEnum.TRUE.getValue());
                updateList.add(voucher);
            }
            return respVO;
        }).collect(Collectors.toList());

        if (markUsed && CollectionUtil.isNotEmpty(updateList)) {
            elderlyConsumerVouchersMapper.updateBatch(updateList);
        }
        return result;
    }

    /**
     * 计算优惠抵扣项
     *
     * @param elderId      长者ID
     * @param billingMonth 账单月份
     * @return 优惠抵扣项列表
     */
    private List<ExpenseItemRespVO> calculateDiscountDeduction(Long elderId, String billingMonth) {
        List<ExpenseItemRespVO> discountCollect = new ArrayList<>();

        // 获取长者优惠规则
        List<DiscountRuleDO> discountRules = discountRuleService.getDiscountRuleListByElderId(elderId);

        YearMonth yearMonth = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
        LocalDate monthStart = yearMonth.atDay(1);
        LocalDate monthEnd = yearMonth.atEndOfMonth();

        // 计算每个优惠规则在该月的抵扣金额
        for (DiscountRuleDO rule : discountRules) {
            // 检查优惠规则是否有效（状态为正常）
            if (rule.getStatus() == null || rule.getStatus() != 1) {
                continue;
            }

            // 检查优惠规则时间是否设置
            if (rule.getStartTime() == null || rule.getEndTime() == null) {
                continue;
            }
            DiscountRecordDO discountRecordDO = discountRecordMapper.selectOne(new LambdaQueryWrapperX<DiscountRecordDO>()
                    .eq(DiscountRecordDO::getRuleId, rule.getId())
                    .eq(DiscountRecordDO::getElderId, elderId)
                    .eq(DiscountRecordDO::getBillingMonth, billingMonth));
            if (discountRecordDO != null) {
                continue;
            }
            LocalDate ruleStart = rule.getStartTime().toLocalDate();
            LocalDate ruleEnd = rule.getEndTime().toLocalDate();

            // 检查优惠规则是否与账单月份有重叠
            // 修改判断逻辑：只要优惠规则的结束时间在账单月份开始之后，且优惠规则开始时间在账单月份结束之前，就认为有重叠
            if (ruleEnd.isBefore(monthStart) || ruleStart.isAfter(monthEnd)) {
                continue;
            }

            // 计算重叠天数
            LocalDate overlapStart = monthStart.isAfter(ruleStart) ? monthStart : ruleStart;
            LocalDate overlapEnd = monthEnd.isBefore(ruleEnd) ? monthEnd : ruleEnd;
            long overlapDays = ChronoUnit.DAYS.between(overlapStart, overlapEnd) + 1;

            // 计算抵扣金额
            BigDecimal deductionAmount = BigDecimal.ZERO;
            if (rule.getDiscountAmount() != null) {
                // 如果是完整月份且完全在优惠期间内，直接使用优惠金额
                if (monthStart.equals(overlapStart) && monthEnd.equals(overlapEnd)) {
                    deductionAmount = rule.getDiscountAmount().negate(); // 抵扣金额为负数
                } else {
                    // 跨月情况，按天数比例计算优惠金额
                    int daysInMonth = monthEnd.lengthOfMonth();
                    BigDecimal dailyDiscount = rule.getDiscountAmount()
                            .divide(BigDecimal.valueOf(daysInMonth), 8, RoundingMode.HALF_UP);
                    deductionAmount = dailyDiscount.multiply(BigDecimal.valueOf(overlapDays))
                            .setScale(2, RoundingMode.HALF_UP)
                            .negate(); // 抵扣金额为负数
                }
            }

            // 创建抵扣项
            if (deductionAmount.compareTo(BigDecimal.ZERO) != 0) {
                ExpenseItemRespVO respVO = new ExpenseItemRespVO();
                respVO.setExpenseType(8); // 8表示优惠抵扣项
                respVO.setExpenseSource(BusinessConstants.DISCOUNT_DEDUCTION); // 优惠抵扣标识
                respVO.setSourceExpenseItemId(rule.getId());
                respVO.setPrice(deductionAmount);
                respVO.setTotalAmount(deductionAmount);
                respVO.setItemName(rule.getRuleName());
                respVO.setItemCategory("优惠抵扣");
                respVO.setCount(1);
                respVO.setStartTime(overlapStart.toString());
                respVO.setEndTime(overlapEnd.toString());
                respVO.setDescription(rule.getRemarks());
                discountCollect.add(respVO);
            }
        }

        return discountCollect;
    }


    @Override
    public void generateExpenseBill(ExpenseOrderSaveVO saveVO) {
        // 获取长者信息，用于判断入住类型
        ElderlyInfoDO elderlyInfo = elderlyInfoMapper.selectById(saveVO.getElderId());
        if (elderlyInfo == null) {
            throw exception(ELDERLY_INFO_NOT_EXISTS);
        }

        // 根据长者的入住类型确定账单类型
        // inStatusType: 1=入住(长住), 2=试住, 3=短住
        // type: 1=入院账单, 2=月度账单, 3=试住账单
        int billType = (elderlyInfo.getInStatusType() != null && elderlyInfo.getInStatusType() == 2) ? 3 : 2;
        //查询是否有当前月份的相同类型的账单
        ExpenseOrderDO dbExpenseOrder;
        if((elderlyInfo.getInStatusType() != null && elderlyInfo.getInStatusType() == 2)){
            dbExpenseOrder = expenseOrderMapper
                    .selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                            .eq(ExpenseOrderDO::getElderId, saveVO.getElderId())
                            .eq(ExpenseOrderDO::getBillingMonth, saveVO.getBillingMonth())
                            .eq(ExpenseOrderDO::getType, billType)); // 增加账单类型条件，确保试住账单和长住账单分开
        }else {
            dbExpenseOrder = expenseOrderMapper
                    .selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                            .eq(ExpenseOrderDO::getElderId, saveVO.getElderId())
                            .eq(ExpenseOrderDO::getBillingMonth, saveVO.getBillingMonth())
                            .in(ExpenseOrderDO::getType, 1,2)); // 增加账单类型条件，确保试住账单和长住账单分开
        }


        if (dbExpenseOrder != null) {
            // 单个处理时校验账单是否已确认
            isExpenseOrderConfirmed(dbExpenseOrder);
            // 单个处理时校验账单是否已锁定
            isExpenseOrderLock(dbExpenseOrder);
        }

        if (dbExpenseOrder == null) {
            //当前月份不存在账单，生成新的账单
            ExpenseOrderDO expenseOrder = new ExpenseOrderDO();
            expenseOrder.setBillingMonth(saveVO.getBillingMonth());
            expenseOrder.setCreatedTime(new Date());
            expenseOrder.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
            expenseOrder.setElderId(saveVO.getElderId());
            expenseOrder.setBillOrderNumber("E" + RandomUtil.randomNumbers(16));
            expenseOrder.setType(billType); // 根据长者入住类型设置账单类型
            expenseOrder.setOrgType(elderlyInfo.getOrgType());
            expenseOrder.setTenantId(saveVO.getTenantId());
            expenseOrderMapper.insert(expenseOrder);
            //生成账单费用项
            BigDecimal totalAmount = BigDecimal.ZERO;
            List<ExpenseItemRespVO> items = saveVO.getItems();
            for (ExpenseItemRespVO item : items) {
                ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
                orderItem.setExpenseOrderId(expenseOrder.getId());
                orderItem.setSourceExpenseItemId(item.getSourceExpenseItemId());
                orderItem.setExpenseSource(item.getExpenseSource());
                orderItem.setCount(item.getCount());
                orderItem.setItemName(item.getItemName());
                orderItem.setDescription(StringUtils.isBlank(item.getDescription()) ?
                        item.getStartTime() + "至" + item.getEndTime() + "的" + item.getItemName() :
                        item.getDescription());
                orderItem.setPrice(item.getPrice());  //单价
                orderItem.setActualPrice(item.getPrice()); //实际单价
                orderItem.setTotalAmount(item.getTotalAmount());  //总金额
                orderItem.setRoundAmount(orderItem.getTotalAmount().setScale(0, RoundingMode.HALF_UP));
                orderItem.setRoundTwoDecimalAmount(orderItem.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
                orderItem.setCreatedTime(new Date());
                orderItem.setType(item.getType());
                orderItem.setItemCategoryName(item.getItemCategory());
                orderItem.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                orderItem.setTenantId(saveVO.getTenantId());
                orderItem.setStartDate(item.getStartTime() == null ? null : LocalDate.parse(item.getStartTime()));
                orderItem.setEndDate(item.getEndTime() == null ? null : LocalDate.parse(item.getEndTime()));
                orderItem.setContractId(item.getContractId());
                expenseOrderItemMapper.insert(orderItem);
                if (item.getExpenseSource().equals(BusinessConstants.DISCOUNT_DEDUCTION)) {
                    addDiscountRecord(saveVO.getElderId(), saveVO.getBillingMonth(), item);
                }
                //判断是否生成过账单(用于标记那些生成账单后再生成的费用项)
                DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(item.getSourceExpenseItemId());
                if (dailyExpenses != null) {
                    dailyExpenses.setIsGenerateBill(BooleanEnum.TRUE.getValue());
                    dailyExpenses.setExpenseOrderNumber(expenseOrder.getBillOrderNumber());
                    dailyExpensesMapper.updateById(dailyExpenses);
                }

                ExpenseSubsidyDO expenseSubsidyDO = expenseSubsidyMapper.selectById(item.getSourceExpenseItemId());
                if (expenseSubsidyDO != null) {
                    expenseSubsidyDO.setStatus(BooleanEnum.TRUE.getValue());
                    expenseSubsidyMapper.updateById(expenseSubsidyDO);
                }

                totalAmount = totalAmount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));
            }
            expenseOrder.setActualAmount(totalAmount);
            expenseOrderMapper.updateById(expenseOrder);
        } else {
            BigDecimal amount = BigDecimal.ZERO;
            List<ExpenseItemRespVO> items = saveVO.getItems();
            for (ExpenseItemRespVO item : items) {
                ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
                orderItem.setExpenseOrderId(dbExpenseOrder.getId());
                orderItem.setSourceExpenseItemId(item.getSourceExpenseItemId());
                orderItem.setExpenseSource(item.getExpenseSource());
                orderItem.setCount(item.getCount());
                orderItem.setItemName(item.getItemName());
                orderItem.setItemCategoryName(item.getItemCategory());
                orderItem.setPrice(item.getPrice());  //单价
                orderItem.setActualPrice(item.getPrice()); //实际单价
                orderItem.setTotalAmount(item.getTotalAmount());  //总金额
                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(saveVO.getTenantId());
                orderItem.setStartDate(item.getStartTime() == null ? null : LocalDate.parse(item.getStartTime()));
                orderItem.setEndDate(item.getEndTime() == null ? null : LocalDate.parse(item.getEndTime()));
                orderItem.setDescription(item.getDescription());
                orderItem.setContractId(item.getContractId());
                expenseOrderItemMapper.insert(orderItem);

                amount = amount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));
                if (item.getExpenseSource().equals(BusinessConstants.DISCOUNT_DEDUCTION)) {
                    addDiscountRecord(saveVO.getElderId(), saveVO.getBillingMonth(), item);
                }
                //判断是否生成过账单(用于标记那些生成账单后再生成的费用项)
                DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(item.getSourceExpenseItemId());
                if (dailyExpenses != null) {
                    dailyExpenses.setIsGenerateBill(BooleanEnum.TRUE.getValue());
                    dailyExpensesMapper.updateById(dailyExpenses);
                }


                ExpenseSubsidyDO expenseSubsidyDO = expenseSubsidyMapper.selectById(item.getSourceExpenseItemId());
                if (expenseSubsidyDO != null) {
                    expenseSubsidyDO.setStatus(BooleanEnum.TRUE.getValue());
                    expenseSubsidyMapper.updateById(expenseSubsidyDO);
                }
            }
            dbExpenseOrder.setPayStatus(BooleanEnum.FALSE.getValue());
//            dbExpenseOrder.setPayableAmount(amount); //更新总金额
            dbExpenseOrder.setActualAmount(dbExpenseOrder.getActualAmount().add(amount));
            dbExpenseOrder.setPayType(null);
            expenseOrderMapper.updateById(dbExpenseOrder);
        }

    }

    private BigDecimal updateExpenseSubsidy(List<ExpenseSubsidyDO> expenseSubsidList, ExpenseOrderDO expenseOrder, BigDecimal totalAmount) {
        if (CollectionUtil.isNotEmpty(expenseSubsidList)) {
            for (ExpenseSubsidyDO expenseSubsidyDO : expenseSubsidList) {
                ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
                orderItem.setExpenseOrderId(expenseOrder.getId());
                orderItem.setSourceExpenseItemId(null);
                orderItem.setExpenseSource(BusinessConstants.LONG_TERM_CARE_INSURANCE);
                orderItem.setCount(1);
                orderItem.setItemName("长护险抵扣");
                orderItem.setPayStatus(1);

                orderItem.setPrice(expenseSubsidyDO.getAmount());  //单价
                orderItem.setActualPrice(expenseSubsidyDO.getAmount()); //实际单价
                orderItem.setTotalAmount(expenseSubsidyDO.getAmount().negate());  //总金额
                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(expenseOrder.getTenantId());
                expenseOrderItemMapper.insert(orderItem);

                totalAmount = totalAmount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));

                //修改长护险抵扣状态
                expenseSubsidyDO.setStatus(1);
                expenseSubsidyMapper.updateById(expenseSubsidyDO);
            }
        }
        return totalAmount;
    }

    @Override
    public void exportExpenseOrderExcel(HttpServletResponse response, ExpenseBillPageReqVO pageReqVO) throws IOException {
        List<ExpenseOrderDO> list = expenseOrderMapper.getExpenseBillPage(null, pageReqVO);

        List<List<String>> head = new ArrayList<>();
        List<List<Object>> data = new ArrayList<>();

        head.add(Arrays.asList("长者姓名"));
        head.add(Arrays.asList("床位号"));
        head.add(Arrays.asList("账单月"));
        head.add(Arrays.asList("推送状态"));
        head.add(Arrays.asList("支付状态"));
        head.add(Arrays.asList("缴费时间"));
        head.add(Arrays.asList("收款人"));
        head.add(Arrays.asList("账单编号"));
        head.add(Arrays.asList("发票号"));


        List<DictDataDO> dictDataDOList = dictDataMapper.selectList(new LambdaQueryWrapperX<DictDataDO>()
                .eq(DictDataDO::getDictType, "fee_identification_type")
                .orderByAsc(DictDataDO::getSort));

        for (DictDataDO dictDataDO : dictDataDOList) {
            head.add(Arrays.asList(dictDataDO.getLabel()));
        }
        head.add(Arrays.asList("长护险"));
        head.add(Arrays.asList("消费券"));
        head.add(Arrays.asList("账单金额"));
        head.add(Arrays.asList("医保金额"));
        head.add(Arrays.asList("抹零金额"));
        List<DictDataDO> billPayTypeList = dictDataMapper.selectList(new LambdaQueryWrapperX<DictDataDO>()
                .eq(DictDataDO::getDictType, "bill_pay_type")
                .orderByAsc(DictDataDO::getSort));
        Map<String, BigDecimal> payTypeMap = new HashMap<>();
        for (DictDataDO dictDataDO : billPayTypeList) {
            head.add(Arrays.asList(dictDataDO.getLabel()));
            payTypeMap.put(dictDataDO.getValue(), BigDecimal.ZERO);
        }
        head.add(Arrays.asList("备注"));

        list.forEach(order -> {
            String remark = "";
            List<Object> row = new ArrayList<>();
            row.add(order.getElderName());
            row.add(buildService.getFullBedName(order.getElderId()));
            row.add(order.getBillingMonth());

            if (order.getPushStatus() == 0) {
                row.add("未推送");
            } else {
                row.add("已推送");
            }
            if (order.getPayStatus() == 1) {
                row.add("已缴费");
            } else {
                row.add("待缴费");
            }
            row.add(order.getPayTime() == null ? null : order.getPayTime());
            row.add(StringUtils.isBlank(order.getPayeeName()) ? null : order.getPayeeName());
            row.add(order.getBillOrderNumber() == null ? "" : order.getBillOrderNumber());
            row.add(order.getInvoiceNumber() == null ? "" : order.getInvoiceNumber());
            List<ExpenseOrderItemDO> orderItems = expenseOrderItemMapper.selectListById(order.getId(),
                    BusinessConstants.EXPENSE_ITEM, BusinessConstants.DAILY_EXPENSES);
            Map<Integer, BigDecimal> identificationTypeMap = new HashMap<>();
            BigDecimal orderActualAmount = BigDecimal.ZERO;
            for (int i = 0; i < orderItems.size(); i++) {
                ExpenseOrderItemDO expenseOrderItemDO = orderItems.get(i);
                orderActualAmount = orderActualAmount.add(expenseOrderItemDO.getTotalAmount());
                setExpenseOrderType(expenseOrderItemDO);

                if (identificationTypeMap.containsKey(expenseOrderItemDO.getType())) {
                    BigDecimal add = expenseOrderItemDO.getTotalAmount()
                            .add(identificationTypeMap.get(expenseOrderItemDO.getType()))
                            .setScale(2, RoundingMode.DOWN);
                    identificationTypeMap.put(expenseOrderItemDO.getType(), add);

                } else {
                    identificationTypeMap.put(expenseOrderItemDO.getType(), expenseOrderItemDO.getTotalAmount());
                }
            }

            for (DictDataDO dictDataDO : dictDataDOList) {
                if (identificationTypeMap.containsKey(Integer.valueOf(dictDataDO.getValue()))) {
                    row.add(identificationTypeMap.get(Integer.valueOf(dictDataDO.getValue())));
                } else {
                    row.add(0);
                }
            }

            List<ExpenseSubsidyDO> expenseSubsidyDOS = expenseSubsidyMapper.selectList(new LambdaQueryWrapperX<ExpenseSubsidyDO>()
                    .eq(ExpenseSubsidyDO::getDeductionBillMonth, order.getBillingMonth())
                    .eq(ExpenseSubsidyDO::getElderId, order.getElderId())
                    .eq(ExpenseSubsidyDO::getStatus, 1));
            BigDecimal subsidyAmount = BigDecimal.ZERO;
            if(CollectionUtil.isNotEmpty(expenseSubsidyDOS)){
                subsidyAmount = expenseSubsidyDOS.stream().map(e -> e.getAmount().negate()).reduce(BigDecimal.ZERO, BigDecimal::add);
            }
            List<ElderlyConsumerVouchersDO> elderlyConsumerVouchersDOS = elderlyConsumerVouchersMapper.selectList(new LambdaQueryWrapperX<ElderlyConsumerVouchersDO>().eq(ElderlyConsumerVouchersDO::getOrderId, order.getId()));
            BigDecimal consumerVouchers = BigDecimal.ZERO;
            if(CollectionUtil.isNotEmpty(elderlyConsumerVouchersDOS)){
                consumerVouchers = elderlyConsumerVouchersDOS.stream().map(e -> e.getAmount().negate()).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));

            List<ExpensePayOrderDO> expensePayOrderList = expensePayOrderMapper.selectList(new LambdaQueryWrapperX<ExpensePayOrderDO>()
                    .eq(ExpensePayOrderDO::getExpenseOrderId, order.getId()));

            List<String> payTypeList = new ArrayList<>();
            BigDecimal discountAmount = BigDecimal.ZERO;
            BigDecimal payAmount = BigDecimal.ZERO;
            if (CollectionUtil.isNotEmpty(expensePayOrderList)) {
                for (ExpensePayOrderDO expensePayOrderDO : expensePayOrderList) {
                    if (expensePayOrderDO.getDiscountAmount() != null) {
                        discountAmount = discountAmount.add(expensePayOrderDO.getDiscountAmount().setScale(2, RoundingMode.DOWN));
                    }
                    if (StringUtil.isNotEmptyORNull(expensePayOrderDO.getRemarks())) {
                        remark = remark + expensePayOrderDO.getRemarks() + ";";
                    }
                    List<ExpensePayOrderItemDO> expensePayOrderItemDOS = expensePayOrderItemMapper.selectList(new LambdaQueryWrapperX<ExpensePayOrderItemDO>()
                            .eq(ExpensePayOrderItemDO::getPayOrderId, expensePayOrderDO.getId()));
                    for (ExpensePayOrderItemDO expensePayOrderItemDO : expensePayOrderItemDOS) {
                        String payType = expensePayOrderItemDO.getPayType();
                        if (payTypeMap.containsKey(payType)) {
                            if (expensePayOrderItemDO.getPayAmount() != null) {
                                BigDecimal decimal = payTypeMap.get(payType);
                                BigDecimal add = decimal.add(expensePayOrderItemDO.getPayAmount());
                                payTypeMap.put(payType, add);
                                if (BigDecimal.ZERO.equals(decimal)) {
                                    payTypeList.add(payType);
                                }
                            }

                        }
                    }

                }

            }
            row.add(discountAmount);
            if (payTypeList.size() > 0) {
                for (DictDataDO dictDataDO : billPayTypeList) {
                    if (payTypeMap.containsKey(dictDataDO.getValue())) {
                        row.add(payTypeMap.get(dictDataDO.getValue()));
                    } else {
                        row.add(BigDecimal.ZERO);
                    }
                }

            } else {
                row.add(payAmount);
            }
            row.add(remark);
            data.add(row);


        });


        ExcelUtils.setResponseProperties(response, "动态数据导出.xlsx");

        // 4. 获取输出流并写入Excel数据
        // 使用 try-with-resources 确保流关闭
        try (// 获取响应输出流
             java.io.OutputStream outputStream = response.getOutputStream();
        ) {
            // 使用 EasyExcel 将数据写入输出流
            EasyExcel.write(outputStream)
                    .sheet("动态数据页")
                    .head(head)
                    .doWrite(data);

            // 5. 刷新输出流，确保所有数据被写出
            outputStream.flush();
        } // try-with-resources 会自动关闭 outputStream

    }

    @Override
    public PageResult<ExpenseOrderDO> getExpenseOrderPageByElderId(ExpenseBillPageReqVO pageReqVO) {
        PageResult<ExpenseOrderDO> pageResult = expenseOrderMapper.selectPage(pageReqVO);
        for (ExpenseOrderDO expenseOrder : pageResult.getList()) {

            BigDecimal bedAmount = BigDecimal.ZERO;
            BigDecimal nurseAmount = BigDecimal.ZERO;
            BigDecimal mealAmount = BigDecimal.ZERO;
            BigDecimal otherAmount = BigDecimal.ZERO;

            List<ExpenseOrderItemDO> orderItems = expenseOrderItemMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderItemDO>()
                    .eq(ExpenseOrderItemDO::getExpenseOrderId, expenseOrder.getId()));

            for (ExpenseOrderItemDO orderItem : orderItems) {
                if(orderItem.getExpenseSource().equals(BusinessConstants.EXPENSE_ITEM)){
                    if (orderItem.getType() != null && orderItem.getType() == 1) {
                        bedAmount = bedAmount.add(orderItem.getTotalAmount());
                    } else if (orderItem.getType() != null && orderItem.getType() == 2) {
                        nurseAmount = nurseAmount.add(orderItem.getTotalAmount());
                    } else if (orderItem.getType() != null && orderItem.getType() == 3) {
                        mealAmount = mealAmount.add(orderItem.getTotalAmount());
                    }else {
                        otherAmount = otherAmount.add(orderItem.getTotalAmount());
                    }
                }else {
                    otherAmount = otherAmount.add(orderItem.getTotalAmount());
                }
            }
            expenseOrder.setBedAmount(bedAmount);
            expenseOrder.setNurseAmount(nurseAmount);
            expenseOrder.setMealAmount(mealAmount);
            expenseOrder.setOtherAmount(otherAmount);
            ElderlyInfoDO elderlyInfo = elderlyInfoMapper.selectById(expenseOrder.getElderId());
            expenseOrder.setElderName(elderlyInfo.getElderName());
        }
        return pageResult;
    }

    @Override
    public List<ElderlyExpenseOrderExportExcelVO> exportElderlyExpenseOrder(ExpenseBillPageReqVO pageReqVO) {
        PageResult<ExpenseOrderDO> pageResult = getExpenseOrderPageByElderId(pageReqVO);
        return pageResult.getList().stream().map(item -> {
            ElderlyExpenseOrderExportExcelVO excelVO = new ElderlyExpenseOrderExportExcelVO();
            BeanUtils.copyProperties(item, excelVO);
            if (item.getPayStatus() == 1) {
                excelVO.setPayStatusStr("已缴费");
            } else {
                excelVO.setPayStatusStr("待缴费");
            }
            return excelVO;
        }).collect(Collectors.toList());
    }

    @Override
    public Map<String, Object> getExpenseOrderAmountByElderId(Long elderId) {
        List<ExpenseOrderDO> expenseOrderList = expenseOrderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
                .eq(ExpenseOrderDO::getElderId, elderId));
        BigDecimal totalAmount = BigDecimal.ZERO;
        BigDecimal arrearsFeesAmount = BigDecimal.ZERO;
        for (ExpenseOrderDO expenseOrderDO : expenseOrderList) {
            if (expenseOrderDO.getPayStatus() == BooleanEnum.TRUE.getValue()) {
                totalAmount = totalAmount.add(expenseOrderDO.getPayableAmount());
            } else {
                arrearsFeesAmount = arrearsFeesAmount.add(expenseOrderDO.getActualAmount());
            }
        }

        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("totalAmount", totalAmount);
        dataMap.put("arrearsFeesAmount", arrearsFeesAmount);
        return dataMap;
    }

    @Override
    public void executeMonthlyBillingStatement(Long tenantId) {
        List<ExpenseDO> expenseList = expenseMapper.selectList(new LambdaQueryWrapperX<ExpenseDO>()
                .eq(ExpenseDO::getTenantId, tenantId));

        List<Long> elderIds = expenseList.stream().map(ExpenseDO::getElderId).distinct().collect(Collectors.toList());

        //过滤掉已经退住的长者
        List<ElderlyInfoDO> elderlyInfoList = elderlyInfoMapper.selectList(new LambdaQueryWrapperX<ElderlyInfoDO>().inIfPresent(ElderlyInfoDO::getId, elderIds));
        List<ElderlyInfoDO> resultCollect = elderlyInfoList.stream().filter(e -> e.getInStatus() == 1).collect(Collectors.toList());
        elderIds = resultCollect.stream().map(ElderlyInfoDO::getId).distinct().collect(Collectors.toList());
//        Map<String, List<ExpenseDO>> groupedByElder = new HashMap<>();
//        for (ExpenseDO expense : expenseList) {
//            groupedByElder.computeIfAbsent(expense.getElderId().toString(), k -> new ArrayList<>()).add(expense);
//        }
//
//        // 遍历Map，对每个分组的账单配置按日期排序，并获取最新的账单配置
//        Map<String, ExpenseDO> latestBillConfigs = new HashMap<>();
//        for (Map.Entry<String, List<ExpenseDO>> entry : groupedByElder.entrySet()) {
//            List<ExpenseDO> configs = entry.getValue();
//            configs.sort(Comparator.comparing(ExpenseDO::getCreatedTime).reversed()); // 降序排序
//            latestBillConfigs.put(entry.getKey(), configs.get(0)); // 获取最新的账单配置
//        }

        LocalDate now = LocalDate.now();
//        LocalDate localDate = now.plusMonths(1);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
        String billingMonth = now.format(formatter);

        for (Long elderId : elderIds) {
            ElderlyInfoDO elderlyInfo = elderlyInfoMapper.selectById(elderId);
            if (elderlyInfo == null) {
                continue; // 如果长者信息不存在，跳过
            }

            // 根据长者的入住类型确定账单类型
            // inStatusType: 1=入住(长住), 2=试住, 3=短住
            // type: 1=入院账单, 2=月度账单, 3=试住账单
            int billType = (elderlyInfo.getInStatusType() != null && elderlyInfo.getInStatusType() == 2) ? 3 : 2;

            //查询相同类型的账单（试住账单或月度账单）
            //查询是否有当前月份的相同类型的账单
            ExpenseOrderDO expenseOrder;
            if((elderlyInfo.getInStatusType() != null && elderlyInfo.getInStatusType() == 2)){
                expenseOrder = expenseOrderMapper
                        .selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                                .eq(ExpenseOrderDO::getElderId,elderId)
                                .eq(ExpenseOrderDO::getBillingMonth, billingMonth)
                                .eq(ExpenseOrderDO::getType, billType)); // 增加账单类型条件，确保试住账单和长住账单分开
            }else {
                expenseOrder = expenseOrderMapper
                        .selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                                .eq(ExpenseOrderDO::getElderId, elderId)
                                .eq(ExpenseOrderDO::getBillingMonth, billingMonth)
                                .in(ExpenseOrderDO::getType, 1,2)); // 增加账单类型条件，确保试住账单和长住账单分开
            }

            //账单不存在
            if (expenseOrder == null) {
                Optional<ExpenseDO> optional = expenseMapper.selectList(new LambdaQueryWrapperX<ExpenseDO>()
                                .eq(ExpenseDO::getElderId, elderId)
                                .orderByDesc(ExpenseDO::getCreatedTime))
                        .stream()
                        .findFirst();

                if (optional.isPresent()) {
                    ExpenseDO expense = optional.get();
                    List<ExpenseItemDO> expenseItems = expenseItemMapper
                            .selectList(new LambdaQueryWrapperX<ExpenseItemDO>()
                                    .eq(ExpenseItemDO::getExpenseId, expense.getId())
                                    .eq(ExpenseItemDO::getIsMonthlyExpense, BooleanEnum.TRUE.getValue()));


                    YearMonth queryTime = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
                    //赠送或者阶段的费用
                    List<ExpenseItemDO> freeGiftItemList = expenseItemMapper.selectList(new LambdaQueryWrapperX<ExpenseItemDO>()
                            .eq(ExpenseItemDO::getExpenseId, expense.getId())
                            .eq(ExpenseItemDO::getIsFreeGift, BooleanEnum.TRUE.getValue())
                            .le(ExpenseItemDO::getFreeStartTime, queryTime.atDay(1))
                            .ge(ExpenseItemDO::getFreeEndTime, queryTime.atEndOfMonth()));
                    expenseItems.addAll(freeGiftItemList);


                    //查询上月未缴费的日常费用
                    YearMonth attributionBillTime = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
                    List<DailyExpensesDO> dailyExpensesList = dailyExpensesMapper.selectList(new LambdaQueryWrapperX<DailyExpensesDO>()
                            .eq(DailyExpensesDO::getElderId, elderId)
                            .like(DailyExpensesDO::getAttributionBillTime, attributionBillTime.toString())
                            .eq(DailyExpensesDO::getStatus, BooleanEnum.FALSE.getValue())
                            .eq(DailyExpensesDO::getIsGenerateBill, BooleanEnum.FALSE.getValue()));


                    //组装数据
                    List<ExpenseItemRespVO> dataList = new ArrayList<>();
                    List<ExpenseItemRespVO> collect = expenseItems.stream().map(item -> {
                        ExpenseItemRespVO respVO = new ExpenseItemRespVO();
                        if (item.getIsFreeGift().equals(BooleanEnum.TRUE.getValue())) {
                            respVO.setExpenseType(6);  //阶段费用  费用来源,0：固定 1:日常 2：变更 3：外出 4：额外 5.分期 6.赠送
                        } else {
                            respVO.setExpenseType(0);  //0是月度固定费用
                        }
                        respVO.setExpenseSource(BusinessConstants.EXPENSE_ITEM);
                        respVO.setSourceExpenseItemId(item.getId());
                        respVO.setPrice(item.getActualAmount());
                        respVO.setTotalAmount(item.getTotalAmount());
                        respVO.setItemName(item.getItemName());
                        respVO.setItemCategory(item.getItemCategoryName());
                        respVO.setCount(item.getCount() == null ? 1 : item.getCount());
                        YearMonth yearMonth = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
                        respVO.setStartTime(yearMonth.atDay(1).toString());
                        respVO.setEndTime(yearMonth.atEndOfMonth().toString());
                        respVO.setType(item.getType());
                        respVO.setItemCategory(item.getItemCategoryName());
                        return respVO;
                    }).collect(Collectors.toList());


                    List<ExpenseItemRespVO> dailyExpensesCollect = dailyExpensesList.stream().map(item -> {
                        ExpenseItemRespVO respVO = new ExpenseItemRespVO();
                        respVO.setExpenseType(item.getType());
                        respVO.setExpenseSource(BusinessConstants.DAILY_EXPENSES);
                        respVO.setSourceExpenseItemId(item.getId());
                        respVO.setPrice(item.getPrice());
                        respVO.setTotalAmount(item.getAmount());
                        respVO.setItemName(item.getItemName());
                        respVO.setItemCategory(item.getItemCategory());
                        respVO.setCount(item.getCount() == null ? 1 : item.getCount());
                        return respVO;
                    }).collect(Collectors.toList());

                    dataList.addAll(collect);
                    dataList.addAll(dailyExpensesCollect);


                    //判断长者合同是否到期，到期后未续签合同不自动生成账单
                    List<ElderlyContractDO> elderlyContractList = elderlyContractMapper.selectList(new LambdaQueryWrapperX<ElderlyContractDO>()
                            .eq(ElderlyContractDO::getElderId, elderId)
                            .orderByDesc(ElderlyContractDO::getCreatedTime));

                    if (CollectionUtil.isEmpty(elderlyContractList)) {
                        continue; // 没有合同则跳过
                    }

                    ElderlyContractDO elderlyContractDO = elderlyContractList.get(0); // 获取最新的合同
                    Date expireTime = elderlyContractDO.getExpireTime();

                    if (expireTime == null) {
                        continue; // 没有设置过期时间则跳过
                    }

                    // 将账单月份和合同过期时间转换为 YearMonth 进行比较
                    YearMonth billYearMonth = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
                    YearMonth expireYearMonth = YearMonth.from(expireTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());

                    // 如果合同在当前账单月之前就已经过期，则跳过生成账单
                    if (expireYearMonth.isBefore(billYearMonth)) {
                        continue;
                    }

                    //生成账单（根据长者类型生成试住账单或月度账单）
                    expenseOrder = new ExpenseOrderDO();
                    expenseOrder.setBillingMonth(billingMonth);
                    expenseOrder.setCreatedTime(new Date());
                    expenseOrder.setCreatedBy("系统生成");
                    expenseOrder.setElderId(elderId);
                    expenseOrder.setBillOrderNumber("E" + RandomUtil.randomNumbers(16));
                    expenseOrder.setType(billType); // 根据长者入住类型设置账单类型
                    expenseOrder.setOrgType(elderlyInfo.getOrgType() == null ? null : elderlyInfo.getElderType());
                    expenseOrder.setTenantId(elderlyInfo.getTenantId());
                    expenseOrderMapper.insert(expenseOrder);

                    // 判断合同是否在账单月份过期
                    if (billYearMonth.equals(expireYearMonth)) {
                        LocalDate expireDate = expireTime.toInstant()
                                .atZone(ZoneId.systemDefault())
                                .toLocalDate();

                        int dayOfMonth = expireDate.getDayOfMonth();  //当前月第几号
                        int monthDays = expireDate.lengthOfMonth();   //当前月天数

                        //生成账单费用项
                        BigDecimal totalAmount = BigDecimal.ZERO;
                        for (ExpenseItemRespVO item : dataList) {
                            ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
                            orderItem.setExpenseOrderId(expenseOrder.getId());
                            orderItem.setSourceExpenseItemId(item.getSourceExpenseItemId());
                            orderItem.setExpenseSource(item.getExpenseSource());
                            orderItem.setCount(item.getCount());
                            orderItem.setItemName(item.getItemName());
                            orderItem.setDescription(LocalDateTimeUtil.format(expireDate.with(TemporalAdjusters.firstDayOfMonth()), DatePattern.NORM_DATE_PATTERN) + "至" + LocalDateTimeUtil.format(expireDate, DatePattern.NORM_DATE_PATTERN) + "的" + item.getItemName());

                            if (item.getExpenseSource().equals(BusinessConstants.EXPENSE_ITEM)) {
                                //计算单价
                                orderItem.setPrice(item.getPrice());  //单价
                                orderItem.setActualPrice(item.getPrice()); //实际单价
                                BigDecimal itemTotalAmount = item.getTotalAmount();
                                BigDecimal dayPrice = itemTotalAmount.divide(BigDecimal.valueOf(monthDays), 8, RoundingMode.DOWN);
                                BigDecimal actualPrice = dayPrice.multiply(BigDecimal.valueOf(dayOfMonth)).setScale(8, RoundingMode.DOWN);
                                orderItem.setTotalAmount(actualPrice);  //总金额
                                orderItem.setRoundAmount(orderItem.getTotalAmount().setScale(0, RoundingMode.HALF_UP));
                                orderItem.setRoundTwoDecimalAmount(orderItem.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
                            } else {
                                orderItem.setPrice(item.getPrice());  //单价
                                orderItem.setActualPrice(item.getPrice()); //实际单价
                                orderItem.setTotalAmount(item.getTotalAmount());  //总金额
                                orderItem.setRoundAmount(orderItem.getTotalAmount().setScale(0, RoundingMode.HALF_UP));
                                orderItem.setRoundTwoDecimalAmount(orderItem.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
                            }
                            orderItem.setCreatedTime(new Date());
                            orderItem.setType(item.getType());
                            orderItem.setItemCategoryName(item.getItemCategory());
                            orderItem.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                            orderItem.setTenantId(expenseOrder.getTenantId());
                            expenseOrderItemMapper.insert(orderItem);

                            //判断是否生成过账单
                            DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(item.getSourceExpenseItemId());
                            if (dailyExpenses != null) {
                                dailyExpenses.setIsGenerateBill(BooleanEnum.TRUE.getValue());
                                dailyExpenses.setExpenseOrderNumber(expenseOrder.getBillOrderNumber());
                                dailyExpensesMapper.updateById(dailyExpenses);
                            }
                            totalAmount = totalAmount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));
                        }

                        expenseOrder.setActualAmount(totalAmount);
                        expenseOrderMapper.updateById(expenseOrder);
                    } else {

                        //生成账单费用项
                        BigDecimal totalAmount = BigDecimal.ZERO;
                        for (ExpenseItemRespVO item : dataList) {
                            ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
                            orderItem.setExpenseOrderId(expenseOrder.getId());
                            orderItem.setSourceExpenseItemId(item.getSourceExpenseItemId());
                            orderItem.setExpenseSource(item.getExpenseSource());
                            orderItem.setCount(item.getCount());
                            orderItem.setItemName(item.getItemName());
                            orderItem.setPrice(item.getPrice());  //单价
                            orderItem.setActualPrice(item.getPrice()); //实际单价
                            orderItem.setTotalAmount(item.getTotalAmount());  //总金额
                            orderItem.setRoundAmount(orderItem.getTotalAmount().setScale(0, RoundingMode.HALF_UP));
                            orderItem.setRoundTwoDecimalAmount(orderItem.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
                            orderItem.setCreatedTime(new Date());
                            orderItem.setType(item.getType());
                            orderItem.setItemCategoryName(item.getItemCategory());
                            orderItem.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                            orderItem.setTenantId(expenseOrder.getTenantId());
                            expenseOrderItemMapper.insert(orderItem);

                            //判断是否生成过账单
                            DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(item.getSourceExpenseItemId());
                            if (dailyExpenses != null) {
                                dailyExpenses.setIsGenerateBill(BooleanEnum.TRUE.getValue());
                                dailyExpenses.setExpenseOrderNumber(expenseOrder.getBillOrderNumber());
                                dailyExpensesMapper.updateById(dailyExpenses);
                            }
                            totalAmount = totalAmount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));
                        }
                        expenseOrder.setActualAmount(totalAmount);
                        expenseOrderMapper.updateById(expenseOrder);
                    }
                }
            } else {
                //账单存在
                //已经生成的获取当月未缴费的日常费用添加到账单中去
                YearMonth attributionBillTime = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
                List<DailyExpensesDO> dailyExpensesList = dailyExpensesMapper.selectList(new LambdaQueryWrapperX<DailyExpensesDO>()
                        .eq(DailyExpensesDO::getElderId, elderId)
                        .like(DailyExpensesDO::getAttributionBillTime, attributionBillTime.toString())
                        .eq(DailyExpensesDO::getStatus, BooleanEnum.FALSE.getValue())
                        .eq(DailyExpensesDO::getIsGenerateBill, BooleanEnum.FALSE.getValue()));

                List<ExpenseItemRespVO> dailyExpensesCollect = dailyExpensesList.stream().map(item -> {
                    ExpenseItemRespVO respVO = new ExpenseItemRespVO();
                    respVO.setExpenseType(item.getType());
                    respVO.setExpenseSource(BusinessConstants.DAILY_EXPENSES);
                    respVO.setSourceExpenseItemId(item.getId());
                    respVO.setPrice(item.getPrice());
                    respVO.setTotalAmount(item.getAmount());
                    respVO.setItemName(item.getItemName());
                    respVO.setItemCategory(item.getItemCategory());
                    respVO.setCount(item.getCount() == null ? 1 : item.getCount());
                    return respVO;
                }).collect(Collectors.toList());


                BigDecimal amount = BigDecimal.ZERO;
                for (ExpenseItemRespVO item : dailyExpensesCollect) {
                    ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
                    orderItem.setExpenseOrderId(expenseOrder.getId());
                    orderItem.setSourceExpenseItemId(item.getSourceExpenseItemId());
                    orderItem.setExpenseSource(item.getExpenseSource());
                    orderItem.setCount(item.getCount());
                    orderItem.setItemName(item.getItemName());
                    orderItem.setItemCategoryName(item.getItemCategory());
                    orderItem.setPrice(item.getPrice());  //单价
                    orderItem.setActualPrice(item.getPrice()); //实际单价
                    orderItem.setTotalAmount(item.getTotalAmount());  //总金额
                    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());
                    expenseOrderItemMapper.insert(orderItem);

                    amount = amount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));

                    //判断是否生成过账单
                    DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(item.getSourceExpenseItemId());
                    if (dailyExpenses != null) {
                        dailyExpenses.setIsGenerateBill(BooleanEnum.TRUE.getValue());
                        dailyExpensesMapper.updateById(dailyExpenses);
                    }
                }
                expenseOrder.setPayStatus(BooleanEnum.FALSE.getValue());
                expenseOrder.setPayableAmount(amount); //更新总金额
                expenseOrder.setPayType(null);
                expenseOrderMapper.updateById(expenseOrder);
            }
        }
    }

//    @Override
//    public void batchGenerateExpenseBill(ExpenseOrderSaveVO saveVO) {
//        Long tenantId = saveVO.getTenantId();
//        String billingMonth = saveVO.getBillingMonth();
//
//        List<ExpenseDO> expenseList = expenseMapper.selectList(new LambdaQueryWrapperX<ExpenseDO>()
//                .eq(ExpenseDO::getTenantId, tenantId));
//
//        List<Long> elderIds = expenseList.stream().map(ExpenseDO::getElderId).distinct().collect(Collectors.toList());
//
//        //过滤掉已经退住的长者
//        List<ElderlyInfoDO> elderlyInfoList = elderlyInfoMapper.selectList(new LambdaQueryWrapperX<ElderlyInfoDO>()
//                .eq(ElderlyInfoDO::getInStatus, 1)
//                .eq(ElderlyInfoDO::getTenantId, tenantId)
//                .inIfPresent(ElderlyInfoDO::getId, elderIds));
////        List<ElderlyInfoDO> resultCollect = elderlyInfoList.stream().filter(e -> e.getInStatus() == 1).collect(Collectors.toList());
//        elderIds = elderlyInfoList.stream().map(ElderlyInfoDO::getId).distinct().collect(Collectors.toList());
//
//
//        for (Long elderId : elderIds) {
////            Boolean isIn = true;
////            List<ElderlyContractDO> elderlyContractDOS = elderlyContractMapper.selectList(new LambdaQueryWrapperX<ElderlyContractDO>().eq(ElderlyContractDO::getElderId, elderId));
////            if (null != elderlyContractDOS && elderlyContractDOS.size() > 0) {
////                for (int i = 0; i < elderlyContractDOS.size(); i++) {
////                    ElderlyContractDO elderlyContractDO = elderlyContractDOS.get(i);
////                    // 比较elderlyContractDO的beginTime是否在billingMonth之前
////                    if (elderlyContractDO.getBeginTime().before(DateUtil.parse(billingMonth + "-01"))) {
////                        isIn = false;
////                    }
////                }
////            }
////
////            if (isIn) {
////                continue;
////            }
//            // 判断合同是否过期，过期的跳过生成长者账单，但是如果是billingMonth 同属月份过期也能生成账单，不会跳过
//            List<ElderlyContractDO> elderlyContractList = elderlyContractMapper.selectList(new LambdaQueryWrapperX<ElderlyContractDO>()
//                    .eq(ElderlyContractDO::getElderId, elderId)
//                    .orderByDesc(ElderlyContractDO::getCreatedTime));
//
//            if (CollectionUtil.isEmpty(elderlyContractList)) {
//                continue; // 没有合同则跳过
//            }
//
//            ElderlyContractDO elderlyContractDO = elderlyContractList.get(0); // 获取最新的合同
//            Date expireTime = elderlyContractDO.getExpireTime();
//
//            if (expireTime == null) {
//                continue; // 没有设置过期时间则跳过
//            }
//
//            // 将账单月份和合同过期时间转换为 YearMonth 进行比较
//            YearMonth billYearMonth = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
//            YearMonth expireYearMonth = YearMonth.from(expireTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
//
//            // 如果合同在当前账单月之前就已经过期，则跳过生成账单
//            if (expireYearMonth.isBefore(billYearMonth)) {
//                continue;
//            }
//
//            //查询月度账单
//            List<ExpenseOrderDO> expenseOrderList = expenseOrderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
//                    .eq(ExpenseOrderDO::getBillingMonth, billingMonth)
//                    .eq(ExpenseOrderDO::getElderId, elderId).orderByDesc(ExpenseOrderDO::getCreatedTime));
//
//            ExpenseOrderDO expenseOrder = null;
//            if (null != expenseOrderList && !expenseOrderList.isEmpty()) {
//                expenseOrder = expenseOrderList.get(0);
//            }
//
//            //账单不存在
//            if (expenseOrder == null) {
//                Optional<ExpenseDO> optional = expenseMapper.selectList(new LambdaQueryWrapperX<ExpenseDO>()
//                                .eq(ExpenseDO::getElderId, elderId)
//                                .orderByDesc(ExpenseDO::getCreatedTime))
//                        .stream()
//                        .findFirst();
//
//                if (optional.isPresent()) {
//                    ExpenseDO expense = optional.get();
//                    List<ExpenseItemDO> expenseItems = expenseItemMapper
//                            .selectList(new LambdaQueryWrapperX<ExpenseItemDO>()
//                                    .eq(ExpenseItemDO::getExpenseId, expense.getId())
//                                    .eq(ExpenseItemDO::getIsMonthlyExpense, BooleanEnum.TRUE.getValue()));
//
//
//                    YearMonth queryTime = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
//                    //赠送或者阶段的费用
//                    List<ExpenseItemDO> freeGiftItemList = expenseItemMapper.selectList(new LambdaQueryWrapperX<ExpenseItemDO>()
//                            .eq(ExpenseItemDO::getExpenseId, expense.getId())
//                            .eq(ExpenseItemDO::getIsFreeGift, BooleanEnum.TRUE.getValue())
//                            .le(ExpenseItemDO::getFreeStartTime, queryTime.atDay(1))
//                            .ge(ExpenseItemDO::getFreeEndTime, queryTime.atEndOfMonth()));
//                    expenseItems.addAll(freeGiftItemList);
//
//
//                    //查询上月未缴费的日常费用
//                    YearMonth attributionBillTime = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
//                    List<DailyExpensesDO> dailyExpensesList = dailyExpensesMapper.selectList(new LambdaQueryWrapperX<DailyExpensesDO>()
//                            .eq(DailyExpensesDO::getElderId, elderId)
//                            .like(DailyExpensesDO::getAttributionBillTime, attributionBillTime.toString())
//                            .eq(DailyExpensesDO::getStatus, BooleanEnum.FALSE.getValue())
//                            .eq(DailyExpensesDO::getIsGenerateBill, BooleanEnum.FALSE.getValue()));
//
//
//                    //组装数据
//                    List<ExpenseItemRespVO> dataList = new ArrayList<>();
//                    List<ExpenseItemRespVO> collect = expenseItems.stream().map(item -> {
//                        ExpenseItemRespVO respVO = new ExpenseItemRespVO();
//                        if (item.getIsFreeGift().equals(BooleanEnum.TRUE.getValue())) {
//                            respVO.setExpenseType(6);  //阶段费用  费用来源,0：固定 1:日常 2：变更 3：外出 4：额外 5.分期 6.赠送
//                        } else {
//                            respVO.setExpenseType(0);  //0是月度固定费用
//                        }
//                        respVO.setExpenseSource(BusinessConstants.EXPENSE_ITEM);
//                        respVO.setSourceExpenseItemId(item.getId());
//                        respVO.setPrice(item.getActualAmount());
//                        respVO.setTotalAmount(item.getTotalAmount());
//                        respVO.setItemName(item.getItemName());
//                        respVO.setItemCategory(item.getItemCategoryName());
//                        respVO.setCount(item.getCount() == null ? 1 : item.getCount());
//                        YearMonth yearMonth = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
//                        respVO.setStartTime(yearMonth.atDay(1).toString());
//                        respVO.setEndTime(yearMonth.atEndOfMonth().toString());
//                        respVO.setType(item.getType());
//                        respVO.setItemCategory(item.getItemCategoryName());
//                        return respVO;
//                    }).collect(Collectors.toList());
//
//
//                    List<ExpenseItemRespVO> dailyExpensesCollect = dailyExpensesList.stream().map(item -> {
//                        ExpenseItemRespVO respVO = new ExpenseItemRespVO();
//                        respVO.setExpenseType(item.getType());
//                        respVO.setExpenseSource(BusinessConstants.DAILY_EXPENSES);
//                        respVO.setSourceExpenseItemId(item.getId());
//                        respVO.setPrice(item.getPrice());
//                        respVO.setTotalAmount(item.getAmount());
//                        respVO.setItemName(item.getItemName());
//                        respVO.setItemCategory(item.getItemCategory());
//                        respVO.setCount(item.getCount() == null ? 1 : item.getCount());
//                        return respVO;
//                    }).collect(Collectors.toList());
//
//                    dataList.addAll(collect);
//                    dataList.addAll(dailyExpensesCollect);
//
//                    ElderlyInfoDO elderlyInfo = elderlyInfoMapper.selectById(elderId);
//
//                    //生成月度账单
//                    expenseOrder = new ExpenseOrderDO();
//                    expenseOrder.setBillingMonth(billingMonth);
//                    expenseOrder.setCreatedTime(new Date());
//                    expenseOrder.setCreatedBy("系统生成");
//                    expenseOrder.setElderId(elderId);
//                    expenseOrder.setBillOrderNumber("E" + RandomUtil.randomNumbers(16));
//                    expenseOrder.setType(2); //月度账单
//                    if (elderlyInfo != null) {
//                        expenseOrder.setOrgType(elderlyInfo.getOrgType() == null ? null : elderlyInfo.getElderType());
//                        expenseOrder.setTenantId(elderlyInfo.getTenantId());
//                    }
//                    expenseOrderMapper.insert(expenseOrder);
//
//                    //判断是否是当前月份
//                    if (billYearMonth.equals(expireYearMonth)) {
//                        LocalDate expireDate = expireTime.toInstant()
//                                .atZone(ZoneId.systemDefault())
//                                .toLocalDate();
//
//
//                        int dayOfMonth = expireDate.getDayOfMonth();  //当前月第几号
//                        int monthDays = expireDate.lengthOfMonth();   //当前月天数
//
//                        //生成账单费用项
//                        BigDecimal totalAmount = BigDecimal.ZERO;
//                        for (ExpenseItemRespVO item : dataList) {
//                            ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
//                            orderItem.setExpenseOrderId(expenseOrder.getId());
//                            orderItem.setSourceExpenseItemId(item.getSourceExpenseItemId());
//                            orderItem.setExpenseSource(item.getExpenseSource());
//                            orderItem.setCount(item.getCount());
//                            orderItem.setItemName(item.getItemName());
//                            orderItem.setDescription(LocalDateTimeUtil.format(expireDate.withDayOfMonth(1), DatePattern.NORM_DATE_PATTERN) + "至" + LocalDateTimeUtil.format(expireDate, DatePattern.NORM_DATE_PATTERN) + "的" + item.getItemName());
//
//                            if (item.getExpenseSource().equals(BusinessConstants.EXPENSE_ITEM)) {
//                                //计算单价
//                                orderItem.setPrice(item.getPrice());  //单价
//                                orderItem.setActualPrice(item.getPrice()); //实际单价
//                                BigDecimal itemTotalAmount = item.getTotalAmount();
//                                BigDecimal dayPrice = itemTotalAmount.divide(BigDecimal.valueOf(monthDays), 8, RoundingMode.DOWN);
//                                BigDecimal actualPrice = dayPrice.multiply(BigDecimal.valueOf(dayOfMonth)).setScale(8, RoundingMode.DOWN);
//                                orderItem.setTotalAmount(actualPrice);  //总金额
//                                orderItem.setRoundAmount(orderItem.getTotalAmount().setScale(0, RoundingMode.HALF_UP));
//                                orderItem.setRoundTwoDecimalAmount(orderItem.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
//                            } else {
//                                orderItem.setPrice(item.getPrice());  //单价
//                                orderItem.setActualPrice(item.getPrice()); //实际单价
//                                orderItem.setTotalAmount(item.getTotalAmount());  //总金额
//                                orderItem.setRoundAmount(orderItem.getTotalAmount().setScale(0, RoundingMode.HALF_UP));
//                                orderItem.setRoundTwoDecimalAmount(orderItem.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
//                            }
//                            orderItem.setCreatedTime(new Date());
//                            orderItem.setType(item.getType());
//                            orderItem.setItemCategoryName(item.getItemCategory());
//                            orderItem.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
//                            orderItem.setTenantId(expenseOrder.getTenantId());
//                            expenseOrderItemMapper.insert(orderItem);
//
//                            //判断是否生成过账单
//                            DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(item.getSourceExpenseItemId());
//                            if (dailyExpenses != null) {
//                                dailyExpenses.setIsGenerateBill(BooleanEnum.TRUE.getValue());
//                                dailyExpenses.setExpenseOrderNumber(expenseOrder.getBillOrderNumber());
//                                dailyExpensesMapper.updateById(dailyExpenses);
//                            }
//                            totalAmount = totalAmount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));
//                        }
//                        //长护险抵扣
////                        List<ExpenseSubsidyDO> expenseSubsidList = expenseSubsidyMapper.selectList(new LambdaQueryWrapperX<ExpenseSubsidyDO>()
////                                .eq(ExpenseSubsidyDO::getElderId, expenseOrder.getElderId())
////                                .eq(ExpenseSubsidyDO::getDeductionBillMonth, expenseOrder.getBillingMonth()));
////                        totalAmount = updateExpenseSubsidy(expenseSubsidList, expenseOrder, totalAmount);
//
//                        expenseOrder.setActualAmount(totalAmount);
//                        expenseOrderMapper.updateById(expenseOrder);
//                    } else {
//
//                        //生成账单费用项
//                        BigDecimal totalAmount = BigDecimal.ZERO;
//                        for (ExpenseItemRespVO item : dataList) {
//                            ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
//                            orderItem.setExpenseOrderId(expenseOrder.getId());
//                            orderItem.setSourceExpenseItemId(item.getSourceExpenseItemId());
//                            orderItem.setExpenseSource(item.getExpenseSource());
//                            orderItem.setCount(item.getCount());
//                            orderItem.setItemName(item.getItemName());
//                            orderItem.setPrice(item.getPrice());  //单价
//                            orderItem.setActualPrice(item.getPrice()); //实际单价
//                            orderItem.setTotalAmount(item.getTotalAmount());  //总金额
//                            orderItem.setRoundAmount(orderItem.getTotalAmount().setScale(0, RoundingMode.HALF_UP));
//                            orderItem.setRoundTwoDecimalAmount(orderItem.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
//                            orderItem.setCreatedTime(new Date());
//                            orderItem.setType(item.getType());
//                            orderItem.setItemCategoryName(item.getItemCategory());
//                            orderItem.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
//                            orderItem.setDescription(LocalDateTimeUtil.format(queryTime.atDay(1), DatePattern.NORM_DATE_PATTERN) + "至" + LocalDateTimeUtil.format(queryTime.atEndOfMonth(), DatePattern.NORM_DATE_PATTERN) + "的" + item.getItemName());
//                            orderItem.setTenantId(expenseOrder.getTenantId());
//                            expenseOrderItemMapper.insert(orderItem);
//
//                            //判断是否生成过账单
//                            DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(item.getSourceExpenseItemId());
//                            if (dailyExpenses != null) {
//                                dailyExpenses.setIsGenerateBill(BooleanEnum.TRUE.getValue());
//                                dailyExpenses.setExpenseOrderNumber(expenseOrder.getBillOrderNumber());
//                                dailyExpensesMapper.updateById(dailyExpenses);
//                            }
//                            totalAmount = totalAmount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));
//                        }
//                        //长护险抵扣
////                        List<ExpenseSubsidyDO> expenseSubsidList = expenseSubsidyMapper.selectList(new LambdaQueryWrapperX<ExpenseSubsidyDO>()
////                                .eq(ExpenseSubsidyDO::getElderId, expenseOrder.getElderId())
////                                .eq(ExpenseSubsidyDO::getDeductionBillMonth, expenseOrder.getBillingMonth()));
////                        totalAmount = updateExpenseSubsidy(expenseSubsidList, expenseOrder, totalAmount);
//
//                        expenseOrder.setActualAmount(totalAmount);
//                        expenseOrderMapper.updateById(expenseOrder);
//                    }
//                }
//            } else {
//                //账单存在
//                //已经生成的获取当月未缴费的日常费用添加到账单中去
//                YearMonth attributionBillTime = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
//                List<DailyExpensesDO> dailyExpensesList = dailyExpensesMapper.selectList(new LambdaQueryWrapperX<DailyExpensesDO>()
//                        .eq(DailyExpensesDO::getElderId, elderId)
//                        .like(DailyExpensesDO::getAttributionBillTime, attributionBillTime.toString())
//                        .eq(DailyExpensesDO::getStatus, BooleanEnum.FALSE.getValue())
//                        .eq(DailyExpensesDO::getIsGenerateBill, BooleanEnum.FALSE.getValue()));
//
//                List<ExpenseItemRespVO> dailyExpensesCollect = dailyExpensesList.stream().map(item -> {
//                    ExpenseItemRespVO respVO = new ExpenseItemRespVO();
//                    respVO.setExpenseType(item.getType());
//                    respVO.setExpenseSource(BusinessConstants.DAILY_EXPENSES);
//                    respVO.setSourceExpenseItemId(item.getId());
//                    respVO.setPrice(item.getPrice());
//                    respVO.setTotalAmount(item.getAmount());
//                    respVO.setItemName(item.getItemName());
//                    respVO.setItemCategory(item.getItemCategory());
//                    respVO.setCount(item.getCount() == null ? 1 : item.getCount());
//                    return respVO;
//                }).collect(Collectors.toList());
//
//
//                BigDecimal amount = BigDecimal.ZERO;
//                for (ExpenseItemRespVO item : dailyExpensesCollect) {
//                    ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
//                    orderItem.setExpenseOrderId(expenseOrder.getId());
//                    orderItem.setSourceExpenseItemId(item.getSourceExpenseItemId());
//                    orderItem.setExpenseSource(item.getExpenseSource());
//                    orderItem.setCount(item.getCount());
//                    orderItem.setItemName(item.getItemName());
//                    orderItem.setItemCategoryName(item.getItemCategory());
//                    orderItem.setPrice(item.getPrice());  //单价
//                    orderItem.setActualPrice(item.getPrice()); //实际单价
//                    orderItem.setTotalAmount(item.getTotalAmount());  //总金额
//                    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(expenseOrder.getTenantId());
//                    expenseOrderItemMapper.insert(orderItem);
//
//                    amount = amount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));
//
//                    //判断是否生成过账单
//                    DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(item.getSourceExpenseItemId());
//                    if (dailyExpenses != null) {
//                        dailyExpenses.setIsGenerateBill(BooleanEnum.TRUE.getValue());
//                        dailyExpensesMapper.updateById(dailyExpenses);
//                    }
//                }
//                if (CollectionUtil.isNotEmpty(dailyExpensesCollect)) {
//                    expenseOrder.setPayStatus(BooleanEnum.FALSE.getValue());
//                    expenseOrder.setPayableAmount(amount); //更新总金额
//                    expenseOrder.setPayType(null);
//                }
//                expenseOrderMapper.updateById(expenseOrder);
//            }
//        }
//    }

    @Override
    public void batchGenerateExpenseBill(ExpenseOrderSaveVO saveVO, String taskId) {
        Long tenantId = saveVO.getTenantId();
        String billingMonth = saveVO.getBillingMonth();

        try {
            // 一次性加载所有数据
            List<Long> elderIds = expenseMapper.selectList(new LambdaQueryWrapperX<ExpenseDO>()
                            .select(ExpenseDO::getElderId)
                            .eq(ExpenseDO::getTenantId, tenantId))
                    .stream()
                    .map(ExpenseDO::getElderId)
                    .distinct()
                    .collect(Collectors.toList());
            // 批量处理长者账单
            processBatchElderlyBilling(elderIds, tenantId, billingMonth, taskId);

        } catch (Exception e) {
            log.error("批量生成账单失败", e);
            if (taskId != null) {
                taskStatusManager.failTask(taskId, e.getMessage());
            }
            throw e;
        }
    }

    /**
     * 批量处理长者账单生成
     *
     * @param elderIds     长者ID列表
     * @param tenantId     租户ID
     * @param billingMonth 账单月份
     */
    private void processBatchElderlyBilling(List<Long> elderIds, Long tenantId, String billingMonth, String taskId) {
        // 批量查询在住长者信息
        List<ElderlyInfoDO> elderlyInfoList = elderlyInfoMapper.selectList(new LambdaQueryWrapperX<ElderlyInfoDO>()
                .eq(ElderlyInfoDO::getInStatus, 1)
                .eq(ElderlyInfoDO::getTenantId, tenantId)
                .inIfPresent(ElderlyInfoDO::getId, elderIds));

        if (CollectionUtil.isEmpty(elderlyInfoList)) {
            return;
        }

        List<Long> validElderIds = elderlyInfoList.stream()
                .map(ElderlyInfoDO::getId)
                .distinct()
                .collect(Collectors.toList());

        // 批量查询合同信息
        Map<Long, List<ElderlyContractDO>> contractMap = new HashMap<>();
        List<ElderlyContractDO> allContracts = elderlyContractMapper.selectList(new LambdaQueryWrapperX<ElderlyContractDO>()
                .in(ElderlyContractDO::getElderId, validElderIds));

        // 按长者ID分组合同，并按创建时间倒序排序
        allContracts.stream()
                .collect(Collectors.groupingBy(ElderlyContractDO::getElderId))
                .forEach((elderId, contracts) -> {
                    contractMap.put(elderId, contracts.stream()
                            .sorted(Comparator.comparing(ElderlyContractDO::getCreatedTime).reversed())
                            .collect(Collectors.toList()));
                });

        YearMonth billYearMonth = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
        // 如果有taskId，更新总数量
        if (taskId != null) {
            taskStatusManager.updateTotalCount(taskId, elderlyInfoList.size());
        }
        // 批量处理每个长者的账单
        for (ElderlyInfoDO elderlyInfo : elderlyInfoList) {
            Long elderId = elderlyInfo.getId();
            List<ElderlyContractDO> elderlyContractList = contractMap.get(elderId);

            if (CollectionUtil.isEmpty(elderlyContractList)) {
                // 记录跳过原因
                if (taskId != null) {
                    taskStatusManager.addProcessDetail(taskId, elderlyInfo.getId(), elderlyInfo.getElderName(), "SKIPPED", "没有有效合同");
                }
                continue; // 没有合同则跳过
            }

            // 选择“在账单月份有效”的合同，而不是简单取最新创建的合同（避免提前续约导致旧月份无法出账）
            LocalDate billStart = billYearMonth.atDay(1);
            LocalDate billEnd = billYearMonth.atEndOfMonth();

            ElderlyContractDO elderlyContractDO = elderlyContractList.stream()
                    .filter(c -> c.getBeginTime() != null && c.getExpireTime() != null)
                    .filter(c -> {
                        LocalDate begin = c.getBeginTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                        LocalDate end = c.getExpireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                        // 合同区间与账单月份区间有交集，则认为该月有效
                        return !begin.isAfter(billEnd) && !end.isBefore(billStart);
                    })
                    .max(Comparator.comparing(ElderlyContractDO::getBeginTime))
                    .orElse(null);

            if (elderlyContractDO == null) {
                if (taskId != null) {
                    taskStatusManager.addProcessDetail(taskId, elderlyInfo.getId(), elderlyInfo.getElderName(), "SKIPPED", "账单月份无有效合同");
                }
                continue;
            }

            Date expireTime = elderlyContractDO.getExpireTime();
            // 将账单月份和合同过期时间转换为 YearMonth 进行比较
            YearMonth expireYearMonth = YearMonth.from(expireTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());

            // 核心过滤：账单月份必须在“入住/合同有效期”内
            // 1) 未来才入住的长者，不应为过去月份生成账单
            if (elderlyInfo.getCheckInTime() != null) {
                YearMonth checkInYearMonth = YearMonth.from(elderlyInfo.getCheckInTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
                if (billYearMonth.isBefore(checkInYearMonth)) {
                    if (taskId != null) {
                        taskStatusManager.addProcessDetail(taskId, elderlyInfo.getId(), elderlyInfo.getElderName(), "SKIPPED", "账单月份早于入住月份");
                    }
                    continue;
                }
            }

            // 2) 合同在当前账单月之前就已经过期，则跳过生成账单
            if (expireYearMonth.isBefore(billYearMonth)) {
                if (taskId != null) {
                    taskStatusManager.addProcessDetail(taskId, elderlyInfo.getId(), elderlyInfo.getElderName(), "SKIPPED", "合同已过期");
                }
                continue;
            }

            // 根据长者的入住类型确定账单类型
            // inStatusType: 1=入住(长住), 2=试住, 3=短住
            // type: 1=入院账单, 2=月度账单, 3=试住账单
            int billType = (elderlyInfo.getInStatusType() != null && elderlyInfo.getInStatusType() == 2) ? 3 : 2;
            List<ExpenseOrderDO> expenseOrderList;
            // 查询对应类型的账单（试住账单或月度账单）
            if((elderlyInfo.getInStatusType() != null && elderlyInfo.getInStatusType() == 2)){
                expenseOrderList = expenseOrderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
                        .eq(ExpenseOrderDO::getBillingMonth, billingMonth)
                        .eq(ExpenseOrderDO::getElderId, elderId)
                        .eq(ExpenseOrderDO::getType, billType) // 根据长者类型查询对应类型的账单
                        .orderByDesc(ExpenseOrderDO::getCreatedTime));
            }else {
                expenseOrderList = expenseOrderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
                        .eq(ExpenseOrderDO::getBillingMonth, billingMonth)
                        .eq(ExpenseOrderDO::getElderId, elderId)
                        .in(ExpenseOrderDO::getType, 1,2) // 根据长者类型查询对应类型的账单
                        .orderByDesc(ExpenseOrderDO::getCreatedTime));
            }

            ExpenseOrderDO expenseOrder = null;
            if (CollectionUtil.isNotEmpty(expenseOrderList)) {
                expenseOrder = expenseOrderList.get(0);
                // 批量处理时校验账单是否已确认，如果已确认则跳过
                if (isExpenseOrderConfirmed(expenseOrder, true)) {
                    if (taskId != null) {
                        taskStatusManager.addProcessDetail(taskId, elderlyInfo.getId(), elderlyInfo.getElderName(), "SKIPPED", "账单已确认");
                    }
                    continue;
                }
                // 批量处理时校验账单是否已确认，如果已确认则跳过
                if (isExpenseOrderLock(expenseOrder, true)) {
                    if (taskId != null) {
                        taskStatusManager.addProcessDetail(taskId, elderlyInfo.getId(), elderlyInfo.getElderName(), "SKIPPED", "账单已锁定");
                    }
                    continue;
                }
            }

            // 账单不存在
            if (expenseOrder == null) {
                handleNewExpenseOrder(elderlyInfo, elderId, tenantId, billingMonth, billYearMonth, expireYearMonth, expireTime);
                if (!checkContractNearlyExpire(elderId, billingMonth)) {
                    if (taskId != null) {
                        taskStatusManager.addProcessDetail(taskId, elderlyInfo.getId(), elderlyInfo.getElderName(), "CONFIRM", "合同即将过期，请确认");
                    }
                    continue;
                }
                if (taskId != null) {
                    taskStatusManager.addProcessDetail(taskId, elderlyInfo.getId(), elderlyInfo.getElderName(), "SUCCESS", "新账单生成成功");
                }
            } else {
                // 账单存在，添加未缴费的日常费用
                handleExistingExpenseOrder(expenseOrder, elderId, tenantId, billingMonth, true);
                if (!checkContractNearlyExpire(elderId, billingMonth)) {
                    if (taskId != null) {
                        taskStatusManager.addProcessDetail(taskId, elderlyInfo.getId(), elderlyInfo.getElderName(), "CONFIRM", "合同即将过期，请确认");
                    }
                    continue;
                }
                if (taskId != null) {
                    taskStatusManager.addProcessDetail(taskId, elderlyInfo.getId(), elderlyInfo.getElderName(), "SUCCESS", "现有账单更新成功");
                }
            }
        }
    }

    /**
     * 处理新账单生成
     */
    private void handleNewExpenseOrder(ElderlyInfoDO elderlyInfo, Long elderId, Long tenantId,
                                       String billingMonth, YearMonth billYearMonth,
                                       YearMonth expireYearMonth, Date expireTime) {
        Optional<ExpenseDO> optional = expenseMapper.selectList(new LambdaQueryWrapperX<ExpenseDO>()
                        .eq(ExpenseDO::getElderId, elderId)
                        .orderByDesc(ExpenseDO::getCreatedTime))
                .stream()
                .findFirst();

        if (!optional.isPresent()) {
            return;
        }

        ExpenseDO expense = optional.get();

        // 1、固定费用 & 阶段（赠送）费用：统一复用 handlerCollectMonthlyExpense 逻辑
        YearMonth yearMonth = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
        LocalDate yearMonthStartDay = yearMonth.atDay(1);
        List<ExpenseItemRespVO> dataList = new ArrayList<>();
        List<ExpenseItemRespVO> collect = handlerCollectMonthlyExpense(billingMonth, elderId);
        LocalDate actualStart = yearMonthStartDay;
        LocalDate actualEnd = yearMonth.atEndOfMonth();
        if (CollectionUtil.isNotEmpty(collect)) {
            // 固定费用的区间在 handlerCollectMonthlyExpense 里已按离住规则处理（1号~离院前一天），这里复用其区间给抵扣项
            actualStart = LocalDate.parse(collect.get(0).getStartTime());
            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=试住账单
        int billType = (elderlyInfo.getInStatusType() != null && elderlyInfo.getInStatusType() == 2) ? 3 : 2;

        // 生成账单（根据长者类型生成试住账单或月度账单）
        ExpenseOrderDO expenseOrder = new ExpenseOrderDO();
        expenseOrder.setBillingMonth(billingMonth);
        expenseOrder.setCreatedTime(new Date());
        expenseOrder.setCreatedBy("系统生成");
        expenseOrder.setElderId(elderId);
        expenseOrder.setBillOrderNumber("E" + RandomUtil.randomNumbers(16));
        expenseOrder.setType(billType); // 根据长者入住类型设置账单类型
        expenseOrder.setOrgType(elderlyInfo.getOrgType() == null ? null : elderlyInfo.getElderType());
        expenseOrder.setTenantId(elderlyInfo.getTenantId());
        expenseOrderMapper.insert(expenseOrder);

        // 判断是否是当前月份
        BigDecimal totalAmount = BigDecimal.ZERO;
        // 生成账单费用项
        for (ExpenseItemRespVO item : dataList) {
            ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
            orderItem.setExpenseOrderId(expenseOrder.getId());
            orderItem.setSourceExpenseItemId(item.getSourceExpenseItemId());
            orderItem.setExpenseSource(item.getExpenseSource());
            orderItem.setCount(item.getCount());
            orderItem.setItemName(item.getItemName());
            orderItem.setPrice(item.getPrice());  // 单价
            orderItem.setActualPrice(item.getPrice()); // 实际单价
            orderItem.setTotalAmount(item.getTotalAmount());  // 总金额
            orderItem.setRoundAmount(orderItem.getTotalAmount().setScale(0, RoundingMode.HALF_UP));
            orderItem.setRoundTwoDecimalAmount(orderItem.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
            orderItem.setCreatedTime(new Date());
            orderItem.setType(item.getType());
            orderItem.setItemCategoryName(item.getItemCategory());
            orderItem.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
            orderItem.setDescription(StringUtils.isBlank(item.getDescription()) ? LocalDateTimeUtil.format(yearMonth.atDay(1), DatePattern.NORM_DATE_PATTERN)
                    + "至" + LocalDateTimeUtil.format(yearMonth.atEndOfMonth(), DatePattern.NORM_DATE_PATTERN) + "的" + item.getItemName() : item.getDescription());
            orderItem.setTenantId(expenseOrder.getTenantId());
            orderItem.setStartDate(billYearMonth.atDay(1));
            orderItem.setEndDate(billYearMonth.atEndOfMonth());
            orderItem.setContractId(item.getContractId());
            expenseOrderItemMapper.insert(orderItem);
            if (item.getExpenseSource().equals(BusinessConstants.DISCOUNT_DEDUCTION)) {
                addDiscountRecord(elderId, billingMonth, item);
            }
            // 判断是否生成过账单
            DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(item.getSourceExpenseItemId());
            if (dailyExpenses != null) {
                dailyExpenses.setIsGenerateBill(BooleanEnum.TRUE.getValue());
                dailyExpenses.setExpenseOrderNumber(expenseOrder.getBillOrderNumber());
                dailyExpensesMapper.updateById(dailyExpenses);
            }
            totalAmount = totalAmount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));
        }

        expenseOrder.setActualAmount(totalAmount);
        expenseOrderMapper.updateById(expenseOrder);
    }

    /**
     * 处理已存在账单的情况
     */
    private void handleExistingExpenseOrder(ExpenseOrderDO expenseOrder, Long elderId, Long tenantId, String billingMonth, boolean isBatch) {
        // 校验账单是否已确认
        if (isExpenseOrderConfirmed(expenseOrder, isBatch)) {
            return; // 如果已确认则直接返回
        }
        if(isExpenseOrderLock(expenseOrder,isBatch)){
            return;
        }

        YearMonth attributionBillTime = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
        List<ExpenseItemRespVO> dataList = new ArrayList<>(collectDailyExpenseItemsHasBill(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(calculateDiscountDeduction(elderId, billingMonth));

        BigDecimal amount = BigDecimal.ZERO;
        for (ExpenseItemRespVO item : dataList) {
            ExpenseOrderItemDO orderItem = new ExpenseOrderItemDO();
            orderItem.setExpenseOrderId(expenseOrder.getId());
            orderItem.setSourceExpenseItemId(item.getSourceExpenseItemId());
            orderItem.setExpenseSource(item.getExpenseSource());
            orderItem.setCount(item.getCount());
            orderItem.setItemName(item.getItemName());
            orderItem.setItemCategoryName(item.getItemCategory());
            orderItem.setPrice(item.getPrice());  // 单价
            orderItem.setActualPrice(item.getPrice()); // 实际单价
            orderItem.setTotalAmount(item.getTotalAmount());  // 总金额
            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(tenantId);
            orderItem.setStartDate(item.getStartTime() != null ? LocalDate.parse(item.getStartTime()) : null);
            orderItem.setEndDate(item.getEndTime() != null ? LocalDate.parse(item.getEndTime()) : null);
            expenseOrderItemMapper.insert(orderItem);

            amount = amount.add(orderItem.getTotalAmount().setScale(2, RoundingMode.DOWN));
            if (item.getExpenseSource().equals(BusinessConstants.DISCOUNT_DEDUCTION)) {
                addDiscountRecord(elderId, billingMonth, item);
            }
            // 判断是否生成过账单
            DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(item.getSourceExpenseItemId());
            if (dailyExpenses != null) {
                dailyExpenses.setIsGenerateBill(BooleanEnum.TRUE.getValue());
                dailyExpensesMapper.updateById(dailyExpenses);
            }
        }

        if (CollectionUtil.isNotEmpty(dataList)) {
            expenseOrder.setPayStatus(BooleanEnum.FALSE.getValue());
            // 更新actualAmount
            BigDecimal currentActualAmount = expenseOrder.getActualAmount() != null ? expenseOrder.getActualAmount() : BigDecimal.ZERO;
            expenseOrder.setActualAmount(currentActualAmount.add(amount)); //更新实际金额
            expenseOrder.setPayType(null);
        }
        expenseOrderMapper.updateById(expenseOrder);
    }


    @Override
    public Map<String, Object> importOneTimeExpenseToOrder(List<OneTimeExpenseImportExcelVO> list) {
        List<OneTimeExpenseImportExcelVO> errorList = new ArrayList<>();
        int successCount = 0;

        for (OneTimeExpenseImportExcelVO importVO : list) {
            StringBuilder errorMsg = new StringBuilder();
            boolean hasError = false;

            // 校验长者姓名
            if (StringUtils.isEmpty(importVO.getElderName())) {
                errorMsg.append("长者姓名不能为空；");
                hasError = true;
            }

            // 校验身份证号
            if (StringUtils.isEmpty(importVO.getIdCardNumber())) {
                errorMsg.append("长者身份证号不能为空；");
                hasError = true;
            }

            // 校验收费项分类名称
            if (StringUtils.isEmpty(importVO.getItemCategoryName())) {
                errorMsg.append("收费项分类名称不能为空；");
                hasError = true;
            }

            // 校验收费项名称
            if (StringUtils.isEmpty(importVO.getItemName())) {
                errorMsg.append("收费项名称不能为空；");
                hasError = true;
            }

            // 校验价格
            if (importVO.getPrice() == null) {
                errorMsg.append("价格不能为空；");
                hasError = true;
            }

            // 如果基础字段校验失败，记录错误并跳过
            if (hasError) {
                importVO.setErrorMsg(errorMsg.toString());
                errorList.add(importVO);
                continue;
            }

            // 校验长者是否存在（通过姓名和身份证号联合查询）
            ElderlyInfoDO elder = elderlyInfoMapper.selectOne(new LambdaQueryWrapperX<ElderlyInfoDO>()
                    .eq(ElderlyInfoDO::getElderName, importVO.getElderName())
                    .eq(ElderlyInfoDO::getIdCard, importVO.getIdCardNumber())
                    .eq(ElderlyInfoDO::getTenantId, TenantContextHolder.getTenantId()));

            if (elder == null) {
                importVO.setErrorMsg("未找到匹配的长者信息（请确认长者姓名和身份证号是否正确）");
                errorList.add(importVO);
                continue;
            }

            // TODO: 这里应该添加创建一次性费用的逻辑
            //查询长者的入院账单
            ExpenseOrderDO expenseOrderDO = expenseOrderMapper.selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                    .eq(ExpenseOrderDO::getElderId, elder.getId())
                    .eq(ExpenseOrderDO::getType, 1)
                    .eq(ExpenseOrderDO::getTenantId, TenantContextHolder.getTenantId()));

            if (expenseOrderDO != null) {
                // 单个处理时校验账单是否已确认
                isExpenseOrderConfirmed(expenseOrderDO);
                // 单个处理时校验账单是否已锁定
                isExpenseOrderLock(expenseOrderDO);
            }

            if (expenseOrderDO == null) {
                importVO.setErrorMsg("未找到匹配的长者入院账单");
                errorList.add(importVO);
                continue;
            }

            List<ExpenseDO> expenseList = expenseMapper.selectList(new LambdaQueryWrapperX<ExpenseDO>()
                    .eq(ExpenseDO::getElderId, elder.getId())
                    .eq(ExpenseDO::getTenantId, TenantContextHolder.getTenantId())
                    .orderByDesc(ExpenseDO::getCreatedTime));

            ExpenseDO expenseDO = expenseList.stream().findFirst().get();

            OverheadChargeDO overheadChargeDO = sysOverheadChargeMapper.selectOne(new LambdaQueryWrapperX<OverheadChargeDO>()
                    .eq(OverheadChargeDO::getChargeName, importVO.getItemName())
                    .eq(OverheadChargeDO::getTenantId, elder.getTenantId()));

            //创建费项
            ExpenseItemDO expenseItemDO = new ExpenseItemDO();
            expenseItemDO.setExpenseId(expenseDO.getId());
            expenseItemDO.setItemId(overheadChargeDO.getId() == null ? null : overheadChargeDO.getId());
            expenseItemDO.setItemCategoryId(Long.valueOf(overheadChargeDO.getSuperiorsId()));
            expenseItemDO.setItemCategoryName(importVO.getItemCategoryName());
            expenseItemDO.setItemName(importVO.getItemName());
            expenseItemDO.setAmount(importVO.getPrice());
            expenseItemDO.setActualAmount(importVO.getPrice());
            expenseItemDO.setIsMonthlyExpense(BooleanEnum.FALSE.getValue());
            expenseItemDO.setCount(importVO.getCount());
            expenseItemDO.setTotalAmount(expenseItemDO.getAmount().multiply(BigDecimal.valueOf(expenseItemDO.getCount())).setScale(2, RoundingMode.HALF_UP));
            expenseItemDO.setCreateTime(new Date());
            if (importVO.getIsDeposit() == 1) {
                expenseItemDO.setIsDeposit(BooleanEnum.TRUE.getValue());
            } else {
                expenseItemDO.setIsOneTimeFee(BooleanEnum.TRUE.getValue());
            }
            expenseItemMapper.insert(expenseItemDO);

            //生成子账单
            ExpenseOrderItemDO orderItemDO = new ExpenseOrderItemDO();
            orderItemDO.setSourceExpenseItemId(expenseItemDO.getId());
            orderItemDO.setExpenseSource(BusinessConstants.EXPENSE_ITEM);
            orderItemDO.setCount(importVO.getCount());
            orderItemDO.setItemName(importVO.getItemName());
            orderItemDO.setExpenseOrderId(expenseOrderDO.getId());
            orderItemDO.setPrice(importVO.getPrice());
            orderItemDO.setActualPrice(importVO.getPrice());
            orderItemDO.setTotalAmount(orderItemDO.getActualPrice().multiply(BigDecimal.valueOf(orderItemDO.getCount())).setScale(8, RoundingMode.DOWN));
            orderItemDO.setRoundAmount(orderItemDO.getTotalAmount().setScale(0, RoundingMode.HALF_UP));
            orderItemDO.setRoundTwoDecimalAmount(orderItemDO.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
            orderItemDO.setCreatedTime(new Date());
            orderItemDO.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
            orderItemDO.setItemCategoryName(importVO.getItemCategoryName());
            orderItemDO.setTenantId(expenseOrderDO.getTenantId());
            orderItemDO.setDescription(importVO.getRemarks());
            expenseOrderItemMapper.insert(orderItemDO);
            //更新金额
            expenseOrderDO.setActualAmount(expenseOrderDO.getActualAmount().add(orderItemDO.getTotalAmount().setScale(2, RoundingMode.DOWN)));
            expenseOrderMapper.updateById(expenseOrderDO);
            successCount++;
        }

        Map<String, Object> result = new HashMap<>();
        result.put("successCount", successCount);
        result.put("failCount", errorList.size());
        result.put("errorList", errorList);
        return result;
    }


    @Override
    @LogRecord(type = EXPENSE_ORDER_CANCEL_PAYMENT_TYPE, subType = EXPENSE_ORDER_CANCEL_PAYMENT_SUB_TYPE, bizNo = "{{#elderlyInfo.id}}",
            success = EXPENSE_ORDER_CANCEL_PAYMENT_SUCCESS)
    @Transactional
    public void cancelPayment(Long id) {
        // 1. 查询账单信息
        ExpenseOrderDO expenseOrder = expenseOrderMapper.selectById(id);
        if (expenseOrder == null) {
            throw exception(ErrorCodeConstants.COMMON_NOT_FOUND);
        }
        ElderlyInfoDO elderlyInfo = elderlyInfoMapper.selectById(expenseOrder.getElderId());
        // 设置日志记录变量
        LogRecordContext.putVariable("operator", SecurityFrameworkUtils.getLoginUserNickname());
        LogRecordContext.putVariable("elderName", elderlyInfo.getElderName());
        LogRecordContext.putVariable("billingMonth", expenseOrder.getBillingMonth());
        LogRecordContext.putVariable("elderlyInfo", elderlyInfo);

        // 2. 校验账单状态，只有已缴费的账单才能撤销
//        if (!expenseOrder.getPayStatus().equals(BooleanEnum.TRUE.getValue())) {
//            throw exception(EXPENSE_ORDER_CANCEL_PAYMENT_FAIL_NOT_PAID);
//        }

        // 3. 删除相关的支付订单项
        List<ExpensePayOrderDO> payOrders = expensePayOrderMapper.selectList(new LambdaQueryWrapperX<ExpensePayOrderDO>()
                .eq(ExpensePayOrderDO::getExpenseOrderId, id));
        for (ExpensePayOrderDO payOrder : payOrders) {
            // 删除支付订单项
            expensePayOrderItemMapper.delete(new LambdaQueryWrapperX<ExpensePayOrderItemDO>()
                    .eq(ExpensePayOrderItemDO::getPayOrderId, payOrder.getId()));
        }

        // 4. 删除支付订单
        expensePayOrderMapper.delete(new LambdaQueryWrapperX<ExpensePayOrderDO>()
                .eq(ExpensePayOrderDO::getExpenseOrderId, id));

        // 5. 更新账单明细项状态为未缴费
        expenseOrderItemMapper.update(null, new LambdaUpdateWrapper<ExpenseOrderItemDO>()
                .set(ExpenseOrderItemDO::getPayStatus, BooleanEnum.FALSE.getValue())
                .set(ExpenseOrderItemDO::getPayTime, null)
                .set(ExpenseOrderItemDO::getPayOrderId, null)
                .set(ExpenseOrderItemDO::getPayAmount,BigDecimal.ZERO)
                .eq(ExpenseOrderItemDO::getExpenseOrderId, id));

        // 6. 更新账单状态为未缴费
        LambdaUpdateWrapper<ExpenseOrderDO> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(ExpenseOrderDO::getPayStatus, BooleanEnum.FALSE.getValue());
        updateWrapper.set(ExpenseOrderDO::getPayTime, null);
        updateWrapper.set(ExpenseOrderDO::getPayableAmount, BigDecimal.ZERO);
        updateWrapper.set(ExpenseOrderDO::getPayeeName, "");
        updateWrapper.set(ExpenseOrderDO::getPayType, "");
        updateWrapper.eq(ExpenseOrderDO::getId, expenseOrder.getId());
        expenseOrderMapper.update(null, updateWrapper);


        // 7. 更新相关日常费用状态为未缴费
        List<ExpenseOrderItemDO> orderItems = expenseOrderItemMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderItemDO>()
                .eq(ExpenseOrderItemDO::getExpenseOrderId, id));
        for (ExpenseOrderItemDO orderItem : orderItems) {
            if (BusinessConstants.DAILY_EXPENSES.equals(orderItem.getExpenseSource())) {
                DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(orderItem.getSourceExpenseItemId());
                if (dailyExpenses != null) {
                    dailyExpenses.setStatus(BooleanEnum.FALSE.getValue());
                    dailyExpenses.setExpenseOrderNumber("");
                    dailyExpensesMapper.updateById(dailyExpenses);
                }
            }
        }

        // 8. 如果是押金缴费，需要回滚押金记录
        for (ExpenseOrderItemDO orderItem : orderItems) {
            if (BusinessConstants.EXPENSE_ITEM.equals(orderItem.getExpenseSource())) {
                ExpenseItemDO expenseItem = expenseItemMapper.selectById(orderItem.getSourceExpenseItemId());
                if (expenseItem != null && expenseItem.getIsDeposit() != null && expenseItem.getIsDeposit().equals(BooleanEnum.TRUE.getValue())) {
                    DepositDO deposit = depositMapper.selectOne(new LambdaQueryWrapperX<DepositDO>()
                            .eq(DepositDO::getElderId, expenseOrder.getElderId()));
                    if (deposit != null) {
                        // 删除押金记录
                        depositRecordMapper.delete(new LambdaQueryWrapperX<DepositRecordDO>()
                                .eq(DepositRecordDO::getElderId, expenseOrder.getElderId())
                                .eq(DepositRecordDO::getType, 1) // 缴纳类型
                                .last("ORDER BY created_time DESC LIMIT 1")); // 最新的一条

                        // 回滚押金金额
                        deposit.setAmount(deposit.getAmount().subtract(orderItem.getTotalAmount()));
                        depositMapper.updateById(deposit);
                    }
                }
            }
        }
        // 9. 回退余额
        BalanceRecordDO balanceRecordDO = balanceRecordMapper.selectOne(new LambdaQueryWrapperX<BalanceRecordDO>()
                .eq(BalanceRecordDO::getOrderNumber, expenseOrder.getBillOrderNumber())
                .eq(BalanceRecordDO::getType,3));
        if(balanceRecordDO != null) {
            BalanceRecordDO refundBalance = new BalanceRecordDO();
            BalanceDO balance = balanceMapper.selectOne(new LambdaQueryWrapperX<BalanceDO>().eq(BalanceDO::getElderId, expenseOrder.getElderId()));
            refundBalance.setPayType(expenseOrder.getPayType());
            refundBalance.setChangeAmount(balanceRecordDO.getChangeAmount());
            refundBalance.setType(2); //消费
            refundBalance.setElderId(balance.getElderId());
            refundBalance.setCreatedTime(new Date());
            refundBalance.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
            refundBalance.setRemarks("账单撤销,撤销月份：" + expenseOrder.getBillingMonth() + ",退回金额：" + refundBalance.getChangeAmount());
            balanceRecordMapper.insert(refundBalance);
            balance.setAmount(balance.getAmount().add(refundBalance.getChangeAmount()));
            balanceMapper.updateById(balance);
        }
    }

    @Override
    @Transactional
    public void deleteExpenseOrder(Long id) {
        // 1. 查询账单信息
        ExpenseOrderDO expenseOrder = expenseOrderMapper.selectById(id);
        if (expenseOrder == null) {
            throw exception(ErrorCodeConstants.COMMON_NOT_FOUND);
        }

        // 2. 校验账单状态，只有未缴费的账单才能删除
        if (!expenseOrder.getPayStatus().equals(BooleanEnum.FALSE.getValue())) {
            throw exception(EXPENSE_ORDER_DELETE_FAIL_PAID);
        }

        // 3. 删除账单明细项
        List<ExpenseOrderItemDO> orderItems = expenseOrderItemMapper.selectList(
                new LambdaQueryWrapperX<ExpenseOrderItemDO>().eq(ExpenseOrderItemDO::getExpenseOrderId, id));

        for (ExpenseOrderItemDO orderItem : orderItems) {
            // 4. 如果是日常费用，需要更新日常费用状态
            if (BusinessConstants.DAILY_EXPENSES.equals(orderItem.getExpenseSource())) {
                DailyExpensesDO dailyExpenses = dailyExpensesMapper.selectById(orderItem.getSourceExpenseItemId());
                if (dailyExpenses != null) {
                    dailyExpenses.setStatus(BooleanEnum.FALSE.getValue());
                    dailyExpenses.setIsGenerateBill(BooleanEnum.FALSE.getValue());
                    dailyExpenses.setExpenseOrderNumber(null);
                    dailyExpensesMapper.updateById(dailyExpenses);
                }
            } else if (orderItem.getExpenseSource().equals(BusinessConstants.DISCOUNT_DEDUCTION)) {
                DiscountRecordDO discountRecordDO = discountRecordMapper.selectOne(new LambdaQueryWrapperX<DiscountRecordDO>()
                        .eq(DiscountRecordDO::getElderId, expenseOrder.getElderId())
                        .eq(DiscountRecordDO::getBillingMonth, expenseOrder.getBillingMonth())
                        .eq(DiscountRecordDO::getRuleId, orderItem.getSourceExpenseItemId()));
                if (discountRecordDO != null) {
                    discountRecordMapper.deleteById(discountRecordDO);
                }
            }
            // 删除账单明细项
            expenseOrderItemMapper.deleteById(orderItem.getId());
        }
        // 长护险更新为未抵扣状态
        expenseSubsidyMapper.update(new ExpenseSubsidyDO()
                        .setStatus(BooleanEnum.FALSE.getValue()),
                new LambdaUpdateWrapper<ExpenseSubsidyDO>()
                        .eq(ExpenseSubsidyDO::getElderId, expenseOrder.getElderId())
                        .eq(ExpenseSubsidyDO::getDeductionBillMonth, expenseOrder.getBillingMonth())
                        .eq(ExpenseSubsidyDO::getTenantId, TenantContextHolder.getTenantId()));
        // 5. 删除主账单
        expenseOrderMapper.deleteById(id);
    }

    @Override
    @Transactional
    public void editExpenseOrder(ExpenseOrderEditSaveVO saveVO) {
        Long orderId = saveVO.getId();
        ExpenseOrderDO expenseOrderDO = expenseOrderMapper.selectById(orderId);
        // 单个处理时校验账单是否已确认
        isExpenseOrderConfirmed(expenseOrderDO);
        // 单个处理时校验账单是否已锁定
        isExpenseOrderLock(expenseOrderDO);
        BigDecimal totalAmount = BigDecimal.ZERO;
        List<ExpenseOrderItemEditVO> items = saveVO.getItems();
        List<DailyExpensesDO> updateDailyExpenseList = new ArrayList<>();
        for (ExpenseOrderItemEditVO editVO : items) {
            ExpenseOrderItemDO orderItemDO = expenseOrderItemMapper.selectById(editVO.getId());
            orderItemDO.setIsShow(editVO.getIsShow());
            orderItemDO.setPrice(editVO.getPrice());
            orderItemDO.setActualPrice(editVO.getActualPrice());
            orderItemDO.setTotalAmount(editVO.getTotalAmount());
            orderItemDO.setRoundAmount(orderItemDO.getTotalAmount().setScale(0, RoundingMode.HALF_UP));
            orderItemDO.setRoundTwoDecimalAmount(orderItemDO.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
            orderItemDO.setDescription(editVO.getDescription() == null ? "" : editVO.getDescription());
            expenseOrderItemMapper.updateById(orderItemDO);
            if(editVO.getExpenseSource().equals(BusinessConstants.DAILY_EXPENSES)){
                DailyExpensesDO update = new DailyExpensesDO();
                update.setId(editVO.getSourceExpenseItemId());
                update.setRemarks(editVO.getDescription());
                update.setCount(editVO.getCount());
                update.setPrice(editVO.getPrice());
                update.setAmount(editVO.getTotalAmount());
                updateDailyExpenseList.add(update);
            }
            totalAmount = totalAmount.add(orderItemDO.getTotalAmount());
        }
        expenseOrderDO.setActualAmount(totalAmount);
        expenseOrderMapper.updateById(expenseOrderDO);
        if(CollectionUtils.isNotEmpty(updateDailyExpenseList)){
            dailyExpensesMapper.updateBatch(updateDailyExpenseList);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @TenantIgnore
    public void markCheckinBillsPaid(MarkCheckinBillsPaidReqVO reqVO) {
        // 参考 payOrder：保持“有缴费，就一定生成缴费单（ExpensePayOrder）和缴费方式明细（ExpensePayOrderItem）”，
        // 同时把账单与账单明细的缴费信息补齐，确保后续查询/导出/统计一致。

        // 1. 查询符合条件的入院账单（类型=1 入院账单，创建时间 < 截止时间，且未完全缴费）
        List<ExpenseOrderDO> expenseOrders = expenseOrderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
                .eq(ExpenseOrderDO::getType, 1)
                .eq(ExpenseOrderDO::getTenantId, reqVO.getTenantId())
                .lt(ExpenseOrderDO::getCreatedTime, reqVO.getCutoffTime())
                .ne(ExpenseOrderDO::getPayStatus, BooleanEnum.TRUE.getValue()));

        if (CollectionUtil.isEmpty(expenseOrders)) {
            return;
        }

        markBillsPaid(expenseOrders);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @TenantIgnore
    public void markSupportElderlyBillsPaid(MarkSupportElderlyBillsPaidReqVO reqVO) {
        // 1. 先查询该机构下供养长者（照护类型=1）
        List<ElderlyInfoDO> supportElders = elderlyInfoMapper.selectList(new LambdaQueryWrapperX<ElderlyInfoDO>()
                .eq(ElderlyInfoDO::getTenantId, reqVO.getTenantId())
                .eq(ElderlyInfoDO::getCareType, 1));
        if (CollectionUtil.isEmpty(supportElders)) {
            return;
        }

        Set<Long> supportElderIds = supportElders.stream().map(ElderlyInfoDO::getId).collect(Collectors.toSet());

        // 2. 查询账单月+机构下，未缴清的账单
        List<ExpenseOrderDO> expenseOrders = expenseOrderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
                .eq(ExpenseOrderDO::getBillingMonth, reqVO.getBillingMonth())
                .eq(ExpenseOrderDO::getTenantId, reqVO.getTenantId())
                .in(ExpenseOrderDO::getElderId, supportElderIds)
                .ne(ExpenseOrderDO::getPayStatus, BooleanEnum.TRUE.getValue()));

        if (CollectionUtil.isEmpty(expenseOrders)) {
            return;
        }

        markBillsPaid(expenseOrders);
    }

    /**
     * 批量将账单置为已缴费（系统补缴）
     */
    private void markBillsPaid(List<ExpenseOrderDO> expenseOrders) {
        Date payTime = new Date();
        String payeeName = SecurityFrameworkUtils.getLoginUserNickname();

        for (ExpenseOrderDO expenseOrder : expenseOrders) {
            List<ExpenseOrderItemDO> toPayItems = expenseOrderItemMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderItemDO>()
                    .eq(ExpenseOrderItemDO::getExpenseOrderId, expenseOrder.getId())
                    .eq(ExpenseOrderItemDO::getPayStatus, BooleanEnum.FALSE.getValue()));

            if (CollectionUtil.isEmpty(toPayItems)) {
                continue;
            }

            BigDecimal payAmount = toPayItems.stream()
                    .map(ExpenseOrderItemDO::getTotalAmount)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add)
                    .setScale(2, RoundingMode.HALF_UP);

            ExpensePayOrderDO payOrder = new ExpensePayOrderDO();
            payOrder.setPayAmount(payAmount);
            payOrder.setRoundPayAmount(payAmount.setScale(0, RoundingMode.HALF_UP));
            payOrder.setRoundPayTwoDecimalAmount(payAmount.setScale(2, RoundingMode.HALF_UP));
            payOrder.setExpenseOrderId(expenseOrder.getId());
            payOrder.setCreatedTime(payTime);
            payOrder.setType(BusinessConstants.EXPENSE_PAY_ORDER_MONTH);
            payOrder.setCreatedBy(payeeName);
            payOrder.setTenantId(expenseOrder.getTenantId());
            expensePayOrderMapper.insert(payOrder);

            ExpensePayOrderItemDO payOrderItem = new ExpensePayOrderItemDO();
            payOrderItem.setPayOrderId(payOrder.getId());
            payOrderItem.setPayType("system");
            payOrderItem.setPayAmount(payAmount);
            payOrderItem.setRoundPayAmount(payAmount.setScale(0, RoundingMode.HALF_UP));
            payOrderItem.setRoundPayTwoDecimalAmount(payAmount.setScale(2, RoundingMode.HALF_UP));
            payOrderItem.setCreatedTime(payTime);
            payOrderItem.setCreatedBy(payeeName);
            payOrderItem.setTenantId(expenseOrder.getTenantId());
            expensePayOrderItemMapper.insert(payOrderItem);

            for (ExpenseOrderItemDO item : toPayItems) {
                ExpenseOrderItemDO updateItem = new ExpenseOrderItemDO();
                updateItem.setId(item.getId());
                updateItem.setPayStatus(BooleanEnum.TRUE.getValue());
                updateItem.setPayTime(payTime);
                updateItem.setPayOrderId(payOrder.getId());
                expenseOrderItemMapper.updateById(updateItem);

                DailyExpensesDO dailyExpenses = null;
                if (Objects.equals(item.getExpenseSource(), BusinessConstants.DAILY_EXPENSES)) {
                    dailyExpenses = dailyExpensesMapper.selectById(item.getSourceExpenseItemId());
                }

                boolean isDailyDeposit = dailyExpenses != null && (
                        (dailyExpenses.getItemName() != null &&
                                (dailyExpenses.getItemName().contains("押金") ||
                                        dailyExpenses.getItemName().contains("风险金") ||
                                        dailyExpenses.getItemName().contains("预备金") ||
                                        dailyExpenses.getItemName().contains("风险预备金"))) ||
                                (dailyExpenses.getItemCategory() != null &&
                                        (dailyExpenses.getItemCategory().contains("押金") ||
                                                dailyExpenses.getItemCategory().contains("风险金") ||
                                                dailyExpenses.getItemCategory().contains("预备金") ||
                                                dailyExpenses.getItemCategory().contains("风险预备金"))));

                ExpenseItemDO expenseItemDO = null;
                if (Objects.equals(item.getExpenseSource(), BusinessConstants.EXPENSE_ITEM)) {
                    expenseItemDO = expenseItemMapper.selectById(item.getSourceExpenseItemId());
                }
                boolean isMonthlyDeposit = expenseItemDO != null && Objects.equals(expenseItemDO.getIsDeposit(), BooleanEnum.TRUE.getValue());

                if (isDailyDeposit || isMonthlyDeposit) {
                    Long recordItemId = isDailyDeposit ? dailyExpenses.getId() : expenseItemDO.getItemId();
                    DepositRecordDO existed = depositRecordMapper.selectOne(new LambdaQueryWrapperX<DepositRecordDO>()
                            .eq(DepositRecordDO::getElderId, expenseOrder.getElderId())
                            .eq(DepositRecordDO::getType, 1)
                            .eq(DepositRecordDO::getItemId, recordItemId));
                    if (existed != null) {
                        continue;
                    }

                    DepositDO dbDeposit = depositMapper.selectOne(new LambdaQueryWrapperX<DepositDO>()
                            .eq(DepositDO::getElderId, expenseOrder.getElderId())
                            .eq(DepositDO::getTenantId, expenseOrder.getTenantId()));
                    if (dbDeposit == null) {
                        dbDeposit = depositMapper.selectOne(new LambdaQueryWrapperX<DepositDO>()
                                .eq(DepositDO::getElderId, expenseOrder.getElderId()));
                    }
                    if (dbDeposit == null) {
                        continue;
                    }

                    DepositRecordDO depositRecordDO = new DepositRecordDO();
                    depositRecordDO.setType(1);
                    depositRecordDO.setItemName(isDailyDeposit ? dailyExpenses.getItemName() : expenseItemDO.getItemName());
                    depositRecordDO.setPayType("system");
                    depositRecordDO.setItemId(recordItemId);
                    depositRecordDO.setCreatedTime(payTime);
                    depositRecordDO.setRemarks(isDailyDeposit ? "日常费用押金缴纳" : "入院押金缴纳");
                    depositRecordDO.setElderId(expenseOrder.getElderId());
                    BigDecimal depositAmount = isDailyDeposit ? dailyExpenses.getAmount() : item.getTotalAmount();
                    depositRecordDO.setAmount(depositAmount);
                    depositRecordDO.setCreatedBy(payeeName);
                    depositRecordDO.setTenantId(dbDeposit.getTenantId());
                    depositRecordMapper.insert(depositRecordDO);

                    dbDeposit.setAmount((dbDeposit.getAmount() == null ? BigDecimal.ZERO : dbDeposit.getAmount()).add(depositAmount));
                    depositMapper.updateById(dbDeposit);

                    if (dailyExpenses != null) {
                        dailyExpenses.setStatus(BooleanEnum.TRUE.getValue());
                        dailyExpenses.setExpenseOrderNumber(expenseOrder.getBillOrderNumber());
                        dailyExpensesMapper.updateById(dailyExpenses);
                    }
                }
            }

            ExpenseOrderDO updateOrder = new ExpenseOrderDO();
            updateOrder.setId(expenseOrder.getId());
            updateOrder.setPayTime(payTime);
            updateOrder.setPayeeName(payeeName);
            updateOrder.setPayType("system");
            updateOrder.setPayableAmount(expenseOrder.getActualAmount() == null ? payAmount : expenseOrder.getActualAmount());
            updateOrder.setPayStatus(BooleanEnum.TRUE.getValue());
            expenseOrderMapper.updateById(updateOrder);
        }
    }

    @Override
    public void updateConfirmStatus(Long orderId, boolean confirmStatus) {
        ExpenseOrderDO expenseOrder = expenseOrderMapper.selectById(orderId);
        if (expenseOrder == null) {
            throw exception(ErrorCodeConstants.COMMON_NOT_FOUND);
        }

        expenseOrder.setConfirmStatus(confirmStatus);
        expenseOrderMapper.updateById(expenseOrder);

        // 记录日志
        log.info("账单确认状态已更新, 账单ID:{}, 确认状态:{}", orderId, confirmStatus);
    }


    /**
     * 校验账单是否已确认
     *
     * @param expenseOrder 账单对象
     * @param isBatch      是否批量处理
     * @return true表示已确认，false表示未确认
     * @throws ServiceException 如果是单个处理且账单已确认
     */
    private boolean isExpenseOrderConfirmed(ExpenseOrderDO expenseOrder, boolean isBatch) {
        // 如果账单已确认
        if (Boolean.TRUE.equals(expenseOrder.getConfirmStatus())) {
            // 批量处理时直接返回true，表示需要跳过
            if (isBatch) {
                log.info("账单已确认，跳过处理, 账单ID:{}", expenseOrder.getId());
                return true;
            } else {
                // 单个处理时报错
                throw exception(EXPENSE_ORDER_ALREADY_CONFIRMED);
            }
        }
        return false;
    }

    /**
     * 校验账单是否已锁定
     *
     * @param expenseOrder 账单对象
     * @param isBatch      是否批量处理
     * @return true表示已确认，false表示未确认
     * @throws ServiceException 如果是单个处理且账单已确认
     */
    private boolean isExpenseOrderLock(ExpenseOrderDO expenseOrder, boolean isBatch) {
        // 如果账单已确认
        if (Boolean.TRUE.equals(expenseOrder.getIsLock())) {
            // 批量处理时直接返回true，表示需要跳过
            if (isBatch) {
                log.info("账单已锁定，跳过处理, 账单ID:{}", expenseOrder.getId());
                return true;
            } else {
                // 单个处理时报错
                throw exception(EXPENSE_ORDER_ALREADY_LOCK);
            }
        }
        return false;
    }

    /**
     * 校验账单是否已确认（默认为单个处理）
     *
     * @param expenseOrder 账单对象
     * @throws ServiceException 如果账单已确认
     */
    private void isExpenseOrderConfirmed(ExpenseOrderDO expenseOrder) {
        isExpenseOrderConfirmed(expenseOrder, false);
    }

    /**
     * 校验账单是否已锁定（默认为单个处理）
     *
     * @param expenseOrder 账单对象
     * @throws ServiceException 如果账单已锁定
     */
    private void isExpenseOrderLock(ExpenseOrderDO expenseOrder) {
        isExpenseOrderLock(expenseOrder, false);
    }

    /**
     * 根据账单ID向长者亲属发送账单通知短信
     *
     * @param orderIds 账单ID列表
     */
    @Override
    public void sendSmsBillOrder(List<Long> orderIds) {
        processBatchBillNotifications(orderIds);
    }


    /**
     * 检查长者合同是否准备到期
     *
     * @param elderId
     * @return
     */
    @Override
    public boolean checkContractNearlyExpire(Long elderId, String billingMonth) {
        LocalDate localDate = YearMonth.parse(billingMonth).atEndOfMonth();
        List<ElderlyContractDO> elderlyContractDOS = elderlyContractMapper.selectList(new LambdaQueryWrapperX<ElderlyContractDO>()
                .eq(ElderlyContractDO::getElderId, elderId)
                .in(ElderlyContractDO::getStatus, 1, 2)
                .gt(ElderlyContractDO::getExpireTime, localDate));
        // 有数据则通过
        return CollectionUtil.isNotEmpty(elderlyContractDOS);
    }

    /**
     * 批量处理账单通知
     *
     * @param billIds 账单ID列表（批次）
     */
    private void processBatchBillNotifications(List<Long> billIds) {
        try {
            // 批量查询账单信息
            List<ExpenseOrderDO> bills = expenseOrderMapper.selectList(
                    new LambdaQueryWrapperX<ExpenseOrderDO>()
                            .in(ExpenseOrderDO::getId, billIds)
            );

            // 获取所有涉及的长者ID
            Set<Long> elderIds = bills.stream()
                    .map(ExpenseOrderDO::getElderId)
                    .collect(Collectors.toSet());

            // 批量查询长者信息
            List<ElderlyInfoDO> elderlyInfos = elderlyInfoMapper.selectList(
                    new LambdaQueryWrapperX<ElderlyInfoDO>()
                            .in(ElderlyInfoDO::getId, elderIds)
            );

            // 获取长者ID与信息的映射
            Map<Long, ElderlyInfoDO> elderlyInfoMap = elderlyInfos.stream()
                    .collect(Collectors.toMap(ElderlyInfoDO::getId, info -> info));

            // 批量查询所有亲属信息
            List<RelativesDO> allRelatives = relativesMapper.selectList(
                    new LambdaQueryWrapperX<RelativesDO>()
                            .in(RelativesDO::getPeopleId, elderIds)
            );

            // 按长者ID分组亲属
            Map<Long, List<RelativesDO>> relativesByElder = allRelatives.stream()
                    .collect(Collectors.groupingBy(RelativesDO::getPeopleId));

            // 按账单ID分组账单
            Map<Long, ExpenseOrderDO> billMap = bills.stream()
                    .collect(Collectors.toMap(ExpenseOrderDO::getId, bill -> bill));

            // 为每个账单发送短信
            for (Long billId : billIds) {
                ExpenseOrderDO bill = billMap.get(billId);
                if (bill == null) {
                    log.warn("未找到账单信息，账单ID: {}", billId);
                    continue;
                }

                Long elderId = bill.getElderId();
                ElderlyInfoDO elderlyInfo = elderlyInfoMap.get(elderId);
                if (elderlyInfo == null) {
                    log.warn("未找到长者信息，长者ID: {}", elderId);
                    continue;
                }

                // 获取账单总金额
                BigDecimal billAmount = bill.getActualAmount();
                if (billAmount == null || billAmount.compareTo(BigDecimal.ZERO) <= 0) {
                    log.info("账单金额为0或为空，跳过发送短信，账单ID: {}", billId);
                    continue;
                }

                // 发送短信
                List<RelativesDO> relatives = relativesByElder.get(elderId);
                if (CollectionUtil.isNotEmpty(relatives)) {
                    sendBillNotificationToRelatives(
                            elderlyInfo,
                            relatives,
                            billAmount,
                            bill.getBillingMonth()
                    );
                } else {
                    log.warn("长者没有亲属信息，长者ID: {}", elderId);
                }
            }
        } catch (Exception e) {
            log.error("批量处理账单通知时发生异常，账单IDs: {}", billIds, e);
        }
    }

    /**
     * 为单个账单构建费用清单详情
     *
     * @param bill 账单
     * @return 费用清单详情字符串
     */
    private String buildFeeDetailsForBill(ExpenseOrderDO bill) {
        StringBuilder feeDetailsBuilder = new StringBuilder();
        try {

            // 查询账单项
            List<ExpenseOrderItemDO> items = expenseOrderItemMapper.selectList(
                    new LambdaQueryWrapperX<ExpenseOrderItemDO>()
                            .eq(ExpenseOrderItemDO::getExpenseOrderId, bill.getId())
            );

            // 构建费用项详情
            List<String> itemDetails = new ArrayList<>();
            for (ExpenseOrderItemDO item : items) {
                if (item.getTotalAmount() != null && item.getTotalAmount().compareTo(BigDecimal.ZERO) != 0) {
                    StringBuilder itemBuilder = new StringBuilder();
                    itemBuilder.append(item.getItemName())
                            .append("：")
                            .append(item.getTotalAmount().setScale(2, RoundingMode.HALF_UP))
                            .append("元");
                    itemDetails.add(itemBuilder.toString());
                }
            }

            // 添加费用项详情
            if (!itemDetails.isEmpty()) {
                feeDetailsBuilder.append(String.join("、", itemDetails));
            }
        } catch (Exception e) {
            log.error("为账单构建费用清单详情时发生异常，账单ID: {}", bill.getId(), e);
            return "费用清单详情生成失败";
        }

        return feeDetailsBuilder.toString();
    }

    /**
     * 向指定长者的亲属发送账单通知短信
     *
     * @param elderlyInfo 长者信息
     * @param relatives   亲属列表
     * @param billAmount  账单金额
     */
    private void sendBillNotificationToRelatives(
            ElderlyInfoDO elderlyInfo,
            List<RelativesDO> relatives,
            BigDecimal billAmount,
            String billMonth) {

        String relativeName = elderlyInfo.getElderName();
        String amount = billAmount.setScale(2, RoundingMode.HALF_UP).toString();
        String period = formatBillMonth(billMonth);

        // 向所有亲属发送短信
        for (RelativesDO relative : relatives) {
            if (StringUtils.isNotBlank(relative.getPhone())) {
                //判断机构
                boolean success = false;
                if (relative.getTenantId().equals(197L)) {

                    String templateParam = String.format(
                            "{\"name\":\"%s\",\"period\":\"%s\",\"amount\":\"%s\"}",
                            elderlyInfo.getElderName(), period, amount
                    );
                    // 发送短信
                    success = AliyunSmsUtils.sendSms(
                            relative.getPhone(),                 // 手机号
                            "颐年健康产业集团",                   // 短信签名
                            "SMS_499180024",                     // 短信模板CODE
                            templateParam                        // 模板参数
                    );

                } else {
                    // 构造短信参数
                    String templateParam = String.format(
                            "{\"relativeName\":\"%s\",\"period\":\"%s\",\"amount\":\"%s\"}",
                            relativeName, period, amount
                    );

                    // 发送短信
                    success = AliyunSmsUtils.sendSms(
                            relative.getPhone(),                 // 手机号
                            "颐年健康产业集团",                   // 短信签名
                            "SMS_498320088",                     // 短信模板CODE
                            templateParam                        // 模板参数
                    );
                }

                if (success) {
                    log.info("账单通知短信发送成功，长者: {}，亲属: {}，手机号: {}",
                            relativeName, relative.getName(), relative.getPhone());
                } else {
                    log.error("账单通知短信发送失败，长者: {}，亲属: {}，手机号: {}",
                            relativeName, relative.getName(), relative.getPhone());
                }
            }
        }
    }

    /**
     * 格式化账单月份为"yyyy年M月"格式
     *
     * @param billMonth 账单月份，格式为"yyyy-MM"
     * @return 格式化后的月份，如"2025年9月"
     */
    private String formatBillMonth(String billMonth) {
        try {
            if (StringUtils.isBlank(billMonth)) {
                return "";
            }

            // 解析账单月份
            YearMonth yearMonth = YearMonth.parse(billMonth, DateTimeFormatter.ofPattern("yyyy-MM"));

            // 格式化为"yyyy年M月"格式
            return yearMonth.format(DateTimeFormatter.ofPattern("yyyy年M月"));
        } catch (Exception e) {
            log.warn("格式化账单月份时发生异常，原始月份: {}", billMonth, e);
            return billMonth; // 如果格式化失败，返回原始值
        }
    }

    private void addDiscountRecord(Long elderId, String billingMonth, ExpenseItemRespVO item) {
        DiscountRecordDO discountRecordDO = new DiscountRecordDO();
        discountRecordDO.setElderId(elderId);
        discountRecordDO.setBillingMonth(billingMonth);
        discountRecordDO.setRuleId(item.getSourceExpenseItemId());
        discountRecordDO.setRuleName(item.getItemName());
        discountRecordDO.setTenantId(TenantContextHolder.getTenantId());
        discountRecordDO.setCreateTime(new Date());
        discountRecordDO.setCreator(SecurityFrameworkUtils.getLoginUserNickname());
        discountRecordMapper.insert(discountRecordDO);
    }


    @Override
    public String pushOrder(List<Long> orderIds) {
        if (orderIds.size() > 100) {
            throw exception(ErrorCodeConstants.BILL_PUSH_ORDER_TOO_MANY);
        }

        List<ExpenseOrderDO> expenseOrderList = expenseOrderMapper.selectBatchIds(orderIds);

        // 初始化统计变量
        int totalProcessed = 0;
        int successCount = 0;
        int failCount = 0;
        List<String> failedElderNames = new ArrayList<>();
        List<String> notFoundUsers = new ArrayList<>();
        Set<Long> pushedOrderIds = new HashSet<>(); // 记录已推送的账单ID

        // 逐个处理账单
        for (ExpenseOrderDO expenseOrderDO : expenseOrderList) {
            totalProcessed++;

            ElderlyInfoDO elderlyInfoDO = elderlyInfoMapper.selectById(expenseOrderDO.getElderId());

            // 查询长者的推送人信息
            List<RelativesDO> relativesList = relativesMapper.selectList(new LambdaQueryWrapperX<RelativesDO>()
                    .eq(RelativesDO::getPeopleId, expenseOrderDO.getElderId())
                    .eq(RelativesDO::getIsBillSender, true));

            // 如果没有找到推送人
            if (CollectionUtil.isEmpty(relativesList)) {
                failedElderNames.add(elderlyInfoDO.getElderName());
                failCount++;
                continue;
            }

            // 处理每个推送人
            for (RelativesDO relativesDO : relativesList) {
                TenantDO tenantDO = tenantMapper.selectById(relativesDO.getTenantId());
                String tenantName = tenantDO != null ? tenantDO.getName() : "";

                // 组装推送参数
                BillRequestDTO billRequestDTO = new BillRequestDTO();
                billRequestDTO.setUserPhone(relativesDO.getPhone());
                billRequestDTO.setElderId(expenseOrderDO.getElderId());
                billRequestDTO.setElderName(relativesDO.getPeopleName());
                billRequestDTO.setOrganizationId(relativesDO.getTenantId());
                billRequestDTO.setOrganizationName(tenantName);
                billRequestDTO.setRelationship(relativesDO.getRelation());

                BillRequestDTO.BillMessage billMessage = new BillRequestDTO.BillMessage();
                Integer type = expenseOrderDO.getType();
                if (type == 1) {
                    billMessage.setBillType("入院账单");
                } else if (type == 2) {
                    billMessage.setBillType("月度账单");
                } else if (type == 3) {
                    billMessage.setBillType("试住账单");
                }
                billMessage.setBillDate(expenseOrderDO.getBillingMonth());
                billMessage.setAmount(String.valueOf(expenseOrderDO.getActualAmount().setScale(2,RoundingMode.HALF_UP)));
                billMessage.setElderName(relativesDO.getPeopleName());
                billMessage.setOrganizationName(tenantName);
                billMessage.setOrderId(expenseOrderDO.getId());
                billRequestDTO.setBillMessage(billMessage);

                // 调用推送接口
                List<BillRequestDTO> billList = Arrays.asList(billRequestDTO);
                Map<String,Object> map = new HashMap<>();
                map.put("billList", billList);

                try {
                    ResponseEntity<String> response = restTemplate.postForEntity(
                            "http://home.ynims.com/api/wechat/pushBill", map, String.class);

                    log.info("推送账单结果：{}", response.getBody());

                    // 解析响应结果
                    ObjectMapper objectMapper = new ObjectMapper();
                    JsonNode rootNode = objectMapper.readTree(response.getBody());
                    int code = rootNode.path("code").asInt();

                    if (code == 200) {
                        JsonNode dataNode = rootNode.path("data");
                        successCount += dataNode.path("successCount").asInt(0);
                        failCount += dataNode.path("failCount").asInt(0);

                        // 根据results结果更新账单推送状态
                        JsonNode resultsNode = dataNode.path("results");
                        if (resultsNode != null && resultsNode.isArray() && resultsNode.size() > 0) {
                            for (JsonNode result : resultsNode) {
                                boolean success = result.path("success").asBoolean(false);
                                if (success) {
                                    // 将成功推送的账单ID加入集合
                                    pushedOrderIds.add(expenseOrderDO.getId());
                                }
                            }
                        }


                        // 收集未找到账单的用户信息
                        JsonNode notFoundBillsNode = dataNode.path("notFoundBills");
                        if (notFoundBillsNode != null && notFoundBillsNode.isArray() && notFoundBillsNode.size() > 0) {
                            for (JsonNode notFound : notFoundBillsNode) {
                                String userPhone = notFound.path("userPhone").asText();
                                String reason = notFound.path("reason").asText();
                                notFoundUsers.add(String.format("%s (%s): %s",
                                        billRequestDTO.getElderName(), userPhone, reason));
                            }
                        }
                    } else {
                        failCount++;
                    }
                } catch (Exception e) {
                    log.error("推送账单失败，长者ID: {}", expenseOrderDO.getElderId(), e);
                    failedElderNames.add(elderlyInfoDO.getElderName());
                    failCount++;
                }
            }
        }

        // 批量更新推送成功的账单状态
        if (!pushedOrderIds.isEmpty()) {
            // 批量更新推送状态
            expenseOrderMapper.update(null, new LambdaUpdateWrapper<ExpenseOrderDO>()
                    .set(ExpenseOrderDO::getPushStatus, 1)
                    .in(ExpenseOrderDO::getId, pushedOrderIds));
        }

        // 构建最终结果报告
        StringBuilder resultReport = new StringBuilder();
        resultReport.append(String.format("账单推送完成总共处理：%d个 成功：%d个 失败：%d个",
                totalProcessed, successCount, failCount));

        // 添加未能找到推送人的长者信息
        if (!failedElderNames.isEmpty()) {
            resultReport.append(String.format("\n以下%d位长者未能找到账单推送人：%s",
                    failedElderNames.size(), String.join(", ", failedElderNames)));
        }

        // 添加未关注公众号的用户信息
        if (!notFoundUsers.isEmpty()) {
            resultReport.append(String.format("\n以下%d位长者的亲属未关注公众号或状态异常：", notFoundUsers.size()));
            for (String user : notFoundUsers) {
                resultReport.append("\n").append(user);
            }
        }

        return resultReport.toString();
    }


    @Override
    public List<ExpenseOrderDO> getBillOrderList(ExpenseBillPageReqVO pageReqVO) {
        List<RelativesDO> relativesList = relativesMapper.selectList(new LambdaQueryWrapperX<RelativesDO>()
                .eq(RelativesDO::getPhone, pageReqVO.getUserPhone()));

        if (CollectionUtil.isEmpty(relativesList)) {
            throw exception(COMMON_NOT_FOUND);
        }

        RelativesDO relativesDO = relativesList.get(0);

        List<ExpenseOrderDO> expenseOrders = expenseOrderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
                .eq(ExpenseOrderDO::getElderId, relativesDO.getPeopleId())
                .betweenIfPresent(ExpenseOrderDO::getCreatedTime, pageReqVO.getCreatedTime())
                .eqIfPresent(ExpenseOrderDO::getPayStatus, pageReqVO.getPayStatus())
                .orderByDesc(ExpenseOrderDO::getBillingMonth)
        );
        expenseOrders.forEach(i -> i.setElderName(relativesDO.getPeopleName()));
        if (CollectionUtil.isEmpty(expenseOrders)) {
            throw exception(RELATIVES_NOT_HAVE_BILL);
        }
        return expenseOrders;
    }

    /**
     * 设置费用项目详情类型，临时使用
     * @param expenseOrderItemDO
     */
    private void setExpenseOrderType(ExpenseOrderItemDO expenseOrderItemDO){
        if(expenseOrderItemDO.getType() != null && expenseOrderItemDO.getType() != 0){
            return;
        }
        if(expenseOrderItemDO.getItemName().contains("气垫床管理费")){
            expenseOrderItemDO.setType(23);
        }else if(expenseOrderItemDO.getItemName().contains("预备金")){
            expenseOrderItemDO.setType(22);
        }else if(expenseOrderItemDO.getItemName().contains("居家服务费")){
            expenseOrderItemDO.setType(21);
        }else if(expenseOrderItemDO.getItemName().contains("陪护费")){
            expenseOrderItemDO.setType(20);
        }else if(expenseOrderItemDO.getItemName().contains("炖汤票")){
            expenseOrderItemDO.setType(19);
        }else if(expenseOrderItemDO.getItemName().contains("匀浆膳")){
            expenseOrderItemDO.setType(18);
        }else if(expenseOrderItemDO.getItemName().contains("电器管理费")){
            expenseOrderItemDO.setType(17);
        }else if(expenseOrderItemDO.getItemName().contains("洗衣")){
            expenseOrderItemDO.setType(16);
        }else if(expenseOrderItemDO.getItemName().contains("善后处理费")){
            expenseOrderItemDO.setType(15);
        }else if(expenseOrderItemDO.getItemName().contains("注射费")){
            expenseOrderItemDO.setType(14);
        }else if(expenseOrderItemDO.getItemName().contains("西药费")){
            expenseOrderItemDO.setType(13);
        }else if(expenseOrderItemDO.getItemName().contains("材料费")){
            expenseOrderItemDO.setType(12);
        }else if(expenseOrderItemDO.getItemName().contains("药品管理费") || expenseOrderItemDO.getItemName().contains("药品保管费")){
            expenseOrderItemDO.setType(11);
        }else if(expenseOrderItemDO.getItemName().contains("治疗费")){
            expenseOrderItemDO.setType(10);
        }else if(expenseOrderItemDO.getItemName().contains("护理用品费")){
            expenseOrderItemDO.setType(9);
        }else if(expenseOrderItemDO.getItemName().contains("一次性费用")){
            expenseOrderItemDO.setType(8);
        }else if(expenseOrderItemDO.getItemName().contains("服务标准")){
            expenseOrderItemDO.setType(7);
        }else if(expenseOrderItemDO.getItemName().contains("水电费")){
            expenseOrderItemDO.setType(6);
        }else if(expenseOrderItemDO.getItemName().contains("试住费用")){
            expenseOrderItemDO.setType(5);
        }else if(expenseOrderItemDO.getItemName().contains("押金")){
            expenseOrderItemDO.setType(4);
        }else if(expenseOrderItemDO.getItemName().contains("餐饮标准")){
            expenseOrderItemDO.setType(3);
        }else if(expenseOrderItemDO.getItemName().contains("护理标准")){
            expenseOrderItemDO.setType(2);
        }else if(expenseOrderItemDO.getItemName().contains("床位类型")){
            expenseOrderItemDO.setType(1);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("气垫床管理费")){
            expenseOrderItemDO.setType(23);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("预备金")){
            expenseOrderItemDO.setType(22);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("居家服务费")){
            expenseOrderItemDO.setType(21);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("陪护费")){
            expenseOrderItemDO.setType(20);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("炖汤票")){
            expenseOrderItemDO.setType(19);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("匀浆膳")){
            expenseOrderItemDO.setType(18);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("电器管理费")){
            expenseOrderItemDO.setType(17);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("洗衣")){
            expenseOrderItemDO.setType(16);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("善后处理费")){
            expenseOrderItemDO.setType(15);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("注射费")){
            expenseOrderItemDO.setType(14);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("西药费")){
            expenseOrderItemDO.setType(13);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("材料费")){
            expenseOrderItemDO.setType(12);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("药品管理费") || expenseOrderItemDO.getItemCategoryName().contains("药品保管费")){
            expenseOrderItemDO.setType(11);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("治疗费")){
            expenseOrderItemDO.setType(10);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("护理用品费")){
            expenseOrderItemDO.setType(9);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("一次性费用")){
            expenseOrderItemDO.setType(8);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("服务")){
            expenseOrderItemDO.setType(7);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("水电费")){
            expenseOrderItemDO.setType(6);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("试住费用")){
            expenseOrderItemDO.setType(5);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("押金")){
            expenseOrderItemDO.setType(4);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("餐饮")){
            expenseOrderItemDO.setType(3);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("护理")){
            expenseOrderItemDO.setType(2);
        }else if(expenseOrderItemDO.getItemCategoryName().contains("床位")){
            expenseOrderItemDO.setType(1);
        }
    }

    private List<ExpenseItemRespVO> handlerCollectMonthlyExpense(String billingMonth, Long elderId){
        // 离住/返院记录：你方约定为“只存在一条记录”，返院时会把该记录 outStatus 更新为 2 并写入 comeTime
        ElderlyTempOutDO tempOutRecord = elderlyTempOutMapper.selectOne(new LambdaQueryWrapperX<ElderlyTempOutDO>()
                .eq(ElderlyTempOutDO::getElderId, elderId)
                .orderByDesc(ElderlyTempOutDO::getOutTime)
                .last("LIMIT 1"));

        List<ExpenseItemRespVO> collect = new ArrayList<>();
        YearMonth yearMonth = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
        LocalDate monthStart = yearMonth.atDay(1);
        LocalDate monthEnd = yearMonth.atEndOfMonth();

        // 合同到期规则：
        // 1) 到期月只计 1号~到期日（包含到期日）
        // 2) 到期后月份不再计固定费用
        // 3) 与离住规则同时命中时，实际结束时间优先采用合同到期时间
        LocalDate contractExpireDate = null;
        ElderlyContractDO latestContract = elderlyContractMapper.selectOne(new LambdaQueryWrapperX<ElderlyContractDO>()
                .eq(ElderlyContractDO::getElderId, elderId)
                .orderByDesc(ElderlyContractDO::getCreatedTime)
                .last("LIMIT 1"));
        boolean isContractExpireInBillingMonth = false;
        if (latestContract != null && latestContract.getExpireTime() != null) {
            contractExpireDate = latestContract.getExpireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            if (contractExpireDate.isBefore(monthStart)) {
                return collect;
            }
            isContractExpireInBillingMonth = !contractExpireDate.isBefore(monthStart) && !contractExpireDate.isAfter(monthEnd);
        }

        // 计费核心：仅当目标月份与“在院区间”有交集才生成固定费用。
        // 这里的“在院区间”按离住/返院时间截断，避免出现：离住后未返院的月份不应计费，但后续补录返院导致回算。
        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 {
                    // 原离住规则在“月初离住”会直接不计费；
                    // 但若本月合同到期，需像离住按区间计费（1号~到期日），故不提前 return
                    if (!isContractExpireInBillingMonth) {
                        return collect;
                    }
                }
            }

            // 离住后的月份（本月月初在离院日之后）
            if (monthStart.isAfter(outDate)) {
                if (comeDate == null) {
                    // 仍处于离住中：离院后月份不计费；
                    // 但若本月合同到期，仍需生成 1号~到期日费用
                    if (!isContractExpireInBillingMonth) {
                        return collect;
                    }
                } else {
                    YearMonth comeYm = YearMonth.from(comeDate);
                    if (yearMonth.isBefore(comeYm)) {
                        // 返院发生在未来月份：本月仍不计费；
                        // 但若本月合同到期，仍需生成 1号~到期日费用
                        if (!isContractExpireInBillingMonth) {
                            return collect;
                        }
                    } else if (yearMonth.equals(comeYm)) {
                        // 返院当月：从返院日开始计费（包含当天）
                        actualStart = comeDate.isAfter(monthStart) ? comeDate : monthStart;
                    }
                    // 返院后的月份：整月计费（保持 monthStart）
                }
            }
        }

        // 合同到期月处理：像离住一样按天折算，只保留月初到到期日区间；
        // 若与离住规则同时命中，结束时间优先取合同到期时间
        if (contractExpireDate != null && !contractExpireDate.isBefore(monthStart) && !contractExpireDate.isAfter(monthEnd)) {
            actualEnd = contractExpireDate;
        }

        if (!actualEnd.isBefore(actualStart)) {
            //如果没有当月订单查询当月未缴费的日常费用和月度费用
            Optional<ExpenseDO> optional = expenseMapper.selectList(new LambdaQueryWrapperX<ExpenseDO>()
                            .eq(ExpenseDO::getElderId, elderId)
                            .orderByDesc(ExpenseDO::getCreatedTime))
                    .stream()
                    .findFirst();
            if (!optional.isPresent()) {
                throw exception(COMMON_NOT_FOUND);
            }
            ExpenseDO expense = optional.get();
            // 查询账单生成月的原金额并赋值返回前端，因为存在扣减的变更日常消费记录，返回变更后的金额会双重扣减金额导致总金额不对
            List<ExpenseItemDO> expenseItems = expenseItemMapper.generateMonthlyBill(elderId, billingMonth, BooleanEnum.FALSE.getValue());
            if (expenseItems.isEmpty()) {
                expenseItems = expenseItemMapper.generateMonthlyBill(elderId, billingMonth, BooleanEnum.TRUE.getValue());
            }
            YearMonth queryTime = YearMonth.parse(billingMonth, DateTimeFormatter.ofPattern("yyyy-MM"));
            //赠送或者阶段的费用
            List<ExpenseItemDO> freeGiftItemList = expenseItemMapper.selectList(new LambdaQueryWrapperX<ExpenseItemDO>()
                    .eq(ExpenseItemDO::getExpenseId, expense.getId())
                    .eq(ExpenseItemDO::getIsFreeGift, BooleanEnum.TRUE.getValue())
                    .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())) {
                    respVO.setExpenseType(6);  //阶段费用  费用来源,0：固定 1:日常 2：变更 3：外出 4：额外 5.分期 6.赠送 .7.长护险抵扣
                } else {
                    respVO.setExpenseType(0);  //0是月度固定费用
                }
                respVO.setExpenseSource(BusinessConstants.EXPENSE_ITEM);
                respVO.setSourceExpenseItemId(item.getId());
                long monthDays = ChronoUnit.DAYS.between(monthStart, monthEnd) + 1;
                if (item.getIsDayCalculate() == 1){
                    respVO.setPrice(item.getActualAmount());
                    respVO.setTotalAmount(respVO.getPrice().multiply(new BigDecimal(monthDays)));
                }else {
                    BigDecimal originAmount = item.getActualAmount();
                    BigDecimal originTotal = item.getTotalAmount();
                    if (finalActualStart.isAfter(monthStart) || finalActualEnd.isBefore(monthEnd)) {
                        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(finalActualStart.toString());
                respVO.setEndTime(finalActualEnd.toString());
                respVO.setType(item.getType());
                respVO.setItemCategory(item.getItemCategoryName());
                respVO.setContractId(expense.getContractId());
                return respVO;
            }).collect(Collectors.toList());
        }
        return collect;
    }

    /**
     * 按实际区间对优惠抵扣项进行二次折算（金额 & 时间）
     *
     * @param elderId      长者ID
     * @param billingMonth 账单月份
     * @param actualStart  实际计费开始日期（例如 1 号或离院前规则截断后的开始）
     * @param actualEnd    实际计费结束日期（例如 月底或离院前一天）
     * @return 按实际区间折算后的优惠抵扣项列表
     */
    private List<ExpenseItemRespVO> adjustDiscountByActualRange(Long elderId, String billingMonth,
                                                                LocalDate actualStart, LocalDate actualEnd) {
        List<ExpenseItemRespVO> discountCollect = calculateDiscountDeduction(elderId, billingMonth);
        List<ExpenseItemRespVO> adjustedDiscountCollect = new ArrayList<>();

        for (ExpenseItemRespVO item : discountCollect) {
            // 原优惠规则在当月内的起止时间（calculateDiscountDeduction 中已按账单月截断）
            LocalDate discountStart = LocalDate.parse(item.getStartTime());
            LocalDate discountEnd = LocalDate.parse(item.getEndTime());
            if (discountEnd.isBefore(discountStart)) {
                continue;
            }
            long discountDays = ChronoUnit.DAYS.between(discountStart, discountEnd) + 1;
            if (discountDays <= 0) {
                continue;
            }

            // 与本账单“实际固定费用区间(actualStart~actualEnd)”的交集
            LocalDate overlapStart = actualStart.isAfter(discountStart) ? actualStart : discountStart;
            LocalDate overlapEnd = actualEnd.isBefore(discountEnd) ? actualEnd : discountEnd;
            if (overlapEnd.isBefore(overlapStart)) {
                // 没有交集则该月实际不享受此优惠
                continue;
            }
            long overlapDays = ChronoUnit.DAYS.between(overlapStart, overlapEnd) + 1;
            if (overlapDays <= 0) {
                continue;
            }

            // 按天数比例折算优惠金额（保持为负值）
            BigDecimal originAmount = item.getTotalAmount(); // 已为负数
            BigDecimal absOrigin = originAmount.abs();
            BigDecimal dailyAmount = absOrigin.divide(BigDecimal.valueOf(discountDays), 8, RoundingMode.HALF_UP);
            BigDecimal newAmount = dailyAmount.multiply(BigDecimal.valueOf(overlapDays))
                    .setScale(2, RoundingMode.HALF_UP)
                    .negate();

            item.setPrice(newAmount);
            item.setTotalAmount(newAmount);
            // 展示区间按实际区间（可能存在离住截断）
            item.setStartTime(actualStart.toString());
            item.setEndTime(actualEnd.toString());
            adjustedDiscountCollect.add(item);
        }

        return adjustedDiscountCollect;
    }
}