diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/drp/mapper/DrpCheckInventoryMapper.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/drp/mapper/DrpCheckInventoryMapper.java index 310a2a18..d18bd471 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/drp/mapper/DrpCheckInventoryMapper.java +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/drp/mapper/DrpCheckInventoryMapper.java @@ -18,4 +18,8 @@ public interface DrpCheckInventoryMapper extends BaseMapper> selectInventoryByPrice(@Param("materialIds") List materialIds, @Param("secondCheckDate") String secondCheckDate, @Param("warehouseId") String warehouseId); + + List> selectActualNumFromInventoryBase(@Param("materialIds") List materialIds, + @Param("warehouseId") String warehouseId, + @Param("secondCheckDate") String secondCheckDate); } diff --git a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/drp/service/impl/DrpCheckInventoryServiceImp.java b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/drp/service/impl/DrpCheckInventoryServiceImp.java index bab5d450..045677b3 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/drp/service/impl/DrpCheckInventoryServiceImp.java +++ b/bonus-modules/bonus-smart-canteen/src/main/java/com/bonus/canteen/core/drp/service/impl/DrpCheckInventoryServiceImp.java @@ -9,6 +9,9 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.Instant; +import java.time.ZoneId; import java.util.*; import java.util.stream.Collectors; @@ -40,75 +43,190 @@ public class DrpCheckInventoryServiceImp implements DrpCheckInventoryService { } @Override - public List getDrpCheckInventoryDetail(DrpCheckDetailVO leRequest) { + public List getDrpCheckInventoryDetail(DrpCheckDetailVO leRequest) + { List collect = Collections.singletonList(leRequest.getCheckId()); List detailList = mapper.selectListByIntoIds(collect); if (detailList.isEmpty()) return Collections.emptyList(); - // 1. 查询 materialId 列表 + // 1. materialId 列表 List materialIds = detailList.stream() .map(DrpCheckDetailVO::getMaterialId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); - if(materialIds.isEmpty()) return Collections.emptyList(); + if (materialIds.isEmpty()) return Collections.emptyList(); - // 2. 查询按价格聚合的账面数 - List> priceBuckets = mapper.selectInventoryByPrice(materialIds, leRequest.getSecondCheckDate(), leRequest.getWarehouseId()); + // 2. 查询账面数(按单价分组) + List> priceBuckets = + mapper.selectInventoryByPrice(materialIds, leRequest.getSecondCheckDate(), leRequest.getWarehouseId()); - if(priceBuckets.isEmpty()) return Collections.emptyList(); + if (priceBuckets == null || priceBuckets.isEmpty()) { + return Collections.emptyList(); + } - // 3. 遍历 priceBuckets 生成最终列表 - List finalList = new ArrayList<>(); + // 3. 按物料ID汇总实盘总数(来自 drp_check_detail.actual_num) + Map actualTotalByMaterial = new HashMap<>(); + for (DrpCheckDetailVO detail : detailList) { + if (detail.getMaterialId() != null) { + BigDecimal a = detail.getActualNum() == null ? BigDecimal.ZERO : detail.getActualNum(); + actualTotalByMaterial.put(detail.getMaterialId(), + actualTotalByMaterial.getOrDefault(detail.getMaterialId(), BigDecimal.ZERO).add(a)); + } + } + + // 4. 按物料分组 priceBuckets + Map>> bucketsByMaterial = new HashMap<>(); for (Map bucket : priceBuckets) { - Long materialId = bucket.get("material_id") != null ? ((Number) bucket.get("material_id")).longValue() : 0L; - Integer unitPrice = bucket.get("unit_price") != null ? ((Number) bucket.get("unit_price")).intValue() : 0; - BigDecimal bookNum = bucket.get("book_num_by_price") != null ? (BigDecimal) bucket.get("book_num_by_price") : BigDecimal.ZERO; + Long materialId = ((Number) bucket.get("material_id")).longValue(); + bucketsByMaterial.computeIfAbsent(materialId, k -> new ArrayList<>()).add(bucket); + } + + // 5. 为每个物料排序价格桶 —— 按先进先出时间排序 + for (Map.Entry>> e : bucketsByMaterial.entrySet()) { + List> buckets = e.getValue(); + + buckets.sort((m1, m2) -> { + // 获取时间对象 + Object t1Obj = m1.get("first_operate_time"); + Object t2Obj = m2.get("first_operate_time"); + + // 转换为Instant + Instant t1 = toInstant(t1Obj); + Instant t2 = toInstant(t2Obj); + + if (t1 != null && t2 != null) { + return t1.compareTo(t2); + } else if (t1 != null) { + return -1; + } else if (t2 != null) { + return 1; + } + + // 如果都为null,则按单价排序 + BigDecimal p1 = new BigDecimal(m1.get("unit_price").toString()); + BigDecimal p2 = new BigDecimal(m2.get("unit_price").toString()); + return p1.compareTo(p2); + }); + } + + // 6. 计算每个 price-bucket 的账面总和(按物料) + Map totalBookNumByMaterial = new HashMap<>(); + Map totalBookAmountByMaterial = new HashMap<>(); + for (Map b : priceBuckets) { + Long mid = ((Number) b.get("material_id")).longValue(); + BigDecimal bn = new BigDecimal(b.get("book_num_by_price").toString()); + BigDecimal up = new BigDecimal(b.get("unit_price").toString()); + + totalBookNumByMaterial.put(mid, totalBookNumByMaterial.getOrDefault(mid, BigDecimal.ZERO).add(bn)); + totalBookAmountByMaterial.put(mid, totalBookAmountByMaterial.getOrDefault(mid, BigDecimal.ZERO).add(bn.multiply(up))); + } + + // 7. 真正按先进先出原则分配实盘数 + Map allocatedActualNum = new HashMap<>(); + + for (Map.Entry>> entry : bucketsByMaterial.entrySet()) { + Long materialId = entry.getKey(); + List> buckets = entry.getValue(); + + BigDecimal remainingActual = actualTotalByMaterial.getOrDefault(materialId, BigDecimal.ZERO); + + for (Map bucket : buckets) { + if (remainingActual.compareTo(BigDecimal.ZERO) <= 0) { + // 实盘数已分配完,后续批次分配0 + BigDecimal unitPrice = new BigDecimal(bucket.get("unit_price").toString()); + String key = materialId + "_" + unitPrice.toPlainString(); + allocatedActualNum.put(key, BigDecimal.ZERO); + continue; + } + + BigDecimal bucketBookNum = new BigDecimal(bucket.get("book_num_by_price").toString()); + BigDecimal unitPrice = new BigDecimal(bucket.get("unit_price").toString()); + String key = materialId + "_" + unitPrice.toPlainString(); + BigDecimal positiveBook = bucketBookNum.max(BigDecimal.ZERO); + BigDecimal alloc = remainingActual.min(positiveBook); + remainingActual = remainingActual.subtract(alloc); + allocatedActualNum.put(key, alloc); + } + + // 如果还有剩余实盘数(实盘数 > 账面总数),这部分不分配到任何价格桶 + // 页面只显示已分配的部分,多余的实盘数不会显示 + } + + // 8. 构建最终列表 - 只生成PRICE_BUCKET行 + Map sampleDetailByMaterial = detailList.stream() + .filter(d -> d.getMaterialId() != null) + .collect(Collectors.toMap(DrpCheckDetailVO::getMaterialId, d -> d, (a, b) -> a)); + + List finalList = new ArrayList<>(); + + // 按物料ID排序,然后按单价排序 + priceBuckets.sort((m1, m2) -> { + Long a1 = ((Number) m1.get("material_id")).longValue(); + Long a2 = ((Number) m2.get("material_id")).longValue(); + int c = a1.compareTo(a2); + if (c != 0) return c; + + BigDecimal p1 = new BigDecimal(m1.get("unit_price").toString()); + BigDecimal p2 = new BigDecimal(m2.get("unit_price").toString()); + return p1.compareTo(p2); + }); + + for (Map b : priceBuckets) { + Long materialId = ((Number) b.get("material_id")).longValue(); + BigDecimal unitPrice = new BigDecimal(b.get("unit_price").toString()); + BigDecimal bookNum = new BigDecimal(b.get("book_num_by_price").toString()); + String key = materialId + "_" + unitPrice.toPlainString(); + BigDecimal alloc = allocatedActualNum.getOrDefault(key, BigDecimal.ZERO); DrpCheckDetailVO vo = new DrpCheckDetailVO(); - vo.setMaterialId(materialId); - vo.setPrice(unitPrice); - vo.setBookNum(bookNum); - vo.setBookAmount(unitPrice * bookNum.intValue()); vo.setRowType("PRICE_BUCKET"); + vo.setMaterialId(materialId); + vo.setPrice(unitPrice.intValue()); + vo.setBookNum(bookNum); + vo.setBookAmount(bookNum.multiply(unitPrice).intValue()); - // 从 detailList 找到任意一个对应 materialId 的记录填充其他信息 - detailList.stream() - .filter(d -> d.getMaterialId() != null && d.getMaterialId().equals(materialId)) - .findFirst() - .ifPresent(d -> { - vo.setDetailId(d.getDetailId()); - vo.setCheckId(d.getCheckId()); - vo.setInventoryId(d.getInventoryId()); - vo.setMaterialCode(d.getMaterialCode()); - vo.setMaterialName(d.getMaterialName()); - vo.setCategoryId(d.getCategoryId()); - vo.setCategoryName(d.getCategoryName()); - vo.setUnitId(d.getUnitId()); - vo.setUnitName(d.getUnitName()); - vo.setImageUrl(d.getImageUrl()); - vo.setSize(d.getSize()); - vo.setActualNum(d.getActualNum()); - vo.setDifferNum(d.getDifferNum()); - vo.setDifferAmount(d.getDifferAmount()); - vo.setActualAmount(d.getActualAmount()); - vo.setDifferReason(d.getDifferReason()); - vo.setCheckPicUrls(d.getCheckPicUrls()); - vo.setCheckPicUrlList(d.getCheckPicUrlList()); - }); + vo.setActualNum(alloc); + vo.setDifferNum(alloc.subtract(bookNum)); + vo.setActualAmount(alloc.multiply(unitPrice).intValue()); + vo.setDifferAmount(alloc.multiply(unitPrice).intValue() - bookNum.multiply(unitPrice).intValue()); + + // 填基础字段 + DrpCheckDetailVO sample = sampleDetailByMaterial.get(materialId); + if (sample != null) { + vo.setDetailId(sample.getDetailId()); + vo.setCheckId(sample.getCheckId()); + vo.setInventoryId(sample.getInventoryId()); + vo.setMaterialCode(sample.getMaterialCode()); + vo.setMaterialName(sample.getMaterialName()); + vo.setCategoryId(sample.getCategoryId()); + vo.setCategoryName(sample.getCategoryName()); + vo.setUnitId(sample.getUnitId()); + vo.setUnitName(sample.getUnitName()); + vo.setImageUrl(sample.getImageUrl()); + vo.setSize(sample.getSize()); + vo.setDifferReason(sample.getDifferReason()); + vo.setCheckPicUrls(sample.getCheckPicUrls()); + vo.setCheckPicUrlList(sample.getCheckPicUrlList()); + } finalList.add(vo); } - // 4. 排序:materialId + PRICE_BUCKET 按 unitPrice 升序,null 排到最后 - finalList.sort(Comparator - .comparing(DrpCheckDetailVO::getMaterialId, Comparator.nullsLast(Long::compareTo)) - .thenComparing(DrpCheckDetailVO::getPrice, Comparator.nullsLast(Integer::compareTo)) - ); - return finalList; } - - -} + private static Instant toInstant(Object date) { + if (date == null) { + return null; + } + if (date instanceof java.util.Date) { + return ((java.util.Date) date).toInstant(); + } + if (date instanceof java.time.LocalDateTime) { + return ((java.time.LocalDateTime) date).atZone(ZoneId.systemDefault()).toInstant(); + } + // 如果有其他类型,可以继续添加 + throw new IllegalArgumentException("Unsupported date type: " + date.getClass()); + } + } diff --git a/bonus-modules/bonus-smart-canteen/src/main/resources/mapper/drp/DrpCheckInventoryMapper.xml b/bonus-modules/bonus-smart-canteen/src/main/resources/mapper/drp/DrpCheckInventoryMapper.xml index 58839a71..777bcb9a 100644 --- a/bonus-modules/bonus-smart-canteen/src/main/resources/mapper/drp/DrpCheckInventoryMapper.xml +++ b/bonus-modules/bonus-smart-canteen/src/main/resources/mapper/drp/DrpCheckInventoryMapper.xml @@ -103,19 +103,25 @@ - + + +