feat(wms/material-coil): 新增钢卷囤积统计与二维码详情查询功能
1. 新增囤积统计接口:计算已发货钢卷的平均囤积周期和平均囤积成本 - 囤积周期 = 发货时间 - 二维码第一步创建时间 - 囤积成本 = 囤积天数 × 钢卷净重 × 1元/吨/天 - 支持与分页列表相同的查询条件筛选 2. 新增二维码详情查询接口:在分页查询基础上填充完整的二维码记录信息 - 前端可通过 qrcodeRecord.content 获取二维码JSON内容 - 采用批量查询避免N+1性能问题 调整前,钢卷查询无法获取二维码详细内容,也无法统计囤积相关指标;调整后,支持二维码内容查看和囤积成本分析,为仓储成本核算提供数据支持。
This commit is contained in:
@@ -99,6 +99,27 @@ public class WmsMaterialCoilController extends BaseController {
|
||||
return iWmsMaterialCoilService.queryPageListWithRejudge(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询钢卷物料表列表(包含二维码记录内容)
|
||||
* 与list接口查询条件完全一致,区别是会填充二维码content到qrcodeRecord字段
|
||||
* 前端可通过qrcodeRecord.content获取二维码JSON内容
|
||||
*/
|
||||
@GetMapping("/listWithQrcode")
|
||||
public TableDataInfo<WmsMaterialCoilVo> listWithQrcode(WmsMaterialCoilBo bo, PageQuery pageQuery) {
|
||||
return iWmsMaterialCoilService.queryPageListWithQrcode(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计已发货钢卷的平均囤积周期和平均囤积成本
|
||||
* 使用与分页列表相同的查询条件,按发货时间筛选
|
||||
* 囤积周期:发货时间 - 二维码中第一步的创建时间
|
||||
* 囤积成本:囤积天数 * 净重(吨) * 1元/吨/天
|
||||
*/
|
||||
@PostMapping("/hoardingStatistics")
|
||||
public R<Map<String, Object>> getHoardingStatistics(@RequestBody WmsMaterialCoilBo bo) {
|
||||
return R.ok(iWmsMaterialCoilService.getHoardingStatistics(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计筛选条件下的全量汇总数据
|
||||
* 独立的统计接口,使用与分页列表相同的查询条件
|
||||
|
||||
@@ -387,5 +387,26 @@ public interface IWmsMaterialCoilService {
|
||||
* 根据入场钢卷号或当前钢卷号查询钢卷,供双机架计划绑定使用
|
||||
*/
|
||||
com.klp.domain.vo.WmsMaterialCoilVo queryByCoilNo(String coilNo);
|
||||
|
||||
/**
|
||||
* 统计已发货钢卷的平均囤积周期和平均囤积成本
|
||||
* 使用与分页列表相同的查询条件,按发货时间筛选已发货钢卷
|
||||
* 囤积周期 = 发货时间 - 二维码中第一个步骤的创建时间
|
||||
* 囤积成本 = 囤积天数 * 净重(吨) * 1元/吨/天
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return avgHoardingDays(平均囤积天数), avgHoardingCost(平均囤积成本), totalCount(已发货钢卷数量)
|
||||
*/
|
||||
Map<String, Object> getHoardingStatistics(WmsMaterialCoilBo bo);
|
||||
|
||||
/**
|
||||
* 查询钢卷物料表列表(包含二维码记录信息)
|
||||
* 与queryPageList查询条件完全一致,区别是会填充qrcodeRecord字段(WmsGenerateRecordVo)
|
||||
* 前端可通过qrcodeRecord.content获取二维码的JSON内容
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
*/
|
||||
TableDataInfo<WmsMaterialCoilVo> queryPageListWithQrcode(WmsMaterialCoilBo bo, PageQuery pageQuery);
|
||||
}
|
||||
|
||||
|
||||
@@ -5849,5 +5849,205 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||||
return baseMapper.selectVoOne(lqw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取材料卷的囤积统计信息
|
||||
* 根据查询条件计算钢卷的平均囤积天数、平均囤积成本和符合条件的钢卷总数
|
||||
* 囤积天数 = 发货时间 - 二维码中第一步操作时间
|
||||
* 囤积成本 = 囤积天数 × 钢卷净重
|
||||
* 注意: 此处是统计当前钢卷的重量,但是钢卷在加工的过程中重量会发生变化,如果想要更精确的数据需要对二维码进行一个更细致的拆分才行
|
||||
* @param bo 查询条件对象,包含筛选钢卷的各种条件
|
||||
* @return 包含平均囤积天数、平均囤积成本和总数的Map
|
||||
* - avgHoardingDays: 平均囤积天数(保留2位小数)
|
||||
* - avgHoardingCost: 平均囤积成本(保留2位小数)
|
||||
* - totalCount: 符合条件的钢卷总数
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getHoardingStatistics(WmsMaterialCoilBo bo) {
|
||||
// 构建查询条件
|
||||
QueryWrapper<WmsMaterialCoil> qw = buildQueryWrapperPlus(bo);
|
||||
|
||||
// 查询符合条件的钢卷列表
|
||||
List<WmsMaterialCoilVo> list = baseMapper.selectVoListWithDynamicJoin(qw);
|
||||
|
||||
// 初始化返回结果,设置默认值
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("avgHoardingDays", BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
|
||||
result.put("avgHoardingCost", BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
|
||||
result.put("totalCount", 0);
|
||||
|
||||
// 如果没有钢卷数据,直接返回默认值
|
||||
if (list == null || list.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 收集所有钢卷的二维码记录ID,用于批量查询二维码信息
|
||||
Set<Long> qrcodeIds = list.stream()
|
||||
.map(WmsMaterialCoilVo::getQrcodeRecordId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 批量查询二维码记录,建立ID到二维码对象的映射
|
||||
Map<Long, WmsGenerateRecordVo> qrcodeMap = new HashMap<>();
|
||||
if (!qrcodeIds.isEmpty()) {
|
||||
List<WmsGenerateRecordVo> qrcodeList = generateRecordMapper.selectVoBatchIds(new ArrayList<>(qrcodeIds));
|
||||
if (qrcodeList != null) {
|
||||
for (WmsGenerateRecordVo qrcode : qrcodeList) {
|
||||
if (qrcode != null && qrcode.getRecordId() != null) {
|
||||
qrcodeMap.put(qrcode.getRecordId(), qrcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化统计变量
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
BigDecimal totalHoardingDays = BigDecimal.ZERO;
|
||||
BigDecimal totalHoardingCost = BigDecimal.ZERO;
|
||||
int validCount = 0;
|
||||
|
||||
// 遍历每个钢卷,计算囤积天数和成本
|
||||
for (WmsMaterialCoilVo vo : list) {
|
||||
Date exportTime = vo.getExportTime();
|
||||
if (exportTime == null) {
|
||||
exportTime = vo.getUpdateTime(); // 没有发货时间则用更新时间
|
||||
}
|
||||
Long qrcodeRecordId = vo.getQrcodeRecordId();
|
||||
if (qrcodeRecordId == null) {
|
||||
continue; // 没有二维码记录,跳过
|
||||
}
|
||||
|
||||
WmsGenerateRecordVo qrcode = qrcodeMap.get(qrcodeRecordId);
|
||||
if (qrcode == null) {
|
||||
continue; // 二维码记录不存在,跳过
|
||||
}
|
||||
|
||||
try {
|
||||
// 解析二维码内容,获取操作步骤
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> contentMap = objectMapper.readValue(qrcode.getContent(), Map.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||||
if (steps == null || steps.isEmpty()) {
|
||||
continue; // 没有操作步骤,跳过
|
||||
}
|
||||
|
||||
// 获取第一步操作的时间(钢卷创建时间)
|
||||
Object createTimeObj = steps.get(0).get("create_time");
|
||||
if (createTimeObj == null) {
|
||||
continue; // 没有创建时间,跳过
|
||||
}
|
||||
|
||||
Date firstCreateTime = parseDateFromObject(createTimeObj);
|
||||
if (firstCreateTime == null) {
|
||||
continue; // 时间解析失败,跳过
|
||||
}
|
||||
|
||||
// 计算囤积天数:发货时间 - 创建时间
|
||||
long diffMs = exportTime.getTime() - firstCreateTime.getTime();
|
||||
BigDecimal days = BigDecimal.valueOf(diffMs)
|
||||
.divide(BigDecimal.valueOf(1000L * 60 * 60 * 24), 2, RoundingMode.HALF_UP);
|
||||
|
||||
totalHoardingDays = totalHoardingDays.add(days);
|
||||
|
||||
// 计算囤积成本:囤积天数 × 钢卷净重
|
||||
BigDecimal netWeight = vo.getNetWeight() != null ? vo.getNetWeight() : BigDecimal.ZERO;
|
||||
BigDecimal cost = days.multiply(netWeight);
|
||||
totalHoardingCost = totalHoardingCost.add(cost);
|
||||
|
||||
validCount++; // 有效数据计数
|
||||
} catch (Exception e) {
|
||||
log.warn("计算囤积统计时解析二维码失败, coilId={}", vo.getCoilId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 计算平均值
|
||||
if (validCount > 0) {
|
||||
result.put("avgHoardingDays", totalHoardingDays.divide(BigDecimal.valueOf(validCount), 2, RoundingMode.HALF_UP));
|
||||
result.put("avgHoardingCost", totalHoardingCost.divide(BigDecimal.valueOf(validCount), 2, RoundingMode.HALF_UP));
|
||||
result.put("totalCount", validCount);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询分页列表并填充二维码记录
|
||||
* 在普通分页查询的基础上,额外填充每个钢卷的二维码详细信息
|
||||
*
|
||||
* @param bo 查询条件对象
|
||||
* @param pageQuery 分页查询参数
|
||||
* @return 包含二维码信息的分页结果
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<WmsMaterialCoilVo> queryPageListWithQrcode(WmsMaterialCoilBo bo, PageQuery pageQuery) {
|
||||
// 先执行普通分页查询
|
||||
TableDataInfo<WmsMaterialCoilVo> result = queryPageList(bo, pageQuery);
|
||||
List<WmsMaterialCoilVo> records = result.getRows();
|
||||
// 如果有数据,填充二维码记录
|
||||
if (records != null && !records.isEmpty()) {
|
||||
fillQrcodeRecords(records);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量填充钢卷列表的二维码记录
|
||||
* 通过批量查询二维码记录,避免N+1查询问题
|
||||
*
|
||||
* @param voList 钢卷列表
|
||||
*/
|
||||
private void fillQrcodeRecords(List<WmsMaterialCoilVo> voList) {
|
||||
// 收集所有二维码记录ID
|
||||
Set<Long> qrcodeRecordIds = voList.stream()
|
||||
.map(WmsMaterialCoilVo::getQrcodeRecordId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (qrcodeRecordIds.isEmpty()) {
|
||||
return; // 没有二维码记录ID,直接返回
|
||||
}
|
||||
|
||||
// 批量查询二维码记录
|
||||
Map<Long, WmsGenerateRecordVo> qrcodeMap = new HashMap<>();
|
||||
List<WmsGenerateRecordVo> qrcodeList = generateRecordMapper.selectVoBatchIds(new ArrayList<>(qrcodeRecordIds));
|
||||
if (qrcodeList != null) {
|
||||
for (WmsGenerateRecordVo qrcode : qrcodeList) {
|
||||
if (qrcode != null && qrcode.getRecordId() != null) {
|
||||
qrcodeMap.put(qrcode.getRecordId(), qrcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将二维码记录设置到对应的钢卷对象中
|
||||
for (WmsMaterialCoilVo vo : voList) {
|
||||
if (vo.getQrcodeRecordId() != null && qrcodeMap.containsKey(vo.getQrcodeRecordId())) {
|
||||
vo.setQrcodeRecord(qrcodeMap.get(vo.getQrcodeRecordId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象解析为Date类型
|
||||
* 支持Number、Date和字符串格式的时间对象
|
||||
*
|
||||
* @param obj 时间对象,可以是Number、Date或String
|
||||
* @return 解析后的Date对象,解析失败返回null
|
||||
*/
|
||||
private Date parseDateFromObject(Object obj) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
if (obj instanceof Number) {
|
||||
// 处理时间戳格式
|
||||
return new Date(((Number) obj).longValue());
|
||||
}
|
||||
if (obj instanceof Date) {
|
||||
// 已经是Date类型,直接返回
|
||||
return (Date) obj;
|
||||
}
|
||||
// 字符串格式,使用工具类解析
|
||||
return DateUtils.parseDate(obj.toString());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user