# SvaDepCategoryHoursStatisticsController.java

    package cloud.wconnect.workperformance.controller.sva.hoursstatistics;

    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.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.jsonview.Views;
    import cloud.wconnect.commonlib.pojo.dto.result.ResultDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.workperformance.entity.sva.SvaDepartmentCategoryStatisticsComment;
    import cloud.wconnect.workperformance.entity.sva.setting.DepartmentCategory;
    import cloud.wconnect.workperformance.pojo.dto.sva.hoursstatistics.*;
    import cloud.wconnect.workperformance.pojo.query.sva.schedulevsactual.SvaHoursStatisticsCommentQuery;
    import cloud.wconnect.workperformance.pojo.query.sva.schedulevsactual.SvaHoursStatisticsDailyQuery;
    import cloud.wconnect.workperformance.pojo.query.sva.schedulevsactual.SvaHoursStatisticsQuery;
    import cloud.wconnect.workperformance.service.sva.scheduleactual.ICategoryRecomHourSumService;
    import cloud.wconnect.workperformance.service.sva.scheduleactual.ISvaDepCategoryHoursStatisticsService;
    import cloud.wconnect.workperformance.service.sva.scheduleactual.ISvaDepartmentCategoryStatisticsCommentService;
    import cloud.wconnect.workperformance.service.sva.setting.IDepartmentCategoryService;
    import com.fasterxml.jackson.annotation.JsonView;
    import com.google.common.collect.ImmutableMap;
    import jakarta.annotation.Resource;
    import jakarta.validation.Valid;
    import org.springframework.web.bind.annotation.*;

    import java.time.LocalDate;
    import java.time.temporal.ChronoUnit;
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;

    /**
     * Controller: 部门分类工时数统计
     *
     * @author zc
     * @since 2025-02-07
     */
    @RestController
    @RequestMapping("/sva/schedule-actual")
    public class SvaDepCategoryHoursStatisticsController {
        @Resource
        private ISvaDepCategoryHoursStatisticsService hoursStatisticsService;
        @Resource
        private IDepartmentCategoryService departmentCategoryService;
        @Resource
        private ICategoryRecomHourSumService categoryRecommendHourSumService;
        @Resource
        private ISvaDepartmentCategoryStatisticsCommentService commentService;
        @Resource
        private PropertyI18nRedisService propertyI18nRedisService;
        @Resource
        private SessionContext sessionContext;
        @Resource
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 获取部门分类指定日期范围内的时数统计数据
         *
         * @param property 资产标识
         * @param query    查询条件
         * @return DepCategoryStatisticDTO 集合
         * @author zc
         * @date 2025/2/7
         */
        @ApiId(379)
        @GetMapping("/department-category-hours-statistics")
        public List<SvaDepCategoryStatisticDTO> getHoursStatistics(@RequestParam String property, @Valid SvaHoursStatisticsQuery query) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            // 校验查询参数
            checkParamForMonth(query.getFromDate(), query.getToDate(), propertyDTO.getTimezone());
            return hoursStatisticsService.getHoursStatistics(propertyDTO, query);
        }

        /**
         * 根据部门分类获取指定日期范围内每日的时数统计数据
         *
         * @param property 资产标识
         * @param query    查询条件
         * @return DepCategoryDailyDTO
         * @author zc
         * @date 2025/2/7
         */
        @ApiId(380)
        @GetMapping("/department-category-hours-statistics/daily")
        public SvaDepCategoryDailyDTO getHoursStatisticsDaily(@RequestParam String property, @Valid SvaHoursStatisticsDailyQuery query) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            // 校验查询参数
            checkParam(query.getFromDate(), query.getToDate(), propertyDTO.getTimezone());
            return hoursStatisticsService.getHoursStatisticsDaily(propertyDTO, query);
        }

        /**
         * 编辑指定部门分类一周内的推荐工时总时数
         *
         * @param property 资产标识
         * @param saveDTO  编辑内容
         * @return empty map
         * @author zc
         * @date 2023/6/28
         */
        @ApiId(381)
        @PutMapping("/department-category-hours-statistics/recommend-hours")
        public Map<Object, Object> saveRecommendHours(@RequestParam String property,
                                                      @Valid @RequestBody SvaDepCategoryRecommendHourTotalDTO saveDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            if (saveDTO.getDepartmentCategoryId() != BaseConstant.ZERO) {
                // 校验部门分类是否存在
                DepartmentCategory departmentCategory = departmentCategoryService.checkExist(saveDTO.getDepartmentCategoryId());
                // 校验数据归属
                departmentCategoryService.checkBelong(propertyId, departmentCategory);
            }
            // 校验参数
            checkParam(saveDTO.getFromDate(), saveDTO.getToDate(), propertyDTO.getTimezone());
            categoryRecommendHourSumService.saveRecommendHours(propertyDTO, saveDTO, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 根据部门分类获取指定日期范围内的工时统计评论
         *
         * @param property 资产标识
         * @param query    查询条件
         * @return ListPagedDTO<SvaDepCategoryStatisticCommentDTO>
         * @author zc
         * @date 2025/2/8
         */
        @ApiId(1339)
        @GetMapping("/department-category-hours-statistics/comment")
        public ListPagedDTO<SvaDepCategoryStatisticCommentDTO> listComment(@RequestParam String property,
                                                                           @Valid SvaHoursStatisticsCommentQuery query) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            return commentService.list(propertyDTO, query);
        }

        /**
         * 根据部门分类保存指定日期范围内的工时统计评论
         *
         * @param property   资产标识
         * @param commentDTO 评论内容
         * @return 评论 id
         * @author zc
         * @date 2025/2/8
         */
        @ApiId(1340)
        @PostMapping("/department-category-hours-statistics/comment")
        @JsonView({Views.ListView.class})
        public Map<String, Long> saveComment(@RequestParam String property,
                                             @Valid @RequestBody SvaDepCategoryStatisticCommentDTO commentDTO) {
            int propertyId = sessionContext.getPropertyId(property);
            // 校验部门分类是否存在
            if (commentDTO.getDepartmentCategoryId() != BaseConstant.ZERO) {
                // 校验部门分类是否存在
                DepartmentCategory departmentCategory = departmentCategoryService.checkExist(commentDTO.getDepartmentCategoryId());
                // 校验数据归属
                departmentCategoryService.checkBelong(propertyId, departmentCategory);
            }

            if (Objects.nonNull(commentDTO.getId())) {
                // 校验是否存在
                SvaDepartmentCategoryStatisticsComment originComment = commentService.checkExist(commentDTO.getId());
                // 校验归属
                commentService.checkBelong(propertyId, sessionContext.getLoginUserId(), originComment);
            }
            // 保存数据
            return ImmutableMap.of("id", commentService.save(propertyId, commentDTO, sessionContext.getLoginUserId()));
        }

        /**
         * 删除评论
         *
         * @param property 资产标识
         * @param id       评论 id
         * @return emptyMap
         * @author zc
         * @date 2025/2/8
         */
        @ApiId(1341)
        @DeleteMapping("/department-category-hours-statistics/comment/{id:\\d+}")
        public Map<Object, Object> deleteComment(@RequestParam String property, @PathVariable long id) {
            int propertyId = sessionContext.getPropertyId(property);
            int loginUserId = sessionContext.getLoginUserId();
            // 校验是否存在
            SvaDepartmentCategoryStatisticsComment originComment = commentService.checkExist(id);
            // 校验归属
            commentService.checkBelong(propertyId, loginUserId, originComment);
            // 删除
            commentService.removeById(id);
            return Collections.emptyMap();
        }

        /**
         * 获取部门一周的时数统计数据
         *
         * @param property 资产标识
         * @param fromDate 开始时间，格式yyyyMMdd
         * @param toDate   结束时间，格式yyyyMMdd
         * @return cloud.wconnect.commonlib.pojo.dto.result.ResultDTO<cloud.wconnect.workperformance.pojo.dto.sva.hoursstatistics.SvaWeeklyStatisticDTO>
         * @author lichen
         * @date 2026/3/5
         */
        @ApiId(1701)
        @GetMapping("/department-weekly-statistics")
        public ResultDTO<SvaWeeklyStatisticDTO> weeklyStatistics(@RequestParam String property, @RequestParam String fromDate, @RequestParam String toDate) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            // 校验日期参数
            checkWeeklyDate(fromDate, toDate);
            // 获取数据
            SvaWeeklyStatisticDTO svaWeeklyStatisticDTO = hoursStatisticsService.weeklyStatistics(propertyDTO, fromDate, toDate);
            return ResultDTO.success(svaWeeklyStatisticDTO);
        }

        /**
         * 校验参数
         *
         * @param fromDate 开始时间
         * @param toDate   结束时间
         * @param timezone 时区
         * @author zc
         * @date 2023/6/22
         */
        private void checkParam(long fromDate, long toDate, String timezone) {
            // 开始时间不能大于结束时间
            if (fromDate > toDate) {
                throw new BadRequestException(CommonErrorI18nConstant.START_DATE_CANNOT_GREATER_THAN_END_DATE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The fromDate cannot be greater than the toDate");
            }

            // 例如：筛选天数为 7 天，from date 与 to date 差值为 6
            int difference = 6;
            if (DateTimeUtils.numberOfDaysApart(fromDate, toDate, timezone) != difference) {
                throw new BadRequestException(CommonErrorI18nConstant.SELECT_DATE_LIMIT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate(7),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The number of screening days exceeds the range of " + (difference + 1) + " days");
            }
        }

        /**
         * 校验参数 for month
         *
         * @param fromDate 开始时间
         * @param toDate   结束时间
         * @param timezone 时区
         * @author zc
         * @date 2023/6/22
         */
        private void checkParamForMonth(long fromDate, long toDate, String timezone) {
            // 开始时间不能大于结束时间
            if (fromDate > toDate) {
                throw new BadRequestException(CommonErrorI18nConstant.START_DATE_CANNOT_GREATER_THAN_END_DATE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The fromDate cannot be greater than the toDate");
            }

            // 例如：筛选天数为 31 天，from date 与 to date 差值为 30
            int difference = 30;
            if (DateTimeUtils.numberOfDaysApart(fromDate, toDate, timezone) > difference) {
                throw new BadRequestException(CommonErrorI18nConstant.SELECT_DATE_LIMIT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate(31),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The number of screening days exceeds the range of " + (difference + 1) + " days");
            }
        }

        /**
         * 校验weekly日期参数
         *
         * @param fromDate 开始时间，格式yyyyMMdd
         * @param toDate   结束时间，格式yyyyMMdd
         * @author lichen
         * @date 2026/3/5
         */
        private void checkWeeklyDate(String fromDate, String toDate) {
            LocalDate fromLocalDate = DateTimeUtils.strToLocalDate(fromDate, BaseConstant.DATE_FORMAT_YYYYMMDD);
            LocalDate toLocalDate = DateTimeUtils.strToLocalDate(toDate, BaseConstant.DATE_FORMAT_YYYYMMDD);
            // 校验日期参数
            if (!toLocalDate.isAfter(fromLocalDate)) {
                throw new BadRequestException(CommonErrorI18nConstant.CANNOT_LESS_THAN,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("toDate", "fromDate"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "toDate must not be less than fromDate");
            }
            // 校验两个日期相差是否为 7 天
            long daysBetween = ChronoUnit.DAYS.between(fromLocalDate, toLocalDate) + 1;
            if (daysBetween != 7) {
                throw new BadRequestException(CommonErrorI18nConstant.MUST_EQUAL_TO,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("Date Range", 7),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the date range must be equal to 7");
            }
        }

    }