# CbCaseWidgetServiceImpl.java

    package cloud.wconnect.finance.service.chargeback.impl;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.StatisticsUnitCons;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.finance.cons.chargeback.CaseActionEnum;
    import cloud.wconnect.finance.cons.chargeback.CaseResultEnum;
    import cloud.wconnect.finance.cons.chargeback.StageEnum;
    import cloud.wconnect.finance.entity.chargeback.CbCase;
    import cloud.wconnect.finance.pojo.dto.chargeback.widget.*;
    import cloud.wconnect.finance.pojo.query.chargeback.widget.DisputeAmountStatisticsQuery;
    import cloud.wconnect.finance.pojo.query.chargeback.widget.ResultStatisticsQuery;
    import cloud.wconnect.finance.service.chargeback.ICbCaseService;
    import cloud.wconnect.finance.service.chargeback.ICbCaseWidgetService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import java.math.BigDecimal;
    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.time.ZonedDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     *
     * @author: yyt
     * 2025-12-08
     */
    @Service
    @DS("finance")
    public class CbCaseWidgetServiceImpl implements ICbCaseWidgetService {

        @Autowired
        private ICbCaseService cbCaseService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;

        /**
         * 获取当前资产待处理统计
         *
         * @param propertyId 资产 id
         * @param userId     用户 id
         * @return 待处理统计
         * @author yyt
         * @date 2025/12/8
         **/
        @Override
        public PendingStatisticsDTO listPendingStatistics(int propertyId, int userId) {
            // 未完成数量（排除关闭和忽略的数据）
            Integer incompleteCount = cbCaseService.statisticsIncompleteCase(propertyId);
            // 等待我处理数量
            Integer waitProcessingCount = cbCaseService.statisticsWaitProcessingCase(propertyId, userId);
            // 近14天数据
            String timezone = propertyI18nRedisService.get(propertyId).getTimezone();
            ZonedDateTime endDate = DateTimeUtils.dateNow(timezone);
            ZonedDateTime startDate = endDate.minusDays(13);
            Map<Long, Integer> countByIssueDate = cbCaseService.listByIssueDate(propertyId, startDate.toEpochSecond(), endDate.toEpochSecond())
                    .stream().collect(Collectors.groupingBy(CbCase::getIssueDate, Collectors.collectingAndThen(Collectors.counting(), Long::intValue)));

            PendingStatisticsDTO pendingStatisticsDTO = new PendingStatisticsDTO();
            pendingStatisticsDTO.setIncompleteCount(incompleteCount);
            pendingStatisticsDTO.setWaitProcessingCount(waitProcessingCount);

            List<PendingStatisticsDetailDTO> data = Lists.newArrayListWithExpectedSize(14);
            for (ZonedDateTime date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
                PendingStatisticsDetailDTO detailDTO = new PendingStatisticsDetailDTO();
                detailDTO.setDate(DateTimeUtils.parseZonedDateTimeToStr(date, BaseConstant.DATE_FORMAT_YYYY_MM_DD));
                detailDTO.setValue(countByIssueDate.getOrDefault(date.toEpochSecond(), BaseConstant.ZERO));
                data.add(detailDTO);
            }
            pendingStatisticsDTO.setData(data);

            return pendingStatisticsDTO;
        }

        /**
         * 获取资产待处理逾期统计
         *
         * @param propertyIds 资产 id
         * @return 待处理逾期统计
         * @author yyt
         * @date 2025/12/8
         **/
        @Override
        public List<PendingOverdueStatisticsDTO> listPendingOverdueStatistics(List<Integer> propertyIds) {
            // 查询未完成数据
            List<CbCase> cbCases = cbCaseService.listIncompleteCase(propertyIds);
            Map<Integer, List<CbCase>> casesByPropertyId = cbCases.stream().collect(Collectors.groupingBy(CbCase::getPropertyId));

            List<PropertyDTO> propertyDTOS = propertyI18nRedisService.list(propertyIds);
            return propertyDTOS.stream().map(item -> {
                Integer propertyId = item.getId();
                PendingOverdueStatisticsDTO statisticsDTO = new PendingOverdueStatisticsDTO();
                statisticsDTO.setPropertyId(propertyId);
                statisticsDTO.setPropertyName(item.getAbbr());
                List<CbCase> cs = casesByPropertyId.getOrDefault(propertyId, Collections.emptyList());
                statisticsDTO.setIncompleteCount(cs.size());
                statisticsDTO.setOverdueCount((int) cs.stream().filter(CbCase::getHasOverdue).count());
                return statisticsDTO;
            }).toList();
        }

        /**
         * 获取资产结果统计【固定日期区间】
         *
         * @param propertyIds 资产 id
         * @return 结果统计
         * @author yyt
         * @date 2025/12/8
         **/
        @Override
        public List<ResultStatisticsDTO> listResultStatisticsForFixedDateRange(List<Integer> propertyIds) {
            // 固定查询上个月
            Map<Integer, List<CbCase>> cbCasesByPropertyId = cbCaseService.listLastMonthCaseByIssueDate(propertyIds)
                    .stream().collect(Collectors.groupingBy(CbCase::getPropertyId));

            List<PropertyDTO> propertyDTOS = propertyI18nRedisService.list(propertyIds);
            return packageResultStatisticsDTO(propertyDTOS, cbCasesByPropertyId);
        }

        /**
         * 获取资产结果统计【动态日期区间】
         *
         * @param query 检索条件
         * @return 结果统计
         * @author yyt
         * @date 2025/12/8
         **/
        @Override
        public List<ResultStatisticsDTO> listResultStatisticsForDynamicDateRange(ResultStatisticsQuery query) {
            // 查询多个资产的数据
            List<Integer> propertyIds = query.getPropertyIds();
            List<PropertyDTO> propertyDTOS = propertyI18nRedisService.list(propertyIds);
            Map<Integer, List<CbCase>> cbCasesByPropertyId = listCase(propertyIds, query.getUnit(), query.getSpan());

            return packageResultStatisticsDTO(propertyDTOS, cbCasesByPropertyId);
        }

        /**
         * 封装结果统计
         *
         * @param propertyDTOS      资产信息
         * @param cbCasesByPropertyId 待处理数据
         * @return 结果统计
         * @author yyt
         * @date 2025/12/8
         **/
        private List<ResultStatisticsDTO> packageResultStatisticsDTO(List<PropertyDTO> propertyDTOS, Map<Integer, List<CbCase>> cbCasesByPropertyId) {
            return propertyDTOS.stream().map(item -> {
                Integer propertyId = item.getId();
                ResultStatisticsDTO statisticsDTO = new ResultStatisticsDTO();
                statisticsDTO.setPropertyId(propertyId);
                statisticsDTO.setPropertyName(item.getAbbr());

                List<CbCase> cases = cbCasesByPropertyId.getOrDefault(propertyId, Collections.emptyList());
                int winCount = 0, lossCount = 0, acceptCount = 0;
                for (CbCase cs : cases) {
                    int result = cs.getResult();
                    int action = cs.getAction();

                    if (result == CaseResultEnum.WIN.getCode()) {
                        winCount++;
                    } else if (result == CaseResultEnum.LOSS.getCode()) {
                        lossCount++;
                    }
                    if (action == CaseActionEnum.ACCEPT.getCode()) {
                        acceptCount++;
                    }
                }
                statisticsDTO.setWinCount(winCount);
                statisticsDTO.setLossCount(lossCount);
                statisticsDTO.setAcceptCount(acceptCount);
                return statisticsDTO;
            }).toList();
        }

        /**
         * 查询多个资产，case 发行日期在不同区间的数据
         *
         * @param propertyIds 资产 id
         * @param unit 统计单位
         * @param span 统计区间
         * @return case
         * @author yyt
         * @date 2025/12/8
         **/
        private Map<Integer, List<CbCase>> listCase(List<Integer> propertyIds, Integer unit, Integer span) {
            // 根据统计单位计算统计日期
            List<LocalDate> statisticDate = DateTimeUtils.calculateStatisticsDate(unit, span);
            if (CollectionUtils.isEmpty(statisticDate)) {
                return Collections.emptyMap();
            }

            // 查询多个资产的数据
            LocalDate startLocalDate = statisticDate.get(0);
            LocalDate endLocalDate = statisticDate.get(1);
            List<PropertyDTO> propertyDTOS = propertyI18nRedisService.list(propertyIds);
            return cbCaseService.listByIssueDate(propertyDTOS, startLocalDate, endLocalDate)
                    .stream().collect(Collectors.groupingBy(CbCase::getPropertyId));
        }

        /**
         * 获取当前资产争议金额统计
         *
         * @param propertyId 资产 id
         * @param query      检索条件
         * @return 争议金额统计
         * @author yyt
         * @date 2025/12/8
         **/
        @Override
        public DisputeAmountStatisticsDTO listCurrentPropertyDisputeAmountStatistics(Integer propertyId, DisputeAmountStatisticsQuery query) {
            Integer unit = query.getUnit();
            Integer span = query.getSpan();
            Map<Integer, List<CbCase>> cbCasesByPropertyId = listCase(Lists.newArrayList(propertyId), unit, span);
            List<CbCase> cases = cbCasesByPropertyId.getOrDefault(propertyId, Collections.emptyList());

            DisputeAmountStatisticsDTO disputeAmountStatisticsDTO = new DisputeAmountStatisticsDTO();
            disputeAmountStatisticsDTO.setRecoveryAmount(filterRecoveryAmount(cases));
            disputeAmountStatisticsDTO.setLossAmount(filterLossAmount(cases));
            disputeAmountStatisticsDTO.setData(generateDisputeAmountStatistics(propertyId, cases, unit, span));
            return disputeAmountStatisticsDTO;
        }

        /**
         * 封装争议金额统计
         *
         * @param propertyId      资产 id
         * @param cases           case 数据
         * @param unit            统计单位
         * @param span            统计区间
         * @return 争议金额统计
         * @author yyt
         * @date 2025/12/8
         **/
        private List<DisputeAmountStatisticsDetailDTO> generateDisputeAmountStatistics(Integer propertyId, List<CbCase> cases,Integer unit,Integer span) {
            String timezone = propertyI18nRedisService.get(propertyId).getTimezone();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM);

            List<DisputeAmountStatisticsDetailDTO> statisticsDetailDTOS = Lists.newArrayListWithCapacity(span);
            LocalDate localDate = LocalDate.now(ZoneId.of(timezone));
            if (StatisticsUnitCons.MONTH == unit) {
                for (int i = 1; i <= span; i++) {
                    // 获取日期,例如：2023-05
                    String date = localDate.format(formatter);
                    // 当月的开始日期
                    long startDate = DateTimeUtils.localDateToLong(localDate.withDayOfMonth(1), timezone);
                    // 当月的结束日期
                    long endDate = DateTimeUtils.localDateToLong(localDate.withDayOfMonth(localDate.lengthOfMonth()), timezone);
                    statisticsDetailDTOS.add(packageDisputeAmountStatisticsDetailDTO(date, startDate, endDate, cases));

                    //往前推一个月
                    localDate = localDate.minusMonths(1);
                }
            } else if(StatisticsUnitCons.QUARTER == unit) {
                for (int i = 1; i <= span; i++) {
                    int year = localDate.getYear();
                    // 当前日期的所属季度
                    int quarter = (localDate.getMonthValue() - 1) / 3 + 1;
                    // 季度的开始日期
                    LocalDate startDate = LocalDate.of(year, quarter * 3 - 2, 1);
                    // 季度的日期格式为：2023-1
                    String date = year + "-" + quarter;
                    statisticsDetailDTOS.add(packageDisputeAmountStatisticsDetailDTO(date,
                            DateTimeUtils.localDateToLong(startDate, timezone),
                            DateTimeUtils.localDateToLong(startDate.plusMonths(3).minusDays(1), timezone),
                            cases));

                    // 往前推一个季度
                    localDate = localDate.minusMonths(3);
                }
            } else {
                localDate = localDate.withDayOfYear(1);
                for (int i = 1; i <= span; i++) {
                    // 当前年的开始日期
                    long startDate = DateTimeUtils.localDateToLong(localDate, timezone);
                    // 当前年的结束日期
                    long endDate = DateTimeUtils.localDateToLong(localDate.plusYears(1).minusDays(1), timezone);
                    statisticsDetailDTOS.add(packageDisputeAmountStatisticsDetailDTO(String.valueOf(localDate.getYear()), startDate, endDate, cases));

                    // 往前推一年
                    localDate = localDate.minusYears(1);
                }
            }

            statisticsDetailDTOS.sort(Comparator.comparing(DisputeAmountStatisticsDetailDTO::getDate));

            return statisticsDetailDTOS;
        }

        /**
         * 封装争议金额统计详情
         *
         * @param date      统计日期
         * @param startDate 开始日期
         * @param endDate   结束日期
         * @param cases     争议 case
         * @return 争议金额统计详情
         * @author yyt
         * @date 2025/12/9
         **/
        private DisputeAmountStatisticsDetailDTO packageDisputeAmountStatisticsDetailDTO(String date, long startDate, long endDate, List<CbCase> cases) {
            DisputeAmountStatisticsDetailDTO detailDTO = new DisputeAmountStatisticsDetailDTO();
            detailDTO.setDate(date);
            List<CbCase> matchList = cases.stream()
                    .filter(item -> item.getIssueDate() >= startDate && item.getIssueDate() <= endDate)
                    .toList();
            detailDTO.setRecoveryAmount(filterRecoveryAmount(matchList));
            detailDTO.setLossAmount(filterLossAmount(matchList));
            return detailDTO;
        }

        /**
         * 获取各个资产争议金额统计
         *
         * @param query 检索条件
         * @return 争议金额统计
         * @author yyt
         * @date 2025/12/9
         **/
        @Override
        public List<MultiplePropertyDisputeAmountStatisticsDTO> listMultiplePropertyDisputeAmountStatistics(DisputeAmountStatisticsQuery query) {
            List<Integer> propertyIds = query.getPropertyIds();
            Map<Integer, List<CbCase>> cbCasesByPropertyId = listCase(propertyIds, query.getUnit(), query.getSpan());

            List<PropertyDTO> propertyDTOS = propertyI18nRedisService.list(propertyIds);
            return propertyDTOS.stream().map(propertyDTO -> {
                MultiplePropertyDisputeAmountStatisticsDTO statisticsDTO = new MultiplePropertyDisputeAmountStatisticsDTO();
                Integer propertyId = propertyDTO.getId();
                statisticsDTO.setPropertyId(propertyId);
                statisticsDTO.setPropertyName(propertyDTO.getAbbr());

                List<CbCase> cases = cbCasesByPropertyId.getOrDefault(propertyId, Collections.emptyList());
                statisticsDTO.setRecoveryAmount(filterRecoveryAmount(cases));
                statisticsDTO.setLossAmount(filterLossAmount(cases));

                return statisticsDTO;
            }).toList();
        }

        /**
         * 过滤出追回金额
         *
         * @param cases case 数据
         * @return 追回金额
         * @author yyt
         * @date 2025/12/9
         **/
        private BigDecimal filterRecoveryAmount(List<CbCase> cases) {
            return cases.stream().filter(item -> item.getResult() == CaseResultEnum.WIN.getCode())
                    .map(CbCase::getTrxAmount).filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
        }

        /**
         * 过滤出损失金额
         *
         * @param cases case 数据
         * @return 损失金额
         * @author yyt
         * @date 2025/12/9
         **/
        private BigDecimal filterLossAmount(List<CbCase> cases) {
            return cases.stream().filter(item -> item.getResult() == CaseResultEnum.LOSS.getCode()
                            || item.getAction() == CaseActionEnum.ACCEPT.getCode())
                    .map(CbCase::getTrxAmount).filter(Objects::nonNull).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
        }

        /**
         * 获取资产超期未处理统计
         *
         * @param propertyIds 资产 id
         * @return 统计结果
         * @author yyt
         * @date 2025/12/9
         **/
        @Override
        public List<OverdueUnprocessStatisticsDTO> listOverdueUnprocessStatistics(List<Integer> propertyIds) {
            List<CbCase> cases = cbCaseService.listOverdueUnprocess(propertyIds);
            Map<Integer, Map<Integer, Integer>> countByPropertyId = cases.stream().collect(Collectors.groupingBy(CbCase::getPropertyId,
                    Collectors.groupingBy(CbCase::getStage, Collectors.collectingAndThen(Collectors.toList(), List::size))));
            List<PropertyDTO> propertyDTOS = propertyI18nRedisService.list(propertyIds);
            return propertyDTOS.stream().map(propertyDTO -> {
                OverdueUnprocessStatisticsDTO statisticsDTO = new OverdueUnprocessStatisticsDTO();
                Integer propertyId = propertyDTO.getId();
                statisticsDTO.setPropertyId(propertyId);
                statisticsDTO.setPropertyName(propertyDTO.getAbbr());

                Map<Integer, Integer> countByStage = countByPropertyId.getOrDefault(propertyId, Collections.emptyMap());
                statisticsDTO.setHotelOperationOverdueUnprocessCount(countByStage.getOrDefault(StageEnum.HOTEL_OPERATION.getCode(), BaseConstant.ZERO));
                statisticsDTO.setLossAdjOverdueUnprocessCount(countByStage.getOrDefault(StageEnum.PATCH.getCode(), BaseConstant.ZERO));

                return statisticsDTO;
            }).toList();
        }

    }