# SalesKpiWconnectAdjustmentServiceImpl.java

    package cloud.wconnect.workperformance.service.saleskpi.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.bo.adj.AdjBO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.workperformance.cons.saleskpi.SalesKpiMonthlyDataSourceCons;
    import cloud.wconnect.workperformance.entity.saleskpi.SalesKpiMonthly;
    import cloud.wconnect.workperformance.entity.saleskpi.SalesKpiMonthlyConfig;
    import cloud.wconnect.workperformance.entity.saleskpi.SalesKpiWconnectAdjustment;
    import cloud.wconnect.workperformance.mapper.saleskpi.SalesKpiWconnectAdjustmentMapper;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.monthly.basic.SalesKpiMonthlyBasicDTO;
    import cloud.wconnect.workperformance.pojo.dto.saleskpi.monthly.basic.SalesKpiMonthlyValueBasicDTO;
    import cloud.wconnect.workperformance.service.saleskpi.ISalesKpiMonthlyConfigService;
    import cloud.wconnect.workperformance.service.saleskpi.ISalesKpiMonthlyService;
    import cloud.wconnect.workperformance.service.saleskpi.ISalesKpiWconnectAdjustmentService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import jakarta.annotation.Resource;
    import org.apache.commons.collections4.CollectionUtils;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.math.BigDecimal;
    import java.time.LocalDate;
    import java.time.YearMonth;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * <p>
     * Hotel KPI w-connect Adjustment 数据 服务实现类
     * </p>
     *
     * @author zc
     * @since 2024-08-27
     */
    @Service
    @DS("work_performance")
    public class SalesKpiWconnectAdjustmentServiceImpl extends ServiceImpl<SalesKpiWconnectAdjustmentMapper, SalesKpiWconnectAdjustment> implements ISalesKpiWconnectAdjustmentService {
        @Resource
        private ISalesKpiMonthlyService monthlyService;
        @Resource
        private ISalesKpiMonthlyConfigService monthlyConfigService;
        @Resource
        private BeanMapper beanMapper;
        @Resource
        private ObjectMapper objectMapper;

        /**
         * 保存数据
         *
         * @param propertyId 资产 id
         * @param adjBOList  adjustment 数据
         * @param updateTime 更新时间（毫秒级别时间戳）
         * @author zc
         * @date 2024/8/27
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void save(int propertyId, List<AdjBO> adjBOList, long updateTime) {
            if (CollectionUtils.isEmpty(adjBOList)) {
                return;
            }
            List<SalesKpiWconnectAdjustment> adjustmentList = beanMapper.mapList(adjBOList,
                    SalesKpiWconnectAdjustment.class, (bo, item) -> {
                        item.setPropertyId(propertyId);
                        item.setUpdateTime(updateTime);
                    });

            // 按唯一索引字段排序，避免死锁
            adjustmentList.sort(Comparator.comparing(SalesKpiWconnectAdjustment::getPropertyId)
                    .thenComparing(SalesKpiWconnectAdjustment::getAdjNumber));

            baseMapper.insertOrUpdateBatchByUnique(adjustmentList);
        }

        /**
         * 删除数据
         *
         * @param propertyId 资产 id
         * @param adjBOList  adjustment 数据
         * @author zc
         * @date 2024/8/27
         */
        @Override
        public void delete(int propertyId, List<AdjBO> adjBOList) {
            if (CollectionUtils.isEmpty(adjBOList)) {
                return;
            }

            Set<String> adjNumList = adjBOList.stream()
                    .map(AdjBO::getAdjNumber).collect(Collectors.toSet());

            remove(new QueryWrapper<SalesKpiWconnectAdjustment>().lambda()
                    .eq(SalesKpiWconnectAdjustment::getPropertyId, propertyId)
                    .in(SalesKpiWconnectAdjustment::getAdjNumber, adjNumList));
        }

        /**
         * 根据指定日期范围获取数据
         *
         * @param propertyId 资产 id
         * @param startDate  开始日期（yyyyMMdd）
         * @param endDate    结束日期（yyyyMMdd）
         * @return SalesKpiWconnectAdjustment 集合
         * @author zc
         * @date 2024/8/27
         */
        @Override
        public List<SalesKpiWconnectAdjustment> listByDateRange(int propertyId, long startDate, long endDate) {
            return list(new LambdaQueryWrapper<SalesKpiWconnectAdjustment>()
                    .eq(SalesKpiWconnectAdjustment::getPropertyId, propertyId)
                    //businessDate等于0表示未配对的数据，此处只查询已配对的数据
                    .between(SalesKpiWconnectAdjustment::getBusinessDate, startDate, endDate));
        }

        /**
         * 组装 Revenue lost 指标数据
         *
         * @param propertyId  资产 id
         * @param monthly     月（yyyyMM）
         * @param actualValue actual value
         * @author zc
         * @date 2024/8/27
         */
        @Override
        public void packageItemValueForRevenueLost(int propertyId, long monthly, SalesKpiMonthlyValueBasicDTO actualValue) {
            YearMonth yearMonth = YearMonthUtils.getYearMonth(Long.valueOf(monthly).toString());
            long startDate = Long.parseLong(DateTimeUtils.localDateToStr(yearMonth.atDay(1),
                    BaseConstant.DATE_FORMAT_YYYYMMDD));
            long endDate = Long.parseLong(DateTimeUtils.localDateToStr(yearMonth.atEndOfMonth(),
                    BaseConstant.DATE_FORMAT_YYYYMMDD));

            List<SalesKpiWconnectAdjustment> adjustmentList = listByDateRange(propertyId, startDate, endDate);
            BigDecimal totalDollar = adjustmentList.stream()
                    .filter(adRequest -> adRequest.getRevenueLost() != null
                            && adRequest.getRevenueLost()
                            && adRequest.getRequestType().equals(SalesKpiWconnectAdjustment.REQUEST_TYPE_SALES))
                    .map(SalesKpiWconnectAdjustment::getAdjustedDollar).reduce(BigDecimal.ZERO, BigDecimal::add);
            actualValue.setValue(totalDollar);
            actualValue.setCrawlValue(totalDollar);
        }

        /**
         * 处理 Revenue Adj Lost
         *
         * @param propertyDTO 资产
         * @param theDateList 日期（yyyyMMdd）
         * @author zc
         * @date 2023/10/27
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleRevenueAdjLostItem(PropertyDTO propertyDTO, List<Long> theDateList) {
            SalesKpiMonthlyConfig monthlyConfig = monthlyConfigService.getByDataSource(propertyDTO.getId(),
                    SalesKpiMonthlyDataSourceCons.DATA_SOURCE_REVENUE_ADJ_LOST);
            if (Objects.isNull(monthlyConfig)) {
                return;
            }

            Set<Long> monthlyList = theDateList.stream().map(item -> {
                LocalDate localDate = DateTimeUtils.strToLocalDate(item.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
                return YearMonthUtils.parseYearMonthToLong(YearMonth.of(localDate.getYear(), localDate.getMonth()));
            }).collect(Collectors.toSet());

            Map<Integer, List<Long>> year2Monthly = monthlyList.stream().map(item ->
                            YearMonthUtils.getYearMonth(item.toString()))
                    .collect(Collectors.groupingBy(YearMonth::getYear,
                            Collectors.mapping(YearMonthUtils::parseYearMonthToLong, Collectors.toList())));

            year2Monthly.forEach((year, monthlies) -> {
                List<SalesKpiMonthly> salesKpiMonthlyList = monthlyService.listByMonthliesForUpdate(propertyDTO.getId(), monthlies);
                if (CollectionUtils.isEmpty(salesKpiMonthlyList)) {
                    return;
                }

                // 取最小日期
                YearMonth minYearMonth = YearMonthUtils.getYearMonth(Collections.min(monthlies).toString());
                long startTheDate = Long.parseLong(DateTimeUtils.localDateToStr(minYearMonth.atDay(1),
                        BaseConstant.DATE_FORMAT_YYYYMMDD));
                // 取最大日期
                YearMonth maxYearMonth = YearMonthUtils.getYearMonth(Collections.max(monthlies).toString());
                long endTheDate = Long.parseLong(DateTimeUtils.localDateToStr(maxYearMonth.atEndOfMonth(),
                        BaseConstant.DATE_FORMAT_YYYYMMDD));

                // 获取 Adj 数据
                Map<Long, List<SalesKpiWconnectAdjustment>> monthly2AdRequestList = listByDateRange(propertyDTO.getId(),
                        startTheDate, endTheDate).stream().peek(item -> {
                    LocalDate localDate = DateTimeUtils.strToLocalDate(item.getBusinessDate().toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
                    item.setMonth(YearMonthUtils.parseYearMonthToLong(YearMonth.of(localDate.getYear(), localDate.getMonth())));
                }).collect(Collectors.groupingBy(SalesKpiWconnectAdjustment::getMonth));

                salesKpiMonthlyList.forEach(item -> {
                    List<SalesKpiMonthlyBasicDTO> salesKpiMonthlyBasicList = JsonUtils.convertStringToList(objectMapper,
                            item.getJsonDetail(), SalesKpiMonthlyBasicDTO.class);
                    salesKpiMonthlyBasicList.forEach(monthlyItem -> {
                        if (!monthlyItem.getItemId().equals(monthlyConfig.getId())) {
                            return;
                        }
                        List<SalesKpiWconnectAdjustment> adRequestList = monthly2AdRequestList.get(item.getMonthly());
                        BigDecimal totalDollar = null;
                        if (CollectionUtils.isNotEmpty(adRequestList)) {
                            totalDollar = adRequestList.stream()
                                    .filter(adRequest -> adRequest.getRevenueLost() != null
                                            && adRequest.getRevenueLost()
                                            && adRequest.getRequestType().equals(SalesKpiWconnectAdjustment.REQUEST_TYPE_SALES))
                                    .map(SalesKpiWconnectAdjustment::getAdjustedDollar).reduce(BigDecimal.ZERO, BigDecimal::add);
                        }
                        monthlyService.setMonthlyCrawlValue(monthlyItem.getActual(), totalDollar);
                    });
                    item.setJsonDetail(JsonUtils.convertObjListToString(objectMapper, salesKpiMonthlyBasicList));
                });

                monthlyService.updateBatchById(salesKpiMonthlyList);
            });

        }
    }