Files
klp-oa/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java
Joshi fe4f191293 fix(wms): 修复材料卷号重复校验逻辑
- 在查询条件中添加数据类型过滤器 (dataType=1)
- 确保只对特定类型的数据进行重复检查
- 修正了进入卷号和当前卷号的重复验证逻辑
2026-01-16 09:13:19 +08:00

2717 lines
127 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.klp.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.esotericsoftware.minlog.Log;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.klp.common.core.domain.entity.SysUser;
import com.klp.common.core.page.TableDataInfo;
import com.klp.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.klp.common.helper.LoginHelper;
import com.klp.common.utils.StringUtils;
import com.klp.common.utils.spring.SpringUtils;
import com.klp.domain.*;
import com.klp.domain.bo.*;
import com.klp.domain.vo.*;
import com.klp.mapper.WmsDeliveryPlanMapper;
import com.klp.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.klp.mapper.WmsMaterialCoilMapper;
import com.klp.mapper.WmsStockMapper;
import com.klp.mapper.WmsProductMapper;
import com.klp.mapper.WmsRawMaterialMapper;
import com.klp.mapper.WmsGenerateRecordMapper;
import com.klp.service.IWmsMaterialCoilService;
import com.klp.service.IWmsStockService;
import com.klp.service.IWmsGenerateRecordService;
import com.klp.service.IWmsWarehouseService;
import com.klp.service.IWmsActualWarehouseService;
import com.klp.service.IWmsRawMaterialService;
import com.klp.service.IWmsBomItemService;
import com.klp.service.IWmsProductService;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.*;
import java.util.stream.Collectors;
import java.math.BigDecimal;
/**
* 钢卷物料表Service业务层处理
*
* @author Joshi
* @date 2025-07-18
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
private final WmsMaterialCoilMapper baseMapper;
private final IWmsStockService stockService;
private final IWmsGenerateRecordService generateRecordService;
private final WmsGenerateRecordMapper generateRecordMapper;
private final IWmsWarehouseService warehouseService;
private final IWmsActualWarehouseService actualWarehouseService;
private final IWmsRawMaterialService rawMaterialService;
private final IWmsBomItemService bomItemService;
private final IWmsProductService productService;
private final ISysUserService userService;
private final WmsDeliveryPlanMapper deliveryPlanMapper;
private final WmsProductMapper productMapper;
private final WmsRawMaterialMapper rawMaterialMapper;
/**
* 查询钢卷物料表
*/
@Override
public WmsMaterialCoilVo queryById(Long coilId) {
WmsMaterialCoilVo vo = baseMapper.selectVoById(coilId);
if (vo == null) {
return null;
}
// 查询关联对象
fillRelatedObjects(vo);
return vo;
}
/**
* 批量填充关联对象信息优化版本避免N+1查询
*/
private void fillRelatedObjectsBatch(List<WmsMaterialCoilVo> voList) {
if (voList == null || voList.isEmpty()) {
return;
}
// 收集所有需要查询的ID
Set<Long> warehouseIds = new HashSet<>();
Set<Long> nextWarehouseIds = new HashSet<>();
Set<Long> actualWarehouseIds = new HashSet<>();
Set<Long> qrcodeRecordIds = new HashSet<>();
Set<Long> rawMaterialIds = new HashSet<>();
Set<Long> productIds = new HashSet<>();
Set<Long> bomIds = new HashSet<>();
for (WmsMaterialCoilVo vo : voList) {
if (vo.getWarehouseId() != null) warehouseIds.add(vo.getWarehouseId());
if (vo.getNextWarehouseId() != null) nextWarehouseIds.add(vo.getNextWarehouseId());
if (vo.getActualWarehouseId() != null) actualWarehouseIds.add(vo.getActualWarehouseId());
if (vo.getQrcodeRecordId() != null) qrcodeRecordIds.add(vo.getQrcodeRecordId());
if ("raw_material".equals(vo.getItemType()) && vo.getItemId() != null) {
rawMaterialIds.add(vo.getItemId());
}
if ("product".equals(vo.getItemType()) && vo.getItemId() != null) {
productIds.add(vo.getItemId());
}
}
// 批量查询库区信息
Map<Long, WmsWarehouseVo> warehouseMap = new HashMap<>();
if (!warehouseIds.isEmpty()) {
for (Long id : warehouseIds) {
WmsWarehouseVo warehouse = warehouseService.queryById(id);
if (warehouse != null) {
warehouseMap.put(id, warehouse);
}
}
}
if (!nextWarehouseIds.isEmpty()) {
for (Long id : nextWarehouseIds) {
if (!warehouseMap.containsKey(id)) {
WmsWarehouseVo warehouse = warehouseService.queryById(id);
if (warehouse != null) {
warehouseMap.put(id, warehouse);
}
}
}
}
// 批量查询实际库区信息
Map<Long, WmsActualWarehouseVo> actualWarehouseMap = new HashMap<>();
if (!actualWarehouseIds.isEmpty()) {
for (Long id : actualWarehouseIds) {
WmsActualWarehouseVo actualWarehouse = actualWarehouseService.queryById(id);
if (actualWarehouse != null) {
actualWarehouseMap.put(id, actualWarehouse);
}
}
}
// 批量查询二维码信息
Map<Long, WmsGenerateRecordVo> qrcodeMap = new HashMap<>();
if (!qrcodeRecordIds.isEmpty()) {
for (Long id : qrcodeRecordIds) {
WmsGenerateRecordVo qrcode = generateRecordService.queryById(id);
if (qrcode != null) {
qrcodeMap.put(id, qrcode);
}
}
}
// 批量查询原材料信息
Map<Long, WmsRawMaterialVo> rawMaterialMap = new HashMap<>();
if (!rawMaterialIds.isEmpty()) {
for (Long id : rawMaterialIds) {
WmsRawMaterialVo rawMaterial = rawMaterialService.queryById(id);
if (rawMaterial != null) {
rawMaterialMap.put(id, rawMaterial);
if (rawMaterial.getBomId() != null) {
bomIds.add(rawMaterial.getBomId());
}
}
}
}
// 批量查询产品信息
Map<Long, WmsProductVo> productMap = new HashMap<>();
if (!productIds.isEmpty()) {
for (Long id : productIds) {
WmsProductVo product = productService.queryById(id);
if (product != null) {
productMap.put(id, product);
if (product.getBomId() != null) {
bomIds.add(product.getBomId());
}
}
}
}
// 批量查询BOM信息
Map<Long, List<WmsBomItemVo>> bomItemMap = new HashMap<>();
if (!bomIds.isEmpty()) {
for (Long bomId : bomIds) {
WmsBomItemBo bomItemBo = new WmsBomItemBo();
bomItemBo.setBomId(bomId);
List<WmsBomItemVo> bomItemList = bomItemService.queryList(bomItemBo);
if (bomItemList != null && !bomItemList.isEmpty()) {
bomItemMap.put(bomId, bomItemList);
}
}
}
// 填充到VO对象中
for (WmsMaterialCoilVo vo : voList) {
if (vo.getWarehouseId() != null && warehouseMap.containsKey(vo.getWarehouseId())) {
vo.setWarehouse(warehouseMap.get(vo.getWarehouseId()));
}
if (vo.getNextWarehouseId() != null && warehouseMap.containsKey(vo.getNextWarehouseId())) {
vo.setNextWarehouse(warehouseMap.get(vo.getNextWarehouseId()));
}
if (vo.getActualWarehouseId() != null && actualWarehouseMap.containsKey(vo.getActualWarehouseId())) {
WmsActualWarehouseVo actualWarehouse = actualWarehouseMap.get(vo.getActualWarehouseId());
vo.setActualWarehouseName(actualWarehouse.getActualWarehouseName());
}
if (vo.getQrcodeRecordId() != null && qrcodeMap.containsKey(vo.getQrcodeRecordId())) {
vo.setQrcodeRecord(qrcodeMap.get(vo.getQrcodeRecordId()));
}
if ("raw_material".equals(vo.getItemType()) && vo.getItemId() != null && rawMaterialMap.containsKey(vo.getItemId())) {
WmsRawMaterialVo rawMaterial = rawMaterialMap.get(vo.getItemId());
vo.setRawMaterial(rawMaterial);
if (rawMaterial.getBomId() != null && bomItemMap.containsKey(rawMaterial.getBomId())) {
vo.setBomItemList(bomItemMap.get(rawMaterial.getBomId()));
}
}
if ("product".equals(vo.getItemType()) && vo.getItemId() != null && productMap.containsKey(vo.getItemId())) {
WmsProductVo product = productMap.get(vo.getItemId());
vo.setProduct(product);
if (product.getBomId() != null && bomItemMap.containsKey(product.getBomId())) {
vo.setBomItemList(bomItemMap.get(product.getBomId()));
}
}
}
}
/**
* 填充关联对象信息(单个对象,保留用于兼容性)
*/
private void fillRelatedObjects(WmsMaterialCoilVo vo) {
// 查询所在库区信息
if (vo.getWarehouseId() != null) {
WmsWarehouseVo warehouse = warehouseService.queryById(vo.getWarehouseId());
vo.setWarehouse(warehouse);
}
// 查询下一库区信息
if (vo.getNextWarehouseId() != null) {
WmsWarehouseVo nextWarehouse = warehouseService.queryById(vo.getNextWarehouseId());
vo.setNextWarehouse(nextWarehouse);
}
// 查询实际库区信息
if (vo.getActualWarehouseId() != null) {
WmsActualWarehouseVo actualWarehouse = actualWarehouseService.queryById(vo.getActualWarehouseId());
if (actualWarehouse != null) {
vo.setActualWarehouseName(actualWarehouse.getActualWarehouseName());
}
}
// 查询二维码信息
if (vo.getQrcodeRecordId() != null) {
WmsGenerateRecordVo qrcodeRecord = generateRecordService.queryById(vo.getQrcodeRecordId());
vo.setQrcodeRecord(qrcodeRecord);
}
// 查询原材料信息当itemType为raw_material时
if ("raw_material".equals(vo.getItemType()) && vo.getItemId() != null) {
WmsRawMaterialVo rawMaterial = rawMaterialService.queryById(vo.getItemId());
vo.setRawMaterial(rawMaterial);
// 查询原材料对应的BOM信息通过bomId查询BomItem列表
if (rawMaterial != null && rawMaterial.getBomId() != null) {
WmsBomItemBo bomItemBo = new WmsBomItemBo();
bomItemBo.setBomId(rawMaterial.getBomId());
List<WmsBomItemVo> bomItemList = bomItemService.queryList(bomItemBo);
vo.setBomItemList(bomItemList);
}
}
// 查询产品信息当itemType为product时
if ("product".equals(vo.getItemType()) && vo.getItemId() != null) {
WmsProductVo product = productService.queryById(vo.getItemId());
vo.setProduct(product);
// 查询产品对应的BOM信息通过bomId查询BomItem列表
if (product != null && product.getBomId() != null) {
WmsBomItemBo bomItemBo = new WmsBomItemBo();
bomItemBo.setBomId(product.getBomId());
List<WmsBomItemVo> bomItemList = bomItemService.queryList(bomItemBo);
vo.setBomItemList(bomItemList);
}
}
}
/**
* 查询钢卷物料表列表
*/
@Override
public TableDataInfo<WmsMaterialCoilVo> queryPageList(WmsMaterialCoilBo bo, PageQuery pageQuery) {
QueryWrapper<WmsMaterialCoil> qw = buildQueryWrapperPlus(bo);
Page<WmsMaterialCoilVo> result = baseMapper.selectVoPagePlus(pageQuery.build(), qw);
List<WmsMaterialCoilVo> records = result.getRecords();
if (records == null || records.isEmpty()) {
return TableDataInfo.build(result);
}
Set<String> userNames = records.stream()
.flatMap(v -> java.util.stream.Stream.of(v.getCreateBy(), v.getUpdateBy(), v.getExportBy()))
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
Map<String, String> nickMap = Collections.emptyMap();
if (!userNames.isEmpty()) {
nickMap = userService.selectNickNameMapByUserNames(new ArrayList<>(userNames));
}
// 单次遍历:填充创建/更新/发货人昵称,并构建物料/产品对象
for (WmsMaterialCoilVo vo : records) {
if (!nickMap.isEmpty()) {
if (StringUtils.isNotBlank(vo.getCreateBy())) {
vo.setCreateByName(nickMap.getOrDefault(vo.getCreateBy(), vo.getCreateBy()));
}
if (StringUtils.isNotBlank(vo.getUpdateBy())) {
vo.setUpdateByName(nickMap.getOrDefault(vo.getUpdateBy(), vo.getUpdateBy()));
}
if (StringUtils.isNotBlank(vo.getExportBy())) {
vo.setExportByName(nickMap.getOrDefault(vo.getExportBy(), vo.getExportBy()));
}
}
// 从联查结果中构建产品和原材料对象(避免单独查询)
buildItemObjectFromJoin(vo);
}
return TableDataInfo.build(result);
}
private QueryWrapper<WmsMaterialCoil> buildQueryWrapperPlus(WmsMaterialCoilBo bo) {
QueryWrapper<WmsMaterialCoil> qw = Wrappers.query();
qw.like(StringUtils.isNotBlank(bo.getEnterCoilNo()), "mc.enter_coil_no", bo.getEnterCoilNo());
qw.like(StringUtils.isNotBlank(bo.getCurrentCoilNo()), "mc.current_coil_no", bo.getCurrentCoilNo());
qw.like(StringUtils.isNotBlank(bo.getSupplierCoilNo()), "mc.supplier_coil_no", bo.getSupplierCoilNo());
qw.eq(bo.getDataType() != null, "mc.data_type", bo.getDataType());
qw.eq(bo.getMaterialType() != null, "mc.material_type", bo.getMaterialType());
qw.eq(bo.getHasMergeSplit() != null, "mc.has_merge_split", bo.getHasMergeSplit());
qw.eq(bo.getStatus() != null, "mc.status", bo.getStatus());
qw.eq(StringUtils.isNotBlank(bo.getItemType()), "mc.item_type", bo.getItemType());
qw.eq(StringUtils.isNotBlank(bo.getCreateBy()), "mc.create_by", bo.getCreateBy());
qw.eq(StringUtils.isNotBlank(bo.getUpdateBy()), "mc.update_by", bo.getUpdateBy());
// 统一处理 warehouseId 与 warehouseIds
List<Long> warehouseIdList = new ArrayList<>();
if (bo.getWarehouseId() != null) {
warehouseIdList.add(bo.getWarehouseId());
}
if (StringUtils.isNotBlank(bo.getWarehouseIds())) {
String[] warehouseIdArray = bo.getWarehouseIds().split(",");
for (String warehouseIdStr : warehouseIdArray) {
if (StringUtils.isNotBlank(warehouseIdStr)) {
try {
warehouseIdList.add(Long.parseLong(warehouseIdStr.trim()));
} catch (NumberFormatException ignore) {
}
}
}
}
if (!warehouseIdList.isEmpty()) {
qw.in("mc.warehouse_id", warehouseIdList.stream().distinct().collect(Collectors.toList()));
}
// 新增长度
qw.eq(bo.getLength() != null, "mc.length", bo.getLength());
// 如果actualWarehouseId不为空则根据实际库区ID进行查询 如果为-1,则查询无库区的数据
if (bo.getActualWarehouseId() != null) {
if (bo.getActualWarehouseId() == -1) {
// 当actualWarehouseId为-1时查询actual_warehouse_id为空的记录无库区
qw.isNull("mc.actual_warehouse_id");
} else {
// 正常传值时,需要处理库位的层级关系
// 如果传入的是二级库位,需要查询其下所有的三级/四级库位
List<Long> warehouseIds = getWarehouseIdsIncludingChildren(bo.getActualWarehouseId());
if (warehouseIds.isEmpty()) {
// 如果没有找到任何库位则直接匹配该ID
qw.eq("mc.actual_warehouse_id", bo.getActualWarehouseId());
} else {
// 使用IN查询包含该库位及其所有子库位的钢卷
qw.in("mc.actual_warehouse_id", warehouseIds);
}
}
}
// 新增查询逻辑也就是当saleId未空时候
if (bo.getSaleId() != null) {
if (bo.getSaleId() == -1) {
// 当saleId为-1时查询sale_id为空的记录
qw.isNull("mc.sale_id");
} else if (bo.getSaleId() > 0) {
// 正常传值时依据saleId作为查询条件
qw.eq("mc.sale_id", bo.getSaleId());
}
}
// 仅查询废品:质量状态为 D+、D、D-
if (Boolean.TRUE.equals(bo.getOnlyScrap())) {
qw.in("mc.quality_status", java.util.Arrays.asList("C+", "C", "C-", "D+", "D", "D-"));
}
// 组合 item_id 条件:改为使用 EXISTS 子查询,替代预查询 + IN
boolean hasSelectType = StringUtils.isNotBlank(bo.getSelectType());
boolean hasAnyItemFilter = StringUtils.isNotBlank(bo.getItemMaterial())
|| StringUtils.isNotBlank(bo.getItemManufacturer())
|| StringUtils.isNotBlank(bo.getItemSurfaceTreatmentDesc())
|| StringUtils.isNotBlank(bo.getItemZincLayer())
|| StringUtils.isNotBlank(bo.getItemName())
|| StringUtils.isNotBlank(bo.getItemSpecification());
// 解析显式 itemIds 或单个 itemId
List<Long> explicitItemIds = null;
if (StringUtils.isNotBlank(bo.getItemIds())) {
String[] itemIdArray = bo.getItemIds().split(",");
List<Long> tmp = new ArrayList<>();
for (String itemIdStr : itemIdArray) {
if (StringUtils.isNotBlank(itemIdStr)) {
try {
tmp.add(Long.parseLong(itemIdStr.trim()));
} catch (NumberFormatException ignore) {}
}
}
if (!tmp.isEmpty()) {
explicitItemIds = tmp.stream().distinct().collect(Collectors.toList());
}
} else if (bo.getItemId() != null) {
explicitItemIds = Collections.singletonList(bo.getItemId());
}
// 使用 EXISTS 针对 selectType 的细粒度筛选(使用参数占位符防注入)
if (hasSelectType) {
qw.eq("mc.item_type", bo.getSelectType());
if (hasAnyItemFilter) {
StringBuilder existsSql = new StringBuilder();
List<Object> existsArgs = new ArrayList<>();
if ("product".equals(bo.getSelectType())) {
existsSql.append(" EXISTS (SELECT 1 FROM wms_product p WHERE p.del_flag = 0 AND p.product_id = mc.item_id");
String clause;
clause = buildOrLikeClause("p.product_name", bo.getItemName(), existsArgs);
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
clause = buildOrLikeClause("p.material", bo.getItemMaterial(), existsArgs);
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
clause = buildOrLikeClause("p.manufacturer", bo.getItemManufacturer(), existsArgs);
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
clause = buildOrLikeClause("p.surface_treatment_desc", bo.getItemSurfaceTreatmentDesc(), existsArgs);
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
clause = buildOrLikeClause("p.zinc_layer", bo.getItemZincLayer(), existsArgs);
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
clause = buildOrLikeClause("p.specification", bo.getItemSpecification(), existsArgs);
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
existsSql.append(")");
} else if ("raw_material".equals(bo.getSelectType())) {
existsSql.append(" EXISTS (SELECT 1 FROM wms_raw_material r WHERE r.del_flag = 0 AND r.raw_material_id = mc.item_id");
String clause;
clause = buildOrLikeClause("r.raw_material_name", bo.getItemName(), existsArgs);
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
clause = buildOrLikeClause("r.material", bo.getItemMaterial(), existsArgs);
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
clause = buildOrLikeClause("r.manufacturer", bo.getItemManufacturer(), existsArgs);
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
clause = buildOrLikeClause("r.surface_treatment_desc", bo.getItemSurfaceTreatmentDesc(), existsArgs);
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
clause = buildOrLikeClause("r.zinc_layer", bo.getItemZincLayer(), existsArgs);
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
clause = buildOrLikeClause("r.specification", bo.getItemSpecification(), existsArgs);
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
existsSql.append(")");
}
if (existsSql.length() > 0) {
qw.apply(existsSql.toString(), existsArgs.toArray());
}
}
}
// 显式 itemId 条件:与 EXISTS 共存时,语义为交集
if (CollectionUtils.isNotEmpty(explicitItemIds)) {
qw.isNotNull("mc.item_id");
qw.in("mc.item_id", explicitItemIds);
} else if (explicitItemIds != null && explicitItemIds.isEmpty()) {
qw.apply("1 = 0");
return qw;
}
// 添加coilIds查询条件支持逗号分隔的多个coilId查询
if (StringUtils.isNotBlank(bo.getCoilIds())) {
String[] coilIdArray = bo.getCoilIds().split(",");
List<Long> coilIdList = new ArrayList<>();
for (String coilIdStr : coilIdArray) {
if (StringUtils.isNotBlank(coilIdStr)) {
try {
coilIdList.add(Long.parseLong(coilIdStr.trim()));
} catch (NumberFormatException e) {
// 忽略无效的ID格式
}
}
}
if (!coilIdList.isEmpty()) {
qw.in("mc.coil_id", coilIdList);
}
}
// 仅查询未发货且未绑定在发货计划里的钢卷
if (Boolean.TRUE.equals(bo.getOnlyUnshippedAndUnplanned())) {
// 未发货:排除状态=1已出库/已发货)
qw.ne("mc.status", 1);
// 未绑定在任一有效发货计划计划未删除coil 字段包含当前 coil_id
// 这里使用 NOT EXISTS + FIND_IN_SET避免将所有计划加载到内存
qw.apply("NOT EXISTS (SELECT 1 FROM wms_delivery_plan dp " +
"WHERE dp.del_flag = 0 AND dp.coil IS NOT NULL AND dp.coil <> '' " +
"AND FIND_IN_SET(CAST(mc.coil_id AS CHAR), dp.coil))");
}
//逻辑删除
qw.eq("mc.del_flag", 0);
//把team字段作为筛选条件
qw.eq(StringUtils.isNotBlank(bo.getTeam()), "mc.team", bo.getTeam());
//根据开始时间和结束时间筛选修改时间
qw.ge(bo.getStartTime() != null, "mc.update_time", bo.getStartTime());
qw.le(bo.getEndTime() != null, "mc.update_time", bo.getEndTime());
qw.ge(bo.getByCreateTimeStart() != null, "mc.create_time", bo.getByCreateTimeStart());
qw.le(bo.getByCreateTimeEnd() != null, "mc.create_time", bo.getByCreateTimeEnd());
// 处理发货时间筛选逻辑(核心修改部分)
if (bo.getByExportTimeStart() != null || bo.getByExportTimeEnd() != null) {
// 开启OR条件分组满足情况1 或 情况2
qw.and(w -> {
// 情况1发货时间不为null且满足时间范围
w.nested(n -> {
n.isNotNull("mc.export_time");
if (bo.getByExportTimeStart() != null) {
n.ge("mc.export_time", bo.getByExportTimeStart());
}
if (bo.getByExportTimeEnd() != null) {
n.le("mc.export_time", bo.getByExportTimeEnd());
}
});
// 情况2状态为1且发货时间为null用更新时间匹配发货时间范围
w.or();
w.nested(n -> {
n.eq("mc.status", 1);
n.isNull("mc.export_time");
if (bo.getByExportTimeStart() != null) {
n.ge("mc.update_time", bo.getByExportTimeStart());
}
if (bo.getByExportTimeEnd() != null) {
n.le("mc.update_time", bo.getByExportTimeEnd());
}
});
});
}
//根据异常数量筛选(大于等于指定值)
if (bo.getMinAbnormalCount() != null) {
qw.apply("COALESCE(ca.abnormal_count, 0) >= {0}", bo.getMinAbnormalCount());
}
//根据创建时间倒叙
qw.orderByDesc("mc.create_time");
return qw;
}
@Override
public Map<String, Object> getDuplicateCoilGroups() {
LambdaQueryWrapper<WmsMaterialCoil> lqw = Wrappers.lambdaQuery();
lqw.eq(WmsMaterialCoil::getDataType, 1);
lqw.eq(WmsMaterialCoil::getDelFlag, 0);
List<WmsMaterialCoil> all = baseMapper.selectList(lqw);
Map<String, List<WmsMaterialCoil>> enterGrouped = all.stream()
.filter(e -> StringUtils.isNotBlank(e.getEnterCoilNo()))
.collect(Collectors.groupingBy(WmsMaterialCoil::getEnterCoilNo));
Map<String, List<WmsMaterialCoil>> currentGrouped = all.stream()
.filter(e -> StringUtils.isNotBlank(e.getCurrentCoilNo()))
.collect(Collectors.groupingBy(WmsMaterialCoil::getCurrentCoilNo));
List<Map<String, Object>> enterGroups = enterGrouped.entrySet().stream()
.filter(en -> en.getValue() != null && en.getValue().size() > 1)
.map(en -> {
List<WmsMaterialCoilVo> vos = en.getValue().stream().map(this::toVoBasic).collect(Collectors.toList());
Map<String, Object> m = new HashMap<>();
m.put("enterCoilNo", en.getKey());
m.put("coils", vos);
return m;
})
.collect(Collectors.toList());
List<Map<String, Object>> currentGroups = currentGrouped.entrySet().stream()
.filter(en -> en.getValue() != null && en.getValue().size() > 1)
.map(en -> {
List<WmsMaterialCoilVo> vos = en.getValue().stream().map(this::toVoBasic).collect(Collectors.toList());
Map<String, Object> m = new HashMap<>();
m.put("currentCoilNo", en.getKey());
m.put("coils", vos);
return m;
})
.collect(Collectors.toList());
// 可选:批量填充关联对象信息
List<WmsMaterialCoilVo> allVos = new ArrayList<>();
for (Map<String, Object> g : enterGroups) {
Object list = g.get("coils");
if (list instanceof List) {
allVos.addAll((List<WmsMaterialCoilVo>) list);
}
}
for (Map<String, Object> g : currentGroups) {
Object list = g.get("coils");
if (list instanceof List) {
allVos.addAll((List<WmsMaterialCoilVo>) list);
}
}
if (!allVos.isEmpty()) {
fillRelatedObjectsBatch(allVos);
}
Map<String, Object> result = new HashMap<>();
result.put("enterGroups", enterGroups);
result.put("currentGroups", currentGroups);
return result;
}
private WmsMaterialCoilVo toVoBasic(WmsMaterialCoil e) {
WmsMaterialCoilVo vo = new WmsMaterialCoilVo();
BeanUtils.copyProperties(e, vo);
return vo;
}
/**
* 构建 OR 连接的 LIKE 子句,使用 MyBatis-Plus apply 的 {index} 占位符并将参数加入 args。
* 例如column = "p.product_name", values = "A,B" -> 返回 "(p.product_name LIKE {0} OR p.product_name LIKE {1})"
* 同时往 args 追加 "%A%", "%B%"。
*/
private String buildOrLikeClause(String column, String csvValues, List<Object> args) {
if (StringUtils.isBlank(csvValues)) {
return "";
}
String[] vals = csvValues.split(",");
List<String> parts = new ArrayList<>();
for (String raw : vals) {
if (raw == null) continue;
String v = raw.trim();
if (v.isEmpty()) continue;
int idx = args.size();
parts.add(column + " LIKE {" + idx + "}");
args.add('%' + v + '%');
}
if (parts.isEmpty()) {
return "";
}
return '(' + String.join(" OR ", parts) + ')';
}
/**
* 获取库位及其所有子库位的ID列表
* 逻辑参考 WmsActualWarehouseServiceImpl.queryList 中的拆分处理逻辑:
* 1. 如果是一级/三级/四级库位直接返回该ID
* 2. 如果是二级库位:
* 查询其下的子库位(通常是四级库位)
*
* @param actualWarehouseId 库位ID
* @return 包含该库位及其所有子库位的ID列表
*/
private List<Long> getWarehouseIdsIncludingChildren(Long actualWarehouseId) {
if (actualWarehouseId == null || actualWarehouseId <= 0) {
return new ArrayList<>();
}
// 第一步:查询当前库位的基础信息,判断类型
WmsActualWarehouseVo warehouse = actualWarehouseService.queryById(actualWarehouseId);
if (warehouse == null) {
return Collections.singletonList(actualWarehouseId);
}
Long warehouseType = warehouse.getActualWarehouseType();
// 当为一级库位:查询其所有二级子节点,然后对每个二级节点调用 queryList 获取有效的最小层级库位(三级未拆分或四级)
if (warehouseType != null && warehouseType == 1L) {
WmsActualWarehouseBo level2Bo = new WmsActualWarehouseBo();
level2Bo.setParentId(actualWarehouseId);
List<WmsActualWarehouseVo> level2List = actualWarehouseService.queryList(level2Bo);
if (level2List == null || level2List.isEmpty()) {
return new ArrayList<>();
}
List<Long> resultIds = new ArrayList<>();
for (WmsActualWarehouseVo level2 : level2List) {
if (level2 == null || level2.getActualWarehouseId() == null) {
continue;
}
WmsActualWarehouseBo childBo = new WmsActualWarehouseBo();
childBo.setParentId(level2.getActualWarehouseId());
List<WmsActualWarehouseVo> children = actualWarehouseService.queryList(childBo);
if (children != null && !children.isEmpty()) {
for (WmsActualWarehouseVo ch : children) {
Long id = ch.getActualWarehouseId();
if (id != null) {
resultIds.add(id);
}
}
}
}
return resultIds;
}
// 三级/四级库位直接返回自身ID无下级
if (warehouseType != null && (warehouseType == 3L || warehouseType == 4L)) {
return Collections.singletonList(actualWarehouseId);
}
// 二级库位:复用 queryList 已实现的“子库位+拆分替换”逻辑
if (warehouseType != null && warehouseType == 2L) {
WmsActualWarehouseBo bo = new WmsActualWarehouseBo();
bo.setParentId(actualWarehouseId);
// 这里 queryList 已经处理了:拆分库位替换为子库位、返回有效子库位列表
List<WmsActualWarehouseVo> children = actualWarehouseService.queryList(bo);
// 提取子库位ID即可
return children.stream()
.map(WmsActualWarehouseVo::getActualWarehouseId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
// 一级库位或其他类型返回自身ID
return Collections.singletonList(actualWarehouseId);
}
/**
* 校验实际库位是否可用于分配给钢卷:
* - 若库位被占用isEnabled=0则禁止除非本次操作是继续使用同一已占用库位的场景可通过 ignoreOccupiedId 放行占用校验)。
* - 若为三级库位且已拆分splitStatus=1禁止直接在三级库位上新增/修改。
* - 若为四级库位且其父级三级库位未拆分splitStatus!=1禁止使用该四级库位。
*/
private void validateActualWarehouseForAssign(Long actualWarehouseId, Long ignoreOccupiedId) {
if (actualWarehouseId == null || actualWarehouseId.equals(-1L)) {
return;
}
WmsActualWarehouseVo aw = actualWarehouseService.queryById(actualWarehouseId);
if (aw == null) {
throw new RuntimeException("实际库区不存在");
}
// 占用校验isEnabled=0 视为已被占用)
if (aw.getIsEnabled() != null && aw.getIsEnabled() == 0
&& (ignoreOccupiedId == null || !actualWarehouseId.equals(ignoreOccupiedId))) {
throw new RuntimeException("实际库区已被占用,请选择其他库区");
}
Long type = aw.getActualWarehouseType();
Integer splitStatus = aw.getSplitStatus();
// 三级库位且已拆分,禁止直接在三级层级使用
if (type != null && type == 3L && splitStatus != null && splitStatus == 1) {
throw new RuntimeException("该库位已被拆分,刷新后重试");
}
// 四级库位:父级必须是已拆分的三级库位
if (type != null && type == 4L) {
Long parentId = aw.getParentId();
if (parentId != null) {
WmsActualWarehouseVo parent = actualWarehouseService.queryById(parentId);
if (parent != null) {
Integer pSplit = parent.getSplitStatus();
Long pType = parent.getActualWarehouseType();
if (pType != null && pType == 3L && (pSplit == null || pSplit != 1)) {
throw new RuntimeException("该库位已被合并,刷新后重试");
}
}
}
}
}
/**
* 查询钢卷物料表列表
*/
@Override
public List<WmsMaterialCoilVo> queryList(WmsMaterialCoilBo bo) {
QueryWrapper<WmsMaterialCoil> lqw = buildQueryWrapperPlus(bo);
List<WmsMaterialCoilVo> list = baseMapper.selectVoListWithDynamicJoin(lqw);
// 从联查结果中构建产品和原材料对象(避免单独查询)
for (WmsMaterialCoilVo vo : list) {
buildItemObjectFromJoin(vo);
}
return list;
}
/**
* 从联查结果中构建物品对象(产品或原材料)
* 直接从VO的临时字段中获取数据构建对象避免单独查询数据库
*/
private void buildItemObjectFromJoin(WmsMaterialCoilVo vo) {
if (vo.getItemId() == null || vo.getItemType() == null) {
return;
}
// 构建原材料对象
if ("raw_material".equals(vo.getItemType())) {
WmsRawMaterialVo rawMaterial = new WmsRawMaterialVo();
rawMaterial.setRawMaterialId(vo.getItemId());
rawMaterial.setRawMaterialCode(vo.getItemCode());
rawMaterial.setRawMaterialName(vo.getItemName());
rawMaterial.setSpecification(vo.getSpecification());
rawMaterial.setMaterial(vo.getMaterial());
rawMaterial.setManufacturer(vo.getManufacturer());
rawMaterial.setSurfaceTreatmentDesc(vo.getSurfaceTreatmentDesc());
rawMaterial.setZincLayer(vo.getZincLayer());
vo.setRawMaterial(rawMaterial);
}
// 构建产品对象
if ("product".equals(vo.getItemType())) {
WmsProductVo product = new WmsProductVo();
product.setProductId(vo.getItemId());
product.setProductCode(vo.getItemCode());
product.setProductName(vo.getItemName());
product.setSpecification(vo.getSpecification());
product.setMaterial(vo.getMaterial());
product.setManufacturer(vo.getManufacturer());
product.setSurfaceTreatmentDesc(vo.getSurfaceTreatmentDesc());
product.setZincLayer(vo.getZincLayer());
vo.setProduct(product);
}
}
/**
* 新增钢卷物料表
*/
@Override
@Transactional(rollbackFor = Exception.class)
public WmsMaterialCoilVo insertByBo(WmsMaterialCoilBo bo) {
// 1. 生成二维码
Long qrcodeRecordId = generateQrcodeForInsert(bo);
bo.setQrcodeRecordId(qrcodeRecordId);
// 2. 查找或创建stock
findOrCreateStock(bo);
// 3. 插入钢卷数据
WmsMaterialCoil add = BeanUtil.toBean(bo, WmsMaterialCoil.class);
if(bo.getDataType() != null && bo.getDataType() == 10){
add.setDataType(10);
}else {
add.setDataType(1); // 新增的钢卷默认为当前数据
}
// 实际库位校验:占用与拆分/层级规则
if (bo.getActualWarehouseId() != null) {
validateActualWarehouseForAssign(bo.getActualWarehouseId(), null);
}
validEntityBeforeSave(add);
// 在新增钢卷数据之前需要先看库区id是否被占用如果被占用则不能新增抛异常为实际库区已被占用 不能再当前库区修改或者新增
int rows = baseMapper.insert(add);
if (rows <= 0) {
throw new RuntimeException("新增钢卷失败");
}
// 设置返回用的ID并更新二维码内容中的coilId
bo.setCoilId(add.getCoilId());
updateQrcodeCoilId(qrcodeRecordId, add.getCoilId());
// 如果提供了actualWarehouseId则更新对应的实际库区为禁用状态
if (bo.getActualWarehouseId() != null) {
WmsActualWarehouseBo actualWarehouseBo = new WmsActualWarehouseBo();
actualWarehouseBo.setActualWarehouseId(bo.getActualWarehouseId());
actualWarehouseBo.setIsEnabled(0); // 设置为禁用状态
actualWarehouseService.updateByBo(actualWarehouseBo);
}
// 4. 返回完整的 VO包含关联对象
return queryById(add.getCoilId());
}
/**
* 生成二维码(新增)
*/
private Long generateQrcodeForInsert(WmsMaterialCoilBo bo) {
try {
Map<String, Object> contentMap = new HashMap<>();
String currentCoilNo = bo.getCurrentCoilNo() != null ? bo.getCurrentCoilNo() : bo.getEnterCoilNo();
contentMap.put("enter_coil_no", bo.getEnterCoilNo()); // 入场钢卷号(唯一不变)
contentMap.put("current_coil_no", currentCoilNo); // 当前钢卷号(可变)
contentMap.put("coil_id", "null"); // 初始钢卷ID新增时暂时为null插入后更新
contentMap.put("current_coil_id", "null"); // 当前有效的钢卷ID新增时暂时为null插入后更新
// 创建steps数组
List<Map<String, Object>> steps = new ArrayList<>();
Map<String, Object> step1 = new HashMap<>();
step1.put("step", 1);
step1.put("action", "新增");
step1.put("current_coil_no", currentCoilNo);
// 判断是合卷还是分卷
if (bo.getHasMergeSplit() != null && bo.getHasMergeSplit() == 2) {
// 合卷:父编号字符串用逗号分隔
step1.put("operation", "合卷");
step1.put("parent_coil_nos", bo.getParentCoilNos());
} else if (bo.getHasMergeSplit() != null && bo.getHasMergeSplit() == 1) {
// 分卷:多个当前钢卷号用逗号分隔
step1.put("operation", "分卷");
step1.put("current_coil_nos", currentCoilNo);
} else {
// 默认:当前钢卷号
step1.put("operation", "新增");
}
steps.add(step1);
contentMap.put("steps", steps);
ObjectMapper objectMapper = new ObjectMapper();
String contentJson = objectMapper.writeValueAsString(contentMap);
WmsGenerateRecordBo recordBo = new WmsGenerateRecordBo();
recordBo.setContent(contentJson);
recordBo.setSerialNumber(bo.getEnterCoilNo()); // 使用入场钢卷号作为编号
recordBo.setQrcodeType(0L);
recordBo.setIsEnabled(0L);
recordBo.setSize(200L);
recordBo.setStatus(1); // 1=当前有效码
WmsGenerateRecordVo record = generateRecordService.insertByBo(recordBo);
return record.getRecordId();
} catch (Exception e) {
throw new RuntimeException("生成二维码失败: " + e.getMessage());
}
}
/**
* 查找或创建stock
*/
private void findOrCreateStock(WmsMaterialCoilBo bo) {
if (bo.getItemType() == null || bo.getItemId() == null) {
throw new RuntimeException("物品类型和物品ID不能为空");
}
// 查询是否存在相同的stock匹配itemType和itemId
WmsStockBo stockBo = new WmsStockBo();
stockBo.setItemType(bo.getItemType());
stockBo.setItemId(bo.getItemId());
List<WmsStockVo> stockList = stockService.queryList(stockBo);
if (stockList.isEmpty()) {
// 如果没有找到匹配的stock新增一条stock记录
WmsStockBo newStockBo = new WmsStockBo();
newStockBo.setItemType(bo.getItemType());
newStockBo.setItemId(bo.getItemId());
// 调用stockService新增stock
Boolean insertResult = stockService.insertByBo(newStockBo);
if (!insertResult) {
throw new RuntimeException("新增库存记录失败");
}
}
// 如果已存在stock记录则不需要重复创建
}
/**
* 修改钢卷物料表
* 如果newCoils不为空则进行批量更新分卷/合卷)
* 如果newCoils为空则进行单个更新
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(WmsMaterialCoilBo bo) {
// 判断是否批量更新
if (bo.getNewCoils() != null && !bo.getNewCoils().isEmpty()) {
// 批量更新逻辑(分卷/合卷)
return updateByBatch(bo);
} else {
// 单个更新逻辑需要coilId
if (bo.getCoilId() == null) {
throw new RuntimeException("钢卷ID不能为空");
}
return updateBySingle(bo);
}
}
/**
* 简单更新钢卷物料表
* 直接更新属性内容,不进行历史记录处理
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateSimple(WmsMaterialCoilBo bo) {
if (bo.getCoilId() == null) {
throw new RuntimeException("钢卷ID不能为空");
}
// 查询原钢卷是否存在
WmsMaterialCoil oldCoil = baseMapper.selectById(bo.getCoilId());
if (oldCoil == null) {
throw new RuntimeException("钢卷不存在");
}
// 若修改实际库位,先进行校验
if (bo.getActualWarehouseId() != null) {
// 若与原库位不同,常规占用校验;若相同,仅校验拆分/层级
Long ignoreOccupiedId = Objects.equals(bo.getActualWarehouseId(), oldCoil.getActualWarehouseId())
? bo.getActualWarehouseId() : null;
validateActualWarehouseForAssign(bo.getActualWarehouseId(), ignoreOccupiedId);
}
// 直接更新钢卷属性
WmsMaterialCoil updateCoil = BeanUtil.toBean(bo, WmsMaterialCoil.class);
validEntityBeforeSave(updateCoil);
// 使用MyBatis-Plus的updateById方法直接更新
boolean flag = baseMapper.updateById(updateCoil) > 0;
// 特殊处理saleId字段确保null值也能被正确更新
if (bo.getSaleId() == null) {
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(WmsMaterialCoil::getCoilId, bo.getCoilId());
updateWrapper.set(WmsMaterialCoil::getSaleId, (Long)null);
baseMapper.update(null, updateWrapper);
}
// 如果实际库区id为-1或状态为1则清空钢卷上的实际库区绑定
if ((bo.getActualWarehouseId() != null && bo.getActualWarehouseId().equals(-1L))
|| (bo.getStatus() != null && bo.getStatus().equals(1))) {
clearActualWarehouseBinding(oldCoil.getActualWarehouseId(), bo.getCoilId());
}
// 更新实际库区的启用状态
if (flag) {
// 只有当新的库区ID不为空且与原库区ID不同时才更新库区状态
if (bo.getActualWarehouseId() != null && !bo.getActualWarehouseId().equals(oldCoil.getActualWarehouseId())) {
updateActualWarehouseEnableStatus(oldCoil.getActualWarehouseId(), bo.getActualWarehouseId());
}
}
return flag;
}
/**
* 单个更新
*/
private Boolean updateBySingle(WmsMaterialCoilBo bo) {
// 查询原钢卷
WmsMaterialCoil oldCoil = baseMapper.selectById(bo.getCoilId());
if (oldCoil == null) {
throw new RuntimeException("原钢卷不存在");
}
// 若修改实际库位,先进行校验
if (bo.getActualWarehouseId() != null) {
Long ignoreOccupiedId = Objects.equals(bo.getActualWarehouseId(), oldCoil.getActualWarehouseId())
? bo.getActualWarehouseId() : null;
validateActualWarehouseForAssign(bo.getActualWarehouseId(), ignoreOccupiedId);
}
// 1. 将原钢卷标记为历史数据dataType = 0
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(WmsMaterialCoil::getCoilId, oldCoil.getCoilId())
.set(WmsMaterialCoil::getDataType, 0); // 设置为历史数据
baseMapper.update(null, updateWrapper);
// 2. 创建新记录
WmsMaterialCoil newCoil = BeanUtil.toBean(bo, WmsMaterialCoil.class);
newCoil.setCoilId(null); // 清空ID让数据库自动生成新ID
newCoil.setDataType(1); // 设置为当前数据
newCoil.setQrcodeRecordId(oldCoil.getQrcodeRecordId()); // 继续使用原二维码
// 如果实际库区id为-1则清空钢卷上的实际库区绑定
if (bo.getActualWarehouseId() != null && bo.getActualWarehouseId().equals(-1L)) {
clearActualWarehouseBinding(oldCoil.getActualWarehouseId(), bo.getCoilId());
}
// 继承原记录的关键字段
if (newCoil.getEnterCoilNo() == null) {
newCoil.setEnterCoilNo(oldCoil.getEnterCoilNo());
}
if (newCoil.getSupplierCoilNo() == null) {
newCoil.setSupplierCoilNo(oldCoil.getSupplierCoilNo());
}
validEntityBeforeSave(newCoil);
// 插入新记录
boolean flag = baseMapper.insert(newCoil) > 0;
if (flag) {
// 3. 更新二维码内容添加更新步骤并更新current_coil_id
if (oldCoil.getQrcodeRecordId() != null) {
updateQrcodeContentForNormalUpdate(oldCoil, bo, newCoil.getCoilId());
}
// 只有当新的库区ID不为空且与原库区ID不同时才更新库区状态
if (bo.getActualWarehouseId() != null && !bo.getActualWarehouseId().equals(oldCoil.getActualWarehouseId())) {
updateActualWarehouseEnableStatus(oldCoil.getActualWarehouseId(), bo.getActualWarehouseId());
}
}
return flag;
}
/**
* 更新实际库区的启用状态
* @param oldActualWarehouseId 原来的实际库区ID
* @param newActualWarehouseId 新的实际库区ID
*/
private void updateActualWarehouseEnableStatus(Long oldActualWarehouseId, Long newActualWarehouseId) {
// 如果实际库区没有变化,则无需更新
if (Objects.equals(oldActualWarehouseId, newActualWarehouseId)) {
return;
}
// 启用原来的库区
if (oldActualWarehouseId != null) {
WmsActualWarehouseBo oldWarehouseBo = new WmsActualWarehouseBo();
oldWarehouseBo.setActualWarehouseId(oldActualWarehouseId);
oldWarehouseBo.setIsEnabled(1); // 设置为启用状态
actualWarehouseService.updateByBo(oldWarehouseBo);
}
// 禁用新的库区
if (newActualWarehouseId != null) {
WmsActualWarehouseBo newWarehouseBo = new WmsActualWarehouseBo();
newWarehouseBo.setActualWarehouseId(newActualWarehouseId);
newWarehouseBo.setIsEnabled(0); // 设置为禁用状态
actualWarehouseService.updateByBo(newWarehouseBo);
}
}
/**
* 清空钢卷上的实际库区绑定
* @param actualWarehouseId 实际库区ID
* @param coilId 钢卷ID
*/
private void clearActualWarehouseBinding(Long actualWarehouseId, Long coilId) {
// 启用原来的库区
if (actualWarehouseId != null) {
WmsActualWarehouseBo warehouseBo = new WmsActualWarehouseBo();
warehouseBo.setActualWarehouseId(actualWarehouseId);
warehouseBo.setIsEnabled(1); // 设置为启用状态
actualWarehouseService.updateByBo(warehouseBo);
}
// 清空钢卷上的实际库区绑定
LambdaUpdateWrapper<WmsMaterialCoil> clearAwWrapper = new LambdaUpdateWrapper<>();
clearAwWrapper.eq(WmsMaterialCoil::getCoilId, coilId);
clearAwWrapper.set(WmsMaterialCoil::getActualWarehouseId, (Long) null);
baseMapper.update(null, clearAwWrapper);
}
/**
* 生成二维码(更新时库区变化)
*/
private Long generateQrcodeForUpdate(WmsMaterialCoil oldCoil, WmsMaterialCoilBo bo) {
try {
// 1. 将原二维码标记为历史码status = 0
if (oldCoil.getQrcodeRecordId() != null) {
WmsGenerateRecordBo oldQrBo = new WmsGenerateRecordBo();
oldQrBo.setRecordId(oldCoil.getQrcodeRecordId());
oldQrBo.setStatus(0); // 0=历史码
generateRecordService.updateByBo(oldQrBo);
}
Map<String, Object> contentMap = new HashMap<>();
String currentCoilNo = bo.getCurrentCoilNo() != null ? bo.getCurrentCoilNo() : oldCoil.getCurrentCoilNo();
contentMap.put("enter_coil_no", oldCoil.getEnterCoilNo()); // 入场钢卷号(始终不变)
contentMap.put("current_coil_no", currentCoilNo); // 当前钢卷号
contentMap.put("coil_id", String.valueOf(oldCoil.getCoilId())); // 初始钢卷ID记录最初的ID
contentMap.put("current_coil_id", "null"); // 当前钢卷ID更新时暂时为null插入后更新
// 复制原钢卷的历史steps
List<Map<String, Object>> steps = new ArrayList<>();
if (oldCoil.getQrcodeRecordId() != null) {
WmsGenerateRecordVo oldRecord = generateRecordService.queryById(oldCoil.getQrcodeRecordId());
if (oldRecord != null) {
ObjectMapper objectMapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> oldContentMap = objectMapper.readValue(oldRecord.getContent(), Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> oldSteps = (List<Map<String, Object>>) oldContentMap.get("steps");
if (oldSteps != null) {
steps.addAll(oldSteps);
}
}
}
// 添加更新步骤(库区变化)
Map<String, Object> updateStep = new HashMap<>();
updateStep.put("step", steps.size() + 1);
updateStep.put("action", "更新");
updateStep.put("operation", "库区变更");
updateStep.put("old_warehouse_id", String.valueOf(oldCoil.getWarehouseId()));
updateStep.put("new_warehouse_id", String.valueOf(bo.getWarehouseId()));
//放入真实库区
updateStep.put("old_actual_warehouse_id", String.valueOf(oldCoil.getActualWarehouseId()));
updateStep.put("new_actual_warehouse_id", String.valueOf(bo.getActualWarehouseId()));
updateStep.put("old_coil_id", String.valueOf(oldCoil.getCoilId()));
updateStep.put("current_coil_no", currentCoilNo);
updateStep.put("operator", LoginHelper.getUsername()); // 操作者
steps.add(updateStep);
contentMap.put("steps", steps);
ObjectMapper objectMapper = new ObjectMapper();
String contentJson = objectMapper.writeValueAsString(contentMap);
// 2. 生成新的二维码status = 1
WmsGenerateRecordBo recordBo = new WmsGenerateRecordBo();
recordBo.setContent(contentJson);
recordBo.setSerialNumber(oldCoil.getEnterCoilNo() + "-W" + bo.getWarehouseId()); // 使用入场钢卷号+库区ID作为编号
recordBo.setQrcodeType(0L);
recordBo.setIsEnabled(0L);
recordBo.setSize(200L);
recordBo.setStatus(1); // 1=当前有效码
WmsGenerateRecordVo record = generateRecordService.insertByBo(recordBo);
return record.getRecordId();
} catch (Exception e) {
throw new RuntimeException("生成更新二维码失败: " + e.getMessage());
}
}
/**
* 批量更新(分卷/合卷)
*/
private Boolean updateByBatch(WmsMaterialCoilBo bo) {
// 查询原钢卷(分卷时需要,合卷时可能不需要)
WmsMaterialCoil oldCoil = null;
if (bo.getCoilId() != null) {
oldCoil = baseMapper.selectById(bo.getCoilId());
if (oldCoil == null) {
throw new RuntimeException("原钢卷不存在");
}
}
// oldCoil 如果是历史卷也就是date_type=0 的时候就不能分卷
if (oldCoil != null && oldCoil.getDataType() == 0) {
throw new RuntimeException("原钢卷已分卷,不能再分卷");
}
// 判断是分卷还是合卷
boolean isSplit = false;
boolean isMerge = false;
// 检查bo本身是否为合卷
if (bo.getHasMergeSplit() != null && bo.getHasMergeSplit() == 2) {
isMerge = true;
} else if (bo.getNewCoils() != null && !bo.getNewCoils().isEmpty()) {
// 检查newCoils中是否有分卷
for (WmsMaterialCoilBo newCoilBo : bo.getNewCoils()) {
if (newCoilBo.getHasMergeSplit() != null && newCoilBo.getHasMergeSplit() == 1) {
isSplit = true;
break;
}
}
}
// 1. 将原数据更新为历史数据data_type=0
// 注意合卷时bo的coilId可能为空因为bo是合卷后的新钢卷
if (bo.getCoilId() != null) {
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(WmsMaterialCoil::getCoilId, bo.getCoilId())
.set(WmsMaterialCoil::getDataType, 0); // 设置为历史数据
baseMapper.update(null, updateWrapper);
}
// 2. 插入多条新的当前数据data_type=1
List<WmsMaterialCoil> newCoils = new ArrayList<>();
List<String> allNewCoilNos = new ArrayList<>();
// 收集所有新钢卷号
for (WmsMaterialCoilBo newCoilBo : bo.getNewCoils()) {
allNewCoilNos.add(newCoilBo.getCurrentCoilNo());
}
if (isSplit) {
// 分卷将bo作为被分卷的原始对象newCoils中的对象作为分卷后产生的新钢卷
if (oldCoil == null) {
throw new RuntimeException("分卷操作需要原钢卷信息");
}
// 1. 将原始钢卷的二维码标记为失效status=0
if (oldCoil.getQrcodeRecordId() != null) {
WmsGenerateRecordBo oldQrBo = new WmsGenerateRecordBo();
oldQrBo.setRecordId(oldCoil.getQrcodeRecordId());
oldQrBo.setStatus(0); // 0=失效
generateRecordService.updateByBo(oldQrBo);
}
// 2. 将原始钢卷标记为历史数据,并记录所有子卷号
// 在母卷的 parent_coil_nos 字段中记录所有子卷号(用逗号分隔)
String childCoilNos = String.join(",", allNewCoilNos);
LambdaUpdateWrapper<WmsMaterialCoil> motherUpdateWrapper = new LambdaUpdateWrapper<>();
motherUpdateWrapper.eq(WmsMaterialCoil::getCoilId, oldCoil.getCoilId())
.set(WmsMaterialCoil::getParentCoilNos, childCoilNos); // 记录子卷号
baseMapper.update(null, motherUpdateWrapper);
// 3. 为每个分卷后的子钢卷生成独立的二维码并插入数据库
for (WmsMaterialCoilBo newCoilBo : bo.getNewCoils()) {
// 校验每个子卷的实际库位
if (newCoilBo.getActualWarehouseId() != null) {
Long ignoreOccupiedId = (oldCoil != null && Objects.equals(newCoilBo.getActualWarehouseId(), oldCoil.getActualWarehouseId()))
? newCoilBo.getActualWarehouseId() : null;
validateActualWarehouseForAssign(newCoilBo.getActualWarehouseId(), ignoreOccupiedId);
}
WmsMaterialCoil newCoil = BeanUtil.toBean(newCoilBo, WmsMaterialCoil.class);
newCoil.setCoilId(null);
newCoil.setDataType(1);
// 继承原钢卷的基本信息(强制继承,不能修改的字段)
newCoil.setEnterCoilNo(oldCoil.getEnterCoilNo());
newCoil.setSupplierCoilNo(oldCoil.getSupplierCoilNo()); // 保留厂家原料卷号
// materialType, itemType 和 itemId 使用前端传递的值,不强制继承
if (newCoil.getItemType() == null) {
newCoil.setItemType(oldCoil.getItemType());
}
if (newCoil.getItemId() == null) {
newCoil.setItemId(oldCoil.getItemId());
}
// 如果前端没传team使用原钢卷的team
if (newCoil.getTeam() == null) {
newCoil.setTeam(oldCoil.getTeam());
}
// 如果没有指定库区,使用原库区
if (newCoil.getWarehouseId() == null) {
newCoil.setWarehouseId(oldCoil.getWarehouseId());
}
// 在子卷的 parent_coil_nos 字段中记录母卷号
newCoil.setParentCoilNos(oldCoil.getCurrentCoilNo());
// 为每个子钢卷生成独立二维码
Long newQrcodeId = generateQrcodeForSplit(oldCoil, newCoilBo, allNewCoilNos);
newCoil.setQrcodeRecordId(newQrcodeId);
validEntityBeforeSave(newCoil);
baseMapper.insert(newCoil);
newCoils.add(newCoil);
// 更新二维码内容中的coilId
updateQrcodeCoilId(newQrcodeId, newCoil.getCoilId());
// 更新实际库区的启用状态
updateActualWarehouseEnableStatus(oldCoil.getActualWarehouseId(), newCoilBo.getActualWarehouseId());
}
} else if (isMerge) {
// 合卷将bo作为合卷后的新钢卷newCoils中的对象作为参与合卷的原始钢卷
// 1. 将参与合卷的原始钢卷的二维码标记为失效,并将钢卷标记为历史数据
//在合卷之前需要判断前端传来的bo.getNewCoils()中的所有原始钢卷的coilId是否已经是历史卷
validateOriginalCoilsForMerge(bo.getNewCoils());
for (WmsMaterialCoilBo originalCoilBo : bo.getNewCoils()) {
if (originalCoilBo.getCoilId() != null) {
WmsMaterialCoil originalCoil = baseMapper.selectById(originalCoilBo.getCoilId());
if (originalCoil != null) {
// 标记二维码为失效
if (originalCoil.getQrcodeRecordId() != null) {
WmsGenerateRecordBo oldQrBo = new WmsGenerateRecordBo();
oldQrBo.setRecordId(originalCoil.getQrcodeRecordId());
oldQrBo.setStatus(0); // 0=失效
generateRecordService.updateByBo(oldQrBo);
}
// 标记钢卷为历史数据
LambdaUpdateWrapper<WmsMaterialCoil> originalUpdateWrapper = new LambdaUpdateWrapper<>();
originalUpdateWrapper.eq(WmsMaterialCoil::getCoilId, originalCoilBo.getCoilId())
.set(WmsMaterialCoil::getDataType, 0); // 设置为历史数据
baseMapper.update(null, originalUpdateWrapper);
// 启用原始钢卷的实际库区
if (originalCoil.getActualWarehouseId() != null) {
WmsActualWarehouseBo disableWarehouseBo = new WmsActualWarehouseBo();
disableWarehouseBo.setActualWarehouseId(originalCoil.getActualWarehouseId());
disableWarehouseBo.setIsEnabled(1); // 设置为启用状态
actualWarehouseService.updateByBo(disableWarehouseBo);
}
}
}
}
// 2. 生成合卷后的新钢卷二维码
Long mergedQrcodeId = generateQrcodeForMerge(bo, bo.getNewCoils());
// 3. 插入合卷后的新钢卷
// 合卷结果若指定了实际库位,先进行校验
if (bo.getActualWarehouseId() != null) {
validateActualWarehouseForAssign(bo.getActualWarehouseId(), null);
}
WmsMaterialCoil newCoil = BeanUtil.toBean(bo, WmsMaterialCoil.class);
newCoil.setCoilId(null);
newCoil.setDataType(1);
// 从第一个参与合卷的原始钢卷获取基本信息
if (!bo.getNewCoils().isEmpty()) {
WmsMaterialCoil firstOriginalCoil = baseMapper.selectById(bo.getNewCoils().get(0).getCoilId());
if (firstOriginalCoil != null) {
// 继承基本信息
if (newCoil.getEnterCoilNo() == null) {
newCoil.setEnterCoilNo(firstOriginalCoil.getEnterCoilNo());
}
if (newCoil.getSupplierCoilNo() == null) {
newCoil.setSupplierCoilNo(firstOriginalCoil.getSupplierCoilNo()); // 保留厂家原料卷号
}
if (newCoil.getItemType() == null) {
newCoil.setItemType(firstOriginalCoil.getItemType());
}
if (newCoil.getItemId() == null) {
newCoil.setItemId(firstOriginalCoil.getItemId());
}
if (newCoil.getTeam() == null) {
newCoil.setTeam(firstOriginalCoil.getTeam());
}
}
}
newCoil.setQrcodeRecordId(mergedQrcodeId);
validEntityBeforeSave(newCoil);
baseMapper.insert(newCoil);
newCoils.add(newCoil);
// 更新二维码内容中的coilId
updateQrcodeCoilId(mergedQrcodeId, newCoil.getCoilId());
// 禁用新钢卷的实际库区
if (bo.getActualWarehouseId() != null) {
WmsActualWarehouseBo enableWarehouseBo = new WmsActualWarehouseBo();
enableWarehouseBo.setActualWarehouseId(bo.getActualWarehouseId());
enableWarehouseBo.setIsEnabled(0); // 设置为禁用状态
actualWarehouseService.updateByBo(enableWarehouseBo);
}
}
return true;
}
/**
* 验证参与合卷的原始钢卷是否都是当前数据(而非历史数据)
* @param originalCoils 参与合卷的原始钢卷列表
*/
private void validateOriginalCoilsForMerge(List<WmsMaterialCoilBo> originalCoils) {
if (CollectionUtils.isEmpty(originalCoils)) {
return;
}
// 收集所有需要验证的coilId
List<Long> coilIds = originalCoils.stream()
.map(WmsMaterialCoilBo::getCoilId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(coilIds)) {
return;
}
// 批量查询钢卷数据避免N+1查询问题
List<WmsMaterialCoil> originalCoilsData = baseMapper.selectBatchIds(coilIds);
// 检查是否有历史数据
List<String> historyCoils = originalCoilsData.stream()
.filter(coil -> coil.getDataType() != null && coil.getDataType() == 0) // dataType=0表示历史数据
.map(coil -> ": " + coil.getCurrentCoilNo())
.collect(Collectors.toList());
if (!historyCoils.isEmpty()) {
String errorDetails = String.join(", ", historyCoils);
throw new RuntimeException("合卷操作需要所有原始钢卷为当前数据,以下卷号已被合卷" + errorDetails);
}
}
/**
* 为分卷生成新二维码(每个子钢卷一个)
*/
private Long generateQrcodeForSplit(WmsMaterialCoil oldCoil, WmsMaterialCoilBo newCoilBo, List<String> allNewCoilNos) {
try {
Map<String, Object> contentMap = new HashMap<>();
contentMap.put("enter_coil_no", oldCoil.getEnterCoilNo());
contentMap.put("current_coil_no", newCoilBo.getCurrentCoilNo());
contentMap.put("coil_id", String.valueOf(oldCoil.getCoilId())); // 初始钢卷ID记录原钢卷的ID
contentMap.put("current_coil_id", "null"); // 当前钢卷ID分卷时暂时为null插入后更新
// 复制原钢卷的历史steps
List<Map<String, Object>> steps = new ArrayList<>();
if (oldCoil.getQrcodeRecordId() != null) {
WmsGenerateRecordVo oldRecord = generateRecordService.queryById(oldCoil.getQrcodeRecordId());
if (oldRecord != null) {
ObjectMapper objectMapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> oldContentMap = objectMapper.readValue(oldRecord.getContent(), Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> oldSteps = (List<Map<String, Object>>) oldContentMap.get("steps");
if (oldSteps != null) {
steps.addAll(oldSteps);
}
}
}
// 添加分卷步骤
Map<String, Object> splitStep = new HashMap<>();
splitStep.put("step", steps.size() + 1);
splitStep.put("action", "更新");
splitStep.put("operation", "分卷");
splitStep.put("old_current_coil_no", oldCoil.getCurrentCoilNo());
splitStep.put("old_coil_id", String.valueOf(oldCoil.getCoilId()));
splitStep.put("new_current_coil_nos", String.join(",", allNewCoilNos));
splitStep.put("child_coils", allNewCoilNos);
splitStep.put("operator", LoginHelper.getUsername()); // 操作者
steps.add(splitStep);
contentMap.put("steps", steps);
ObjectMapper objectMapper = new ObjectMapper();
String contentJson = objectMapper.writeValueAsString(contentMap);
WmsGenerateRecordBo recordBo = new WmsGenerateRecordBo();
recordBo.setContent(contentJson);
recordBo.setSerialNumber(oldCoil.getEnterCoilNo() + "-" + newCoilBo.getCurrentCoilNo());
recordBo.setQrcodeType(0L);
recordBo.setIsEnabled(0L);
recordBo.setSize(200L);
recordBo.setStatus(1); // 1=当前有效码
WmsGenerateRecordVo record = generateRecordService.insertByBo(recordBo);
return record.getRecordId();
} catch (Exception e) {
throw new RuntimeException("生成分卷二维码失败: " + e.getMessage());
}
}
/**
* 为合卷生成新二维码(合并多个父钢卷的二维码信息)
*/
private Long generateQrcodeForMerge(WmsMaterialCoilBo mergedCoilBo, List<WmsMaterialCoilBo> originalCoils) {
try {
if (mergedCoilBo == null) {
throw new RuntimeException("合卷后的钢卷数据不能为空");
}
Map<String, Object> contentMap = new HashMap<>();
// 获取enterCoilNo优先使用mergedCoilBo的如果没有则从原始钢卷中获取
String enterCoilNo = mergedCoilBo.getEnterCoilNo();
if (enterCoilNo == null && originalCoils != null && !originalCoils.isEmpty()) {
WmsMaterialCoil firstOriginalCoil = baseMapper.selectById(originalCoils.get(0).getCoilId());
if (firstOriginalCoil != null) {
enterCoilNo = firstOriginalCoil.getEnterCoilNo();
}
}
contentMap.put("enter_coil_no", enterCoilNo);
contentMap.put("current_coil_no", mergedCoilBo.getCurrentCoilNo());
contentMap.put("coil_id", "null"); // 初始钢卷ID合卷时为null
contentMap.put("current_coil_id", "null"); // 当前钢卷ID合卷时暂时为null插入后更新
// 合并所有参与合卷的原始钢卷的历史steps
List<Map<String, Object>> steps = new ArrayList<>();
// 从参与合卷的原始钢卷中获取二维码信息并合并
if (originalCoils != null && !originalCoils.isEmpty()) {
for (WmsMaterialCoilBo originalCoilBo : originalCoils) {
if (originalCoilBo.getCoilId() != null) {
// 查询原始钢卷的二维码信息
WmsMaterialCoil originalCoil = baseMapper.selectById(originalCoilBo.getCoilId());
if (originalCoil != null && originalCoil.getQrcodeRecordId() != null) {
WmsGenerateRecordVo originalQr = generateRecordService.queryById(originalCoil.getQrcodeRecordId());
if (originalQr != null) {
ObjectMapper objectMapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> originalContentMap = objectMapper.readValue(originalQr.getContent(), Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> originalSteps = (List<Map<String, Object>>) originalContentMap.get("steps");
if (originalSteps != null) {
steps.addAll(originalSteps);
}
}
}
}
}
}
// 添加合卷步骤
Map<String, Object> mergeStep = new HashMap<>();
mergeStep.put("step", steps.size() + 1);
mergeStep.put("action", "更新");
mergeStep.put("operation", "合卷");
// 收集参与合卷的原始钢卷号和ID
List<String> originalCoilNos = new ArrayList<>();
List<String> originalCoilIds = new ArrayList<>();
if (originalCoils != null && !originalCoils.isEmpty()) {
for (WmsMaterialCoilBo originalCoilBo : originalCoils) {
if (originalCoilBo.getCurrentCoilNo() != null) {
originalCoilNos.add(originalCoilBo.getCurrentCoilNo());
}
if (originalCoilBo.getCoilId() != null) {
originalCoilIds.add(originalCoilBo.getCoilId().toString());
}
}
}
mergeStep.put("parent_coil_nos", String.join(",", originalCoilNos));
mergeStep.put("parent_coil_ids", String.join(",", originalCoilIds));
mergeStep.put("new_current_coil_no", mergedCoilBo.getCurrentCoilNo());
mergeStep.put("operator", LoginHelper.getUsername()); // 操作者
steps.add(mergeStep);
contentMap.put("steps", steps);
// 将父钢卷ID也存储到顶层方便快速查询
contentMap.put("parent_coil_ids", String.join(",", originalCoilIds));
ObjectMapper objectMapper = new ObjectMapper();
String contentJson = objectMapper.writeValueAsString(contentMap);
WmsGenerateRecordBo recordBo = new WmsGenerateRecordBo();
recordBo.setContent(contentJson);
recordBo.setSerialNumber(enterCoilNo + "-" + mergedCoilBo.getCurrentCoilNo());
recordBo.setQrcodeType(0L);
recordBo.setIsEnabled(0L);
recordBo.setSize(200L);
recordBo.setStatus(1); // 1=当前有效码
WmsGenerateRecordVo record = generateRecordService.insertByBo(recordBo);
return record.getRecordId();
} catch (Exception e) {
throw new RuntimeException("生成合卷二维码失败: " + e.getMessage());
}
}
/**
* 更新二维码内容中的coilId
*/
private void updateQrcodeCoilId(Long qrcodeRecordId, Long coilId) {
try {
// 获取二维码记录
WmsGenerateRecordVo record = generateRecordService.queryById(qrcodeRecordId);
if (record == null) {
throw new RuntimeException("二维码记录不存在");
}
// 解析现有content
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> contentMap = objectMapper.readValue(record.getContent(), Map.class);
// 如果是第一次设置coilId从"null"变为实际ID则同时设置coil_id和current_coil_id
if ("null".equals(contentMap.get("coil_id"))) {
contentMap.put("coil_id", String.valueOf(coilId)); // 初始ID不再改变
}
// 始终更新current_coil_id为最新的钢卷ID
contentMap.put("current_coil_id", String.valueOf(coilId));
// 更新二维码记录
String newContentJson = objectMapper.writeValueAsString(contentMap);
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
updateBo.setRecordId(qrcodeRecordId);
updateBo.setContent(newContentJson);
generateRecordService.updateByBo(updateBo);
} catch (Exception e) {
throw new RuntimeException("更新二维码coilId失败: " + e.getMessage());
}
}
/**
* 更新二维码内容正常更新添加step
* @param oldCoil 旧钢卷记录
* @param bo 更新数据
* @param newCoilId 新钢卷ID如果创建了新记录
*/
private void updateQrcodeContentForNormalUpdate(WmsMaterialCoil oldCoil, WmsMaterialCoilBo bo, Long newCoilId) {
try {
// 获取原二维码记录
WmsGenerateRecordVo oldRecord = generateRecordService.queryById(oldCoil.getQrcodeRecordId());
if (oldRecord == null) {
throw new RuntimeException("二维码记录不存在");
}
// 解析现有content
ObjectMapper objectMapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> contentMap = objectMapper.readValue(oldRecord.getContent(), Map.class);
// 获取现有steps
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null) {
steps = new ArrayList<>();
}
// 添加新的step记录更新信息
Map<String, Object> newStep = new HashMap<>();
newStep.put("step", steps.size() + 1);
newStep.put("action", "更新");
newStep.put("operation", "信息更新");
newStep.put("old_current_coil_no", oldCoil.getCurrentCoilNo());
newStep.put("new_current_coil_no", bo.getCurrentCoilNo());
newStep.put("old_coil_id", String.valueOf(oldCoil.getCoilId()));
newStep.put("new_coil_id", String.valueOf(newCoilId));
newStep.put("operator", LoginHelper.getUsername());
// 记录具体的变更字段
List<String> changedFields = new ArrayList<>();
if (bo.getCurrentCoilNo() != null && !bo.getCurrentCoilNo().equals(oldCoil.getCurrentCoilNo())) {
changedFields.add("钢卷号: " + oldCoil.getCurrentCoilNo() + "" + bo.getCurrentCoilNo());
}
if (bo.getTeam() != null && !bo.getTeam().equals(oldCoil.getTeam())) {
changedFields.add("班组: " + oldCoil.getTeam() + "" + bo.getTeam());
}
if (bo.getWarehouseId() != null && !bo.getWarehouseId().equals(oldCoil.getWarehouseId())) {
changedFields.add("逻辑库区ID: " + oldCoil.getWarehouseId() + "" + bo.getWarehouseId());
}
if (bo.getActualWarehouseId() != null && !bo.getActualWarehouseId().equals(oldCoil.getActualWarehouseId())) {
changedFields.add("真实库区ID: " + oldCoil.getActualWarehouseId() + "" + bo.getActualWarehouseId());
}
if (bo.getGrossWeight() != null && !bo.getGrossWeight().equals(oldCoil.getGrossWeight())) {
changedFields.add("毛重: " + oldCoil.getGrossWeight() + "" + bo.getGrossWeight());
}
if (bo.getNetWeight() != null && !bo.getNetWeight().equals(oldCoil.getNetWeight())) {
changedFields.add("净重: " + oldCoil.getNetWeight() + "" + bo.getNetWeight());
}
if (bo.getRemark() != null && !bo.getRemark().equals(oldCoil.getRemark())) {
changedFields.add("备注: " + oldCoil.getRemark() + "" + bo.getRemark());
}
if (bo.getQualityStatus() != null && !bo.getQualityStatus().equals(oldCoil.getQualityStatus())) {
changedFields.add("质量状态: " + oldCoil.getQualityStatus() + "" + bo.getQualityStatus());
}
if (bo.getTrimmingRequirement() != null && !bo.getTrimmingRequirement().equals(oldCoil.getTrimmingRequirement())) {
changedFields.add("切边要求: " + oldCoil.getTrimmingRequirement() + "" + bo.getTrimmingRequirement());
}
if (bo.getPackingStatus() != null && !bo.getPackingStatus().equals(oldCoil.getPackingStatus())) {
changedFields.add("打包状态: " + oldCoil.getPackingStatus() + "" + bo.getPackingStatus());
}
if (bo.getPackagingRequirement() != null && !bo.getPackagingRequirement().equals(oldCoil.getPackagingRequirement())) {
changedFields.add("包装要求: " + oldCoil.getPackagingRequirement() + "" + bo.getPackagingRequirement());
}
newStep.put("changed_fields", String.join("; ", changedFields));
newStep.put("update_time", new java.util.Date());
steps.add(newStep);
contentMap.put("steps", steps);
// 更新当前钢卷号
if (bo.getCurrentCoilNo() != null) {
contentMap.put("current_coil_no", bo.getCurrentCoilNo());
}
// 更新 current_coil_id 为新记录的ID
if (newCoilId != null) {
contentMap.put("current_coil_id", String.valueOf(newCoilId));
}
// 更新二维码记录
String newContentJson = objectMapper.writeValueAsString(contentMap);
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
updateBo.setRecordId(oldCoil.getQrcodeRecordId());
updateBo.setContent(newContentJson);
generateRecordService.updateByBo(updateBo);
} catch (Exception e) {
throw new RuntimeException("更新二维码失败: " + e.getMessage());
}
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(WmsMaterialCoil entity) {
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除钢卷物料表
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
//TODO 做一些业务上的校验,判断是否需要校验
}
// 获取要删除的钢卷记录
List<WmsMaterialCoil> coilList = baseMapper.selectBatchIds(ids);
if (coilList != null && !coilList.isEmpty()) {
// 遍历每个要删除的钢卷,释放其占用的实际库区
for (WmsMaterialCoil coil : coilList) {
if (coil.getActualWarehouseId() != null) {
WmsActualWarehouseBo actualWarehouseBo = new WmsActualWarehouseBo();
actualWarehouseBo.setActualWarehouseId(coil.getActualWarehouseId());
actualWarehouseBo.setIsEnabled(1); // 设置为启用状态,释放库区
actualWarehouseService.updateByBo(actualWarehouseBo);
}
}
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 钢卷溯源查询
* 根据入场钢卷号查询二维码解析content中的steps然后根据steps中的钢卷号反向查询数据库
*
* @param enterCoilNo 入场钢卷号
* @param currentCoilNo 当前钢卷号(可选,用于查询特定子钢卷)
* @return 溯源结果(包含二维码信息和数据库记录)
*/
@Override
public Map<String, Object> queryTrace(String enterCoilNo, String currentCoilNo) {
try {
// 优化1: 使用LIKE查询替代查询所有记录大幅提升性能
List<WmsGenerateRecordVo> allQrRecords = new ArrayList<>();
// 首先查询主二维码(以入场钢卷号为序列号的)
WmsGenerateRecordBo qrBo = new WmsGenerateRecordBo();
qrBo.setSerialNumber(enterCoilNo);
List<WmsGenerateRecordVo> mainQrRecords = generateRecordService.queryList(qrBo);
allQrRecords.addAll(mainQrRecords);
// 优化使用LIKE查询所有以该入场钢卷号开头的二维码分卷后的二维码
// 而不是查询所有记录然后在内存中过滤
LambdaQueryWrapper<com.klp.domain.WmsGenerateRecord> splitWrapper = Wrappers.lambdaQuery();
splitWrapper.like(com.klp.domain.WmsGenerateRecord::getSerialNumber, enterCoilNo + "-");
List<WmsGenerateRecordVo> splitRecords = generateRecordMapper.selectVoList(splitWrapper);
// 去重使用recordId作为唯一标识
Set<Long> existingRecordIds = allQrRecords.stream()
.map(WmsGenerateRecordVo::getRecordId)
.collect(Collectors.toSet());
for (WmsGenerateRecordVo record : splitRecords) {
if (record.getRecordId() != null && !existingRecordIds.contains(record.getRecordId())) {
allQrRecords.add(record);
existingRecordIds.add(record.getRecordId());
}
}
if (allQrRecords.isEmpty()) {
throw new RuntimeException("未找到对应的二维码记录");
}
// 优化2: ObjectMapper在循环外创建避免重复创建
ObjectMapper objectMapper = new ObjectMapper();
// 2. 合并所有二维码的steps信息去重并重新编号
Map<String, Map<String, Object>> uniqueSteps = new HashMap<>(); // 用于去重
Set<String> allCoilNos = new HashSet<>();
// 收集所有操作人用户名
Set<String> operatorUsernames = new HashSet<>();
for (WmsGenerateRecordVo qrRecord : allQrRecords) {
@SuppressWarnings("unchecked")
Map<String, Object> contentMap = (Map<String, Object>) objectMapper.readValue(qrRecord.getContent(), Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps != null) {
for (Map<String, Object> step : steps) {
// 创建唯一标识:操作类型 + 相关钢卷号
String stepKey = createStepKey(step);
// 如果是新的步骤,添加到唯一步骤集合中
if (!uniqueSteps.containsKey(stepKey)) {
Map<String, Object> uniqueStep = new HashMap<>(step);
uniqueStep.put("qrcode_serial", qrRecord.getSerialNumber());
uniqueStep.put("qrcode_id", qrRecord.getRecordId());
uniqueSteps.put(stepKey, uniqueStep);
// 收集操作人用户名
Object operator = step.get("operator");
if (operator != null) {
operatorUsernames.add(operator.toString());
}
}
// 提取钢卷号
extractCoilNo(step, "current_coil_no", allCoilNos);
extractCoilNo(step, "new_current_coil_no", allCoilNos);
extractCoilNo(step, "old_current_coil_no", allCoilNos);
extractCoilNo(step, "new_current_coil_nos", allCoilNos);
extractCoilNo(step, "merged_from", allCoilNos);
extractCoilNo(step, "parent_coil_nos", allCoilNos);
extractCoilId(step, "coil_id", allCoilNos);
extractCoilId(step, "old_coil_id", allCoilNos);
extractCoilId(step, "parent_coil_ids", allCoilNos);
}
}
}
// 获取操作人昵称映射
Map<String, String> operatorNicknameMap = getOperatorNicknames(operatorUsernames);
// 转换为列表并按原始步骤号排序(保持时间顺序)
List<Map<String, Object>> allSteps = new ArrayList<>(uniqueSteps.values());
// 按原始步骤号排序,保持实际操作的时间顺序
allSteps.sort((a, b) -> {
Integer stepA = (Integer) a.get("step");
Integer stepB = (Integer) b.get("step");
if (stepA == null) stepA = 0;
if (stepB == null) stepB = 0;
return stepA.compareTo(stepB);
});
// 重新编号(保持连续性)
for (int i = 0; i < allSteps.size(); i++) {
allSteps.get(i).put("display_step", i + 1);
// 保留原始步骤号用于调试
allSteps.get(i).put("original_step", allSteps.get(i).get("step"));
// 替换操作人为昵称
Object operator = allSteps.get(i).get("operator");
if (operator != null) {
String username = operator.toString();
String nickname = operatorNicknameMap.get(username);
allSteps.get(i).put("operator_nickname", nickname != null ? nickname : username);
}
}
// 3. 如果指定了当前钢卷号,过滤出相关的钢卷号
Set<String> filteredCoilNos = allCoilNos;
if (currentCoilNo != null && !currentCoilNo.trim().isEmpty()) {
final String filterValue = currentCoilNo;
filteredCoilNos = allCoilNos.stream()
.filter(coilNo -> coilNo.contains(filterValue))
.collect(Collectors.toSet());
}
// 优化3: 使用IN查询替代多个OR条件提升查询性能
// 4. 根据提取的钢卷号反向查询数据库
List<WmsMaterialCoilVo> result = new ArrayList<>();
if (!filteredCoilNos.isEmpty()) {
LambdaQueryWrapper<WmsMaterialCoil> lqw = Wrappers.lambdaQuery();
lqw.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo);
// 优化使用IN查询替代多个OR条件当钢卷号数量较少时
// 如果数量太多(>1000分批查询避免SQL过长
List<String> coilNoList = new ArrayList<>(filteredCoilNos);
if (coilNoList.size() <= 1000) {
lqw.in(WmsMaterialCoil::getCurrentCoilNo, coilNoList);
} else {
// 分批查询
int batchSize = 1000;
for (int i = 0; i < coilNoList.size(); i += batchSize) {
int end = Math.min(i + batchSize, coilNoList.size());
List<String> batch = coilNoList.subList(i, end);
LambdaQueryWrapper<WmsMaterialCoil> batchLqw = Wrappers.lambdaQuery();
batchLqw.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo);
batchLqw.in(WmsMaterialCoil::getCurrentCoilNo, batch);
batchLqw.orderByAsc(WmsMaterialCoil::getCreateTime);
List<WmsMaterialCoilVo> batchResult = baseMapper.selectVoList(batchLqw);
result.addAll(batchResult);
}
}
if (coilNoList.size() <= 1000) {
lqw.orderByAsc(WmsMaterialCoil::getCreateTime);
result = baseMapper.selectVoList(lqw);
}
// 优化4: 批量填充关联对象避免N+1查询
if (!result.isEmpty()) {
fillRelatedObjectsBatch(result);
}
}
// 如果没有找到记录,尝试查询所有相关的钢卷(包括历史数据)
if (result.isEmpty()) {
LambdaQueryWrapper<WmsMaterialCoil> lqw = Wrappers.lambdaQuery();
lqw.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo);
lqw.orderByAsc(WmsMaterialCoil::getCreateTime);
result = baseMapper.selectVoList(lqw);
// 优化:批量填充关联对象
if (!result.isEmpty()) {
fillRelatedObjectsBatch(result);
}
}
// 5. 构建返回结果
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("qrcode", allQrRecords.get(0)); // 主二维码
resultMap.put("all_qrcodes", allQrRecords); // 所有相关二维码
resultMap.put("steps", allSteps); // 所有步骤
resultMap.put("records", result); // 所有钢卷记录
return resultMap;
} catch (Exception e) {
throw new RuntimeException("溯源查询失败: " + e.getMessage());
}
}
/**
* 根据用户名获取用户昵称映射
* @param usernames 用户名集合
* @return 用户名到昵称的映射
*/
private Map<String, String> getOperatorNicknames(Set<String> usernames) {
Map<String, String> nicknameMap = new HashMap<>();
if (usernames.isEmpty()) {
return nicknameMap;
}
for (String username : usernames) {
try {
// 通过用户名查找用户
SysUser user = userService.selectUserByUserName(username);
if (user != null) {
nicknameMap.put(username, user.getNickName());
} else {
nicknameMap.put(username, username); // 找不到则使用原用户名
}
} catch (Exception e) {
// 出现异常时使用原用户名
nicknameMap.put(username, username);
}
}
return nicknameMap;
}
/**
* 创建步骤唯一标识
*/
private String createStepKey(Map<String, Object> step) {
StringBuilder keyBuilder = new StringBuilder();
// 使用操作类型和主要标识符创建唯一key
String operation = (String) step.get("operation");
keyBuilder.append(operation).append("-");
// 根据操作类型使用不同的标识符
if ("分卷".equals(operation)) {
// 分卷:使用原钢卷号 + 分卷列表
keyBuilder.append(step.get("old_current_coil_no")).append("->");
keyBuilder.append(step.get("new_current_coil_nos"));
} else if ("合卷".equals(operation)) {
// 合卷:使用父钢卷列表 + 新钢卷号
keyBuilder.append(step.get("parent_coil_nos")).append("->");
keyBuilder.append(step.get("new_current_coil_no"));
} else if ("新增".equals(operation)) {
// 新增:使用当前钢卷号
keyBuilder.append(step.get("current_coil_no"));
} else {
// 其他更新:使用原钢卷号 -> 新钢卷号
keyBuilder.append(step.get("old_current_coil_no")).append("->");
keyBuilder.append(step.get("new_current_coil_no"));
}
return keyBuilder.toString();
}
/**
* 从step中提取钢卷号
*/
private void extractCoilNo(Map<String, Object> step, String fieldName, Set<String> coilNos) {
Object value = step.get(fieldName);
if (value != null) {
String strValue = value.toString();
if (strValue.contains(",")) {
// 如果是逗号分隔的多个钢卷号,分割后添加
String[] coilArray = strValue.split(",");
for (String coilNo : coilArray) {
coilNos.add(coilNo.trim());
}
} else {
coilNos.add(strValue.trim());
}
}
}
/**
* 从step中提取钢卷ID
*/
private void extractCoilId(Map<String, Object> step, String fieldName, Set<String> coilNos) {
Object value = step.get(fieldName);
if (value != null) {
String strValue = value.toString();
if (strValue.contains(",")) {
// 如果是逗号分隔的多个钢卷ID分割后添加
String[] coilArray = strValue.split(",");
for (String coilId : coilArray) {
coilNos.add(coilId.trim());
}
} else {
coilNos.add(strValue.trim());
}
}
}
/**
* 查询各个库区中不同类型的钢卷分布情况
* 按库区分组统计每种物品类型和物品ID的钢卷数量和重量
*
* @param itemType 物品类型(可选)
* @param itemId 物品ID可选
* @return 分布情况列表包含库区信息、物品类型、物品ID、数量、重量等
*/
@Override
public List<WmsMaterialCoilVo> getDistributionByWarehouse(String itemType, Long itemId) {
List<Map<String, Object>> mapList = baseMapper.getDistributionByWarehouse(itemType, itemId);
return convertMapListToVoList(mapList);
}
@Override
public List<WmsMaterialCoilVo> getDistributionByActualWarehouse(String itemType, Long itemId) {
List<Map<String, Object>> mapList = baseMapper.getDistributionByActualWarehouse(itemType, itemId);
return convertMapListToVoListActual(mapList);
}
private List<WmsMaterialCoilVo> convertMapListToVoListActual(List<Map<String, Object>> mapList) {
List<WmsMaterialCoilVo> voList = new ArrayList<>();
for (Map<String, Object> map : mapList) {
WmsMaterialCoilVo vo = new WmsMaterialCoilVo();
vo.setActualWarehouseId(map.get("actual_warehouse_id") != null ? Long.valueOf(map.get("actual_warehouse_id").toString()) : null);
vo.setActualWarehouseName(map.get("actual_warehouse_name") != null ? map.get("actual_warehouse_name").toString() : null);
vo.setItemType(map.get("item_type") != null ? map.get("item_type").toString() : null);
vo.setItemId(map.get("item_id") != null ? Long.valueOf(map.get("item_id").toString()) : null);
vo.setCoilCount(map.get("coil_count") != null ? Long.valueOf(map.get("coil_count").toString()) : 0L);
vo.setTotalGrossWeight(map.get("total_gross_weight") != null ? new BigDecimal(map.get("total_gross_weight").toString()) : BigDecimal.ZERO);
vo.setTotalNetWeight(map.get("total_net_weight") != null ? new BigDecimal(map.get("total_net_weight").toString()) : BigDecimal.ZERO);
setSubMaterialVo(vo, map, voList);
}
return voList;
}
/**
* 查询不同类型的钢卷在不同库区的分布情况
* 按物品类型和物品ID分组统计每个库区的钢卷数量和重量
*
* @param itemType 物品类型(可选)
* @param itemId 物品ID可选
* @return 分布情况列表包含物品类型、物品ID、库区信息、数量、重量等
*/
@Override
public List<WmsMaterialCoilVo> getDistributionByItemType(String itemType, Long itemId) {
List<Map<String, Object>> mapList = baseMapper.getDistributionByItemType(itemType, itemId);
return convertMapListToVoList(mapList);
}
/**
* 将Map列表转换为WmsMaterialCoilVo列表
*/
private List<WmsMaterialCoilVo> convertMapListToVoList(List<Map<String, Object>> mapList) {
List<WmsMaterialCoilVo> voList = new ArrayList<>();
for (Map<String, Object> map : mapList) {
WmsMaterialCoilVo vo = new WmsMaterialCoilVo();
vo.setWarehouseId(map.get("warehouse_id") != null ? Long.valueOf(map.get("warehouse_id").toString()) : null);
vo.setWarehouseName(map.get("warehouse_name") != null ? map.get("warehouse_name").toString() : null);
vo.setItemType(map.get("item_type") != null ? map.get("item_type").toString() : null);
vo.setItemId(map.get("item_id") != null ? Long.valueOf(map.get("item_id").toString()) : null);
vo.setCoilCount(map.get("coil_count") != null ? Long.valueOf(map.get("coil_count").toString()) : 0L);
vo.setTotalGrossWeight(map.get("total_gross_weight") != null ? new BigDecimal(map.get("total_gross_weight").toString()) : BigDecimal.ZERO);
vo.setTotalNetWeight(map.get("total_net_weight") != null ? new BigDecimal(map.get("total_net_weight").toString()) : BigDecimal.ZERO);
setSubMaterialVo(vo, map, voList);
}
return voList;
}
private void setSubMaterialVo(WmsMaterialCoilVo vo, Map<String, Object> map, List<WmsMaterialCoilVo> voList) {
String itemType = vo.getItemType();
if (itemType == null) {
voList.add(vo);
return;
}
if ("raw_material".equals(itemType)) {
WmsRawMaterialVo rawMaterial = new WmsRawMaterialVo();
rawMaterial.setRawMaterialName(map.get("itemName") != null ? map.get("itemName").toString() : null);
rawMaterial.setRawMaterialCode(map.get("itemCode") != null ? map.get("itemCode").toString() : null);
rawMaterial.setSpecification(map.get("specification") != null ? map.get("specification").toString() : null);
rawMaterial.setMaterial(map.get("material") != null ? map.get("material").toString() : null);
rawMaterial.setSurfaceTreatmentDesc(map.get("surfaceTreatmentDesc") != null ? map.get("surfaceTreatmentDesc").toString() : null);
rawMaterial.setZincLayer(map.get("zincLayer") != null ? map.get("zincLayer").toString() : null);
rawMaterial.setManufacturer(map.get("manufacturer") != null ? map.get("manufacturer").toString() : null);
vo.setRawMaterial(rawMaterial);
} else if ("product".equals(itemType)) {
WmsProductVo product = new WmsProductVo();
product.setProductName(map.get("itemName") != null ? map.get("itemName").toString() : null);
product.setProductCode(map.get("itemCode") != null ? map.get("itemCode").toString() : null);
product.setSpecification(map.get("specification") != null ? map.get("specification").toString() : null);
product.setMaterial(map.get("material") != null ? map.get("material").toString() : null);
product.setSurfaceTreatmentDesc(map.get("surfaceTreatmentDesc") != null ? map.get("surfaceTreatmentDesc").toString() : null);
product.setZincLayer(map.get("zincLayer") != null ? map.get("zincLayer").toString() : null);
product.setManufacturer(map.get("manufacturer") != null ? map.get("manufacturer").toString() : null);
vo.setProduct(product);
}
voList.add(vo);
}
@Override
public List<WmsMaterialCoilVo> getDistributionByActualItemType(String itemType, Long itemId) {
List<Map<String, Object>> mapList = baseMapper.getDistributionByActualItemType(itemType, itemId);
return convertMapListToVoListActual(mapList);
}
/**
* 查询钢卷导出数据列表
*/
@Override
public List<WmsMaterialCoilExportVo> queryExportList(WmsMaterialCoilBo bo) {
QueryWrapper<WmsMaterialCoil> lqw = buildQueryWrapper(bo);
List<WmsMaterialCoilExportVo> wmsMaterialCoilExportVos = baseMapper.selectExportList(lqw);
// 遍历数据,根据状态替换日期字段,并处理空值兜底
wmsMaterialCoilExportVos.stream().forEach(vo -> {
// 判断查询条件中的status是否为1已发货
if (bo.getStatus() != null && bo.getStatus() == 1) {
Date finalDate = null;
// 1. 优先使用发货时间
if (vo.getExportTime() != null) {
finalDate = vo.getExportTime();
}
// 2. 发货时间为空时,使用更新时间兜底
else if (vo.getUpdateTime() != null) {
finalDate = vo.getUpdateTime();
}
// 3. 若更新时间也为空保留原有createTime避免日期字段为空
else {
finalDate = vo.getCreateTime();
}
// 赋值最终的日期
vo.setCreateTime(finalDate);
}
// 非1的情况保持原有create_time不变
});
return wmsMaterialCoilExportVos;
}
/**
* 钢卷发货
* @param coilId
* @return
*/
@Override
public int exportCoil(Long coilId) {
// 查询当前钢卷信息记录原实际库区ID
WmsMaterialCoilVo wmsMaterialCoilVo = queryById(coilId);
Long oldActualWarehouseId = wmsMaterialCoilVo != null ? wmsMaterialCoilVo.getActualWarehouseId() : null;
// 1. 更新钢卷为已发货并记录发货时间同时清空实际库区占用改用Wrapper实现
int rows = 0;
//获取当前调用接口的这个人的username
String username = LoginHelper.getUsername();
if (wmsMaterialCoilVo != null) {
UpdateWrapper<WmsMaterialCoil> updateWrapper = new UpdateWrapper<>();
// 设置更新条件钢卷ID
updateWrapper.eq("coil_id", coilId);
// 设置要更新的字段
updateWrapper.set("export_time", new Date()) // 发货时间
.set("status", 1) // 已发货状态
.set("actual_warehouse_id", null) // 清空实际库区ID关键
.set("export_by", username); // 发货人
// 执行更新操作
rows = baseMapper.update(null, updateWrapper);
}
// 2. 释放原实际库区(若存在):将库位设置为可用(启用)
if (rows > 0 && oldActualWarehouseId != null) {
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
releaseBo.setActualWarehouseId(oldActualWarehouseId);
releaseBo.setIsEnabled(1); // 释放:设置为启用
actualWarehouseService.updateByBo(releaseBo);
}
return rows;
}
/**
* 钢卷发货撤回
* @param coilId
* @return
*/
@Override
@Transactional
public int withdrawExportCoil(Long coilId) {
// 参数校验
if (coilId == null) {
throw new IllegalArgumentException("钢卷ID不能为空");
}
WmsMaterialCoilVo wmsMaterialCoilVo = queryById(coilId);
if (wmsMaterialCoilVo == null) {
throw new RuntimeException("钢卷不存在");
}
// 检查当前状态是否为已发货状态
if (wmsMaterialCoilVo.getStatus() != 1) {
throw new RuntimeException("该钢卷并没有发货,无法撤回");
}
// 调用自定义的Mapper方法只更新发货时间和状态字段
return baseMapper.updateForWithdrawExport(coilId, 0);
}
/**
* 构建查询条件
*/
private QueryWrapper<WmsMaterialCoil> buildQueryWrapper(WmsMaterialCoilBo bo) {
QueryWrapper<WmsMaterialCoil> lqw = Wrappers.query();
// 基础字段筛选
lqw.eq(bo.getCoilId() != null, "mc.coil_id", bo.getCoilId());
lqw.eq(StringUtils.isNotBlank(bo.getEnterCoilNo()), "mc.enter_coil_no", bo.getEnterCoilNo());
lqw.like(StringUtils.isNotBlank(bo.getCurrentCoilNo()), "mc.current_coil_no", bo.getCurrentCoilNo());
lqw.like(StringUtils.isNotBlank(bo.getSupplierCoilNo()), "mc.supplier_coil_no", bo.getSupplierCoilNo());
lqw.eq(bo.getDataType() != null, "mc.data_type", bo.getDataType());
lqw.eq(bo.getWarehouseId() != null, "mc.warehouse_id", bo.getWarehouseId());
lqw.eq(bo.getActualWarehouseId() != null, "mc.actual_warehouse_id", bo.getActualWarehouseId());
lqw.eq(StringUtils.isNotBlank(bo.getItemType()), "mc.item_type", bo.getItemType());
lqw.eq(bo.getHasMergeSplit() != null, "mc.has_merge_split", bo.getHasMergeSplit());
// 修改itemId筛选逻辑支持逗号分隔的多个ID查询
if (StringUtils.isNotBlank(bo.getItemIds())) {
String[] itemIdArray = bo.getItemIds().split(",");
List<Long> itemIdList = new ArrayList<>();
for (String itemIdStr : itemIdArray) {
if (StringUtils.isNotBlank(itemIdStr)) {
try {
itemIdList.add(Long.parseLong(itemIdStr.trim()));
} catch (NumberFormatException e) {
// 忽略无效的ID格式
}
}
}
if (!itemIdList.isEmpty()) {
lqw.in("mc.item_id", itemIdList);
}
} else if (bo.getItemId() != null) {
// 兼容原来的itemId单值查询
lqw.eq("mc.item_id", bo.getItemId());
}
// 添加coilIds查询条件支持逗号分隔的多个coilId查询
if (StringUtils.isNotBlank(bo.getCoilIds())) {
String[] coilIdArray = bo.getCoilIds().split(",");
List<Long> coilIdList = new ArrayList<>();
for (String coilIdStr : coilIdArray) {
if (StringUtils.isNotBlank(coilIdStr)) {
try {
coilIdList.add(Long.parseLong(coilIdStr.trim()));
} catch (NumberFormatException e) {
// 忽略无效的ID格式
}
}
}
if (!coilIdList.isEmpty()) {
lqw.in("mc.coil_id", coilIdList);
}
}
lqw.eq(bo.getStatus() != null, "mc.status", bo.getStatus());
lqw.eq(StringUtils.isNotBlank(bo.getMaterialType()), "mc.material_type", bo.getMaterialType());
lqw.eq(StringUtils.isNotBlank(bo.getQualityStatus()), "mc.quality_status", bo.getQualityStatus());
lqw.eq(StringUtils.isNotBlank(bo.getPackingStatus()), "mc.packing_status", bo.getPackingStatus());
// 把team字段作为筛选条件
lqw.eq(StringUtils.isNotBlank(bo.getTeam()), "mc.team", bo.getTeam());
// 根据开始时间和结束时间筛选修改时间
lqw.ge(bo.getStartTime() != null, "mc.update_time", bo.getStartTime());
lqw.le(bo.getEndTime() != null, "mc.update_time", bo.getEndTime());
// 根据发货开始和结束筛选发货时间
lqw.ge(bo.getByExportTimeStart() != null, "mc.export_time", bo.getByExportTimeStart());
lqw.le(bo.getByExportTimeEnd() != null, "mc.export_time", bo.getByExportTimeEnd());
// 逻辑删除
lqw.eq("mc.del_flag", 0);
// 根据创建时间倒序
lqw.orderByDesc("mc.create_time");
return lqw;
}
/**
* 批量更新钢卷发货状态
*
* @param coilIds 钢卷ID列表
* @param status 目标状态 (0=在库, 1=在途/已发货, 2=已出库)
* @return 是否更新成功
*/
@Override
public Boolean batchUpdateDeliveryStatus(List<Long> coilIds, Integer status) {
if (coilIds == null || coilIds.isEmpty()) {
return false;
}
if (status == null || status < 0 || status > 2) {
throw new RuntimeException("无效的状态值状态必须在0-2之间");
}
// ********** 批量查询钢卷原库区ID仅当发货状态时需要**********
Map<Long, Long> coilWarehouseMap = new HashMap<>(); // key: coilId, value: oldActualWarehouseId
if (status == 1) { // 仅当设置为"已发货/在途"状态时,才处理库区释放逻辑
// 批量查询钢卷信息获取原实际库区ID
List<WmsMaterialCoilVo> coilVoList = queryBatchByIds(coilIds);
for (WmsMaterialCoilVo vo : coilVoList) {
if (vo != null && vo.getActualWarehouseId() != null) {
coilWarehouseMap.put(vo.getCoilId(), vo.getActualWarehouseId());
}
}
}
// 构造更新条件
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.in(WmsMaterialCoil::getCoilId, coilIds);
updateWrapper.set(WmsMaterialCoil::getStatus, status);
// ********** 发货状态时清空库区ID + 更新出库时间 **********
if (status == 1) {
updateWrapper.set(WmsMaterialCoil::getExportTime, new Date());
updateWrapper.set(WmsMaterialCoil::getActualWarehouseId, null); // 清空实际库区ID
}
// 执行批量更新
int updateCount = baseMapper.update(null, updateWrapper);
boolean updateSuccess = updateCount > 0;
// ********** 批量释放原库区占用仅当更新成功且有库区ID时**********
if (updateSuccess && status == 1 && !coilWarehouseMap.isEmpty()) {
// 去重库区ID避免重复更新同一个库区
Set<Long> warehouseIds = new HashSet<>(coilWarehouseMap.values());
for (Long warehouseId : warehouseIds) {
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
releaseBo.setActualWarehouseId(warehouseId);
releaseBo.setIsEnabled(1); // 释放:设置为启用
actualWarehouseService.updateByBo(releaseBo);
}
}
return updateSuccess;
}
// 补充批量查询钢卷信息的方法需要确保你的Mapper里有对应的批量查询逻辑
private List<WmsMaterialCoilVo> queryBatchByIds(List<Long> coilIds) {
if (coilIds == null || coilIds.isEmpty()) {
return Collections.emptyList();
}
// 这里替换成你实际的批量查询逻辑比如用IN条件查询
LambdaQueryWrapper<WmsMaterialCoil> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.in(WmsMaterialCoil::getCoilId, coilIds);
List<WmsMaterialCoil> coilList = baseMapper.selectList(queryWrapper);
// 转换为Vo
return coilList.stream()
.map(coil -> {
WmsMaterialCoilVo vo = new WmsMaterialCoilVo();
BeanUtils.copyProperties(coil, vo);
return vo;
})
.collect(Collectors.toList());
}
/**
* 根据更新前的钢卷ID列表获取对应的直接下一步钢卷ID
* 分卷场景返回所有子卷ID逗号分隔合卷/普通更新返回单个ID
*
* @param oldCoilIds 更新前的钢卷ID列表
* @return Map<旧钢卷ID, 下一步钢卷ID字符串>
* - 普通更新/合卷value为单个ID字符串如"1002"
* - 分卷value为逗号分隔的子卷ID字符串如"1003,1004,1005"
* - 无下一步value为null
*/
public Map<Long, String> getUpdatedCoilIdsByOldCoilIds(List<Long> oldCoilIds) {
// 初始化返回的Mapkey=旧IDvalue=下一步ID字符串逗号分隔
Map<Long, String> old2NextCoilIdMap = new LinkedHashMap<>();
if (oldCoilIds == null || oldCoilIds.isEmpty()) {
return old2NextCoilIdMap;
}
// 先将所有旧ID放入Map默认value为null
for (Long oldCoilId : oldCoilIds) {
old2NextCoilIdMap.put(oldCoilId, null);
}
ObjectMapper objectMapper = new ObjectMapper();
// 遍历每个旧ID解析对应的下一步ID
for (Long oldCoilId : oldCoilIds) {
String nextCoilIdsStr = null; // 存储最终返回的ID字符串单个/逗号分隔)
try {
// 1. 查询旧钢卷记录
WmsMaterialCoil oldCoil = baseMapper.selectById(oldCoilId);
if (oldCoil == null || oldCoil.getQrcodeRecordId() == null) {
old2NextCoilIdMap.put(oldCoilId, null);
continue;
}
// 2. 查询关联的二维码记录
LambdaQueryWrapper<WmsGenerateRecord> qrWrapper = Wrappers.lambdaQuery();
qrWrapper.eq(WmsGenerateRecord::getSerialNumber, oldCoil.getEnterCoilNo())
.or()
.like(WmsGenerateRecord::getSerialNumber, oldCoil.getEnterCoilNo() + "-");
List<WmsGenerateRecordVo> qrRecords = generateRecordMapper.selectVoList(qrWrapper);
if (qrRecords.isEmpty()) {
old2NextCoilIdMap.put(oldCoilId, null);
continue;
}
// 3. 解析每个二维码的步骤
for (WmsGenerateRecordVo qrRecord : qrRecords) {
if (qrRecord.getContent() == null || qrRecord.getContent().trim().isEmpty()) {
continue;
}
Map<String, Object> contentMap;
try {
contentMap = objectMapper.readValue(qrRecord.getContent(), Map.class);
} catch (JsonProcessingException e) {
log.warn("解析二维码记录[{}]的content失败: {}", qrRecord.getRecordId(), e.getMessage());
continue;
}
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null || steps.isEmpty()) {
continue;
}
String oldCoilIdStr = oldCoilId.toString();
// 4. 遍历步骤找下一步ID
for (Map<String, Object> step : steps) {
String stepOldCoilIdStr = step.get("old_coil_id") != null ? step.get("old_coil_id").toString() : null;
String newCoilIdStr = step.get("new_coil_id") != null ? step.get("new_coil_id").toString() : null;
// 普通更新场景单个ID
if (stepOldCoilIdStr != null && stepOldCoilIdStr.equals(oldCoilIdStr)
&& newCoilIdStr != null && !"null".equals(newCoilIdStr)) {
nextCoilIdsStr = newCoilIdStr;
break;
}
// 合卷场景单个ID
String parentCoilIds = step.get("parent_coil_ids") != null ? step.get("parent_coil_ids").toString() : null;
if (parentCoilIds != null && parentCoilIds.contains(oldCoilIdStr)) {
String newCurrentCoilNo = step.get("new_current_coil_no") != null ? step.get("new_current_coil_no").toString() : null;
if (newCurrentCoilNo != null) {
LambdaQueryWrapper<WmsMaterialCoil> coilWrapper = Wrappers.lambdaQuery();
coilWrapper.eq(WmsMaterialCoil::getCurrentCoilNo, newCurrentCoilNo)
.eq(WmsMaterialCoil::getDataType, 1);
WmsMaterialCoil nextCoil = baseMapper.selectOne(coilWrapper);
if (nextCoil != null) {
nextCoilIdsStr = nextCoil.getCoilId().toString();
break;
}
}
}
// 分卷场景所有子卷ID逗号分隔
String operation = step.get("operation") != null ? step.get("operation").toString() : "";
if ("分卷".equals(operation) && oldCoilIdStr.equals(stepOldCoilIdStr)) {
String newCurrentCoilNos = step.get("new_current_coil_nos") != null ? step.get("new_current_coil_nos").toString() : null;
List<String> childCoilNos = null;
// 解析子卷号列表
if (newCurrentCoilNos != null && !newCurrentCoilNos.isEmpty()) {
childCoilNos = Arrays.asList(newCurrentCoilNos.split(","));
} else if (step.get("child_coils") instanceof List) {
childCoilNos = (List<String>) step.get("child_coils");
}
// 批量查询所有子卷ID并拼接为逗号分隔的字符串
if (childCoilNos != null && !childCoilNos.isEmpty()) {
// 去重+trim避免空字符串/重复卷号
List<String> validChildCoilNos = childCoilNos.stream()
.map(String::trim)
.filter(no -> !no.isEmpty())
.distinct()
.collect(Collectors.toList());
if (!validChildCoilNos.isEmpty()) {
// 批量查询(性能优化,避免循环查询)
LambdaQueryWrapper<WmsMaterialCoil> coilWrapper = Wrappers.lambdaQuery();
coilWrapper.in(WmsMaterialCoil::getCurrentCoilNo, validChildCoilNos)
.eq(WmsMaterialCoil::getDataType, 1);
List<WmsMaterialCoil> childCoils = baseMapper.selectList(coilWrapper);
// 拼接子卷ID为逗号分隔的字符串
if (!childCoils.isEmpty()) {
nextCoilIdsStr = childCoils.stream()
.map(coil -> coil.getCoilId().toString())
.collect(Collectors.joining(","));
break;
}
}
}
}
}
if (nextCoilIdsStr != null) {
break; // 找到结果,跳出二维码循环
}
}
// 5. 将结果放入Map
old2NextCoilIdMap.put(oldCoilId, nextCoilIdsStr);
} catch (Exception e) {
log.error("解析钢卷ID[{}]的下一步ID失败", oldCoilId, e);
old2NextCoilIdMap.put(oldCoilId, null);
}
}
return old2NextCoilIdMap;
}
/**
* 检查钢卷号是否重复
* 根据入场钢卷号和当前钢卷号查询数据库,判断哪个钢卷号重复
*/
@Override
public Map<String, Object> checkCoilNoDuplicate(String enterCoilNo, String currentCoilNo) {
Map<String, Object> result = new HashMap<>();
boolean enterCoilNoDuplicate = false;
boolean currentCoilNoDuplicate = false;
// 检查入场钢卷号是否重复
if (StringUtils.isNotBlank(enterCoilNo)) {
LambdaQueryWrapper<WmsMaterialCoil> enterWrapper = Wrappers.lambdaQuery();
enterWrapper.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo)
.eq(WmsMaterialCoil::getDelFlag, 0)
.eq(WmsMaterialCoil::getDataType, 1);
long enterCount = baseMapper.selectCount(enterWrapper);
enterCoilNoDuplicate = enterCount > 0;
}
// 检查当前钢卷号是否重复
if (StringUtils.isNotBlank(currentCoilNo)) {
LambdaQueryWrapper<WmsMaterialCoil> currentWrapper = Wrappers.lambdaQuery();
currentWrapper.eq(WmsMaterialCoil::getCurrentCoilNo, currentCoilNo)
.eq(WmsMaterialCoil::getDelFlag, 0)
.eq(WmsMaterialCoil::getDataType, 1);
long currentCount = baseMapper.selectCount(currentWrapper);
currentCoilNoDuplicate = currentCount > 0;
}
// 判断重复类型
String duplicateType;
if (enterCoilNoDuplicate && currentCoilNoDuplicate) {
duplicateType = "both";
} else if (enterCoilNoDuplicate) {
duplicateType = "enter";
} else if (currentCoilNoDuplicate) {
duplicateType = "current";
} else {
duplicateType = "none";
}
result.put("duplicateType", duplicateType);
result.put("enterCoilNoDuplicate", enterCoilNoDuplicate);
result.put("currentCoilNoDuplicate", currentCoilNoDuplicate);
return result;
}
/**
* 根据入场钢卷号前缀查询最大的入场钢卷号
* 前端传入入场钢卷号的前四位,查询所有符合的入场钢卷号,返回数值上的最大值
*
* @param enterCoilNoPrefix 入场钢卷号前缀建议为4位
* @return 包含最大钢卷号和前缀的Mapkey分别为maxEnterCoilNo、prefix
*/
@Override
public Map<String, Object> getMaxEnterCoilNoByPrefix(String enterCoilNoPrefix) {
Map<String, Object> result = new HashMap<>(2); // 初始化固定容量,提升性能
result.put("prefix", enterCoilNoPrefix); // 先赋值前缀,避免重复代码
// 1. 边界校验:前缀为空/空白字符串直接返回null
if (StringUtils.isBlank(enterCoilNoPrefix)) {
log.warn("查询最大入场钢卷号失败:前缀为空");
result.put("maxEnterCoilNo", null);
return result;
}
// 2. 前缀长度校验可选根据业务要求比如强制4位
if (enterCoilNoPrefix.length() != 4) {
log.warn("查询最大入场钢卷号失败前缀长度不符合要求需4位当前前缀{}", enterCoilNoPrefix);
result.put("maxEnterCoilNo", null);
return result;
}
try {
// 3. 构建查询条件:匹配前缀 + 未删除
LambdaQueryWrapper<WmsMaterialCoil> wrapper = Wrappers.lambdaQuery();
wrapper.likeRight(WmsMaterialCoil::getEnterCoilNo, enterCoilNoPrefix)
.eq(WmsMaterialCoil::getDelFlag, 0)
.select(WmsMaterialCoil::getEnterCoilNo); // 仅查询需要的字段,提升性能
// 4. 查询所有匹配的钢卷记录
List<WmsMaterialCoil> coilList = baseMapper.selectList(wrapper);
log.info("根据前缀{}查询到匹配的钢卷记录数:{}", enterCoilNoPrefix, coilList.size());
// 5. 手动筛选数值最大的钢卷号(解决字符串排序问题)
String maxEnterCoilNo = null;
long maxNum = -1;
for (WmsMaterialCoil coil : coilList) {
String coilNo = coil.getEnterCoilNo();
// 跳过空值、非纯数字的钢卷号
if (StringUtils.isBlank(coilNo) || !coilNo.matches("^\\d+$")) {
log.debug("跳过无效钢卷号:{}(空值或非纯数字)", coilNo);
continue;
}
// 处理大数溢出风险如果钢卷号超过Long范围可改用BigDecimal
long currentNum;
try {
currentNum = Long.parseLong(coilNo);
} catch (NumberFormatException e) {
log.warn("钢卷号{}转换为数字失败:{}", coilNo, e.getMessage());
continue;
}
// 更新最大值
if (currentNum > maxNum) {
maxNum = currentNum;
maxEnterCoilNo = coilNo;
}
}
// 6. 结果赋值 + 日志记录
result.put("maxEnterCoilNo", maxEnterCoilNo);
log.info("前缀{}对应的最大入场钢卷号:{}", enterCoilNoPrefix, maxEnterCoilNo == null ? "无匹配记录" : maxEnterCoilNo);
} catch (Exception e) {
// 7. 全局异常捕获,避免接口报错
log.error("查询最大入场钢卷号失败,前缀:{}", enterCoilNoPrefix, e);
result.put("maxEnterCoilNo", null);
}
return result;
}
}