This commit is contained in:
lizhenhua 2025-12-15 13:38:39 +08:00
parent 1a942d9370
commit 3dce4e1868
3 changed files with 180 additions and 52 deletions

View File

@ -18,4 +18,8 @@ public interface DrpCheckInventoryMapper extends BaseMapper<DrpCheckInventoryPag
List<Map<String, Object>> selectInventoryByPrice(@Param("materialIds") List<Long> materialIds,
@Param("secondCheckDate") String secondCheckDate,
@Param("warehouseId") String warehouseId);
List<Map<String, Object>> selectActualNumFromInventoryBase(@Param("materialIds") List<Long> materialIds,
@Param("warehouseId") String warehouseId,
@Param("secondCheckDate") String secondCheckDate);
}

View File

@ -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<DrpCheckDetailVO> getDrpCheckInventoryDetail(DrpCheckDetailVO leRequest) {
public List<DrpCheckDetailVO> getDrpCheckInventoryDetail(DrpCheckDetailVO leRequest)
{
List<String> collect = Collections.singletonList(leRequest.getCheckId());
List<DrpCheckDetailVO> detailList = mapper.selectListByIntoIds(collect);
if (detailList.isEmpty()) return Collections.emptyList();
// 1. 查询 materialId 列表
// 1. materialId 列表
List<Long> 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<Map<String, Object>> priceBuckets = mapper.selectInventoryByPrice(materialIds, leRequest.getSecondCheckDate(), leRequest.getWarehouseId());
// 2. 查询账面数按单价分组
List<Map<String, Object>> 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<DrpCheckDetailVO> finalList = new ArrayList<>();
// 3. 按物料ID汇总实盘总数来自 drp_check_detail.actual_num
Map<Long, BigDecimal> 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<Long, List<Map<String, Object>>> bucketsByMaterial = new HashMap<>();
for (Map<String, Object> 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<Long, List<Map<String, Object>>> e : bucketsByMaterial.entrySet()) {
List<Map<String, Object>> 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<Long, BigDecimal> totalBookNumByMaterial = new HashMap<>();
Map<Long, BigDecimal> totalBookAmountByMaterial = new HashMap<>();
for (Map<String, Object> 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<String, BigDecimal> allocatedActualNum = new HashMap<>();
for (Map.Entry<Long, List<Map<String, Object>>> entry : bucketsByMaterial.entrySet()) {
Long materialId = entry.getKey();
List<Map<String, Object>> buckets = entry.getValue();
BigDecimal remainingActual = actualTotalByMaterial.getOrDefault(materialId, BigDecimal.ZERO);
for (Map<String, Object> 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<Long, DrpCheckDetailVO> sampleDetailByMaterial = detailList.stream()
.filter(d -> d.getMaterialId() != null)
.collect(Collectors.toMap(DrpCheckDetailVO::getMaterialId, d -> d, (a, b) -> a));
List<DrpCheckDetailVO> 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<String, Object> 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());
}
}

View File

@ -103,19 +103,25 @@
</foreach>
</select>
<!-- 按单价聚合账面数量 -->
<!-- 按单价聚合账面数量(并返回最早的操作时间用于 FIFO 排序) -->
<select id="selectInventoryByPrice" resultType="map">
SELECT material_id, unit_price,
SELECT
material_id,
unit_price,
SUM(CASE WHEN record_type = 1 THEN out_into_num
WHEN record_type = 2 THEN -out_into_num ELSE 0 END) AS book_num_by_price
WHEN record_type = 2 THEN -out_into_num ELSE 0 END) AS book_num_by_price,
MIN(operate_time) AS first_operate_time
FROM report_drp_inventory_base
WHERE material_id IN
<foreach collection="materialIds" item="mid" open="(" close=")" separator=",">
#{mid}
</foreach>
AND operate_time &lt;= #{secondCheckDate} and warehouse_id = #{warehouseId}
AND operate_time &lt;= #{secondCheckDate}
AND warehouse_id = #{warehouseId}
GROUP BY material_id, unit_price
</select>
</mapper>