| | |
| | | import com.ruoyi.common.utils.bean.BeanUtils; |
| | | import com.ruoyi.common.utils.poi.ExcelUtil; |
| | | import com.ruoyi.framework.web.domain.R; |
| | | import com.ruoyi.procurementrecord.pojo.ProcurementDemand; |
| | | import com.ruoyi.procurementrecord.service.ProcurementDemandService; |
| | | import com.ruoyi.stock.mapper.StockInventoryMapper; |
| | | import com.ruoyi.stock.pojo.StockInventory; |
| | | import com.ruoyi.technology.bean.dto.BomImportDto; |
| | | import com.ruoyi.technology.bean.dto.BomKitCheckDto; |
| | | import com.ruoyi.technology.bean.dto.TechnologyBomDto; |
| | | import com.ruoyi.technology.bean.dto.TechnologyBomStructureDto; |
| | | import com.ruoyi.technology.bean.vo.BomKitCheckItemVo; |
| | | import com.ruoyi.technology.bean.vo.TechnologyBomStructureVo; |
| | | import com.ruoyi.technology.bean.vo.TechnologyBomVo; |
| | | import com.ruoyi.technology.mapper.TechnologyBomMapper; |
| | |
| | | private final TechnologyRoutingMapper technologyRoutingMapper; |
| | | private final IProductService productService; |
| | | private final TechnologyBomStructureMapper technologyBomStructureMapper; |
| | | private final StockInventoryMapper stockInventoryMapper; |
| | | private final ProcurementDemandService procurementDemandService; |
| | | |
| | | /** |
| | | * 分页查询BOM列表。 |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public R add(TechnologyBom technologyBom) { |
| | | validateProductModel(technologyBom.getProductModelId()); |
| | | if (technologyBom.getStatus() == null) { |
| | | technologyBom.setStatus(1); |
| | | } |
| | | boolean saved = technologyBomMapper.insert(technologyBom) > 0; |
| | | if (!saved) { |
| | | return R.fail("Add BOM failed"); |
| | | } |
| | | technologyBom.setBomNo("BM." + String.format("%05d", technologyBom.getId())); |
| | | technologyBomMapper.updateById(technologyBom); |
| | | if (Integer.valueOf(1).equals(technologyBom.getStatus())) { |
| | | deactivateOtherVersions(technologyBom.getProductModelId(), technologyBom.getId().longValue()); |
| | | } |
| | | initRootStructure(technologyBom.getId().longValue(), technologyBom.getProductModelId()); |
| | | return R.ok(); |
| | | } |
| | |
| | | technologyBom.setBomNo(oldBom.getBomNo()); |
| | | } |
| | | technologyBomMapper.updateById(technologyBom); |
| | | if (Integer.valueOf(1).equals(technologyBom.getStatus())) { |
| | | deactivateOtherVersions(technologyBom.getProductModelId(), technologyBom.getId().longValue()); |
| | | } |
| | | return R.ok(); |
| | | } |
| | | |
| | | /** |
| | | * 同一产品规格只保留一个启用版本 |
| | | */ |
| | | private void deactivateOtherVersions(Long productModelId, Long keepId) { |
| | | if (productModelId == null) { |
| | | return; |
| | | } |
| | | technologyBomMapper.update(null, Wrappers.<TechnologyBom>lambdaUpdate() |
| | | .set(TechnologyBom::getStatus, 0) |
| | | .eq(TechnologyBom::getProductModelId, productModelId) |
| | | .ne(TechnologyBom::getId, keepId) |
| | | .eq(TechnologyBom::getStatus, 1)); |
| | | } |
| | | |
| | | /** |
| | |
| | | newTechnologyBom.setProductModelId(oldTechnologyBom.getProductModelId()); |
| | | newTechnologyBom.setVersion("FZ" + oldTechnologyBom.getVersion()); |
| | | newTechnologyBom.setRemark(oldTechnologyBom.getRemark()); |
| | | newTechnologyBom.setStatus(0); |
| | | boolean saved = technologyBomMapper.insert(newTechnologyBom) > 0; |
| | | if (!saved) { |
| | | return R.fail("Copy BOM failed"); |
| | |
| | | long randomNum = ThreadLocalRandom.current().nextLong(min, max + 1); |
| | | return String.valueOf(randomNum); |
| | | } |
| | | |
| | | /** |
| | | * BOM齐套分析:逐层展开树(需求量=父需求×单位用量),按规格汇总后与可用库存比对。 |
| | | * 可用库存 = Σ(库存数量 - 锁定数量),缺口 = max(需求量 - 可用库存, 0)。 |
| | | */ |
| | | @Override |
| | | public List<BomKitCheckItemVo> kitCheck(BomKitCheckDto dto) { |
| | | if (dto == null || dto.getBomId() == null || dto.getDemandQty() == null |
| | | || dto.getDemandQty().compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new ServiceException("请传入bomId和需求数量(须大于0)"); |
| | | } |
| | | TechnologyBom bom = technologyBomMapper.selectById(dto.getBomId()); |
| | | if (bom == null) { |
| | | throw new ServiceException("BOM不存在"); |
| | | } |
| | | List<TechnologyBomStructureVo> roots = technologyBomStructureService.listByBomId(dto.getBomId()); |
| | | if (CollUtil.isEmpty(roots)) { |
| | | throw new ServiceException("BOM结构为空,请先维护物料清单"); |
| | | } |
| | | |
| | | Map<Long, BigDecimal> requiredMap = new LinkedHashMap<>(); |
| | | for (TechnologyBomStructureVo root : roots) { |
| | | expandRequirements(root, dto.getDemandQty(), requiredMap, true); |
| | | } |
| | | if (requiredMap.isEmpty()) { |
| | | throw new ServiceException("BOM下无物料节点"); |
| | | } |
| | | |
| | | Map<Long, BigDecimal> availMap = new HashMap<>(); |
| | | List<StockInventory> stocks = stockInventoryMapper.selectList(Wrappers.<StockInventory>lambdaQuery() |
| | | .in(StockInventory::getProductModelId, requiredMap.keySet())); |
| | | for (StockInventory s : stocks) { |
| | | BigDecimal avail = s.getQualitity() == null ? BigDecimal.ZERO : s.getQualitity(); |
| | | avail = avail.subtract(s.getLockedQuantity() == null ? BigDecimal.ZERO : s.getLockedQuantity()); |
| | | availMap.merge(s.getProductModelId(), avail, BigDecimal::add); |
| | | } |
| | | |
| | | List<ProductModel> models = productModelService.listByIds(requiredMap.keySet()); |
| | | Map<Long, ProductModel> modelMap = models.stream() |
| | | .collect(Collectors.toMap(ProductModel::getId, m -> m, (a, b) -> a)); |
| | | Map<Long, String> productNameMap = new HashMap<>(); |
| | | Set<Long> productIds = models.stream().map(ProductModel::getProductId) |
| | | .filter(Objects::nonNull).collect(Collectors.toSet()); |
| | | if (!productIds.isEmpty()) { |
| | | productService.listByIds(productIds) |
| | | .forEach(p -> productNameMap.put(p.getId().longValue(), p.getProductName())); |
| | | } |
| | | |
| | | List<BomKitCheckItemVo> result = new ArrayList<>(); |
| | | for (Map.Entry<Long, BigDecimal> entry : requiredMap.entrySet()) { |
| | | BigDecimal required = entry.getValue(); |
| | | BigDecimal available = availMap.getOrDefault(entry.getKey(), BigDecimal.ZERO); |
| | | ProductModel pm = modelMap.get(entry.getKey()); |
| | | BomKitCheckItemVo vo = new BomKitCheckItemVo(); |
| | | vo.setProductModelId(entry.getKey()); |
| | | vo.setProductName(pm == null ? null : productNameMap.get(pm.getProductId())); |
| | | vo.setModel(pm == null ? null : pm.getModel()); |
| | | vo.setUnit(pm == null ? null : pm.getUnit()); |
| | | vo.setRequiredQty(required); |
| | | vo.setAvailableQty(available); |
| | | vo.setShortageQty(required.subtract(available).max(BigDecimal.ZERO)); |
| | | result.add(vo); |
| | | } |
| | | result.sort(Comparator.comparing(BomKitCheckItemVo::getShortageQty).reversed()); |
| | | return result; |
| | | } |
| | | |
| | | private void expandRequirements(TechnologyBomStructureVo node, BigDecimal requiredQty, |
| | | Map<Long, BigDecimal> requiredMap, boolean isRoot) { |
| | | if (!isRoot && node.getProductModelId() != null) { |
| | | requiredMap.merge(node.getProductModelId(), requiredQty, BigDecimal::add); |
| | | } |
| | | if (CollUtil.isNotEmpty(node.getChildren())) { |
| | | for (TechnologyBomStructureVo child : node.getChildren()) { |
| | | BigDecimal unitQty = child.getUnitQuantity() == null ? BigDecimal.ZERO : child.getUnitQuantity(); |
| | | expandRequirements(child, requiredQty.multiply(unitQty), requiredMap, false); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 将缺口物料写入采购需求:同规格已存在待处理记录则累加缺口,否则新增。 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public R generateProcurementDemand(BomKitCheckDto dto) { |
| | | List<BomKitCheckItemVo> items = kitCheck(dto); |
| | | List<BomKitCheckItemVo> shortages = items.stream() |
| | | .filter(item -> item.getShortageQty().compareTo(BigDecimal.ZERO) > 0) |
| | | .collect(Collectors.toList()); |
| | | if (shortages.isEmpty()) { |
| | | return R.ok("库存齐套,无需生成采购需求"); |
| | | } |
| | | TechnologyBom bom = technologyBomMapper.selectById(dto.getBomId()); |
| | | int created = 0; |
| | | int merged = 0; |
| | | for (BomKitCheckItemVo item : shortages) { |
| | | ProcurementDemand exist = procurementDemandService.getOne(Wrappers.<ProcurementDemand>lambdaQuery() |
| | | .eq(ProcurementDemand::getProductModelId, item.getProductModelId()) |
| | | .eq(ProcurementDemand::getStatus, 0) |
| | | .last("limit 1")); |
| | | if (exist != null) { |
| | | exist.setDemandQuantity(exist.getDemandQuantity().add(item.getShortageQty())); |
| | | exist.setStockQuantity(item.getAvailableQty()); |
| | | exist.setSourceBomId(dto.getBomId()); |
| | | exist.setSourceBomNo(bom == null ? null : bom.getBomNo()); |
| | | procurementDemandService.updateById(exist); |
| | | merged++; |
| | | } else { |
| | | ProcurementDemand demand = new ProcurementDemand(); |
| | | demand.setProductModelId(item.getProductModelId()); |
| | | demand.setProductName(item.getProductName()); |
| | | demand.setProductModel(item.getModel()); |
| | | demand.setUnit(item.getUnit()); |
| | | demand.setDemandQuantity(item.getShortageQty()); |
| | | demand.setStockQuantity(item.getAvailableQty()); |
| | | demand.setSourceBomId(dto.getBomId()); |
| | | demand.setSourceBomNo(bom == null ? null : bom.getBomNo()); |
| | | demand.setStatus(0); |
| | | procurementDemandService.save(demand); |
| | | created++; |
| | | } |
| | | } |
| | | return R.ok(String.format("采购需求生成完成:新增 %d 条,累加合并 %d 条", created, merged)); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public R changeStatus(Integer id, Integer status) { |
| | | if (id == null || status == null || (status != 0 && status != 1)) { |
| | | return R.fail("参数错误:需传入id和状态(1启用/0停用)"); |
| | | } |
| | | TechnologyBom bom = technologyBomMapper.selectById(id); |
| | | if (bom == null) { |
| | | return R.fail("BOM不存在"); |
| | | } |
| | | TechnologyBom update = new TechnologyBom(); |
| | | update.setId(id); |
| | | update.setStatus(status); |
| | | technologyBomMapper.updateById(update); |
| | | if (Integer.valueOf(1).equals(status)) { |
| | | deactivateOtherVersions(bom.getProductModelId(), id.longValue()); |
| | | } |
| | | return R.ok(); |
| | | } |
| | | } |