# HkInventoryItemServiceImpl.java

    package cloud.wconnect.workperformance.service.hkinventory.impl;

    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.commonlib.util.UpdateLogUtil;
    import cloud.wconnect.workperformance.entity.hkinventory.*;
    import cloud.wconnect.workperformance.mapper.hkinventory.HkInventoryItemMapper;
    import cloud.wconnect.workperformance.pojo.dto.hkinventory.ItemInventoryDTO;
    import cloud.wconnect.workperformance.pojo.dto.hkinventory.SaveItemInventoryDTO;
    import cloud.wconnect.workperformance.service.hkinventory.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 客房库存管理-物品库存表 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2025-04-19
     */
    @Service
    @DS("work_performance")
    public class HkInventoryItemServiceImpl extends ServiceImpl<HkInventoryItemMapper, HkInventoryItem> implements IHkInventoryItemService {

        @Autowired
        @Lazy
        private IHkInventoryService hkInventoryService;
        @Autowired
        private IHkInventoryItemConfigService itemConfigService;
        @Autowired
        private IHkInventoryPurchaseOrderService hkInventoryPurchaseOrderService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private IHkInventoryCategoryService categoryService;
        @Autowired
        private IHkInventoryRoomTypeService roomTypeService;
        @Autowired
        private UpdateLogUtil updateLogUtil;
        @Autowired
        private IHkInventoryRoomTypeConfigService roomTypeConfigService;
        @Autowired
        private IHkInventoryTaxRateService taxRateService;

        /**
         * 根据物品库存id和分类id集合查询物品库存
         *
         * @param propertyId    资产id
         * @param hkInventoryId 库存id
         * @param categoryIds   分类id集合
         * @return java.util.List<cloud.wconnect.workperformance.entity.hkinventory.HkInventoryItem>
         * @author lichen
         * @date 2025/4/21
         */
        @Override
        public List<HkInventoryItem> listByHkInventoryIdAndCategoryIds(Integer propertyId, Integer hkInventoryId, List<Integer> categoryIds) {
            if (CollectionUtils.isEmpty(categoryIds)) return Collections.emptyList();
            return list(new LambdaQueryWrapper<HkInventoryItem>().eq(HkInventoryItem::getPropertyId, propertyId)
                    .eq(HkInventoryItem::getHkInventoryId, hkInventoryId)
                    .in(HkInventoryItem::getCategoryId, categoryIds)
                    .orderByDesc(HkInventoryItem::getId));
        }

        /**
         * 保存物品库存数量
         *
         * @param hkInventory          客房库存管理
         * @param saveItemInventoryDTO 保存物品库存数量
         * @param loginUserId          登录用户id
         * @return void
         * @author lichen
         * @date 2025/4/21
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveInventoryCount(HkInventory hkInventory, SaveItemInventoryDTO saveItemInventoryDTO, int loginUserId) {
            //物品id集合
            List<Integer> itemIds = saveItemInventoryDTO.getItems().stream().map(ItemInventoryDTO::getItemId).toList();

            //查询修改前的item
            List<HkInventoryItem> hkInventoryItems = listByHkInventoryIdAndCategoryIdAndRoomTypeIdAndItemIds(hkInventory.getPropertyId(), hkInventory.getId(),
                    saveItemInventoryDTO.getCategoryId(), saveItemInventoryDTO.getRoomTypeId(), itemIds);
            List<Integer> existItemIds = hkInventoryItems.stream().map(HkInventoryItem::getItemId).toList();

            //校验item是否存在
            if (itemIds.size() != existItemIds.size()) {
                List<Integer> notExistItemIds = Lists.newArrayList(itemIds).stream().filter(itemId -> !existItemIds.contains(itemId)).toList();
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "Housekeeping Inventory item not found,ids=" + notExistItemIds);
            }
            Map<Integer, HkInventoryItem> itemId2Item = hkInventoryItems.stream().collect(Collectors.toMap(HkInventoryItem::getItemId, Function.identity()));

            ArrayList<HkInventoryItem> batchList = Lists.newArrayListWithExpectedSize(itemIds.size());
            for (ItemInventoryDTO itemInventoryDTO : saveItemInventoryDTO.getItems()) {
                //修改前的数据
                HkInventoryItem item = itemId2Item.get(itemInventoryDTO.getItemId());

                Integer oriInventoryCount = item.getInventory();
                //库存数量没有变更时，无需计算
                if (Objects.nonNull(oriInventoryCount) && oriInventoryCount.equals(itemInventoryDTO.getInventory())) {
                    continue;
                }
                item.setUpdateLog(updateLogUtil.append(item.getUpdateLog(), loginUserId));

                //设置库存数量
                item.setInventory(itemInventoryDTO.getInventory());
                batchList.add(item);
            }

            saveOrUpdateBatch(batchList);
        }

        /**
         * 根据客房库存管理id和分类id和房型id和物品id集合查询物品库存
         *
         * @param propertyId    资产id
         * @param HkInventoryId 客房库存管理id
         * @param categoryId    分类id
         * @param roomTypeId    房型id
         * @param itemIds       物品id集合
         * @return java.util.List<cloud.wconnect.workperformance.entity.hkinventory.HkInventoryItem>
         * @author lichen
         * @date 2025/4/21
         */
        @Override
        public List<HkInventoryItem> listByHkInventoryIdAndCategoryIdAndRoomTypeIdAndItemIds(Integer propertyId, Integer HkInventoryId, Integer categoryId, Integer roomTypeId, List<Integer> itemIds) {
            if (CollectionUtils.isEmpty(itemIds)) return Collections.emptyList();
            return list(new LambdaQueryWrapper<HkInventoryItem>().eq(HkInventoryItem::getPropertyId, propertyId)
                    .eq(HkInventoryItem::getHkInventoryId, HkInventoryId)
                    .eq(HkInventoryItem::getCategoryId, categoryId)
                    .eq(HkInventoryItem::getRoomTypeId, roomTypeId)
                    .in(HkInventoryItem::getItemId, itemIds)
                    .orderByDesc(HkInventoryItem::getId));
        }

        /**
         * 根据客房库存管理id和库存id集合查询物品库存
         *
         * @param propertyId     资产id
         * @param hkInventoryIds 客房库存管理id集合
         * @return java.util.List<cloud.wconnect.workperformance.entity.hkinventory.HkInventoryItem>
         * @author lichen
         * @date 2025/4/21
         */
        @Override
        public List<HkInventoryItem> listByHkInventoryIds(Integer propertyId, List<Integer> hkInventoryIds) {
            if (CollectionUtils.isEmpty(hkInventoryIds)) return Collections.emptyList();
            return list(new LambdaQueryWrapper<HkInventoryItem>().eq(HkInventoryItem::getPropertyId, propertyId)
                    .in(HkInventoryItem::getHkInventoryId, hkInventoryIds));
        }

        /**
         * 根据客房库存管理id集合和房型id查询物品库存
         *
         * @param propertyId     资产id
         * @param hkInventoryIds 客房库存管理id集合
         * @param roomTypeId     房型id
         * @return java.util.List<cloud.wconnect.workperformance.entity.hkinventory.HkInventoryItem>
         * @author lichen
         * @date 2025/4/22
         */
        @Override
        public List<HkInventoryItem> listByHkInventoryIdsAndRoomTypeId(Integer propertyId, List<Integer> hkInventoryIds, Integer roomTypeId) {
            if (CollectionUtils.isEmpty(hkInventoryIds)) return Collections.emptyList();
            return list(new LambdaQueryWrapper<HkInventoryItem>().eq(HkInventoryItem::getPropertyId, propertyId)
                    .in(HkInventoryItem::getHkInventoryId, hkInventoryIds)
                    .eq(HkInventoryItem::getRoomTypeId, roomTypeId));
        }

        /**
         * 更新item name
         *
         * @param propertyId 资产id
         * @param categoryId 分类id
         * @param roomTypeId 房型id
         * @param itemId     item id
         * @param itemName   item 名称
         * @return void
         * @author lichen
         * @date 2025/4/23
         */
        @Override
        public void updateItemNameByCategoryIdAndRoomTypeIdAndItemId(Integer propertyId, Integer categoryId, Integer roomTypeId, Integer itemId, String itemName) {
            update(new LambdaUpdateWrapper<HkInventoryItem>()
                    .eq(HkInventoryItem::getPropertyId, propertyId)
                    .eq(HkInventoryItem::getCategoryId, categoryId)
                    .eq(HkInventoryItem::getRoomTypeId, roomTypeId)
                    .eq(HkInventoryItem::getItemId, itemId)
                    .set(HkInventoryItem::getItemName, itemName));
        }

        /**
         * 指定客房库存管理id指定分类指定房型指定item的数据
         *
         * @param propertyId     资产id
         * @param hkInventoryIds 客房库存管理id集合
         * @param categoryId     分类id
         * @param roomTypeId     房型id
         * @param itemId         item id
         * @return java.util.List<cloud.wconnect.workperformance.entity.hkinventory.HkInventoryItem>
         * @author lichen
         * @date 2025/4/23
         */
        @Override
        public List<HkInventoryItem> listByHkInventoryIdsAndCategoryIdAndRoomTypeIdAndItemId(Integer propertyId, List<Integer> hkInventoryIds, Integer categoryId,
                                                                                             Integer roomTypeId, Integer itemId) {
            if (CollectionUtils.isEmpty(hkInventoryIds)) return Collections.emptyList();
            return list(new LambdaUpdateWrapper<HkInventoryItem>()
                    .eq(HkInventoryItem::getPropertyId, propertyId)
                    .in(HkInventoryItem::getHkInventoryId, hkInventoryIds)
                    .eq(HkInventoryItem::getCategoryId, categoryId)
                    .eq(HkInventoryItem::getRoomTypeId, roomTypeId)
                    .eq(HkInventoryItem::getItemId, itemId));
        }

        /**
         * 指定客房库存下的item数量
         *
         * @param propertyId    资产id
         * @param hkInventoryId 客房库存id
         * @return long
         * @author lichen
         * @date 2025/4/25
         */
        @Override
        public long countByHkInventoryId(Integer propertyId, Integer hkInventoryId) {
            return count(new LambdaQueryWrapper<HkInventoryItem>().eq(HkInventoryItem::getPropertyId, propertyId)
                    .eq(HkInventoryItem::getHkInventoryId, hkInventoryId));
        }

        /**
         * 指定客房库存下的未清点的item数量
         *
         * @param propertyId    资产id
         * @param hkInventoryId 客房库存id
         * @return long
         * @author lichen
         * @date 2025/4/25
         */
        @Override
        public long countByHkInventoryIdAndInventoryIsNull(Integer propertyId, Integer hkInventoryId) {
            return count(new LambdaQueryWrapper<HkInventoryItem>().eq(HkInventoryItem::getPropertyId, propertyId)
                    .eq(HkInventoryItem::getHkInventoryId, hkInventoryId)
                    .isNull(HkInventoryItem::getInventory));
        }

        /**
         * 计算指标
         *
         * @param hkInventory 客房库存
         * @return void
         * @author lichen
         * @date 2025/4/25
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void calculateIndicators(HkInventory hkInventory) {
            Integer propertyId = hkInventory.getPropertyId();
            Integer hkInventoryId = hkInventory.getId();

            //资产配置的税率
            BigDecimal taxRate = taxRateService.getByPropertyId(propertyId).getTaxRate();

            //计算方式
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(
                    propertyI18nRedisService.get(propertyId).getInternationalization().getNumber().getCalculationRule());

            ArrayList<Integer> hkInventoryIds = Lists.newArrayList(hkInventoryId);
            //上期客房库存
            HkInventory previousHkInventory = hkInventoryService.getPreviousInventory(hkInventory);
            Integer previousHkInventoryId = 0;
            if (Objects.nonNull(previousHkInventory)) {
                previousHkInventoryId = previousHkInventory.getId();
                hkInventoryIds.add(previousHkInventoryId);
            }

            //当期和上期客房库存的item集合
            Map<Integer, List<HkInventoryItem>> hkInventoryId2Items = listByHkInventoryIds(propertyId, hkInventoryIds)
                    .stream().collect(Collectors.groupingBy(HkInventoryItem::getHkInventoryId));
            //上期item的库存数量
            Map<String, Integer> previousItem2InventoryCount = hkInventoryId2Items.getOrDefault(previousHkInventoryId, Collections.emptyList())
                    .stream()
                    .filter(item -> Objects.nonNull(item.getInventory()))
                    .collect(Collectors.toMap(item -> ItemCommonService.generateKey(item.getCategoryId(), item.getRoomTypeId(), item.getItemId()),
                            HkInventoryItem::getInventory));

            //当期的客房库存item集合
            List<HkInventoryItem> hkInventoryItems = hkInventoryId2Items.get(hkInventoryId);
            if(CollectionUtils.isEmpty(hkInventoryItems)) return;
            List<Integer> itemIds = hkInventoryItems.stream().map(HkInventoryItem::getItemId).toList();
            List<String> keys = hkInventoryItems.stream().map(item -> ItemCommonService.generateKey(item.getCategoryId(), item.getRoomTypeId(), item.getItemId())).toList();
            Set<Integer> roomTypeIds = hkInventoryItems.stream().map(HkInventoryItem::getRoomTypeId).collect(Collectors.toSet());

            //查询item config
            Map<String, HkInventoryItemConfig> key2ItemConfig = itemConfigService.listByIds(itemIds).stream().collect(Collectors.toMap(
                    itemConfig -> ItemCommonService.generateKey(itemConfig.getCategoryId(), itemConfig.getRoomTypeId(), itemConfig.getId()),
                    Function.identity()));
            //房型对应的总房间数
            Map<Integer, Integer> roomTypeId2TotalRooms = roomTypeConfigService.listByIds(roomTypeIds).stream().collect(
                    Collectors.toMap(HkInventoryRoomTypeConfig::getId, HkInventoryRoomTypeConfig::getTotalRooms));
            //客房库存管理下的所有订单
            Map<String, List<HkInventoryPurchaseOrder>> key2PurchaseOrders = hkInventoryPurchaseOrderService.listByHkInventoryId(propertyId, hkInventory).stream().collect(Collectors.groupingBy(
                    purchaseOrder -> ItemCommonService.generateKey(purchaseOrder.getCategoryId(), purchaseOrder.getRoomTypeId(), purchaseOrder.getItemId())));
            Set<String> existPurchaseOrderKeys = key2PurchaseOrders.keySet();
            //当前客房库存管理下不存在订单的item，再查询item的最新采购单
            Map<String, HkInventoryPurchaseOrder> key2LatestPurchaseOrder = hkInventoryPurchaseOrderService.listItemsLatestPurchaseOrder(propertyId,
                            keys.stream().filter(key -> !existPurchaseOrderKeys.contains(key))
                                    .map(key -> {
                                        String[] split = key.split("_");
                                        HkInventoryItem hkInventoryItem = new HkInventoryItem();
                                        hkInventoryItem.setCategoryId(Integer.valueOf(split[0]));
                                        hkInventoryItem.setRoomTypeId(Integer.valueOf(split[1]));
                                        hkInventoryItem.setItemId(Integer.valueOf(split[2]));
                                        return hkInventoryItem;
                                    }).toList())
                    .stream()
                    .collect(Collectors.toMap(purchaseOrder -> ItemCommonService.generateKey(purchaseOrder.getCategoryId(), purchaseOrder.getRoomTypeId(), purchaseOrder.getItemId()),
                            Function.identity(), (a, b) -> a.getId() >= b.getId() ? a : b));

            ArrayList<HkInventoryItem> batchList = Lists.newArrayListWithCapacity(hkInventoryItems.size());
            for (HkInventoryItem item : hkInventoryItems) {
                //如果库存数量为空，则跳过
                if (Objects.isNull(item.getInventory())) continue;
                //当前key
                String currentKey = ItemCommonService.generateKey(item.getCategoryId(), item.getRoomTypeId(), item.getItemId());

                //item 配置
                HkInventoryItemConfig itemConfig = key2ItemConfig.get(currentKey);
                item.setMinPerRoom(itemConfig.getMinPerRoom());
                item.setParTarget(itemConfig.getParTarget());

                //设置订单相关参数
                setPurchaseOrderRelativeParameters(item, key2PurchaseOrders, key2LatestPurchaseOrder, itemConfig, taxRate);

                //设置使用数量（Previous + purchase - Inventory）
                Integer previousInventoryCount = previousItem2InventoryCount.get(currentKey);
                //当前item对应的房型的总房间数
                Integer totalRooms = roomTypeId2TotalRooms.get(item.getRoomTypeId());
                //设置其他指标
                ItemCommonService.calculate(item, totalRooms, previousInventoryCount, roundingMode);

                batchList.add(item);
            }

            if (CollectionUtils.isEmpty(batchList)) return;
            updateBatchById(batchList);

            //汇总分类的数据
            categoryService.summary(propertyId, hkInventoryId, hkInventoryItems);
            //汇总房型的数据
            roomTypeService.summary(propertyId, hkInventoryId, hkInventoryItems);
            //汇总月度数据
            hkInventoryService.summary(hkInventoryId, hkInventoryItems);

        }

        /**
         * 删除指定Housekeeping Inventory指定分类指定房型下的item
         *
         * @param propertyId     资产id
         * @param hkInventoryIds Housekeeping Inventory id集合
         * @param categoryId     分类id集合
         * @param roomTypeIds    房型id集合
         * @return void
         * @author lichen
         * @date 2025/4/26
         */
        @Override
        public void deleteByHkInventoryIdsAndCategoryIdAndRoomTypeIds(Integer propertyId, List<Integer> hkInventoryIds,
                                                                      Integer categoryId, List<Integer> roomTypeIds) {
            if (CollectionUtils.isEmpty(hkInventoryIds) || CollectionUtils.isEmpty(roomTypeIds))
                return;

            remove(new LambdaQueryWrapper<HkInventoryItem>()
                    .eq(HkInventoryItem::getPropertyId, propertyId)
                    .in(HkInventoryItem::getHkInventoryId, hkInventoryIds)
                    .eq(HkInventoryItem::getCategoryId, categoryId)
                    .in(HkInventoryItem::getRoomTypeId, roomTypeIds));
        }

        /**
         * 设置订单相关参数
         *
         * @param item                    item
         * @param key2PurchaseOrders      指定库存下的订单集合
         * @param key2LatestPurchaseOrder 其他库存下的最新订单集合
         * @param itemConfig              item配置
         * @param taxRate                 税率
         * @return void
         * @author lichen
         * @date 2025/4/25
         */
        private void setPurchaseOrderRelativeParameters(HkInventoryItem item, Map<String, List<HkInventoryPurchaseOrder>> key2PurchaseOrders,
                                                        Map<String, HkInventoryPurchaseOrder> key2LatestPurchaseOrder, HkInventoryItemConfig itemConfig,
                                                        BigDecimal taxRate) {
            //当前key
            String currentKey = ItemCommonService.generateKey(item.getCategoryId(), item.getRoomTypeId(), item.getItemId());

            int purchaseCount;
            BigDecimal unitCost;
            Integer purchaseOrderId;
            //item对应的采购单集合
            List<HkInventoryPurchaseOrder> hkInventoryPurchaseOrders = key2PurchaseOrders.get(currentKey);
            if (CollectionUtils.isNotEmpty(hkInventoryPurchaseOrders)) {
                //购买数量是所有订单的数量之和
                purchaseCount = hkInventoryPurchaseOrders.stream().map(HkInventoryPurchaseOrder::getPurchaseCount).reduce(Integer::sum).orElse(0);
                //采购日期倒序
                hkInventoryPurchaseOrders.sort(Comparator.comparing(HkInventoryPurchaseOrder::getPurchaseTime).reversed());
                //最新的订单
                HkInventoryPurchaseOrder latestPurchaseOrder = hkInventoryPurchaseOrders.get(0);
                unitCost = latestPurchaseOrder.getUnitCost();
                purchaseOrderId = latestPurchaseOrder.getId();
            } else {
                purchaseCount = 0;

                //获取最新采购单
                HkInventoryPurchaseOrder latestPurchaseOrder = key2LatestPurchaseOrder.get(currentKey);
                if (Objects.nonNull(latestPurchaseOrder)) {
                    //最新单价
                    unitCost = latestPurchaseOrder.getUnitCost();
                    //单价来源的订单id
                    purchaseOrderId = latestPurchaseOrder.getId();
                } else {
                    //通过初始化的价格计算出税后单价
                    unitCost = itemConfig.getUnitCost().multiply(new BigDecimal(1).add(taxRate));
                    //单价来源的订单id
                    purchaseOrderId = 0;
                }
            }
            //设置购买数量
            item.setPurchase(purchaseCount);
            //设置最新的单价
            item.setUnitCost(unitCost);
            //设置单价来源id
            item.setPurchaseOrderId(purchaseOrderId);
        }
    }