# GuestLedgerCheckWidgetController.java

    package cloud.wconnect.frontoffice.controller.guestledgercheck;

    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.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.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.frontoffice.entity.guestledgercheck.GstLedgOpBalanceReservation;
    import cloud.wconnect.frontoffice.entity.guestledgercheck.GstLedgOvBalanceReservation;
    import cloud.wconnect.frontoffice.pojo.query.guestledgercheck.PropertyLossAmountQuery;
    import cloud.wconnect.frontoffice.pojo.query.guestledgercheck.PropertyLossAmountTrendQuery;
    import cloud.wconnect.frontoffice.pojo.vo.guestledgercheck.PropertyLossAmountTrendVO;
    import cloud.wconnect.frontoffice.pojo.vo.guestledgercheck.PropertyLossAmountVO;
    import cloud.wconnect.frontoffice.pojo.vo.guestledgercheck.GuestLedgerCheckToDoVO;
    import cloud.wconnect.frontoffice.service.guestledgercheck.IGstLedgOpBalanceReservationService;
    import cloud.wconnect.frontoffice.service.guestledgercheck.IGstLedgOvBalanceReservationService;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.google.common.collect.Sets;
    import org.apache.commons.collections4.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    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.ZonedDateTime;
    import java.time.temporal.ChronoUnit;
    import java.util.*;

    /**
     * 组件 控制层
     *
     * @author lichen
     * @date 2023/12/9
     */
    @RestController
    @RequestMapping("/guest-ledger-check/widget")
    public class GuestLedgerCheckWidgetController {

        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private IGstLedgOpBalanceReservationService opBalanceReservationService;
        @Autowired
        private IGstLedgOvBalanceReservationService ovBalanceReservationService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 查询待办事项
         * 包括：当日未解决欠款笔数，累计未解决欠款笔数，当日未解决欠款金额，累计未解决欠款金额，累计总经理未知晓笔数，在住欠款笔数
         *
         * @param property 资产标识
         * @return cloud.wconnect.frontoffice.pojo.vo.guestledger.ToDoVO
         * @author lichen
         * @date 2023/12/9
         */
        @ApiId(987)
        @GetMapping("/statistics/to-do")
        public GuestLedgerCheckToDoVO statisticsToDo(String property) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            GuestLedgerCheckToDoVO guestLedgerCheckToDoVO = new GuestLedgerCheckToDoVO();

            //当前时间
            ZonedDateTime currentDateOfStartZT = DateTimeUtils.dateNow(propertyDTO.getTimezone());
            //当前时间零点时刻的时间戳
            long currentDateOfStart = currentDateOfStartZT.toEpochSecond();
            //下一天零点时刻的时间戳
            long nextDateOfStart = currentDateOfStartZT.plusDays(1).toEpochSecond();

            //查询未解决的欠款数据
            List<GstLedgOpBalanceReservation> unResolveOpBalanceReservations = opBalanceReservationService.listByResolved(propertyDTO.getId(), false);

            //累计未解决欠款笔数
            guestLedgerCheckToDoVO.setTotalUnResolveNum((long) unResolveOpBalanceReservations.size());
            //累计未解决欠款金额
            BigDecimal totalUnResolveAmount = unResolveOpBalanceReservations.stream().map(GstLedgOpBalanceReservation::getBalanceAmount)
                    .reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            guestLedgerCheckToDoVO.setTotalUnResolveAmount(totalUnResolveAmount);

            //当前时间，未解决的欠款数据
            List<GstLedgOpBalanceReservation> unResolveOpBalanceReservationsByCurrentDate = unResolveOpBalanceReservations.stream()
                    .filter(item -> item.getCreatedTime() >= currentDateOfStart && item.getCreatedTime() < nextDateOfStart)
                    .toList();
            //当日未解决欠款笔数
            guestLedgerCheckToDoVO.setDailyUnResolveNum((long) unResolveOpBalanceReservationsByCurrentDate.size());
            //当日未解决欠款金额
            BigDecimal dailyUnResolveAmount = unResolveOpBalanceReservationsByCurrentDate.stream().map(GstLedgOpBalanceReservation::getBalanceAmount)
                    .reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            guestLedgerCheckToDoVO.setDailyUnResolveAmount(dailyUnResolveAmount);

            //总经理未知晓笔数
            guestLedgerCheckToDoVO.setTotalGMUnKnownNum(opBalanceReservationService.getGMUnKnownCount(propertyDTO.getId()));

            //在住欠款笔数
            Long inLiveNum = ovBalanceReservationService.count(new LambdaQueryWrapper<GstLedgOvBalanceReservation>()
                    .eq(GstLedgOvBalanceReservation::getPropertyId, propertyDTO.getId()));
            guestLedgerCheckToDoVO.setInLiveNum(inLiveNum);
            return guestLedgerCheckToDoVO;
        }

        /**
         * 统计多资产【总损失金额】
         *
         * @param query 查询对象
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.guestledger.PropertyLossAmountVO>
         * @author lichen
         * @date 2023/12/9
         */
        @ApiId(988)
        @GetMapping("/statistics/property")
        public List<PropertyLossAmountVO> statisticsPropertyLossAmount(@Valid PropertyLossAmountQuery 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.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");
            }
            if (query.getPropertyIds().size() != Sets.newHashSet(query.getPropertyIds()).size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("propertyIds"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "propertyIds cannot repeat");
            }

            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");
            }

            List<PropertyDTO> propertyDTOS = propertyI18nRedisService.list(propertyIds);
            if (CollectionUtils.isEmpty(propertyDTOS)) return Collections.emptyList();

            //查询资产的总损失金额
            return opBalanceReservationService.listPropertyTotalLossAmount(query.getStartDate(), query.getEndDate(), propertyDTOS);
        }

        /**
         * 统计多资产【总损失金额】的趋势
         *
         * @param query 查询对象
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.guestledger.PropertyLossAmountTrendVO>
         * @author lichen
         * @date 2023/12/9
         */
        @ApiId(989)
        @GetMapping("/statistics/property/trend")
        public List<PropertyLossAmountTrendVO> statisticsPropertyLossAmountTrend(@Valid PropertyLossAmountTrendQuery 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(), "propertyIds cannot repeat");
            }

            checkSpan(query.getUnit(), query.getSpan());

            //有效的资产
            List<PropertyDTO> propertyDTOS = propertyI18nRedisService.list(propertyIds);
            if (CollectionUtils.isEmpty(propertyDTOS)) return Collections.emptyList();

            //统计多资产【总损失金额】的趋势
            return opBalanceReservationService.listPropertyTotalLossAmountTrend(query, propertyDTOS);
        }

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

    }