This commit is contained in:
parent
1a942d9370
commit
3dce4e1868
|
|
@ -18,4 +18,8 @@ public interface DrpCheckInventoryMapper extends BaseMapper<DrpCheckInventoryPag
|
||||||
List<Map<String, Object>> selectInventoryByPrice(@Param("materialIds") List<Long> materialIds,
|
List<Map<String, Object>> selectInventoryByPrice(@Param("materialIds") List<Long> materialIds,
|
||||||
@Param("secondCheckDate") String secondCheckDate,
|
@Param("secondCheckDate") String secondCheckDate,
|
||||||
@Param("warehouseId") String warehouseId);
|
@Param("warehouseId") String warehouseId);
|
||||||
|
|
||||||
|
List<Map<String, Object>> selectActualNumFromInventoryBase(@Param("materialIds") List<Long> materialIds,
|
||||||
|
@Param("warehouseId") String warehouseId,
|
||||||
|
@Param("secondCheckDate") String secondCheckDate);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
@ -40,75 +43,190 @@ public class DrpCheckInventoryServiceImp implements DrpCheckInventoryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DrpCheckDetailVO> getDrpCheckInventoryDetail(DrpCheckDetailVO leRequest) {
|
public List<DrpCheckDetailVO> getDrpCheckInventoryDetail(DrpCheckDetailVO leRequest)
|
||||||
|
{
|
||||||
List<String> collect = Collections.singletonList(leRequest.getCheckId());
|
List<String> collect = Collections.singletonList(leRequest.getCheckId());
|
||||||
List<DrpCheckDetailVO> detailList = mapper.selectListByIntoIds(collect);
|
List<DrpCheckDetailVO> detailList = mapper.selectListByIntoIds(collect);
|
||||||
if (detailList.isEmpty()) return Collections.emptyList();
|
if (detailList.isEmpty()) return Collections.emptyList();
|
||||||
|
|
||||||
// 1. 查询 materialId 列表
|
// 1. materialId 列表
|
||||||
List<Long> materialIds = detailList.stream()
|
List<Long> materialIds = detailList.stream()
|
||||||
.map(DrpCheckDetailVO::getMaterialId)
|
.map(DrpCheckDetailVO::getMaterialId)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.distinct()
|
.distinct()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if(materialIds.isEmpty()) return Collections.emptyList();
|
if (materialIds.isEmpty()) return Collections.emptyList();
|
||||||
|
|
||||||
// 2. 查询按价格聚合的账面数
|
// 2. 查询账面数(按单价分组)
|
||||||
List<Map<String, Object>> priceBuckets = mapper.selectInventoryByPrice(materialIds, leRequest.getSecondCheckDate(), leRequest.getWarehouseId());
|
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 生成最终列表
|
// 3. 按物料ID汇总实盘总数(来自 drp_check_detail.actual_num)
|
||||||
List<DrpCheckDetailVO> finalList = new ArrayList<>();
|
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) {
|
for (Map<String, Object> bucket : priceBuckets) {
|
||||||
Long materialId = bucket.get("material_id") != null ? ((Number) bucket.get("material_id")).longValue() : 0L;
|
Long materialId = ((Number) bucket.get("material_id")).longValue();
|
||||||
Integer unitPrice = bucket.get("unit_price") != null ? ((Number) bucket.get("unit_price")).intValue() : 0;
|
bucketsByMaterial.computeIfAbsent(materialId, k -> new ArrayList<>()).add(bucket);
|
||||||
BigDecimal bookNum = bucket.get("book_num_by_price") != null ? (BigDecimal) bucket.get("book_num_by_price") : BigDecimal.ZERO;
|
}
|
||||||
|
|
||||||
|
// 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();
|
DrpCheckDetailVO vo = new DrpCheckDetailVO();
|
||||||
vo.setMaterialId(materialId);
|
|
||||||
vo.setPrice(unitPrice);
|
|
||||||
vo.setBookNum(bookNum);
|
|
||||||
vo.setBookAmount(unitPrice * bookNum.intValue());
|
|
||||||
vo.setRowType("PRICE_BUCKET");
|
vo.setRowType("PRICE_BUCKET");
|
||||||
|
vo.setMaterialId(materialId);
|
||||||
|
vo.setPrice(unitPrice.intValue());
|
||||||
|
vo.setBookNum(bookNum);
|
||||||
|
vo.setBookAmount(bookNum.multiply(unitPrice).intValue());
|
||||||
|
|
||||||
// 从 detailList 找到任意一个对应 materialId 的记录填充其他信息
|
vo.setActualNum(alloc);
|
||||||
detailList.stream()
|
vo.setDifferNum(alloc.subtract(bookNum));
|
||||||
.filter(d -> d.getMaterialId() != null && d.getMaterialId().equals(materialId))
|
vo.setActualAmount(alloc.multiply(unitPrice).intValue());
|
||||||
.findFirst()
|
vo.setDifferAmount(alloc.multiply(unitPrice).intValue() - bookNum.multiply(unitPrice).intValue());
|
||||||
.ifPresent(d -> {
|
|
||||||
vo.setDetailId(d.getDetailId());
|
// 填基础字段
|
||||||
vo.setCheckId(d.getCheckId());
|
DrpCheckDetailVO sample = sampleDetailByMaterial.get(materialId);
|
||||||
vo.setInventoryId(d.getInventoryId());
|
if (sample != null) {
|
||||||
vo.setMaterialCode(d.getMaterialCode());
|
vo.setDetailId(sample.getDetailId());
|
||||||
vo.setMaterialName(d.getMaterialName());
|
vo.setCheckId(sample.getCheckId());
|
||||||
vo.setCategoryId(d.getCategoryId());
|
vo.setInventoryId(sample.getInventoryId());
|
||||||
vo.setCategoryName(d.getCategoryName());
|
vo.setMaterialCode(sample.getMaterialCode());
|
||||||
vo.setUnitId(d.getUnitId());
|
vo.setMaterialName(sample.getMaterialName());
|
||||||
vo.setUnitName(d.getUnitName());
|
vo.setCategoryId(sample.getCategoryId());
|
||||||
vo.setImageUrl(d.getImageUrl());
|
vo.setCategoryName(sample.getCategoryName());
|
||||||
vo.setSize(d.getSize());
|
vo.setUnitId(sample.getUnitId());
|
||||||
vo.setActualNum(d.getActualNum());
|
vo.setUnitName(sample.getUnitName());
|
||||||
vo.setDifferNum(d.getDifferNum());
|
vo.setImageUrl(sample.getImageUrl());
|
||||||
vo.setDifferAmount(d.getDifferAmount());
|
vo.setSize(sample.getSize());
|
||||||
vo.setActualAmount(d.getActualAmount());
|
vo.setDifferReason(sample.getDifferReason());
|
||||||
vo.setDifferReason(d.getDifferReason());
|
vo.setCheckPicUrls(sample.getCheckPicUrls());
|
||||||
vo.setCheckPicUrls(d.getCheckPicUrls());
|
vo.setCheckPicUrlList(sample.getCheckPicUrlList());
|
||||||
vo.setCheckPicUrlList(d.getCheckPicUrlList());
|
}
|
||||||
});
|
|
||||||
|
|
||||||
finalList.add(vo);
|
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;
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,19 +103,25 @@
|
||||||
</foreach>
|
</foreach>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 按单价聚合账面数量 -->
|
<!-- 按单价聚合账面数量(并返回最早的操作时间用于 FIFO 排序) -->
|
||||||
<select id="selectInventoryByPrice" resultType="map">
|
<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
|
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
|
FROM report_drp_inventory_base
|
||||||
WHERE material_id IN
|
WHERE material_id IN
|
||||||
<foreach collection="materialIds" item="mid" open="(" close=")" separator=",">
|
<foreach collection="materialIds" item="mid" open="(" close=")" separator=",">
|
||||||
#{mid}
|
#{mid}
|
||||||
</foreach>
|
</foreach>
|
||||||
AND operate_time <= #{secondCheckDate} and warehouse_id = #{warehouseId}
|
AND operate_time <= #{secondCheckDate}
|
||||||
|
AND warehouse_id = #{warehouseId}
|
||||||
GROUP BY material_id, unit_price
|
GROUP BY material_id, unit_price
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue