# HkInventoryController.java

    package cloud.wconnect.workperformance.controller.hkinventory;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.InternalServerErrorException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.workperformance.cons.hkinventory.HkInventoryErrorI18nConstant;
    import cloud.wconnect.workperformance.cons.hkinventory.StatusEnum;
    import cloud.wconnect.workperformance.entity.hkinventory.HkInventory;
    import cloud.wconnect.workperformance.entity.hkinventory.HkInventoryCategoryConfig;
    import cloud.wconnect.workperformance.entity.hkinventory.HkInventoryRoomTypeConfig;
    import cloud.wconnect.workperformance.pojo.dto.hkinventory.HkInventoryDetailDTO;
    import cloud.wconnect.workperformance.pojo.dto.hkinventory.HkInventoryItemExcelDTO;
    import cloud.wconnect.workperformance.pojo.dto.hkinventory.ItemInventoryDTO;
    import cloud.wconnect.workperformance.pojo.dto.hkinventory.SaveItemInventoryDTO;
    import cloud.wconnect.workperformance.pojo.query.hkinventory.HkInventoryQuery;
    import cloud.wconnect.workperformance.pojo.vo.hkinventory.HkInventoryVO;
    import cloud.wconnect.workperformance.service.hkinventory.IHkInventoryCategoryConfigService;
    import cloud.wconnect.workperformance.service.hkinventory.IHkInventoryItemService;
    import cloud.wconnect.workperformance.service.hkinventory.IHkInventoryRoomTypeConfigService;
    import cloud.wconnect.workperformance.service.hkinventory.IHkInventoryService;
    import com.google.common.collect.Sets;
    import jakarta.servlet.http.HttpServletResponse;
    import jakarta.validation.Valid;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.jxls.transform.poi.JxlsPoiTemplateFillerBuilder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.i18n.LocaleContextHolder;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;

    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.charset.StandardCharsets;
    import java.text.MessageFormat;
    import java.time.YearMonth;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * 客房库存管理控制器
     *
     * @author lichen
     * @date 2025/4/19
     */
    @RestController
    @RequestMapping("/hk-inventory")
    public class HkInventoryController {

        @Autowired
        private IHkInventoryService hkInventoryService;
        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private UserInternalService userInternalService;
        @Autowired
        private IHkInventoryItemService itemService;
        @Autowired
        private IHkInventoryCategoryConfigService categoryConfigService;
        @Autowired
        private IHkInventoryRoomTypeConfigService roomTypeConfigService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 查询客房库存管理月度汇总信息
         *
         * @param property         资产标识
         * @param hkInventoryQuery 查询对象
         * @return java.util.List<cloud.wconnect.workperformance.pojo.vo.hkinventory.HkInventoryVO>
         * @author lichen
         * @date 2025/4/19
         */
        @ApiId(1414)
        @GetMapping
        public List<HkInventoryVO> list(String property, @Validated HkInventoryQuery hkInventoryQuery) {
            int propertyId = sessionContext.getPropertyId(property);
            Integer tenantId = sessionContext.getTenantSymbolBO().getId();
            List<HkInventory> hkInventoryList = hkInventoryService.listByCondition(propertyId, hkInventoryQuery.getYear(), null, null);
            if (CollectionUtils.isEmpty(hkInventoryList)) return Collections.emptyList();

            //发布人集合
            Set<Integer> userIds = hkInventoryList.stream().map(HkInventory::getPublishedBy)
                    .filter(publishedBy -> Objects.nonNull(publishedBy) && publishedBy > 0)
                    .collect(Collectors.toSet());
            Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(tenantId, userIds);

            return beanMapper.mapList(hkInventoryList, HkInventoryVO.class, (entity, vo) -> {
                vo.setPublishedByName(userId2Name.get(entity.getPublishedBy()));
            });
        }

        /**
         * 查询指定客房库存管理的详情
         *
         * @param property      资产标识
         * @param hkInventoryId 主键id
         * @return cloud.wconnect.workperformance.pojo.dto.hkinventory.HkInventoryDetailDTO
         * @author lichen
         * @date 2025/4/19
         */
        @ApiId(1415)
        @GetMapping("/detail/{hkInventoryId:\\d+}")
        public HkInventoryDetailDTO detail(String property, @PathVariable Integer hkInventoryId) {
            int propertyId = sessionContext.getPropertyId(property);

            //校验存在
            HkInventory hkInventory = hkInventoryService.checkExist(hkInventoryId);

            //校验归属
            hkInventoryService.checkBelong(hkInventory, propertyId);

            //查询指定客房库存管理的详情
            return hkInventoryService.detail(hkInventory);
        }

        /**
         * 保存指定分类指定房型下的库存
         *
         * @param property             资产标识
         * @param saveItemInventoryDTO 保存的库存数量DTO
         * @return java.util.Map<java.lang.String, java.lang.String>
         * @author lichen
         * @date 2025/4/21
         */
        @ApiId(1417)
        @PostMapping("/item")
        public Map<String, String> saveInventory(String property, @RequestBody @Valid SaveItemInventoryDTO saveItemInventoryDTO) {
            int propertyId = sessionContext.getPropertyId(property);

            //校验item不能重复
            List<ItemInventoryDTO> items = saveItemInventoryDTO.getItems();
            if (items.size() != Sets.newHashSet(items).size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_CANNOT_REPEAT,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("items"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "items cannot repeat");
            }

            //校验客房库存管理是否存在
            HkInventory hkInventory = hkInventoryService.checkExist(saveItemInventoryDTO.getHkInventoryId());
            //校验客房库存管理归属
            hkInventoryService.checkBelong(hkInventory, propertyId);
            //校验客房库存管理必须是待发布状态
            hkInventoryService.checkStatusMustBe(hkInventory.getStatus(), StatusEnum.PENDING.getStatus());

            //校验分类是否存在
            HkInventoryCategoryConfig hkInventoryCategoryConfig = categoryConfigService.checkExist(saveItemInventoryDTO.getCategoryId());
            //校验分类是否属于资产
            categoryConfigService.checkBelong(hkInventoryCategoryConfig, propertyId);
            //校验分类是否包含指定房型
            categoryConfigService.checkContainRoomType(hkInventoryCategoryConfig, saveItemInventoryDTO.getRoomTypeId());

            //校验房型是否存在
            HkInventoryRoomTypeConfig hkInventoryRoomTypeConfig = roomTypeConfigService.checkExist(saveItemInventoryDTO.getRoomTypeId());
            //校验房型是否归属于资产
            roomTypeConfigService.checkBelongProperty(hkInventoryRoomTypeConfig, propertyId);

            //保存库存数量
            itemService.saveInventoryCount(hkInventory, saveItemInventoryDTO, sessionContext.getLoginUserId());

            return Collections.emptyMap();
        }

        /**
         * 发布客房库存管理
         *
         * @param property      资产标识
         * @param hkInventoryId 客房库存管理id
         * @return java.util.Map<java.lang.String, java.lang.String>
         * @author lichen
         * @date 2025/4/21
         */
        @ApiId(1418)
        @PutMapping("/published/{hkInventoryId:\\d+}")
        public Map<String, String> published(String property, @PathVariable Integer hkInventoryId) {
            int propertyId = sessionContext.getPropertyId(property);

            //校验客房库存管理是否存在
            HkInventory hkInventory = hkInventoryService.checkExist(hkInventoryId);
            //校验客房库存管理归属
            hkInventoryService.checkBelong(hkInventory, propertyId);
            //校验客房库存管理必须是待发布状态
            hkInventoryService.checkStatusMustBe(hkInventory.getStatus(), StatusEnum.PENDING.getStatus());

            //校验上一期是否已发布
            HkInventory previousInventory = hkInventoryService.getPreviousInventory(hkInventory);
            if (Objects.nonNull(previousInventory)) {
                if (previousInventory.getStatus() != StatusEnum.PUBLISHED.getStatus()) {
                    throw new BadRequestException(HkInventoryErrorI18nConstant.PREVIOUS_MONTH_DATA_NOT_PUBLISHED,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The data of the previous month has not been published.");
                }
            }

            //校验item是否配置
            long itemCount = itemService.countByHkInventoryId(hkInventory.getPropertyId(), hkInventory.getId());
            if (itemCount == 0) {
                throw new BadRequestException(HkInventoryErrorI18nConstant.UNCONFIGURED_ITEMS,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "List of unconfigured items");
            }
            //校验是否存在未清点的item
            long itemInventoryIsNullCount = itemService.countByHkInventoryIdAndInventoryIsNull(hkInventory.getPropertyId(), hkInventory.getId());
            if (itemInventoryIsNullCount > 0) {
                throw new BadRequestException(HkInventoryErrorI18nConstant.ITEM_INVENTORY_COUNT_CAN_NOT_NULL,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "There are items that haven't been counted");
            }

            hkInventoryService.published(hkInventory, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

        /**
         * 导出客房库存管理
         *
         * @param property      资产标识
         * @param hkInventoryId 客房库存管理id
         * @param response      响应
         * @author lichen
         * @date 2025/4/21
         */
        @ApiId(1539)
        @GetMapping("/export/{hkInventoryId:\\d+}")
        public void export(String property, @PathVariable Integer hkInventoryId, HttpServletResponse response) {
            int propertyId = sessionContext.getPropertyId(property);
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            //校验客房库存管理是否存在
            HkInventory hkInventory = hkInventoryService.checkExist(hkInventoryId);
            //校验客房库存管理归属
            hkInventoryService.checkBelong(hkInventory, propertyId);
            //校验客房库存管理必须是已发布状态
            hkInventoryService.checkStatusMustBe(hkInventory.getStatus(), StatusEnum.PUBLISHED.getStatus());

            YearMonth yearMonth = YearMonth.of(hkInventory.getYear(), hkInventory.getMonth());

            //获取导出的数据
            List<HkInventoryItemExcelDTO> excelDTOS = hkInventoryService.getExportExcelData(propertyDTO, hkInventory);

            //当前上下文语言
            Locale currentThreadLocale = LocaleContextHolder.getLocale();
            //模板名称
            String templateFileName = StringUtils.join("tmpl-xls/housekeeping_inventory_tmpl_", currentThreadLocale, ".xlsx");
            //导出Excel
            try (InputStream is = new ClassPathResource(templateFileName).getInputStream()) {
                HashMap<String, Object> data = new HashMap<>();
                data.put("list", excelDTOS);

                String fileName = property + "_housekeeping_inventory_" + yearMonth + ".xlsx";
                fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
                response.setContentType("application/vnd.ms-excel");
                response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
                response.setHeader("Content-Disposition", String.join("", "attachment; filename=", fileName));

                //填充模板
                JxlsPoiTemplateFillerBuilder.newInstance()
                        .withTemplate(is)
                        .buildAndFill(data, response::getOutputStream);
            } catch (IOException e) {
                throw new InternalServerErrorException(
                        BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(), BaseErrorCodeEnum.SYSTEM_ERROR.getCode(),
                        MessageFormat.format("Export Housekeeping Inventory file error. propertyId:{0},hkInventoryId:{1}", propertyId, hkInventoryId),
                        e);
            }

        }

        /**
         * 撤回客房库存管理
         *
         * @param property      资产标识
         * @param hkInventoryId 客房库存管理id
         * @return java.util.Map<java.lang.String, java.lang.String>
         * @author lichen
         * @date 2025/4/21
         */
        @ApiId(1587)
        @PutMapping("/withdraw/{hkInventoryId:\\d+}")
        public Map withdraw(String property, @PathVariable Integer hkInventoryId) {
            int propertyId = sessionContext.getPropertyId(property);

            //校验客房库存管理是否存在
            HkInventory hkInventory = hkInventoryService.checkExist(hkInventoryId);
            //校验客房库存管理归属
            hkInventoryService.checkBelong(hkInventory, propertyId);
            //校验客房库存管理必须是已发布状态
            hkInventoryService.checkStatusMustBe(hkInventory.getStatus(), StatusEnum.PUBLISHED.getStatus());

            //只有当月、上一个月、下个月才可以撤回
            YearMonth yearMonth = YearMonth.of(hkInventory.getYear(), hkInventory.getMonth());
            YearMonth currentYearMonth = YearMonth.now();
            if (!yearMonth.equals(currentYearMonth) && !yearMonth.equals(currentYearMonth.minusMonths(1))
                    && !yearMonth.equals(currentYearMonth.plusMonths(1))) {
                throw new BadRequestException(HkInventoryErrorI18nConstant.ONLY_SPECIFIED_MONTHS_CAN_WITHDRAWN,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Only the data of the current month, last month and next month can be withdrawn.");
            }

            //校验下一期是否未发布
            HkInventory nextInventory = hkInventoryService.getNextInventory(hkInventory);
            if (Objects.nonNull(nextInventory)) {
                if (nextInventory.getStatus() != StatusEnum.PENDING.getStatus()) {
                    throw new BadRequestException(HkInventoryErrorI18nConstant.NEXT_MONTH_DATA_ALREADY_PUBLISHED_CANNOT_WITHDRAWN,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The data for next month has been released and cannot be withdrawn");
                }
            }

            hkInventoryService.withdraw(hkInventory, sessionContext.getLoginUserId());
            return Collections.emptyMap();
        }

    }