# AdjWidgetController.java

    package cloud.wconnect.frontoffice.controller.adjustment;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.cons.ReportImportResultCons;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.frontoffice.cons.adjustment.FlowStatusEnum;
    import cloud.wconnect.frontoffice.cons.I18nConstant;
    import cloud.wconnect.frontoffice.cons.adjustment.AdjustmentStatusEnum;
    import cloud.wconnect.frontoffice.cons.adjustment.MistakeTypeEnum;
    import cloud.wconnect.frontoffice.entity.adjustment.AdRequest;
    import cloud.wconnect.frontoffice.pojo.query.adjustment.AdRequestQuery;
    import cloud.wconnect.frontoffice.pojo.query.adjustment.StatisticsTypeQuery;
    import cloud.wconnect.frontoffice.pojo.query.adjustment.StatisticsTypeTrendQuery;
    import cloud.wconnect.frontoffice.pojo.vo.adjustment.AdjToDoVO;
    import cloud.wconnect.frontoffice.pojo.vo.adjustment.statistic.AdjAmountByTypeVO;
    import cloud.wconnect.frontoffice.pojo.vo.adjustment.statistic.AdjCountByTypeVO;
    import cloud.wconnect.frontoffice.pojo.vo.adjustment.statistic.StatisticsTrendVO;
    import cloud.wconnect.frontoffice.pojo.vo.adjustment.statistic.StatisticsTypeVO;
    import cloud.wconnect.frontoffice.service.adjustment.IAdJournalLogService;
    import cloud.wconnect.frontoffice.service.adjustment.IAdRequestService;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.google.common.collect.ImmutableMap;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import org.apache.commons.collections4.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import jakarta.validation.Valid;
    import java.math.BigDecimal;
    import java.text.MessageFormat;
    import java.time.LocalDate;
    import java.time.temporal.ChronoUnit;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * 组件控制层
     *
     * @author lichen
     * @date 2023/12/11
     */
    @RestController
    @RequestMapping("/adjustment/widget")
    public class AdjWidgetController {

        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private IAdRequestService adRequestService;
        @Autowired
        private IAdJournalLogService adJournalLogService;
        @Autowired
        private UserInternalService userInternalService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;

        /**
         * 统计金额
         *
         * @param property 资产标识
         * @return java.util.Map<java.lang.String, java.lang.Object>
         * @author lichen
         * @date 2023/12/11
         */
        @ApiId(994)
        @GetMapping("/statistics/amount")
        public Map<String, Object> statisticsAmount(String property) {
            int propertyId = sessionContext.getPropertyId(property);
            return ImmutableMap.of("totalAmount", adRequestService.getMatchedTotalAmount(propertyId));
        }

        /**
         * 统计代办
         *
         * @param property 资产标识
         * @return cloud.wconnect.frontoffice.pojo.vo.adjustment.AdjToDoVO
         * @author lichen
         * @date 2023/12/11
         */
        @ApiId(995)
        @GetMapping("/statistics/to-do")
        public AdjToDoVO statisticsToDo(String property) {
            int propertyId = sessionContext.getPropertyId(property);
            int loginUserId = sessionContext.getLoginUserId();

            AdjToDoVO adjToDoVO = new AdjToDoVO();

            //查询当前登录人有权审批的数量
            Long waitApprovalTotal = adRequestService.getWaitApprovalTotalByApprover(propertyId, loginUserId);
            adjToDoVO.setWaitApprovalTotal(waitApprovalTotal);

            //查询草稿的数量
            Long totalByDraft = adRequestService.countByFlowStatusAndDutyUser(propertyId, loginUserId, FlowStatusEnum.DRAFT.getFlowStatus());
            adjToDoVO.setDraftTotal(totalByDraft);

            //查询归属于当前登录人的无效数据
            Long invalidTotal = adRequestService.countByStatusAndDutyUser(propertyId, loginUserId, AdjustmentStatusEnum.INVALID.getStatus());
            adjToDoVO.setInvalidTotal(invalidTotal);

            //查询异常的导入报告数量
            adjToDoVO.setExceptionTotal(adJournalLogService.countByStatus(propertyId, ReportImportResultCons.IMPORT_RESULT_FAIL));

            //查询待分配的数量
            Long needAssignTotal = adRequestService.countNeedsAssign(propertyId, loginUserId);
            adjToDoVO.setNeedAssignTotal(needAssignTotal);

            //查询指定状态的adj金额
            AdRequestQuery adRequestQuery = new AdRequestQuery();
            adRequestQuery.setQueryStatusList(Lists.newArrayList(AdRequestQuery.QUERY_STATUS_DRAFT));
            adRequestQuery.setDutyUser(loginUserId);
            Map<String, BigDecimal> draftTotalAmount  = adRequestService.summary(propertyId, adRequestQuery);
            adjToDoVO.setDraftTotalAmount(draftTotalAmount.get("totalAmount"));
            return adjToDoVO;
        }

        /**
         * 统计不同类型下的数量和金额
         *
         * @param property 资产标识
         * @param query    查询条件
         * @return cloud.wconnect.frontoffice.pojo.vo.adjustment.statistic.StatisticsTypeVO
         * @author lichen
         * @date 2023/12/11
         */
        @ApiId(996)
        @GetMapping("/statistics/type")
        public StatisticsTypeVO statisticsTotalAndAmountByCondition(String property, @Valid StatisticsTypeQuery query) {
            if (query.getEndDate() < query.getStartDate()) {
                throw new BadRequestException(CommonErrorI18nConstant.START_DATE_CANNOT_GREATER_THAN_END_DATE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The endDate must be greater than or equal to the startDate");
            }
            LocalDate startDate = DateTimeUtils.strToLocalDate(query.getStartDate().toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
            LocalDate endDate = DateTimeUtils.strToLocalDate(query.getEndDate().toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);
            if (ChronoUnit.DAYS.between(startDate, endDate) > 366) {
                throw new BadRequestException(CommonErrorI18nConstant.SELECT_DATE_LIMIT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate(366),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The time span cannot exceed 1 year");
            }

            int propertyId = sessionContext.getPropertyId(property);
            Integer tenantId = sessionContext.getTenantSymbolBO().getId();

            //查询日期范围内的adjustment集合
            List<AdRequest> adjList = adRequestService.listMatchedByDateRange(propertyId,
                    query.getStartDate().toString(), query.getEndDate().toString());
            if (CollectionUtils.isEmpty(adjList)) return new StatisticsTypeVO();

            //当前语言
            Locale locale = LocaleContextHolder.getLocale();
            StatisticsTypeVO statisticsTypeVO = statisticsByCondition(adjList, query.getCondition(), locale);

            if (StatisticsTypeQuery.DUTY_USER == query.getCondition()) {
                //责任人集合
                Set<Integer> dutyUserSet = adjList.stream().map(AdRequest::getDutyUser).collect(Collectors.toSet());
                //用户集合
                Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(tenantId, dutyUserSet);

                //把type替换成用户姓名
                statisticsTypeVO.getCountList().forEach(item -> item.setType(userId2Name.get(Integer.valueOf(item.getType()))));
                statisticsTypeVO.getAmountList().forEach(item -> item.setType(userId2Name.get(Integer.valueOf(item.getType()))));
            }

            return statisticsTypeVO;
        }

        /**
         * 统计不同资产下的数量和金额的趋势
         *
         * @param query 查询条件
         * @return cloud.wconnect.frontoffice.pojo.vo.adjustment.statistic.StatisticsTrendVO
         * @author lichen
         * @date 2023/12/11
         */
        @ApiId(997)
        @GetMapping("/statistics/property/trend")
        public StatisticsTrendVO statisticsTotalAndAmountTrend(@Valid StatisticsTypeTrendQuery query) {
            List<Integer> hasPermissionPropertyIds = sessionContext.getPropertyIds();
            //校验用户是否有权限查询这些资产的数据
            List<Integer> propertyIds = query.getPropertyIds();
            if (propertyIds.stream().anyMatch(propertyId -> !hasPermissionPropertyIds.contains(propertyId))) {
                propertyIds.removeAll(hasPermissionPropertyIds);
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(), BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(),
                        MessageFormat.format("Users do not have permission to query data on these property, not have permission propertyIds:{0}",
                                propertyIds.toString()));
            }

            if (query.getPropertyIds().size() != Sets.newHashSet(query.getPropertyIds()).size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("propertyIds"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "The propertyIds cannot repeat");
            }

            //校验时间跨度
            checkSpan(query.getUnit(), query.getSpan());

            //有效的资产
            List<PropertyDTO> propertyDTOList = propertyI18nRedisService.list(propertyIds);

            if (CollectionUtils.isEmpty(propertyDTOList)) return new StatisticsTrendVO();

            //查询资产adj数量和金额的趋势
            return adRequestService.listPropertyTotalAndAmountTrend(query, propertyDTOList);
        }

        /**
         * 根据条件进行统计
         *
         * @param adjList   adj数据集合
         * @param condition 条件
         * @param locale    语言
         * @return cloud.wconnect.frontoffice.pojo.vo.adjustment.statistic.StatisticsTypeVO
         * @author lichen
         * @date 2023/12/11
         */
        private StatisticsTypeVO statisticsByCondition(List<AdRequest> adjList, Integer condition, Locale locale) {
            if (StatisticsTypeQuery.ADJUSTMENT_REASON == condition
                    || StatisticsTypeQuery.REQUEST_TYPE == condition
                    || StatisticsTypeQuery.DUTY_USER == condition) {

                Map<Integer, List<AdRequest>> adjListByCondition = Collections.emptyMap();
                switch (condition) {
                    case StatisticsTypeQuery.ADJUSTMENT_REASON:
                        adjListByCondition = adjList.stream().collect(Collectors.groupingBy(AdRequest::getChoosedReason));
                        break;
                    case StatisticsTypeQuery.REQUEST_TYPE:
                        adjListByCondition = adjList.stream().collect(Collectors.groupingBy(AdRequest::getRequestType));
                        break;
                    case StatisticsTypeQuery.DUTY_USER:
                        adjListByCondition = adjList.stream().collect(Collectors.groupingBy(AdRequest::getDutyUser));
                        break;
                    default:
                }
                List<AdjCountByTypeVO> countList = Lists.newArrayListWithCapacity(adjListByCondition.size());
                List<AdjAmountByTypeVO> amountList = Lists.newArrayListWithCapacity(adjListByCondition.size());

                for (Map.Entry<Integer, List<AdRequest>> entry : adjListByCondition.entrySet()) {
                    String type = entry.getKey().toString();
                    List<AdRequest> value = entry.getValue();

                    countList.add(new AdjCountByTypeVO(type, value.size()));

                    amountList.add(new AdjAmountByTypeVO(type, value.stream()
                            .map(AdRequest::getAdjustedDollar)
                            .reduce(BigDecimal::add).orElse(BigDecimal.ZERO)));
                }
                return new StatisticsTypeVO(countList, amountList);
            } else if (StatisticsTypeQuery.MISTAKE_BY == condition) {
                Map<Boolean, List<AdRequest>> isEmployeeName2AdjList = adjList.stream()
                        .collect(Collectors.partitioningBy(item -> item.getMistakeType() == MistakeTypeEnum.EMPLOYEE_NAME.getType()));

                //有责任者的数据
                Map<String, List<AdRequest>> mistakeBy2Adjustment = isEmployeeName2AdjList.get(true).stream().collect(Collectors.groupingBy(item -> item.getMistakeBy().trim()));
                List<AdjCountByTypeVO> countList = Lists.newArrayListWithCapacity(mistakeBy2Adjustment.size() + 1);
                List<AdjAmountByTypeVO> amountList = Lists.newArrayListWithCapacity(mistakeBy2Adjustment.size() + 1);

                for (Map.Entry<String, List<AdRequest>> entry : mistakeBy2Adjustment.entrySet()) {
                    String type = entry.getKey();
                    List<AdRequest> value = entry.getValue();

                    countList.add(new AdjCountByTypeVO(type, value.size()));

                    amountList.add(new AdjAmountByTypeVO(type, value.stream()
                            .map(AdRequest::getAdjustedDollar)
                            .reduce(BigDecimal::add).orElse(BigDecimal.ZERO)));
                }
                //责任者为空的数据
                String i18nNAStr = i18nMessageUtils.message(I18nConstant.NA, locale);
                List<AdRequest> adjListByNotMistaker = isEmployeeName2AdjList.get(false);
                countList.add(new AdjCountByTypeVO(i18nNAStr, adjListByNotMistaker.size()));
                amountList.add(new AdjAmountByTypeVO(i18nNAStr, adjListByNotMistaker.stream()
                        .map(AdRequest::getAdjustedDollar)
                        .reduce(BigDecimal::add).orElse(BigDecimal.ZERO)));
                return new StatisticsTypeVO(countList, amountList);

            }
            return new StatisticsTypeVO();
        }

        /**
         * 校验时间跨度的大小
         *
         * @param unit 单位
         * @param span 跨度
         * @author lichen
         * @date 2023/12/9
         */
        public void checkSpan(Integer unit, Integer span) {
            if (StatisticsTypeTrendQuery.MONTH == unit) {
                if (span > 12) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_GREATER_THAN,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("month", 12),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The span must be less than 13 when unit is month");
                }
            } else {
                if (span > 4) {
                    throw new BadRequestException(CommonErrorI18nConstant.CANNOT_GREATER_THAN,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("quarter", 4),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The span must be less than 5 when unit is quarter");
                }
            }
        }

    }