package cn.iocoder.yudao.module.system.api.bpm;

import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.corp.CorpMsgUtil;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
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.bpm.enums.task.BpmProcessInstanceStatusEnum;
import cn.iocoder.yudao.module.system.api.bpm.vo.*;
import cn.iocoder.yudao.module.system.api.notify.convert.message.UserMessageConvert;
import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.api.wechat.dto.CorpSendMessage;
import cn.iocoder.yudao.module.system.api.wechat.dto.CorpWechatTextcard;
import cn.iocoder.yudao.module.system.api.wechat.dto.WechatMessageSaveReq;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.BedChangeCategoryVO;
import cn.iocoder.yudao.module.system.convert.tenant.TenantConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.*;
import cn.iocoder.yudao.module.system.dal.dataobject.sys.BusinessParamsConfigDO;
import cn.iocoder.yudao.module.system.dal.mysql.biz.*;
import cn.iocoder.yudao.module.system.dal.mysql.notify.NotifyMessageMapper;
import cn.iocoder.yudao.module.system.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.system.enums.NoticeConstants;
import cn.iocoder.yudao.module.system.enums.change.BusinessConstants;
import cn.iocoder.yudao.module.system.enums.change.BusinessTypeEnum;
import cn.iocoder.yudao.module.system.enums.change.ChangeTypeEnum;
import cn.iocoder.yudao.module.system.enums.common.BooleanEnum;
import cn.iocoder.yudao.module.system.enums.notice.NotieBusinessTypeEnum;
import cn.iocoder.yudao.module.system.enums.notice.NotifyPriorityEnum;
import cn.iocoder.yudao.module.system.enums.notice.NotifyTypeEnum;
import cn.iocoder.yudao.module.system.util.BizUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.parameters.P;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
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;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.enums.NoticeConstants.EXPENSE_ORDER_HANDLE_URL;
import static cn.iocoder.yudao.module.system.enums.change.ChangeConstants.*;

/**
 * @Author: jinyd
 * @CreateTime: 2024/8/7  10:02
 * @Description: Bpm事件 API接口实现类
 * @Version: 4.0
 */


@Service
@Validated
@Slf4j
public class BpmElderlyChangeApiImpl implements BpmElderlyChangeApi {

    @Resource
    private ElderlyChangeRecordMapper changeRecordMapper;

    @Resource
    private ElderlyInfoMapper elderlyInfoMapper;

    @Resource
    private ExpenseOrderMapper expenseOrderMapper;

    @Resource
    private DailyExpensesMapper dailyExpensesMapper;

    @Resource
    private ExpenseMapper expenseMapper;

    @Resource
    private ExpenseItemMapper expenseItemMapper;

    @Resource
    private SysChargeCategoryMapper chargeCategoryMapper;

    @Resource
    private BuildBedMapper buildBedMapper;

    @Resource
    private PriceChangeRecordMapper priceChangeRecordMapper;

    @Resource
    private BuildMapper buildMapper;
    @Resource
    private BuildFloorMapper buildFloorMapper;
    @Resource
    private BuildRoomMapper buildRoomMapper;

    @Resource
    private NotifyMessageMapper notifyMessageMapper;

    @Resource
    private BpmElderlyApi bpmElderlyApi;

    @Resource
    private ElderlyRetreatRecordMapper retreatRecordMapper;

    @Resource
    private ElderlyContractMapper elderlyContractMapper;

    @Resource
    private BizUtil bizUtil;

    @Resource
    private ExpenseOrderItemMapper expenseOrderItemMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long addElderlyBedChangeRecord(ElderlyChangeRecordReqVO bedChangeRecordReqVO) {
        ElderlyChangeRecordDO bedChangeRecordDO = BeanUtils.toBean(bedChangeRecordReqVO, ElderlyChangeRecordDO.class);
        bedChangeRecordDO.setCreator(SecurityFrameworkUtils.getLoginUserNickname());
        changeRecordMapper.insert(bedChangeRecordDO);
        return bedChangeRecordDO.getId();
    }

    @Override
    public PageResult<BedChangeRecordRespVO> getBedChangeRecordPage(BedChangeRecordPageReqApiVO pageReqVO) {
        Page<BedChangeRecordRespVO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
        pageReqVO.setType(BusinessTypeEnum.BED_CHANGE.getValue());
        List<BedChangeRecordRespVO> list = changeRecordMapper.selectBedListPage(page, pageReqVO);
        return new PageResult<>(list, page.getTotal());
    }

    @Override
    public PageResult<NurseChangeRecordRespVO> getNurseChangeRecordPage(ChangeRecordPageReqApiVO pageReqVO) {
        Page<NurseChangeRecordRespVO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
        pageReqVO.setType(BusinessTypeEnum.NURSE_CHANGE.getValue());
        pageReqVO.setChangeType(ChangeTypeEnum.NURSE.getValue());
        List<NurseChangeRecordRespVO> list = changeRecordMapper.selectNurseListPage(page, pageReqVO);
        return new PageResult<>(list, page.getTotal());
    }

    @Override
    public PageResult<CateringChangeRecordRespVO> getCateringChangeRecordPage(ChangeRecordPageReqApiVO pageReqVO) {
        Page<CateringChangeRecordRespVO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
        pageReqVO.setType(BusinessTypeEnum.CATERING_CHANGE.getValue());
        pageReqVO.setChangeType(ChangeTypeEnum.CATERING.getValue());
        List<CateringChangeRecordRespVO> list = changeRecordMapper.selectCateringListPage(page, pageReqVO);
        return new PageResult<>(list, page.getTotal());
    }

    @Override
    public PageResult<PriceChangeRecordRespVO> getPriceChangeRecordPage(ChangeRecordPageReqApiVO pageReqVO) {
        Page<PriceChangeRecordRespVO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
        pageReqVO.setType(BusinessTypeEnum.PRICE_CHANGE.getValue());
        pageReqVO.setChangeType(ChangeTypeEnum.PRICE.getValue());
        List<PriceChangeRecordRespVO> list = changeRecordMapper.selectPriceListPage(page, pageReqVO);
        return new PageResult<>(list, page.getTotal());
    }

    @Override
    public PageResult<TransferChangeRecordRespVO> getTransferChangeRecordPage(ChangeRecordPageReqApiVO pageReqVO) {
        Page<TransferChangeRecordRespVO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
        pageReqVO.setType(BusinessTypeEnum.TRANSFER_CHANGE.getValue());
        pageReqVO.setChangeType(ChangeTypeEnum.TRANSFER.getValue());
        List<TransferChangeRecordRespVO> list = changeRecordMapper.selectTransferListPage(page, pageReqVO);
        return new PageResult<>(list, page.getTotal());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @TenantIgnore
    public void updateTransferChange(Long businessId) {
        ElderlyChangeRecordDO changeRecordDO = changeRecordMapper.selectById(businessId);
        ElderlyInfoDO elderlyInfoDO = elderlyInfoMapper.selectById(changeRecordDO.getElderlyId());
        elderlyInfoMapper.updateById(new ElderlyInfoDO().setId(changeRecordDO.getElderlyId()).setInStatus(2).setCheckOutTime(new Date()));

        BuildBedDO bludBedDO = new BuildBedDO();
        bludBedDO.setId(elderlyInfoDO.getId());
        bludBedDO.setStatus(0);
        buildBedMapper.updateById(bludBedDO);


        elderlyInfoDO.setId(null);
        elderlyInfoDO.setRoomId(null);
        elderlyInfoDO.setRoomName("");
        elderlyInfoDO.setFloorId(null);
        elderlyInfoDO.setFloorName("");
        elderlyInfoDO.setBuildId(null);
        elderlyInfoDO.setBedName("");
        elderlyInfoDO.setBedId(null);
        elderlyInfoDO.setBedName("");
        elderlyInfoDO.setInStatus(0);
        elderlyInfoDO.setCheckInTime(null);
        elderlyInfoDO.setCheckOutTime(null);
        elderlyInfoDO.setCreateBy(SecurityFrameworkUtils.getLoginUserNickname());
        elderlyInfoDO.setCreateTime(new Date());
        elderlyInfoDO.setTenantId(changeRecordDO.getExpectId());
        elderlyInfoMapper.insert(elderlyInfoDO);
        bpmElderlyApi.generateAdmissionAssessment(elderlyInfoDO.getId(), changeRecordDO.getExpectId());

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCurrentFlag(Long id) {
        ElderlyChangeRecordDO bedChangeRecordDO = new ElderlyChangeRecordDO();
        bedChangeRecordDO.setId(id);
        // 状态为1，表示不是最新的流程
        bedChangeRecordDO.setCurrentFlag(1);
        changeRecordMapper.updateById(bedChangeRecordDO);
    }

    @Override
    @TenantIgnore
    public ElderlyInfoReqVO getElderlyInfoByChangeId(Long businessId) {
        ElderlyChangeRecordDO bedChangeRecordDO = changeRecordMapper.selectById(businessId);
        ElderlyInfoDO elderlyInfoDO = elderlyInfoMapper.selectById(bedChangeRecordDO.getElderlyId());
        ElderlyInfoReqVO elderlyInfoReqVO = BeanUtils.toBean(elderlyInfoDO, ElderlyInfoReqVO.class);
        return elderlyInfoReqVO;
    }

    @Override
    public Boolean checkRepeat(Long elderlyId, LocalDate changeDate, Integer changeType, Long tenantId) {
        Boolean checkResult = null;
        List<ElderlyChangeRecordDO> list = changeRecordMapper.selectList(new LambdaQueryWrapperX<ElderlyChangeRecordDO>()
                .eq(ElderlyChangeRecordDO::getElderlyId, elderlyId)
                .eq(ElderlyChangeRecordDO::getChangeDate, changeDate)
                .eq(ElderlyChangeRecordDO::getCurrentFlag, 0)
                .eq(ElderlyChangeRecordDO::getChangeType, changeType)
                .eq(ElderlyChangeRecordDO::getTenantId, tenantId));
        //判断是否有数据
        if (list != null && list.size() > 0) {
            checkResult = Boolean.TRUE;
        } else {
            checkResult = Boolean.FALSE;
        }
        return checkResult;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @TenantIgnore
    public void generateBill(Long businessId, Integer identification, Long startTenantId) {
        ElderlyChangeRecordDO bedChangeRecordDO = changeRecordMapper.selectById(businessId);
        // 1. 获取当前费用的价格 TODO 不应该在这里查询，前端是能获取到的，应该在插入变更记录的时候就要录入
        Long elderlyId = bedChangeRecordDO.getElderlyId();
        checkChangeStartDate(elderlyId,bedChangeRecordDO.getChangeDate());
        BedChangeCategoryVO chargeCategory =  expenseMapper.getChargeCategory(elderlyId, identification);

        if (null == chargeCategory) return;


        String itemCategory = getNameRecursive(chargeCategory.getItemCategoryId(), "");

        BigDecimal currentExpense = chargeCategory.getActualAmount();
        // 增加原始金额的更新，因为原始金额是后续增加的字段，需要补充
        bedChangeRecordDO.setOriginalAmount(currentExpense);
        changeRecordMapper.updateById(bedChangeRecordDO);
        // 滞后月
        String hysteresisMonth = bedChangeRecordDO.getHysteresisMonth();
        // 2. 对比变更后的价格
        BigDecimal changeExpense = bedChangeRecordDO.getActualAmount();

        // 3. 生成日常费用
        BigDecimal daysInCurrentMonth = BigDecimal.valueOf(bedChangeRecordDO.getChangeDate().lengthOfMonth());
        LocalDate firstDayOfMonth = bedChangeRecordDO.getChangeDate().withDayOfMonth(1);
        // 原价格使用天数
        BigDecimal currentDays = BigDecimal.valueOf(bedChangeRecordDO.getChangeDate().toEpochDay() - firstDayOfMonth.toEpochDay());
        LocalDate endDay = bizUtil.getEndDateWithExpireDate(elderlyId,null,firstDayOfMonth.with(TemporalAdjusters.lastDayOfMonth()));
        // 新价格使用天数
        BigDecimal changeDays = new BigDecimal(Math.abs(ChronoUnit.DAYS.between(endDay,bedChangeRecordDO.getChangeDate()))+1);
//        BED_CHANGE_DAILY_PAY_REMARK
//        String currentRemark = String.format(BED_CHANGE_DAILY_REFUND_REMARK, bedChangeRecordDO.getChangeDate(),
//                chargeCategory.getItemName(), currentExpense.setScale(2),
//                bedChangeRecordDO.getExpectName(), changeExpense.setScale(2), firstDayOfMonth,
//                bedChangeRecordDO.getChangeDate(), currentDays.multiply(currentDayAmount).setScale(2),
//                bedChangeRecordDO.getChangeDate(), lastDayOfMonth, currentFinalAmount.setScale(2));

//        String changeRemark = String.format(BED_CHANGE_DAILY_PAY_REMARK, bedChangeRecordDO.getChangeDate(),
//                chargeCategory.getItemName(), currentExpense.setScale(2),
//                bedChangeRecordDO.getExpectName(), changeExpense.setScale(2),
//                bedChangeRecordDO.getChangeDate(), lastDayOfMonth, changeFinalAmount.setScale(2));
        String preRemark;
         // '申请类型，1-申请入住，2-申请退住，3-外出登记，4-床位变更，5-护理变更，6-餐饮变更，7-价格变更，8-转院变更',
        // 变更类型，0-床位，1-护理，2-餐饮，3-价格，4-转院
        Integer bpmType = 0;
        Integer changeType = 0;
        if (1 == identification) {
            preRemark = BED_CHANGE_DAILY_PAY_REMARK;
            bpmType = 4;
            changeType = 0;
        } else if (2 == identification) {
            preRemark = NURSE_CHANGE_DAILY_PAY_REMARK;
            bpmType = 5;
            changeType = 1;
        } else if (3 == identification) {
            preRemark = CATERING_CHANGE_DAILY_PAY_REMARK;
            bpmType = 6;
            changeType = 2;
        } else {
            preRemark = "";
        }

        String currentRemark = String.format(preRemark, currentExpense.setScale(2), currentExpense.setScale(2),
                changeExpense.setScale(2), currentDays, changeDays);
        BigDecimal finalAmount = null;
        if (0 == currentExpense.setScale(2).compareTo(changeExpense.setScale(2))) {
            finalAmount = BigDecimal.ZERO;
        } else if (daysInCurrentMonth.equals(changeDays)) {
            finalAmount = changeExpense.subtract(currentExpense);
        } else {
            // 原价格，每天价格
            BigDecimal currentDayAmount = currentExpense.divide(daysInCurrentMonth,8, RoundingMode.HALF_UP);
            // 新价格，每天价格
            BigDecimal changeDayAmount = changeExpense.divide(daysInCurrentMonth, 8, RoundingMode.HALF_UP);
            // 原价格剩余费用 = 改变天数 * 原价格
            BigDecimal currentFinalAmount = changeDays.multiply(currentDayAmount);
            // 新价格剩余费用 = 改变天数 * 新价格
            BigDecimal changeFinalAmount = changeDays.multiply(changeDayAmount);
            // 最终价格 = 新 - 原
            finalAmount = changeFinalAmount.subtract(currentFinalAmount);
        }
        if(finalAmount.compareTo(BigDecimal.ZERO) != 0){
            DateTimeFormatter billingMonthFormatter = DateTimeFormatter.ofPattern("yyyy-MM");
            String changeBillingMonth = bedChangeRecordDO.getChangeDate().format(billingMonthFormatter);
            YearMonth changeYearMonth = YearMonth.parse(changeBillingMonth, billingMonthFormatter);
            YearMonth hysteresisYearMonth = null;
            if (StringUtils.isNotBlank(hysteresisMonth)) {
                try {
                    hysteresisYearMonth = YearMonth.parse(hysteresisMonth, billingMonthFormatter);
                } catch (Exception ignored) {
                    hysteresisYearMonth = null;
                }
            }
            Date currentDate = new Date();
            BigDecimal dailyExpensesDOAmount = finalAmount;
            // 查询已生成账单，对生效期内的已生成账单增加日常费用记录
            List<ExpenseOrderDO> generateExpenseOrder = expenseOrderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
                    .eq(ExpenseOrderDO::getElderId, elderlyId)
                    .ge(ExpenseOrderDO::getBillingMonth, changeBillingMonth));
            if (generateExpenseOrder.size() > 0) {
                List<String> targetBillingMonths = new ArrayList<>(generateExpenseOrder.size());
                Set<String> targetBillingMonthSet = new HashSet<>(generateExpenseOrder.size());
                for (ExpenseOrderDO e : generateExpenseOrder) {
                    String targetBillingMonth = resolveAttributionBillMonth(e.getBillingMonth(), changeYearMonth, hysteresisYearMonth);
                    targetBillingMonths.add(targetBillingMonth);
                    targetBillingMonthSet.add(targetBillingMonth);
                }

                Map<String, ExpenseOrderDO> targetOrderMap = new HashMap<>();
                if (CollectionUtil.isNotEmpty(targetBillingMonthSet)) {
                    List<ExpenseOrderDO> targetOrders = expenseOrderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
                            .eq(ExpenseOrderDO::getElderId, elderlyId)
                            .in(ExpenseOrderDO::getBillingMonth, targetBillingMonthSet)
                            .orderByDesc(ExpenseOrderDO::getCreatedTime));
                    for (ExpenseOrderDO targetOrder : targetOrders) {
                        targetOrderMap.putIfAbsent(targetOrder.getBillingMonth(), targetOrder);
                    }
                }

                YearMonth finalHysteresisYearMonth = hysteresisYearMonth;
                List<DailyExpensesDO> currentDailyExpensesDOList= generateExpenseOrder.stream().map((e) -> {
                    DailyExpensesDO currentDailyExpensesDO = new DailyExpensesDO();
                    currentDailyExpensesDO.setElderId(elderlyId);
                    currentDailyExpensesDO.setItemId(chargeCategory.getOverheadChargeId());
                    currentDailyExpensesDO.setItemCategory(itemCategory);
                    currentDailyExpensesDO.setItemName(chargeCategory.getItemName());
                    currentDailyExpensesDO.setType(2);
                    currentDailyExpensesDO.setPrice(changeExpense.setScale(2));
                    if(changeBillingMonth.equals(e.getBillingMonth())){
                        currentDailyExpensesDO.setAmount(dailyExpensesDOAmount);
                        currentDailyExpensesDO.setStartDate(bedChangeRecordDO.getChangeDate());
                        currentDailyExpensesDO.setEndDate(endDay);
                        currentDailyExpensesDO.setRemarks(currentRemark);
                    }else{
                        currentDailyExpensesDO.setAmount(changeExpense.subtract(currentExpense));
                        YearMonth yearMonth = YearMonth.parse(e.getBillingMonth());
                        currentDailyExpensesDO.setStartDate(yearMonth.atDay(1));
                        currentDailyExpensesDO.setEndDate(yearMonth.atEndOfMonth());
                        currentDailyExpensesDO.setRemarks(String.format(preRemark, currentExpense.setScale(2), currentExpense.setScale(2),
                                changeExpense.setScale(2), 0, yearMonth.lengthOfMonth()));
                    }
                    currentDailyExpensesDO.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                    currentDailyExpensesDO.setCreatedTime(currentDate);
                    currentDailyExpensesDO.setAccountingTime(currentDate);
                    currentDailyExpensesDO.setCount(1);
                    String targetBillingMonth = resolveAttributionBillMonth(e.getBillingMonth(), changeYearMonth, finalHysteresisYearMonth);
                    currentDailyExpensesDO.setAttributionBillTime(targetBillingMonth);
                    ExpenseOrderDO targetExpenseOrder = targetOrderMap.get(targetBillingMonth);
                    if(targetExpenseOrder != null && !(targetExpenseOrder.getPayStatus().equals(BooleanEnum.TRUE.getValue())) && !targetExpenseOrder.getIsLock()){
                        currentDailyExpensesDO.setIsGenerateBill(BooleanEnum.TRUE.getValue());
                    } else {
                        currentDailyExpensesDO.setIsGenerateBill(BooleanEnum.FALSE.getValue());
                    }
                    return currentDailyExpensesDO;
                }).collect(Collectors.toList());
                dailyExpensesMapper.insertBatch(currentDailyExpensesDOList);
                
                // 为未缴费的账单创建账单明细项并更新账单金额
                // 不需要做日常费用isGenerateBill字段更新，因为上面插入时已经完成了，减少了数据库的操作
                for (int i = 0; i < generateExpenseOrder.size(); i++) {
                    DailyExpensesDO currentDailyExpensesDO = currentDailyExpensesDOList.get(i);
                    ExpenseOrderDO targetExpenseOrder = targetOrderMap.get(targetBillingMonths.get(i));
                    if(targetExpenseOrder != null && !targetExpenseOrder.getPayStatus().equals(BooleanEnum.TRUE.getValue()) && !targetExpenseOrder.getIsLock()){
                        ExpenseOrderItemDO expenseOrderItemDO = new ExpenseOrderItemDO();
                        expenseOrderItemDO.setExpenseOrderId(targetExpenseOrder.getId());
                        expenseOrderItemDO.setSourceExpenseItemId(currentDailyExpensesDO.getId());
                        expenseOrderItemDO.setExpenseSource(BusinessConstants.DAILY_EXPENSES);
                        expenseOrderItemDO.setItemCategoryName(currentDailyExpensesDO.getItemCategory());
                        expenseOrderItemDO.setItemName(currentDailyExpensesDO.getItemName());
                        expenseOrderItemDO.setPrice(currentDailyExpensesDO.getPrice());
                        expenseOrderItemDO.setActualPrice(currentDailyExpensesDO.getPrice());
                        expenseOrderItemDO.setTotalAmount(currentDailyExpensesDO.getAmount());
                        expenseOrderItemDO.setRoundAmount(currentDailyExpensesDO.getAmount().setScale(0, RoundingMode.HALF_UP));
                        expenseOrderItemDO.setRoundTwoDecimalAmount(currentDailyExpensesDO.getAmount().setScale(2, RoundingMode.HALF_UP));
                        expenseOrderItemDO.setCount(currentDailyExpensesDO.getCount());
                        expenseOrderItemDO.setType(currentDailyExpensesDO.getType());
                        expenseOrderItemDO.setStartDate(currentDailyExpensesDO.getStartDate());
                        expenseOrderItemDO.setEndDate(currentDailyExpensesDO.getEndDate());
                        expenseOrderItemDO.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                        expenseOrderItemDO.setCreatedTime(currentDate);
                        expenseOrderItemDO.setTenantId(targetExpenseOrder.getTenantId());
                        expenseOrderItemDO.setPayStatus(0); // 未缴费状态
                        expenseOrderItemMapper.insert(expenseOrderItemDO);
                        
                        // 更新账单金额
                        BigDecimal currentAmount = targetExpenseOrder.getActualAmount() != null ? targetExpenseOrder.getActualAmount() : BigDecimal.ZERO;
                        BigDecimal newAmount = currentAmount.add(expenseOrderItemDO.getTotalAmount());
                        ExpenseOrderDO updateExpenseOrderDO = new ExpenseOrderDO();
                        updateExpenseOrderDO.setId(targetExpenseOrder.getId());
                        updateExpenseOrderDO.setActualAmount(newAmount);
                        expenseOrderMapper.updateById(updateExpenseOrderDO);
                        targetExpenseOrder.setActualAmount(newAmount);
                    }
                }
            }
            // 生效的当月查询是否有账单，无账单则生成日常费用，有账单的情况上面已生成不用重复生成
            ExpenseOrderDO expenseOrder = expenseOrderMapper.selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                    .eq(ExpenseOrderDO::getElderId, elderlyId)
                    .eq(ExpenseOrderDO::getBillingMonth, changeBillingMonth));
            if(expenseOrder == null){
                String targetBillingMonth = resolveAttributionBillMonth(changeBillingMonth, changeYearMonth, hysteresisYearMonth);
                ExpenseOrderDO targetExpenseOrder = expenseOrderMapper.selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                        .eq(ExpenseOrderDO::getElderId, elderlyId)
                        .eq(ExpenseOrderDO::getBillingMonth, targetBillingMonth));
                DailyExpensesDO currentDailyExpensesDO = new DailyExpensesDO();
                currentDailyExpensesDO.setElderId(elderlyId);
                currentDailyExpensesDO.setItemId(chargeCategory.getOverheadChargeId());
                currentDailyExpensesDO.setItemCategory(itemCategory);
                currentDailyExpensesDO.setItemName(chargeCategory.getItemName());
                currentDailyExpensesDO.setType(2);
                currentDailyExpensesDO.setPrice(changeExpense.setScale(2));
                currentDailyExpensesDO.setAmount(dailyExpensesDOAmount);
                currentDailyExpensesDO.setRemarks(currentRemark);
                currentDailyExpensesDO.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                currentDailyExpensesDO.setCreatedTime(currentDate);
                currentDailyExpensesDO.setStartDate(bedChangeRecordDO.getChangeDate());
                currentDailyExpensesDO.setEndDate(endDay);
                currentDailyExpensesDO.setAccountingTime(currentDate);
                currentDailyExpensesDO.setCount(1);
                currentDailyExpensesDO.setAttributionBillTime(targetBillingMonth);
                if(targetExpenseOrder != null && !targetExpenseOrder.getPayStatus().equals(BooleanEnum.TRUE.getValue()) && !targetExpenseOrder.getIsLock()){
                    currentDailyExpensesDO.setIsGenerateBill(BooleanEnum.TRUE.getValue());
                } else {
                    currentDailyExpensesDO.setIsGenerateBill(BooleanEnum.FALSE.getValue());
                }
                dailyExpensesMapper.insert(currentDailyExpensesDO);
                if(currentDailyExpensesDO.getIsGenerateBill().equals(BooleanEnum.TRUE.getValue())){
                    ExpenseOrderItemDO expenseOrderItemDO = new ExpenseOrderItemDO();
                    expenseOrderItemDO.setExpenseOrderId(targetExpenseOrder.getId());
                    expenseOrderItemDO.setSourceExpenseItemId(currentDailyExpensesDO.getId());
                    expenseOrderItemDO.setExpenseSource(BusinessConstants.DAILY_EXPENSES);
                    expenseOrderItemDO.setItemCategoryName(currentDailyExpensesDO.getItemCategory());
                    expenseOrderItemDO.setItemName(currentDailyExpensesDO.getItemName());
                    expenseOrderItemDO.setPrice(currentDailyExpensesDO.getPrice());
                    expenseOrderItemDO.setActualPrice(currentDailyExpensesDO.getPrice());
                    expenseOrderItemDO.setTotalAmount(currentDailyExpensesDO.getAmount());
                    expenseOrderItemDO.setRoundAmount(currentDailyExpensesDO.getAmount().setScale(0, RoundingMode.HALF_UP));
                    expenseOrderItemDO.setRoundTwoDecimalAmount(currentDailyExpensesDO.getAmount().setScale(2, RoundingMode.HALF_UP));
                    expenseOrderItemDO.setCount(currentDailyExpensesDO.getCount());
                    expenseOrderItemDO.setType(currentDailyExpensesDO.getType());
                    expenseOrderItemDO.setStartDate(currentDailyExpensesDO.getStartDate());
                    expenseOrderItemDO.setEndDate(currentDailyExpensesDO.getEndDate());
                    expenseOrderItemDO.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                    expenseOrderItemDO.setCreatedTime(currentDate);
                    expenseOrderItemDO.setTenantId(targetExpenseOrder.getTenantId());
                    expenseOrderItemDO.setPayStatus(0);
                    expenseOrderItemMapper.insert(expenseOrderItemDO);

                    BigDecimal currentAmount = targetExpenseOrder.getActualAmount() != null ? targetExpenseOrder.getActualAmount() : BigDecimal.ZERO;
                    BigDecimal newAmount = currentAmount.add(expenseOrderItemDO.getTotalAmount());
                    expenseOrderMapper.updateById(new ExpenseOrderDO().setId(targetExpenseOrder.getId()).setActualAmount(newAmount));
                }
            }
        }

        if (0 == currentExpense.setScale(2).compareTo(changeExpense.setScale(2))) {
            return;
        }
        // 获取最后一次变更的时间
//        ElderlyChangeRecordDO currentLastRecordDO = changeRecordMapper.selectLastRecordByElderId(elderlyId,
//                bpmType, changeType, bedChangeRecordDO.getChangeDate(), bedChangeRecordDO.getId());
        // 4. 更新月度账单配置
        List<ExpenseItemDO> expenseItemDOS = expenseItemMapper.selectList(new LambdaQueryWrapperX<ExpenseItemDO>()
                .eq(ExpenseItemDO::getExpenseId, chargeCategory.getExpenseId()));
        ExpenseDO expenseDO = new ExpenseDO();
        expenseMapper.insert(expenseDO.setElderId(elderlyId)
                .setCreatedTime(new Date())
                .setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname())
                .setTenantId(startTenantId));
        BigDecimal totalAmout = BigDecimal.ZERO;
        //规则
        //更新就费用的结束日期，1、对于变更的项目结束日期截止到变更开始日期的前一天。2、其他费用结束日期截止到开始日期的前一天
        //新增费用记录，结束日期为null
        LocalDate changeEndDate = bedChangeRecordDO.getChangeDate().minusDays(1);
        expenseItemDOS.forEach(item -> {
            item.setExpenseId(expenseDO.getId());
            item.setTenantId(startTenantId);
            if (chargeCategory.getItemCategoryId().equals(item.getItemCategoryId())) {
                item.setItemId(bedChangeRecordDO.getExpectOverheadChargeId());
                //item.setItemName(chargeCategory.getItemName());
                item.setItemName(bedChangeRecordDO.getExpectName());
                item.setIsDeposit(bedChangeRecordDO.getIsDiscount());
                item.setDiscount(bedChangeRecordDO.getDiscount());
                item.setDiscountAmount(bedChangeRecordDO.getDiscountAmount());
                item.setAmount(bedChangeRecordDO.getAmount());
                item.setActualAmount(bedChangeRecordDO.getActualAmount());
                item.setTotalAmount(bedChangeRecordDO.getAmount());


                ExpenseItemDO udpateExpenseItemDO = new ExpenseItemDO();
                udpateExpenseItemDO.setId(item.getId());
                udpateExpenseItemDO.setChangeEndDate(changeEndDate);
                expenseItemMapper.updateById(udpateExpenseItemDO);
                item.setChangeStartDate(bedChangeRecordDO.getChangeDate());
            } else {
                ExpenseItemDO udpateExpenseItemDO = new ExpenseItemDO();
                udpateExpenseItemDO.setId(item.getId());
                udpateExpenseItemDO.setChangeFlag(1);
                if (item.getIsMonthlyExpense() == 1) {
                    udpateExpenseItemDO.setChangeEndDate(item.getChangeStartDate().minusDays(1));
                }
                expenseItemMapper.updateById(udpateExpenseItemDO);
            }
            item.setId(null);
            totalAmout.add(item.getActualAmount());
        });
        if(totalAmout.compareTo(BigDecimal.ZERO) != 0) {
            expenseMapper.updateById(new ExpenseDO().setId(expenseDO.getId()).setTotalAmount(totalAmout));
        }
        expenseItemMapper.insertBatch(expenseItemDOS);

        // 5. 修改账单状态
        ExpenseOrderDO expenseOrderDO = expenseOrderMapper.selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                        .eq(ExpenseOrderDO::getElderId, elderlyId)
                        .eq(ExpenseOrderDO::getBillingMonth, bedChangeRecordDO.getChangeDate().format(DateTimeFormatter.ofPattern("yyyy-MM")))
                        .eq(ExpenseOrderDO::getType, 2)
                        .eq(ExpenseOrderDO::getTenantId, startTenantId));
        if (null == expenseOrderDO) {
            //throw exception(ELDERLY_NOT_GENERATE_BILL_ERROR);
        } else {
            ExpenseOrderDO updateExpenseOrderDO = new ExpenseOrderDO();
            updateExpenseOrderDO.setId(expenseOrderDO.getId());
            updateExpenseOrderDO.setChangeStatus(1);
            expenseOrderMapper.updateById(updateExpenseOrderDO);
        }

    }

    @Override
    @Transactional
    public void convertBed(Long businessId) {
        ElderlyChangeRecordDO elderlyChangeRecordDO = changeRecordMapper.selectById(businessId);
        BuildBedDO originalBuildBedDO = new BuildBedDO();
        originalBuildBedDO.setId(elderlyChangeRecordDO.getOriginalId());
        originalBuildBedDO.setStatus(0);
        buildBedMapper.updateById(originalBuildBedDO);
        BuildBedDO expectBuildBedDO = new BuildBedDO();
        expectBuildBedDO.setId(elderlyChangeRecordDO.getExpectId());
        expectBuildBedDO.setStatus(1);
        buildBedMapper.updateById(expectBuildBedDO);
        ElderlyInfoDO elderlyInfoDO = new ElderlyInfoDO();
        elderlyInfoDO.setId(elderlyChangeRecordDO.getElderlyId());
        elderlyInfoDO.setBedId(elderlyChangeRecordDO.getExpectId());
        BuildBedDO buildBedDO = buildBedMapper.selectById(elderlyChangeRecordDO.getExpectId());
        elderlyInfoDO.setBuildId(buildBedDO.getBuildId());
        elderlyInfoDO.setFloorId(buildBedDO.getFloorId());
        elderlyInfoDO.setRoomId(buildBedDO.getRoomId());
        StringBuilder sb = new StringBuilder();
        String bedName = "";
        String roomName = "";
        String floorName = "";
        String buildName = "";
        Long bedId = elderlyInfoDO.getBedId();
        BuildBedDO bed = buildBedMapper.selectById(bedId);
        if (bed != null) {
            bedName = bed.getBedName();

            BuildRoomDO room = buildRoomMapper.selectById(bed.getRoomId());
            if (room != null) {
                roomName = room.getRoomName();
                BuildFloorDO buildFloor = buildFloorMapper.selectById(room.getFloorId());
                if (buildFloor != null) {
                    floorName = buildFloor.getFloorName();
                    BuildDO build = buildMapper.selectById(buildFloor.getBuildId());
                    buildName = build.getBuildName();
                }
            }
        }
        if (StringUtils.isNotBlank(buildName)){
            sb.append(buildName);
            sb.append("-");
            sb.append(floorName);
            sb.append("-");
            sb.append(roomName);
            sb.append("-");
            sb.append(bedName);
        }
        elderlyInfoDO.setBedName(sb.toString());
        elderlyInfoMapper.updateById(elderlyInfoDO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void generatePriceChangeBill(Long businessId) {
        ElderlyChangeRecordDO changeRecordDO = changeRecordMapper.selectById(businessId);

        Long elderlyId = changeRecordDO.getElderlyId();
        checkChangeStartDate(elderlyId,changeRecordDO.getChangeDate());
        List<PriceChangeRecordDO> priceChangeRecordDOS = priceChangeRecordMapper.selectList(new LambdaQueryWrapperX<PriceChangeRecordDO>()
                .eq(PriceChangeRecordDO::getChangeId, changeRecordDO.getId()));
        List<BedChangeCategoryVO> categoryList =  expenseMapper.getChargeCategoryList(elderlyId);
        if (CollectionUtils.isEmpty(categoryList)) {
            return;
        }
        List<ExpenseItemDO> expenseItemDOS = expenseItemMapper.selectList(new LambdaQueryWrapperX<ExpenseItemDO>()
                .eq(ExpenseItemDO::getExpenseId, categoryList.get(0).getExpenseId()));
        ExpenseDO expenseDO = new ExpenseDO();
        expenseMapper.insert(expenseDO.setElderId(elderlyId)
                .setTenantId(TenantContextHolder.getTenantId())
                .setCreatedTime(new Date())
                .setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname()));
        BigDecimal totalAmout = BigDecimal.ZERO;
        Date date = new Date();
        priceChangeRecordDOS.forEach(priceChange ->{
            categoryList.forEach(category ->{
                if (category.getOverheadChargeId().equals(priceChange.getOriginalId())){
                    String itemCategory = getNameRecursive(category.getItemCategoryId(), "");

                    BigDecimal currentExpense = category.getActualAmount();

                    // 2. 对比变更后的价格
                    BigDecimal changeExpense = priceChange.getAmount();

                    BigDecimal daysInCurrentMonth = BigDecimal.valueOf(changeRecordDO.getChangeDate().lengthOfMonth());
                    LocalDate firstDayOfMonth = changeRecordDO.getChangeDate().withDayOfMonth(1);
                    BigDecimal currentDays = BigDecimal.valueOf(changeRecordDO.getChangeDate().toEpochDay() - firstDayOfMonth.toEpochDay());

                    LocalDate endDay = bizUtil.getEndDateWithExpireDate(elderlyId,null,firstDayOfMonth.with(TemporalAdjusters.lastDayOfMonth()));
                    BigDecimal changeDays = new BigDecimal(Math.abs(ChronoUnit.DAYS.between(endDay,changeRecordDO.getChangeDate()))+1);

                    //                    String currentRemark = String.format(PRICE_CHANGE_DAILY_REFUND_REMARK, changeRecordDO.getChangeDate(),
//                            category.getItemName(), currentExpense.setScale(2, RoundingMode.HALF_UP),
//                            changeExpense.setScale(2, RoundingMode.HALF_UP), firstDayOfMonth, changeRecordDO.getChangeDate(),
//                            currentDays.multiply(currentDayAmount).setScale(2, RoundingMode.HALF_UP),
//                            changeRecordDO.getChangeDate(), lastDayOfMonth, currentFinalAmount.setScale(2, RoundingMode.HALF_UP));

//                    String changeRemark = String.format(PRICE_CHANGE_DAILY_PAY_REMARK, changeRecordDO.getChangeDate(),
//                            category.getItemName(), currentExpense.setScale(2, RoundingMode.HALF_UP),
//                            changeExpense.setScale(2, RoundingMode.HALF_UP), changeRecordDO.getChangeDate(),
//                            lastDayOfMonth, changeFinalAmount.setScale(2, RoundingMode.HALF_UP));


                    BigDecimal finalAmount = null;

                    if (0 == currentExpense.setScale(2).compareTo(changeExpense.setScale(2))) {
                        finalAmount = BigDecimal.ZERO;
                    } else if(daysInCurrentMonth.equals(changeDays)){
                        finalAmount = changeExpense.subtract(currentExpense);
                    }else {
                        // 原价格，每天价格
                        BigDecimal currentDayAmount = currentExpense.divide(daysInCurrentMonth,8, RoundingMode.HALF_UP);
                        // 新价格，每天价格
                        BigDecimal changeDayAmount = changeExpense.divide(daysInCurrentMonth, 8, RoundingMode.HALF_UP);
                        // 原价格剩余费用 = 改变天数 * 原价格
                        BigDecimal currentFinalAmount = changeDays.multiply(currentDayAmount);
                        // 新价格剩余费用 = 改变天数 * 新价格
                        BigDecimal changeFinalAmount = changeDays.multiply(changeDayAmount);
                        // 最终价格 = 新 - 原
                        finalAmount = changeFinalAmount.subtract(currentFinalAmount);
                    }
                    String currentRemark = String.format(PRICE_CHANGE_DAILY_PAY_REMARK, currentExpense.setScale(2),
                            currentExpense.setScale(2), changeExpense.setScale(2), currentDays, changeDays);
                    Date currentDate = new Date();
                    // 查询已生成账单，对生效期内的已生成账单增加日常费用记录
                    List<ExpenseOrderDO> generateExpenseOrder = expenseOrderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
                            .eq(ExpenseOrderDO::getElderId, elderlyId)
                            .ge(ExpenseOrderDO::getBillingMonth, changeRecordDO.getChangeDate().format(DateTimeFormatter.ofPattern("yyyy-MM"))));
                    BigDecimal dailyExpensesDOAmount = finalAmount;
                    if (generateExpenseOrder.size() > 0) {
                        List<DailyExpensesDO> currentDailyExpensesDOList= generateExpenseOrder.stream().map((e) -> {
                            DailyExpensesDO currentDailyExpensesDO = new DailyExpensesDO();
                            currentDailyExpensesDO.setElderId(elderlyId);
                            currentDailyExpensesDO.setItemId(category.getOverheadChargeId());
                            currentDailyExpensesDO.setItemCategory(itemCategory);
                            currentDailyExpensesDO.setItemName(category.getItemName());
                            currentDailyExpensesDO.setType(2);
                            currentDailyExpensesDO.setPrice(changeExpense.setScale(2));
                            if(changeRecordDO.getChangeDate().format(DateTimeFormatter.ofPattern("yyyy-MM")).equals(e.getBillingMonth())){
                                currentDailyExpensesDO.setAmount(dailyExpensesDOAmount);
                                currentDailyExpensesDO.setStartDate(changeRecordDO.getChangeDate());
                                currentDailyExpensesDO.setEndDate(endDay);
                                currentDailyExpensesDO.setRemarks(currentRemark);
                            }else{
                                currentDailyExpensesDO.setAmount(changeExpense.subtract(currentExpense));
                                YearMonth yearMonth = YearMonth.parse(e.getBillingMonth());
                                currentDailyExpensesDO.setStartDate(yearMonth.atDay(1));
                                currentDailyExpensesDO.setEndDate(yearMonth.atEndOfMonth());
                                currentDailyExpensesDO.setRemarks(String.format(PRICE_CHANGE_DAILY_PAY_REMARK, currentExpense.setScale(2), currentExpense.setScale(2),
                                        changeExpense.setScale(2), 0, yearMonth.lengthOfMonth()));
                            }
                            currentDailyExpensesDO.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                            currentDailyExpensesDO.setCreatedTime(currentDate);
                            currentDailyExpensesDO.setAccountingTime(currentDate);
                            currentDailyExpensesDO.setCount(1);
                            currentDailyExpensesDO.setAttributionBillTime(e.getBillingMonth());
                            // 未缴费并且未锁定的账单
                            if(!(e.getPayStatus().equals(BooleanEnum.TRUE.getValue())) && !e.getIsLock()){
                                currentDailyExpensesDO.setIsGenerateBill(BooleanEnum.TRUE.getValue());
                            }
                            return currentDailyExpensesDO;
                        }).collect(Collectors.toList());
                        dailyExpensesMapper.insertBatch(currentDailyExpensesDOList);
                        // 为未缴费的账单创建账单明细项并更新账单金额
                        // 不需要做日常费用isGenerateBill字段更新，因为上面插入时已经完成了，减少了数据库的操作
                        for (int i = 0; i < generateExpenseOrder.size(); i++) {
                            ExpenseOrderDO e = generateExpenseOrder.get(i);
                            DailyExpensesDO currentDailyExpensesDO = currentDailyExpensesDOList.get(i);
                            // 未缴费并且未锁定的账单
                            if(!e.getPayStatus().equals(BooleanEnum.TRUE.getValue()) && !e.getIsLock()){
                                ExpenseOrderItemDO expenseOrderItemDO = new ExpenseOrderItemDO();
                                expenseOrderItemDO.setExpenseOrderId(e.getId());
                                expenseOrderItemDO.setSourceExpenseItemId(currentDailyExpensesDO.getId());
                                expenseOrderItemDO.setExpenseSource(BusinessConstants.DAILY_EXPENSES);
                                expenseOrderItemDO.setItemCategoryName(currentDailyExpensesDO.getItemCategory());
                                expenseOrderItemDO.setItemName(currentDailyExpensesDO.getItemName());
                                expenseOrderItemDO.setPrice(currentDailyExpensesDO.getPrice());
                                expenseOrderItemDO.setActualPrice(currentDailyExpensesDO.getPrice());
                                expenseOrderItemDO.setTotalAmount(currentDailyExpensesDO.getAmount());
                                expenseOrderItemDO.setRoundAmount(currentDailyExpensesDO.getAmount().setScale(0, RoundingMode.HALF_UP));
                                expenseOrderItemDO.setRoundTwoDecimalAmount(currentDailyExpensesDO.getAmount().setScale(2, RoundingMode.HALF_UP));
                                expenseOrderItemDO.setCount(currentDailyExpensesDO.getCount());
                                expenseOrderItemDO.setType(currentDailyExpensesDO.getType());
                                expenseOrderItemDO.setStartDate(currentDailyExpensesDO.getStartDate());
                                expenseOrderItemDO.setEndDate(currentDailyExpensesDO.getEndDate());
                                expenseOrderItemDO.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                                expenseOrderItemDO.setCreatedTime(currentDate);
                                expenseOrderItemDO.setTenantId(e.getTenantId());
                                expenseOrderItemDO.setPayStatus(0); // 未缴费状态
                                expenseOrderItemMapper.insert(expenseOrderItemDO);
                                // 更新账单金额
                                BigDecimal currentAmount = e.getActualAmount() != null ? e.getActualAmount() : BigDecimal.ZERO;
                                BigDecimal newAmount = currentAmount.add(expenseOrderItemDO.getTotalAmount());
                                ExpenseOrderDO updateExpenseOrderDO = new ExpenseOrderDO();
                                updateExpenseOrderDO.setId(e.getId());
                                updateExpenseOrderDO.setActualAmount(newAmount);
                                expenseOrderMapper.updateById(updateExpenseOrderDO);
                            }
                        }
                    }
                    // 生效的当月查询是否有账单，无账单则生成日常费用，有账单的情况上面已生成不用重复生成
                    ExpenseOrderDO expenseOrder = expenseOrderMapper.selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                            .eq(ExpenseOrderDO::getElderId, elderlyId)
                            .eq(ExpenseOrderDO::getBillingMonth, changeRecordDO.getChangeDate().format(DateTimeFormatter.ofPattern("yyyy-MM"))));
                    if(expenseOrder == null){
                        DailyExpensesDO currentDailyExpensesDO = new DailyExpensesDO();
                        currentDailyExpensesDO.setElderId(elderlyId);
                        currentDailyExpensesDO.setItemId(category.getOverheadChargeId());
                        currentDailyExpensesDO.setItemCategory(itemCategory);
                        currentDailyExpensesDO.setItemName(category.getItemName());
                        currentDailyExpensesDO.setType(2);
                        currentDailyExpensesDO.setPrice(changeExpense.setScale(2));
                        currentDailyExpensesDO.setAmount(dailyExpensesDOAmount);
                        currentDailyExpensesDO.setRemarks(currentRemark);
                        currentDailyExpensesDO.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                        currentDailyExpensesDO.setCreatedTime(currentDate);
                        currentDailyExpensesDO.setStartDate(changeRecordDO.getChangeDate());
                        currentDailyExpensesDO.setEndDate(endDay);
                        currentDailyExpensesDO.setAccountingTime(currentDate);
                        currentDailyExpensesDO.setCount(1);
                        currentDailyExpensesDO.setAttributionBillTime(changeRecordDO.getChangeDate().format(DateTimeFormatter.ofPattern("yyyy-MM")));
                        dailyExpensesMapper.insert(currentDailyExpensesDO);
                    }
                }
            });
            //规则
            //更新就费用的结束日期，1、对于变更的项目结束日期截止到变更开始日期的前一天。2、其他费用结束日期截止到开始日期的前一天
            //新增费用记录，结束日期为null
            LocalDate changeEndDate = changeRecordDO.getChangeDate().minusDays(1);
            expenseItemDOS.forEach(item -> {
                item.setExpenseId(expenseDO.getId());
                item.setTenantId(TenantContextHolder.getTenantId());
                ExpenseItemDO udpateExpenseItemDO = new ExpenseItemDO();
                udpateExpenseItemDO.setId(item.getId());
                if (priceChange.getChargeCategoryId().equals(item.getItemCategoryId())) {
                    item.setActualAmount(priceChange.getAmount());
                    item.setAmount(priceChange.getAmount());
                    item.setTotalAmount(priceChange.getAmount());
                    udpateExpenseItemDO.setChangeFlag(1);
                    udpateExpenseItemDO.setChangeEndDate(changeEndDate);
                    item.setChangeStartDate(changeRecordDO.getChangeDate());
                    expenseItemMapper.updateById(udpateExpenseItemDO);
                }else {
                    if(item.getIsMonthlyExpense().equals(BooleanEnum.TRUE.getValue())){
                        udpateExpenseItemDO.setChangeEndDate(item.getChangeStartDate().minusDays(1));
                        expenseItemMapper.updateById(udpateExpenseItemDO);
                    }
                }
                item.setId(null);
                item.setCreateTime(date);
                totalAmout.add(item.getActualAmount());
            });
        });

        if(totalAmout.compareTo(BigDecimal.ZERO) != 0) {
            expenseMapper.updateById(new ExpenseDO().setId(expenseDO.getId()).setTotalAmount(totalAmout));
        }

        expenseItemMapper.insertBatch(expenseItemDOS);


        ExpenseOrderDO expenseOrderDO = expenseOrderMapper.selectOne(new LambdaQueryWrapperX<ExpenseOrderDO>()
                .eq(ExpenseOrderDO::getElderId, elderlyId)
                .eq(ExpenseOrderDO::getBillingMonth, changeRecordDO.getChangeDate().format(DateTimeFormatter.ofPattern("yyyy-MM")))
                .eq(ExpenseOrderDO::getType, 2));
        if (null == expenseOrderDO) {
//            throw exception(ELDERLY_NOT_GENERATE_BILL_ERROR);
        } else {
            ExpenseOrderDO updateExpenseOrderDO = new ExpenseOrderDO();
            updateExpenseOrderDO.setId(expenseOrderDO.getId());
            updateExpenseOrderDO.setChangeStatus(1);
            expenseOrderMapper.updateById(updateExpenseOrderDO);
        }

    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void generatePriceChangeBillV2(Long businessId) {
        // 存在变更日期
        ElderlyChangeRecordDO changeRecordDO = changeRecordMapper.selectById(businessId);
        LocalDate nowLocalDate = LocalDate.now();
        // 存在变更金额,原始金额
        List<PriceChangeRecordDO> priceChangeRecordDOS = priceChangeRecordMapper.selectList(new LambdaQueryWrapperX<PriceChangeRecordDO>()
                .eq(PriceChangeRecordDO::getChangeId, changeRecordDO.getId()));
        List<BedChangeCategoryVO> categoryList =  expenseMapper.getChargeCategoryList(changeRecordDO.getElderlyId());
        if (CollectionUtils.isEmpty(categoryList)) {
            return;
        }

        List<ExpenseItemDO> expenseItemDOS = expenseItemMapper.selectList(new LambdaQueryWrapperX<ExpenseItemDO>()
                .eq(ExpenseItemDO::getExpenseId, categoryList.get(0).getExpenseId()));
        ExpenseDO expenseDO = new ExpenseDO();
        expenseMapper.insert(expenseDO.setElderId(changeRecordDO.getElderlyId())
                .setCreatedTime(new Date())
                .setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname()));
        BigDecimal totalAmout = BigDecimal.ZERO;
        Date date = new Date();

        priceChangeRecordDOS.forEach(priceChange ->{
            categoryList.forEach(category ->{
                if (category.getOverheadChargeId().equals(priceChange.getOriginalId())){
                    String itemCategory = getNameRecursive(category.getItemCategoryId(), "");
                    BigDecimal currentExpense = category.getActualAmount();
                    // 2. 对比变更后的价格
                    BigDecimal changeExpense = priceChange.getAmount();
                    BigDecimal daysInCurrentMonth = BigDecimal.valueOf(changeRecordDO.getChangeDate().lengthOfMonth());
                    LocalDate firstDayOfMonth = changeRecordDO.getChangeDate().withDayOfMonth(1);
                    BigDecimal currentDays = BigDecimal.valueOf(changeRecordDO.getChangeDate().toEpochDay() - firstDayOfMonth.toEpochDay());
                    LocalDate lastDayOfMonth = firstDayOfMonth.with(TemporalAdjusters.lastDayOfMonth());
                    BigDecimal changeDays = daysInCurrentMonth.subtract(currentDays);
                    BigDecimal finalAmount = null;
                    if (0 == currentExpense.setScale(2).compareTo(changeExpense.setScale(2))) {
                        finalAmount = BigDecimal.ZERO;
                    } else {
                        // 计算当前支出的日均金额，保留8位小数，采用四舍五入方式
                        BigDecimal currentDayAmount = currentExpense.divide(daysInCurrentMonth,8, RoundingMode.HALF_UP);
                        // 计算变动支出的日均金额，保留8位小数，采用四舍五入方式
                        BigDecimal changeDayAmount = changeExpense.divide(daysInCurrentMonth, 8, RoundingMode.HALF_UP);
                        // 计算剩余天数的总金额，包括当前支出和变动支出的每日金额乘以各自的天数
                        BigDecimal remainingDaysAmount = changeDays.multiply(currentDayAmount).add(changeDays.multiply(changeDayAmount));
                        BigDecimal currentFinalAmount = changeDays.multiply(changeDayAmount).subtract(remainingDaysAmount);
                        BigDecimal changeFinalAmount = remainingDaysAmount.subtract(changeDays.multiply(currentDayAmount));
                        finalAmount = currentFinalAmount.add(changeFinalAmount);
                    }
                    String currentRemark = String.format(PRICE_CHANGE_DAILY_PAY_REMARK, currentExpense.setScale(2),
                            currentExpense.setScale(2), changeExpense.setScale(2), currentDays, changeDays);
                    Date currentDate = new Date();
                    DailyExpensesDO currentDailyExpensesDO = new DailyExpensesDO();
                    currentDailyExpensesDO.setElderId(changeRecordDO.getElderlyId());
                    currentDailyExpensesDO.setItemId(category.getOverheadChargeId());
                    currentDailyExpensesDO.setItemCategory(itemCategory);
                    currentDailyExpensesDO.setItemName(category.getItemName());
                    currentDailyExpensesDO.setType(2);
                    currentDailyExpensesDO.setPrice(changeExpense.setScale(2));
                    currentDailyExpensesDO.setAmount(finalAmount);
                    currentDailyExpensesDO.setRemarks(currentRemark);
                    currentDailyExpensesDO.setCreatedBy(SecurityFrameworkUtils.getLoginUserNickname());
                    currentDailyExpensesDO.setCreatedTime(currentDate);
                    currentDailyExpensesDO.setStartDate(changeRecordDO.getChangeDate());
                    currentDailyExpensesDO.setEndDate(lastDayOfMonth);
                    currentDailyExpensesDO.setAccountingTime(currentDate);
                    currentDailyExpensesDO.setCount(1);
                    currentDailyExpensesDO.setAttributionBillTime(String.format("%d-%02d", changeRecordDO.getChangeDate().getYear(), changeRecordDO.getChangeDate().getMonthValue()));
                    dailyExpensesMapper.insert(currentDailyExpensesDO);
                }
                LocalDate firstDayOfMonth = changeRecordDO.getChangeDate().withDayOfMonth(1);
                expenseItemDOS.forEach(item -> {

                    item.setExpenseId(expenseDO.getId());
                    ExpenseItemDO udpateExpenseItemDO = new ExpenseItemDO();
                    if (priceChange.getChargeCategoryId().equals(item.getItemCategoryId())) {
                        item.setActualAmount(priceChange.getAmount());
                        item.setAmount(priceChange.getAmount());
                        item.setTotalAmount(priceChange.getAmount());
                    } else {
                        udpateExpenseItemDO.setChangeFlag(1);
                    }

                    // 申请类型，1-申请入住，2-申请退住，3-外出登记，4-床位变更，5-护理变更，6-餐饮变更，7-价格变更，8-转院变更',
                    // 变更类型，0-床位，1-护理，2-餐饮，3-价格，4-转院
                    Integer bpmType = 0;
                    Integer changeType = 0;
                    Integer type = item.getType() == null ? 0 : item.getType();
                    if (1 == type) {
                        bpmType = 4;
                        changeType = 0;
                    } else if (2 == type) {
                        bpmType = 5;
                        changeType = 1;
                    } else if (3 == type) {
                        bpmType = 6;
                        changeType = 2;
                    }
                    ElderlyChangeRecordDO currentLastRecordDO = null;
                    if (0 != type) {
                        currentLastRecordDO = changeRecordMapper.selectLastRecordByElderId(changeRecordDO.getElderlyId(),
                                bpmType, changeType, changeRecordDO.getChangeDate(), changeRecordDO.getId());
                    }



                    udpateExpenseItemDO.setId(item.getId());
                    LocalDate currentChangeDate = changeRecordDO.getChangeDate().minusDays(1);
                    if (null == currentLastRecordDO) {
                        udpateExpenseItemDO.setChangeStartDate(firstDayOfMonth);
                        udpateExpenseItemDO.setChangeEndDate(currentChangeDate);
                    } else {
                        // TODO 上一次变更的时间
//                    udpateExpenseItemDO.setChangeStartDate(currentLastRecordDO.getChangeDate());
                        udpateExpenseItemDO.setChangeEndDate(currentChangeDate);
                    }
                    expenseItemMapper.updateById(udpateExpenseItemDO);
                    item.setChangeStartDate(changeRecordDO.getChangeDate());
                    item.setId(null);
                    item.setCreateTime(date);
                    totalAmout.add(item.getActualAmount());
                });
            });
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchInsertOrUpdatePriceChange(List<PriceChangeRecordReqVO> priceChangeList, Long id, Long tenantId) {
        String categoryName = "";
        for (int i = 0; i < priceChangeList.size(); i++) {
            PriceChangeRecordDO priceChangeRecordDO = BeanUtils.toBean(priceChangeList.get(i), PriceChangeRecordDO.class);
            priceChangeRecordDO.setChangeId(id);
            priceChangeRecordDO.setTenantId(tenantId);
            priceChangeRecordMapper.insert(priceChangeRecordDO);
            if (0 == i) {
                categoryName = priceChangeRecordDO.getCategoryName();
            } else {
                categoryName = categoryName +  "、" + priceChangeRecordDO.getCategoryName();
            }
        }
        changeRecordMapper.updateById(new ElderlyChangeRecordDO().setId(id).setCategoryName(categoryName));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delectPriceChange(Long changeId, Long tenantId) {
        priceChangeRecordMapper.update(new LambdaUpdateWrapper<PriceChangeRecordDO>()
                .eq(PriceChangeRecordDO::getChangeId, changeId)
                .eq(PriceChangeRecordDO::getTenantId, tenantId)
                .set(PriceChangeRecordDO::getDeleted, true));
    }

    private String getNameRecursive(Long itemCategoryId, String name) {

        SysChargeCategoryDO sysChargeCategoryDO = chargeCategoryMapper.selectById(itemCategoryId);
        if (null == sysChargeCategoryDO || null == sysChargeCategoryDO.getParentId()) {
            String[] split = name.split("/");
            if (null != split && split.length > 2) {
                return split[1] + "/" + split[2];
            }
            if (null != split && split.length > 1) {
                return split[0] + "/" + split[1];
            }
            return name;
        }
        String returnName = "";
        if (StringUtil.isEmpty(name)) {
            returnName = sysChargeCategoryDO.getName();
        } else {
            returnName = sysChargeCategoryDO.getName() + "/" + name;
        }

        return getNameRecursive(sysChargeCategoryDO.getParentId(), returnName);

    }

    @Override
    public Long getMyCopyCount(Long userId) {

        Integer businessType = NotieBusinessTypeEnum.BPM.getValue();
        Integer completedStatus = CommonStatusEnum.ENABLE.getStatus();
        Integer readStatus = CommonStatusEnum.ENABLE.getStatus();

        Integer notifyType = NotifyTypeEnum.NOTIFY_COPY.getValue();
        Long  copyCount = notifyMessageMapper.selectMyCopyCount(userId, null, businessType,
                notifyType, completedStatus, readStatus);
        return copyCount;
    }

    @Override
    @TenantIgnore
    public ElderlyRetreatRecordRespVO getRetreatRecord(Long businessId) {
        ElderlyRetreatRecordDO retreatRecordDO = retreatRecordMapper.selectById(businessId);
        ElderlyRetreatRecordRespVO bean = BeanUtils.toBean(retreatRecordDO, ElderlyRetreatRecordRespVO.class);
        return bean;
    }

    @Override
    public ElderlyChangeRecordReqVO getChangeRecord(Long businessId) {
        ElderlyChangeRecordDO changeRecordDO = changeRecordMapper.selectById(businessId);
        if (null != changeRecordDO) {
            ElderlyChangeRecordReqVO bean = BeanUtils.toBean(changeRecordDO, ElderlyChangeRecordReqVO.class);
            return bean;
        }
        return null;
    }
    private void checkChangeStartDate(Long elderId,LocalDate changeStartDate){
        List<ElderlyContractDO> elderlyContractDOS = elderlyContractMapper.selectList(new LambdaQueryWrapperX<ElderlyContractDO>()
                .eq(ElderlyContractDO::getElderId, elderId)
                .in(ElderlyContractDO::getStatus, 1,2));
        if(CollectionUtil.isEmpty(elderlyContractDOS)){
            throw exception(ErrorCodeConstants.ELDERLY_RETREAT_CONTRACT_NOT_EXISTS);
        }
        for (ElderlyContractDO elderlyContractDO : elderlyContractDOS) {
            LocalDate expireLocalDate = elderlyContractDO.getExpireTime().toInstant()
                    .atZone(ZoneId.systemDefault())
                    .toLocalDate();
            if(expireLocalDate.isAfter(changeStartDate)){
                return;
            }
        }
        throw exception(ErrorCodeConstants.CHANGE_START_DATE_GREATER_EXPIRE_DATE);
    }

    private String resolveAttributionBillMonth(String sourceBillingMonth, YearMonth changeYearMonth, YearMonth hysteresisYearMonth) {
        if (hysteresisYearMonth == null || changeYearMonth == null || StringUtils.isBlank(sourceBillingMonth)) {
            return sourceBillingMonth;
        }
        YearMonth sourceYearMonth = YearMonth.parse(sourceBillingMonth);
        long monthOffset = ChronoUnit.MONTHS.between(changeYearMonth, sourceYearMonth);
        return hysteresisYearMonth.plusMonths(monthOffset).toString();
    }
}
