Files
klp-oa/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java
Joshi c0f7c699a8 fix(wms): 修复钢卷更新时二维码步骤类型检查逻辑
- 在非退火步骤类型时才进行独占状态检查
- 添加对qrcodeStepType为空的边界情况处理
- 修复二维码内容更新中的空指针检查逻辑
2026-04-16 16:30:05 +08:00

5081 lines
237 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.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
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.DateUtils;
import com.klp.common.utils.StringUtils;
import com.klp.domain.*;
import com.klp.domain.bo.*;
import com.klp.domain.vo.*;
import com.klp.domain.vo.dashboard.CoilTrimStatisticsVo;
import com.klp.domain.vo.dashboard.CoilTrimRawVo;
import com.klp.domain.vo.dashboard.TrimWidthStatisticsVo;
import com.klp.domain.vo.dashboard.CategoryWidthStatisticsVo;
import com.klp.domain.vo.dashboard.CategoryWidthRawVo;
import com.klp.domain.WmsCoilPendingAction;
import com.klp.domain.bo.WmsCoilPendingActionBo;
import com.klp.mapper.*;
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.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.IWmsCoilPendingActionService;
import com.klp.service.IWmsProductService;
import com.klp.service.IWmsCoilAbnormalService;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.*;
import java.util.stream.Collectors;
import java.math.BigDecimal;
import java.util.Arrays;
/**
* 钢卷物料表Service业务层处理
*
* @author Joshi
* @date 2025-07-18
*/
@Slf4j
@RequiredArgsConstructor
@Service
@DS("master")
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 IWmsCoilPendingActionService coilPendingActionService;
private final WmsCoilPendingActionMapper coilPendingActionMapper;
private final IWmsProductService productService;
private final ISysUserService userService;
// private final WmsDeliveryPlanMapper deliveryPlanMapper;
private final WmsProductMapper productMapper;
private final WmsRawMaterialMapper rawMaterialMapper;
private final WmsDeliveryWaybillDetailMapper deliveryWaybillDetailMapper;
private final WmsCoilWarehouseOperationLogMapper wmsCoilWarehouseOperationLogMapper;
private final IWmsCoilAbnormalService coilAbnormalService;
/**
* 查询钢卷物料表
*/
@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());
}
}
}
}
// 填充到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.setItemName(rawMaterial.getRawMaterialName());
vo.setItemCode(rawMaterial.getRawMaterialCode());
vo.setSpecification(rawMaterial.getSpecification());
vo.setMaterial(rawMaterial.getMaterial());
vo.setManufacturer(rawMaterial.getManufacturer());
vo.setSurfaceTreatmentDesc(rawMaterial.getSurfaceTreatmentDesc());
vo.setZincLayer(rawMaterial.getZincLayer());
}
if ("product".equals(vo.getItemType()) && vo.getItemId() != null && productMap.containsKey(vo.getItemId())) {
WmsProductVo product = productMap.get(vo.getItemId());
vo.setItemId(product.getProductId());
vo.setItemName(product.getProductName());
vo.setItemCode(product.getProductCode());
vo.setSpecification(product.getSpecification());
vo.setMaterial(product.getMaterial());
vo.setManufacturer(product.getManufacturer());
vo.setSurfaceTreatmentDesc(product.getSurfaceTreatmentDesc());
vo.setZincLayer(product.getZincLayer());
}
}
}
/**
* 填充关联对象信息(单个对象,保留用于兼容性)
*/
private void fillRelatedObjects(WmsMaterialCoilVo vo) {
// 查询所在库区信息
if (vo.getWarehouseId() != null) {
WmsWarehouseVo warehouse = warehouseService.queryById(vo.getWarehouseId());
vo.setWarehouse(warehouse);
vo.setWarehouseName(warehouse.getWarehouseName());
}
// 查询下一库区信息
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());
vo.setActualWarehouse(actualWarehouse);
}
}
// 查询二维码信息
if (vo.getQrcodeRecordId() != null) {
WmsGenerateRecordVo qrcodeRecord = generateRecordService.queryById(vo.getQrcodeRecordId());
vo.setQrcodeRecord(qrcodeRecord);
}
// 查询原材料并直接回填扁平字段(避免返回嵌套对象)
if ("raw_material".equals(vo.getItemType()) && vo.getItemId() != null) {
WmsRawMaterialVo rawMaterial = rawMaterialService.queryById(vo.getItemId());
if (rawMaterial != null) {
vo.setItemName(rawMaterial.getRawMaterialName());
vo.setItemCode(rawMaterial.getRawMaterialCode());
vo.setSpecification(rawMaterial.getSpecification());
vo.setMaterial(rawMaterial.getMaterial());
vo.setManufacturer(rawMaterial.getManufacturer());
vo.setSurfaceTreatmentDesc(rawMaterial.getSurfaceTreatmentDesc());
// 锌层
if (rawMaterial.getZincLayer() != null) {
vo.setZincLayer(rawMaterial.getZincLayer());
}
}
}
// 查询产品并直接回填扁平字段(避免返回嵌套对象)
if ("product".equals(vo.getItemType()) && vo.getItemId() != null) {
WmsProductVo product = productService.queryById(vo.getItemId());
if (product != null) {
vo.setItemId(product.getProductId());
vo.setItemName(product.getProductName());
vo.setItemCode(product.getProductCode());
vo.setSpecification(product.getSpecification());
vo.setMaterial(product.getMaterial());
vo.setManufacturer(product.getManufacturer());
vo.setSurfaceTreatmentDesc(product.getSurfaceTreatmentDesc());
// 锌层
if (product.getZincLayer() != null) {
vo.setZincLayer(product.getZincLayer());
}
}
}
}
/**
* 查询钢卷物料表列表
*/
@Override
public TableDataInfo<WmsMaterialCoilVo> queryPageList(WmsMaterialCoilBo bo, PageQuery pageQuery) {
Page<WmsMaterialCoilVo> result = queryMaterialCoilPage(bo, pageQuery);
List<WmsMaterialCoilVo> records = result.getRecords();
if (records == null || records.isEmpty()) {
return TableDataInfo.build(result);
}
fillPageCommonFields(records);
return TableDataInfo.build(result);
}
@Override
public TableDataInfo<WmsMaterialCoilBindVo> queryPageListWithBindInfo(WmsMaterialCoilBo bo, PageQuery pageQuery) {
Page<WmsMaterialCoilVo> result = queryMaterialCoilPage(bo, pageQuery);
List<WmsMaterialCoilVo> baseRecords = result.getRecords();
if (baseRecords == null || baseRecords.isEmpty()) {
return TableDataInfo.build(new Page<>(result.getCurrent(), result.getSize(), result.getTotal()));
}
List<WmsMaterialCoilBindVo> bindRecords = baseRecords.stream().map(item -> {
WmsMaterialCoilBindVo bindVo = new WmsMaterialCoilBindVo();
BeanUtil.copyProperties(item, bindVo);
return bindVo;
}).collect(Collectors.toList());
fillBindInfoForPage(bindRecords);
fillPageCommonFields(bindRecords);
Page<WmsMaterialCoilBindVo> bindResult = new Page<>(result.getCurrent(), result.getSize(), result.getTotal());
bindResult.setRecords(bindRecords);
return TableDataInfo.build(bindResult);
}
/**
* 统计筛选条件下的全量汇总数据高性能只查sum/count
* 独立的统计接口,不影响分页查询
*/
@Override
public Map<String, BigDecimal> getStatistics(WmsMaterialCoilBo bo) {
QueryWrapper<WmsMaterialCoil> qw = buildQueryWrapperPlus(bo);
return selectMaterialCoilStatistics(qw);
}
private Page<WmsMaterialCoilVo> queryMaterialCoilPage(WmsMaterialCoilBo bo, PageQuery pageQuery) {
QueryWrapper<WmsMaterialCoil> qw = buildQueryWrapperPlus(bo);
Page<WmsMaterialCoilVo> result;
if (Boolean.TRUE.equals(bo.getOrderBy())) {
result = baseMapper.selectVoPagePlusOrderBy(pageQuery.build(), qw);
} else {
result = baseMapper.selectVoPagePlus(pageQuery.build(), qw);
}
return result;
}
private void fillPageCommonFields(List<? extends WmsMaterialCoilVo> records) {
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()));
}
}
}
}
/**
* 统计筛选条件下的全量汇总数据高性能只查sum/count
*/
private Map<String, BigDecimal> selectMaterialCoilStatistics(QueryWrapper<WmsMaterialCoil> qw) {
Map<String, BigDecimal> result = new HashMap<>();
result.put("total_gross_weight", BigDecimal.ZERO);
result.put("total_net_weight", BigDecimal.ZERO);
result.put("total_count", BigDecimal.ZERO);
try {
Map<String, Object> stats = baseMapper.selectStatistics(qw);
if (stats != null) {
Object grossWeight = stats.get("total_gross_weight");
Object netWeight = stats.get("total_net_weight");
result.put("total_gross_weight", grossWeight != null ? new BigDecimal(grossWeight.toString()) : BigDecimal.ZERO);
result.put("total_net_weight", netWeight != null ? new BigDecimal(netWeight.toString()) : BigDecimal.ZERO);
}
} catch (Exception e) {
log.warn("统计查询失败: {}", e.getMessage());
}
return result;
}
private void fillBindInfoForPage(List<WmsMaterialCoilBindVo> records) {
// 绑定信息仅用于专用接口,避免污染通用分页结构
try {
List<Long> coilIds = records.stream()
.map(WmsMaterialCoilVo::getCoilId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (coilIds.isEmpty()) {
return;
}
Map<Long, WmsCoilBindInfoVo> bindMap = deliveryWaybillDetailMapper
.selectBindInfoByCoilIds(coilIds)
.stream()
.collect(Collectors.toMap(
WmsCoilBindInfoVo::getCoilId,
v -> v,
(a, b) -> a
));
for (WmsMaterialCoilBindVo vo : records) {
WmsCoilBindInfoVo bind = bindMap.get(vo.getCoilId());
if (bind != null) {
vo.setBound(Boolean.TRUE);
vo.setBindDetailId(bind.getDetailId());
vo.setBindWaybillId(bind.getWaybillId());
vo.setBindWaybillNo(bind.getWaybillNo());
vo.setBindWaybillName(bind.getWaybillName());
vo.setBindPlanId(bind.getPlanId());
vo.setBindPlanName(bind.getPlanName());
vo.setBindPlanDate(bind.getPlanDate());
vo.setBindLicensePlate(bind.getLicensePlate());
vo.setBindConsigneeUnit(bind.getConsigneeUnit());
vo.setBindSenderUnit(bind.getSenderUnit());
vo.setBindDeliveryTime(bind.getDeliveryTime());
vo.setBindWeighbridge(bind.getWeighbridge());
vo.setBindSalesPerson(bind.getSalesPerson());
vo.setBindPrincipal(bind.getPrincipal());
vo.setBindPrincipalPhone(bind.getPrincipalPhone());
vo.setBindWaybillStatus(bind.getWaybillStatus());
vo.setBindWaybillRemark(bind.getWaybillRemark());
vo.setBindDetailSettlementType(bind.getDetailSettlementType());
vo.setBindDetailUnitPrice(bind.getDetailUnitPrice());
} else {
vo.setBound(Boolean.FALSE);
}
}
} catch (Exception ignore) {
// 绑定信息属于增强字段,查询失败时不影响钢卷列表主流程
}
}
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());
// 切边要求
qw.eq(StringUtils.isNotBlank(bo.getTrimmingRequirement()), "mc.trimming_requirement", bo.getTrimmingRequirement());
// 打包状态
qw.eq(StringUtils.isNotBlank(bo.getPackingStatus()), "mc.packing_status", bo.getPackingStatus());
// 质量状态
qw.eq(StringUtils.isNotBlank(bo.getQualityStatus()), "mc.quality_status", bo.getQualityStatus());
// 包装
qw.eq(StringUtils.isNotBlank(bo.getPackagingRequirement()), "mc.packaging_requirement", bo.getPackagingRequirement());
// 锌层种类和调制度
qw.eq(StringUtils.isNotBlank(bo.getCoatingType()), "mc.coating_type", bo.getCoatingType());
qw.eq(StringUtils.isNotBlank(bo.getTemperGrade()), "mc.temper_grade", bo.getTemperGrade());
// 独占状态
qw.eq(bo.getExclusiveStatus() != null, "mc.exclusive_status", bo.getExclusiveStatus());
// 业务用途
qw.eq(StringUtils.isNotBlank(bo.getBusinessPurpose()), "mc.business_purpose", bo.getBusinessPurpose());
// 是否与订单相关0=否1=是)
qw.eq(bo.getIsRelatedToOrder() != null, "mc.is_related_to_order", bo.getIsRelatedToOrder());
//逻辑删除
qw.eq("mc.del_flag", 0);
// 统一处理 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()));
}
List<String> qualityStatusList = new ArrayList<>();
if (StringUtils.isNotBlank(bo.getQualityStatusCsv())) {
String[] qualityStatusArray = bo.getQualityStatusCsv().split(",");
for (String qualityStatusStr : qualityStatusArray) {
if (StringUtils.isNotBlank(qualityStatusStr)) {
try {
qualityStatusList.add(qualityStatusStr.trim());
} catch (NumberFormatException ignore) {
}
}
}
}
if (!qualityStatusList.isEmpty()) {
qw.in("mc.quality_status", qualityStatusList.stream().distinct().collect(Collectors.toList()));
}
// 统一处理 nextWarehouseId 与 nextWarehouseIds
List<Long> nextWarehouseIdList = new ArrayList<>();
if (bo.getNextWarehouseId() != null) {
nextWarehouseIdList.add(bo.getNextWarehouseId());
}
if (StringUtils.isNotBlank(bo.getNextWarehouseIds())) {
String[] nextWarehouseIdArray = bo.getNextWarehouseIds().split(",");
for (String nextWarehouseIdStr : nextWarehouseIdArray) {
if (StringUtils.isNotBlank(nextWarehouseIdStr)) {
try {
nextWarehouseIdList.add(Long.parseLong(nextWarehouseIdStr.trim()));
} catch (NumberFormatException ignore) {
}
}
}
}
if (!nextWarehouseIdList.isEmpty()) {
qw.in("mc.next_warehouse_id", nextWarehouseIdList.stream().distinct().collect(Collectors.toList()));
}
// 新增长度
qw.eq(bo.getLength() != null, "mc.length", bo.getLength());
// 实测长度
qw.eq(bo.getActualLength() != null, "mc.actual_length", bo.getActualLength());
// 实测宽度
qw.eq(bo.getActualWidth() != null, "mc.actual_width", bo.getActualWidth());
// 实测厚度
qw.eq(StringUtils.isNotBlank(bo.getActualThickness()), "mc.actual_thickness", bo.getActualThickness());
// 生产开始时间
qw.eq(bo.getProductionStartTime() != null, "mc.production_start_time", bo.getProductionStartTime());
// 生产结束时间
qw.eq(bo.getProductionEndTime() != null, "mc.production_end_time", bo.getProductionEndTime());
// 生产耗时
qw.eq(bo.getProductionDuration() != null, "mc.production_duration", bo.getProductionDuration());
// 预留宽度
qw.eq(bo.getReservedWidth() != null, "mc.reserved_width", bo.getReservedWidth());
// 钢卷表面处理
qw.eq(bo.getCoilSurfaceTreatment() != null, "mc.coil_surface_treatment", bo.getCoilSurfaceTreatment());
// 调拨类型
qw.eq(StringUtils.isNotBlank(bo.getTransferType()), "mc.transfer_type", bo.getTransferType());
// 如果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());
}
}
// 销售人员姓名模糊查询
if (bo.getSaleName() != null && !bo.getSaleName().isEmpty()) {
qw.like("su.nick_name", bo.getSaleName());
}
// 仅查询废品:质量状态为 D+、D、D-
if (Boolean.TRUE.equals(bo.getOnlyScrap())) {
qw.in("mc.quality_status", java.util.Arrays.asList("C+", "C", "C-", "D+", "D", "D-"));
}
// 排除已被发货单明细绑定的钢卷SQL级过滤分页总数准确
if (Boolean.TRUE.equals(bo.getExcludeBound())) {
qw.apply("NOT EXISTS (SELECT 1 FROM wms_delivery_waybill_detail d WHERE d.del_flag = 0 AND d.coil_id = mc.coil_id)");
}
// 排除已打包的钢卷(防止重复打包)
if (Boolean.TRUE.equals(bo.getExcludePacked())) {
qw.apply("NOT EXISTS (SELECT 1 FROM wms_coil_packing_detail pd WHERE pd.del_flag = 0 AND pd.coil_id = mc.coil_id)");
}
// 组合 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 && hasAnyItemFilter) {
// 执行筛选逻辑(和上面完全一样)
qw.eq("mc.item_type", bo.getSelectType());
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))");
// }
//把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 (StringUtils.isNotBlank(bo.getCreateBys())) {
List<String> createByList = Arrays.stream(bo.getCreateBys().split(","))
.filter(StringUtils::isNotBlank)
.map(String::trim)
.collect(Collectors.toList());
if (!createByList.isEmpty()) {
qw.in("mc.create_by", createByList);
}
}
//根据异常数量筛选(大于等于指定值)
if (bo.getMinAbnormalCount() != null) {
qw.apply("COALESCE(ca.abnormal_count, 0) >= {0}", bo.getMinAbnormalCount());
}
// 排序:
// 已绑定钢卷列表中,未发货(status=0)的排在前面
if (Boolean.TRUE.equals(bo.getStatusFirst())) {
qw.orderByAsc("mc.status = 1");
qw.orderByAsc("mc.status");
} else if (Boolean.TRUE.equals(bo.getOrderBy())) {
// 按库位全局排序号排序
qw.orderByAsc("mc.actual_warehouse_id IS NULL");
// 全局交错排序(仅三/四级):使用 SQL 中计算好的辅助字段,避免 MP 注入拦截
qw.orderByAsc("aw_sort_key");
qw.orderByAsc("aw_layer_key");
qw.orderByAsc("aw_id_key");
} else if (Boolean.TRUE.equals(bo.getOrderByAbnormal())) {
// 按异常排序:异常的钢卷在前按创建时间倒序,没异常的钢卷在后按创建时间倒序
qw.orderByAsc("CASE WHEN COALESCE(ca.abnormal_count, 0) > 0 THEN 0 ELSE 1 END");
qw.orderByDesc("mc.create_time");
} else {
//根据创建时间倒叙
qw.orderByDesc("mc.create_time");
}
return qw;
}
@Override
public Map<String, Object> getDuplicateCoilGroups() {
// 使用优化的数据库查询方法,直接获取重复入场卷号的钢卷信息
List<WmsMaterialCoilVo> enterDuplicates = baseMapper.selectDuplicateEnterCoilNoList();
// 使用优化的数据库查询方法,直接获取重复当前卷号的钢卷信息
List<WmsMaterialCoilVo> currentDuplicates = baseMapper.selectDuplicateCurrentCoilNoList();
// 按入场卷号分组重复项
Map<String, List<WmsMaterialCoilVo>> enterGrouped = enterDuplicates.stream()
.filter(e -> StringUtils.isNotBlank(e.getEnterCoilNo()))
.collect(Collectors.groupingBy(WmsMaterialCoilVo::getEnterCoilNo));
// 按当前卷号分组重复项
Map<String, List<WmsMaterialCoilVo>> currentGrouped = currentDuplicates.stream()
.filter(e -> StringUtils.isNotBlank(e.getCurrentCoilNo()))
.collect(Collectors.groupingBy(WmsMaterialCoilVo::getCurrentCoilNo));
// 构建入场卷号重复组
List<Map<String, Object>> enterGroups = enterGrouped.entrySet().stream()
.filter(entry -> entry.getValue() != null && entry.getValue().size() > 1)
.map(entry -> {
Map<String, Object> group = new HashMap<>();
group.put("enterCoilNo", entry.getKey());
group.put("coils", entry.getValue());
return group;
})
.collect(Collectors.toList());
// 构建当前卷号重复组
List<Map<String, Object>> currentGroups = currentGrouped.entrySet().stream()
.filter(entry -> entry.getValue() != null && entry.getValue().size() > 1)
.map(entry -> {
Map<String, Object> group = new HashMap<>();
group.put("currentCoilNo", entry.getKey());
group.put("coils", entry.getValue());
return group;
})
.collect(Collectors.toList());
Map<String, Object> result = new HashMap<>();
result.put("enterGroups", enterGroups);
result.put("currentGroups", currentGroups);
return result;
}
/**
* 构建 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 WmsMaterialCoilLocationGridVo queryLocationGrid(Long actualWarehouseId,
String itemType,
String enterCoilNo,
String currentCoilNo,
String manufacturer) {
WmsMaterialCoilLocationGridVo result = new WmsMaterialCoilLocationGridVo();
if (actualWarehouseId == null) {
result.setWarehouses(Collections.emptyList());
result.setCoils(Collections.emptyList());
result.setTotal(0);
return result;
}
WmsActualWarehouseBo warehouseBo = new WmsActualWarehouseBo();
warehouseBo.setParentId(actualWarehouseId);
List<WmsActualWarehouseVo> warehouses = actualWarehouseService.queryList(warehouseBo);
WmsMaterialCoilBo coilBo = new WmsMaterialCoilBo();
coilBo.setActualWarehouseId(actualWarehouseId);
coilBo.setItemType(StringUtils.isBlank(itemType) ? "raw_material" : itemType);
coilBo.setEnterCoilNo(enterCoilNo);
coilBo.setCurrentCoilNo(currentCoilNo);
coilBo.setItemManufacturer(manufacturer);
coilBo.setDataType(1);
List<WmsMaterialCoilVo> coils = this.queryList(coilBo);
result.setWarehouses(warehouses);
result.setCoils(coils);
result.setTotal(coils.size());
return result;
}
/**
* 查询钢卷物料表列表
*/
@Override
public List<WmsMaterialCoilVo> queryList(WmsMaterialCoilBo bo) {
QueryWrapper<WmsMaterialCoil> lqw = buildQueryWrapperPlus(bo);
List<WmsMaterialCoilVo> list = baseMapper.selectVoListWithDynamicJoin(lqw);
return list;
}
@Override
public String queryQualityStatusByWarehouseIdAndCurrentCoilNo(Long warehouseId, String currentCoilNo) {
if (warehouseId == null || StringUtils.isBlank(currentCoilNo)) {
return null;
}
LambdaQueryWrapper<WmsMaterialCoil> lqw = Wrappers.lambdaQuery();
lqw.select(WmsMaterialCoil::getQualityStatus);
lqw.eq(WmsMaterialCoil::getDelFlag, 0);
lqw.eq(WmsMaterialCoil::getDataType, 1);
lqw.eq(WmsMaterialCoil::getWarehouseId, warehouseId);
lqw.eq(WmsMaterialCoil::getCurrentCoilNo, currentCoilNo);
lqw.last("LIMIT 1");
WmsMaterialCoil one = baseMapper.selectOne(lqw);
return one == null ? null : one.getQualityStatus();
}
/**
* 新增钢卷物料表
*/
@Override
@Transactional(rollbackFor = Exception.class)
public WmsMaterialCoilVo insertByBo(WmsMaterialCoilBo bo) {
// 1. 生成二维码
Long qrcodeRecordId = generateQrcodeForInsert(bo);
bo.setQrcodeRecordId(qrcodeRecordId);
// 2. 查找或创建stock
findOrCreateStock(bo);
// 处理实际库区:-1 表示空置库,统一转 NULL
if (bo.getActualWarehouseId() != null && bo.getActualWarehouseId() == -1) {
bo.setActualWarehouseId(null);
}
// 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);
// 新增时间
step1.put("create_time", DateUtils.getNowDate());
// 新增创建人
step1.put("create_by", LoginHelper.getNickName());
// 判断是合卷还是分卷
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 String updateByBo(WmsMaterialCoilBo bo, String qrcodeStepType) {
// 判断是否批量更新
if (bo.getNewCoils() != null && !bo.getNewCoils().isEmpty()) {
// 批量更新逻辑(分卷/合卷)
return updateByBatch(bo); // 分卷返回逗号分隔的ID合卷返回单个ID
} else {
// 单个更新逻辑需要coilId
if (bo.getCoilId() == null) {
throw new RuntimeException("钢卷ID不能为空");
}
return updateBySingle(bo, qrcodeStepType); // 返回新钢卷ID字符串
}
}
/**
* 简单更新钢卷物料表
* 直接更新属性内容,不进行历史记录处理
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateSimple(WmsMaterialCoilBo bo) {
if (bo.getCoilId() == null) {
throw new RuntimeException("钢卷ID不能为空");
}
// 检查独占状态
validateCoilOperationPermission(bo.getCoilId(), "简单更新");
// 查询原钢卷是否存在
WmsMaterialCoil oldCoil = baseMapper.selectById(bo.getCoilId());
if (oldCoil == null) {
throw new RuntimeException("钢卷不存在");
}
// 校验itemId是否存在根据itemType
if (bo.getItemId() != null && StringUtils.isNotBlank(bo.getItemType())) {
if ("raw_material".equals(bo.getItemType())) {
WmsRawMaterial rawMaterial = rawMaterialMapper.selectById(bo.getItemId());
if (rawMaterial == null) {
throw new RuntimeException("原材料不存在ID: " + bo.getItemId());
}
} else if ("product".equals(bo.getItemType())) {
WmsProduct product = productMapper.selectById(bo.getItemId());
if (product == null) {
throw new RuntimeException("产品不存在ID: " + bo.getItemId());
}
} else {
throw new RuntimeException("无效的物品类型: " + bo.getItemType());
}
}
// 若修改实际库位,先进行校验
if (bo.getActualWarehouseId() != null) {
// 若与原库位不同,常规占用校验;若相同,仅校验拆分/层级
Long ignoreOccupiedId = Objects.equals(bo.getActualWarehouseId(), oldCoil.getActualWarehouseId())
? bo.getActualWarehouseId() : null;
validateActualWarehouseForAssign(bo.getActualWarehouseId(), ignoreOccupiedId);
}
// 1. 历史钢卷dataType=0禁止修改实际库区actualWarehouseId非空且不等于-1
if (0 == oldCoil.getDataType()) { // 原钢卷是历史钢卷
// 场景1传入了新的实际库区ID且不是置空-1禁止操作
if (bo.getActualWarehouseId() != null && !bo.getActualWarehouseId().equals(-1L)) {
throw new RuntimeException("历史钢卷不允许占用实际库区!");
}
}
// 2. 已发货的钢卷不能占用实际库区,给出提醒
if (bo.getStatus() != null && bo.getStatus().equals(1)) {
if (bo.getActualWarehouseId() != null && !bo.getActualWarehouseId().equals(-1L)) {
throw new RuntimeException("已发货的钢卷不允许占用实际库区!");
}
}
// 直接更新钢卷属性
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) {
// 已发货的钢卷不能占用实际库区,释放旧库区
if (bo.getStatus() != null && bo.getStatus().equals(1)) {
if (oldCoil.getActualWarehouseId() != null) {
updateActualWarehouseEnableStatus(oldCoil.getActualWarehouseId(), null);
}
} else if (bo.getActualWarehouseId() != null && !bo.getActualWarehouseId().equals(-1L)
&& !bo.getActualWarehouseId().equals(oldCoil.getActualWarehouseId())) {
// 只有当新的库区ID不为空且不为-1且与原库区ID不同时才更新库区状态
updateActualWarehouseEnableStatus(oldCoil.getActualWarehouseId(), bo.getActualWarehouseId());
}
}
// 简单更新记录调拨日志operationType=3
// 如果老的和新的实际库区相等则不用创建记录
Long oldActualWarehouseId = oldCoil.getActualWarehouseId();
Long newActualWarehouseId = bo.getActualWarehouseId();
if (!Objects.equals(oldActualWarehouseId, newActualWarehouseId)) {
// 如果以前有真实库区,加一条调拨的出库记录
if (oldActualWarehouseId != null && oldActualWarehouseId != -1L) {
recordWarehouseOperationLog(bo.getCoilId(), oldActualWarehouseId, 3, 2, "简单更新-调拨出库");
}
// 如果前端传了新的(非-1表示入库-1表示置空出库
if (newActualWarehouseId != null && newActualWarehouseId != -1L) {
// 有新库区,算入库
recordWarehouseOperationLog(bo.getCoilId(), newActualWarehouseId, 3, 1, "简单更新-调拨入库");
}
}
return flag;
}
/**
* 单个更新
* @param bo 更新数据
* @param qrcodeStepType 二维码步骤类型null/空-默认"更新", "annealing"-退火
* @return 新钢卷ID字符串
*/
private String updateBySingle(WmsMaterialCoilBo bo, String qrcodeStepType) {
if (StringUtils.isNotBlank(qrcodeStepType) && !"annealing".equals(qrcodeStepType)){
// 检查独占状态
validateCoilOperationPermission(bo.getCoilId(), "单个更新");
}
// 查询原钢卷
WmsMaterialCoil oldCoil = baseMapper.selectById(bo.getCoilId());
if (oldCoil == null) {
throw new RuntimeException("原钢卷不存在");
}
// oldCoil 如果是历史卷也就是date_type=0 的时候就是历史钢卷
if (oldCoil.getDataType() == 0) {
throw new RuntimeException("原钢卷已被更新");
}
// 若修改实际库位,先进行校验
if (bo.getActualWarehouseId() != null) {
Long ignoreOccupiedId = Objects.equals(bo.getActualWarehouseId(), oldCoil.getActualWarehouseId())
? bo.getActualWarehouseId() : null;
validateActualWarehouseForAssign(bo.getActualWarehouseId(), ignoreOccupiedId);
}
if (oldCoil.getActualWarehouseId() != null){
updateActualWarehouseEnableStatus(oldCoil.getActualWarehouseId(), null);
}
// 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.setTransferType(null);
newCoil.setQrcodeRecordId(oldCoil.getQrcodeRecordId()); // 继续使用原二维码
// 继承原记录的关键字段
if (newCoil.getEnterCoilNo() == null) {
newCoil.setEnterCoilNo(oldCoil.getEnterCoilNo());
}
if (newCoil.getSupplierCoilNo() == null) {
newCoil.setSupplierCoilNo(oldCoil.getSupplierCoilNo());
}
validEntityBeforeSave(newCoil);
// 把老记录的coilId赋值给新纪录的parentCoilId
newCoil.setParentCoilId(String.valueOf(oldCoil.getCoilId()));
// 插入新记录
boolean flag = baseMapper.insert(newCoil) > 0;
if (!flag) {
throw new RuntimeException("创建新钢卷失败");
}
// 如果实际库区id为-1则清空钢卷上的实际库区绑定
if (bo.getActualWarehouseId() != null && bo.getActualWarehouseId().equals(-1L)) {
clearActualWarehouseBinding(newCoil.getActualWarehouseId(), newCoil.getCoilId());
}
// 3. 更新二维码内容添加更新步骤并更新current_coil_id
if (oldCoil.getQrcodeRecordId() != null) {
if (StringUtils.isNotBlank(qrcodeStepType) && "annealing".equals(qrcodeStepType)) {
updateQrcodeContentForCustomStep(oldCoil, bo, newCoil.getCoilId(), "退火", "退火操作");
} else {
updateQrcodeContentForNormalUpdate(oldCoil, bo, newCoil.getCoilId());
}
}
// 只有当新的库区ID不为空时更新库区状态
if (bo.getActualWarehouseId() != null) {
updateActualWarehouseEnableStatus(null, bo.getActualWarehouseId());
}
// 单个更新记录加工日志operationType=2
// 老的实际库区存在则加一条加工出库记录使用老钢卷ID
// 新的实际库区存在则加一条加工入库记录使用新钢卷ID
// 新的传-1置空则算一条出库记录
// 就算老的和新的实际库区相等也应该建两条因为钢卷ID不一样
Long oldActualWarehouseId = oldCoil.getActualWarehouseId();
Long newActualWarehouseId = bo.getActualWarehouseId();
// 老的实际库区存在,加一条加工出库记录
if (oldActualWarehouseId != null && oldActualWarehouseId != -1L) {
recordWarehouseOperationLog(oldCoil.getCoilId(), oldActualWarehouseId, 2, 2, "单个更新-加工出库");
}
// 新的实际库区存在,加一条加工入库记录
if (newActualWarehouseId != null && newActualWarehouseId != -1L) {
// 有新库区,算入库
recordWarehouseOperationLog(newCoil.getCoilId(), newActualWarehouseId, 2, 1, "单个更新-加工入库");
}
// 插入钢卷异常信息
if (bo.getAbnormals() != null && !bo.getAbnormals().isEmpty()) {
for (WmsCoilAbnormalBo abnormalBo : bo.getAbnormals()) {
abnormalBo.setCoilId(newCoil.getCoilId());
coilAbnormalService.insertByBo(abnormalBo);
}
}
// 返回新钢卷ID字符串
return String.valueOf(newCoil.getCoilId());
}
/**
* 更新实际库区的启用状态
* @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 && newActualWarehouseId != -1L) {
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 && actualWarehouseId != -1L) {
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);
}
/**
* 获取钢卷的独占状态
* @param coilId 钢卷ID
* @return exclusiveStatus值null或0表示未独占
*/
private Integer getExclusiveStatus(Long coilId) {
if (coilId == null) {
return null;
}
WmsMaterialCoil coil = baseMapper.selectById(coilId);
return coil == null ? null : coil.getExclusiveStatus();
}
/**
* 验证钢卷操作权限(检查独占状态)
* @param coilId 钢卷ID
* @param operation 操作名称(用于错误信息)
*/
private void validateCoilOperationPermission(Long coilId, String operation) {
Integer status = getExclusiveStatus(coilId);
if (status == null || status == 0) {
return;
}
if (status == 1) {
throw new RuntimeException("钢卷正在进行单步分卷操作中,无法执行" + operation + "操作");
}
if (status == 2) {
throw new RuntimeException("钢卷正在进行退火操作中,无法执行" + operation + "操作");
}
throw new RuntimeException("钢卷已被独占,无法执行" + operation + "操作");
}
/**
* 批量更新(分卷/合卷)
*/
/**
* 批量更新(分卷/合卷)
* @return 分卷时返回逗号分隔的新钢卷ID字符串合卷时返回单个新钢卷ID字符串
*/
private String updateByBatch(WmsMaterialCoilBo bo) {
// 检查独占状态
if (bo.getCoilId() != null) {
validateCoilOperationPermission(bo.getCoilId(), "批量更新");
}
// 查询原钢卷(分卷时需要,合卷时可能不需要)
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.setTransferType(null);
// 继承原钢卷的基本信息(强制继承,不能修改的字段)
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);
// 把老记录的coilId赋值给新纪录的parentCoilId
newCoil.setParentCoilId(String.valueOf(oldCoil.getCoilId()));
baseMapper.insert(newCoil);
newCoils.add(newCoil);
// 更新二维码内容中的coilId
updateQrcodeCoilId(newQrcodeId, newCoil.getCoilId());
// 更新实际库区的启用状态
updateActualWarehouseEnableStatus(oldCoil.getActualWarehouseId(), newCoilBo.getActualWarehouseId());
// 插入子钢卷的异常信息
if (newCoilBo.getAbnormals() != null && !newCoilBo.getAbnormals().isEmpty()) {
for (WmsCoilAbnormalBo abnormalBo : newCoilBo.getAbnormals()) {
abnormalBo.setCoilId(newCoil.getCoilId());
coilAbnormalService.insertByBo(abnormalBo);
}
}
}
// 更新所有子钢卷二维码中的分卷步骤child_coil_ids
List<Long> newCoilIds = newCoils.stream()
.map(WmsMaterialCoil::getCoilId)
.collect(Collectors.toList());
String childCoilIdsStr = newCoilIds.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
for (WmsMaterialCoil newChildCoil : newCoils) {
if (newChildCoil.getQrcodeRecordId() != null) {
// 更新该子钢卷的分卷步骤中的child_coil_ids字段
updateChildCoilIdsQrcodeCoilId(oldCoil.getCoilId(), newChildCoil.getQrcodeRecordId(), newChildCoil.getCoilId(), childCoilIdsStr);
} else {
log.warn("子钢卷缺少二维码记录ID跳过分卷步骤child_coil_ids更新coilId: {}", newChildCoil.getCoilId());
}
}
// 分卷操作记录加工日志operationType=2
// 老的实际库区存在则加一条加工出库记录
Long oldSplitActualWarehouseId = oldCoil.getActualWarehouseId();
if (oldSplitActualWarehouseId != null && oldSplitActualWarehouseId != -1L) {
recordWarehouseOperationLog(oldCoil.getCoilId(), oldSplitActualWarehouseId, 2, 2, "分卷操作-加工出库");
}
// 为每个子钢卷的实际库区加一条加工入库记录(有的加,没有不加)
for (WmsMaterialCoil childCoil : newCoils) {
Long childActualWarehouseId = childCoil.getActualWarehouseId();
if (childActualWarehouseId != null && childActualWarehouseId != -1L) {
recordWarehouseOperationLog(childCoil.getCoilId(), childActualWarehouseId, 2, 1, "分卷操作-加工入库");
}
}
// 返回分卷后的所有新钢卷ID逗号分隔
return childCoilIdsStr;
} 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);
newCoil.setTransferType(null);
// 从第一个参与合卷的原始钢卷获取基本信息
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);
// 收集所有参与合卷的原始钢卷ID并用逗号分隔
List<Long> parentCoilIds = new ArrayList<>();
for (WmsMaterialCoilBo originalCoilBo : bo.getNewCoils()) {
if (originalCoilBo.getCoilId() != null) {
parentCoilIds.add(originalCoilBo.getCoilId());
}
}
String parentCoilIdsStr = parentCoilIds.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
newCoil.setParentCoilId(parentCoilIdsStr);
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);
}
// 合卷操作记录加工日志operationType=2
// 为每个参与合卷的原始钢卷加一条加工出库记录
for (WmsMaterialCoilBo originalCoilBo : bo.getNewCoils()) {
if (originalCoilBo.getCoilId() != null) {
WmsMaterialCoil originalCoil = baseMapper.selectById(originalCoilBo.getCoilId());
if (originalCoil != null && originalCoil.getActualWarehouseId() != null
&& originalCoil.getActualWarehouseId() != -1L) {
recordWarehouseOperationLog(originalCoil.getCoilId(), originalCoil.getActualWarehouseId(), 2, 2, "合卷操作-加工出库");
}
}
}
// 为新合卷的钢卷加一条加工入库记录
Long mergeNewActualWarehouseId = bo.getActualWarehouseId();
if (mergeNewActualWarehouseId != null && mergeNewActualWarehouseId != -1L) {
recordWarehouseOperationLog(newCoil.getCoilId(), mergeNewActualWarehouseId, 2, 1, "合卷操作-加工入库");
}
// 插入合卷钢卷的异常信息
if (bo.getAbnormals() != null && !bo.getAbnormals().isEmpty()) {
for (WmsCoilAbnormalBo abnormalBo : bo.getAbnormals()) {
abnormalBo.setCoilId(newCoil.getCoilId());
coilAbnormalService.insertByBo(abnormalBo);
}
}
// 合卷完成后设置新钢卷ID到bo对象中方便外部获取
bo.setMergedCoilId(newCoil.getCoilId());
// 返回合卷后的新钢卷ID
return String.valueOf(newCoil.getCoilId());
}
throw new RuntimeException("未知的批量更新类型");
}
/**
* 验证参与合卷的原始钢卷是否都是当前数据(而非历史数据)
* @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);
// 分卷产生的子钢卷ID将在后续步骤中设置
splitStep.put("operator", LoginHelper.getUsername()); // 操作者
// 分卷的操作时间
splitStep.put("create_time", DateUtils.getNowDate());
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());
}
if (bo.getItemId() != null && !bo.getItemId().equals(oldCoil.getItemId())) {
changedFields.add("物品ID: " + oldCoil.getItemId() + "" + bo.getItemId());
}
if (bo.getMaterialType() != null && !bo.getMaterialType().equals(oldCoil.getMaterialType())) {
changedFields.add("材料类型: " + oldCoil.getMaterialType() + "" + bo.getMaterialType());
}
if (bo.getLength() != null && !bo.getLength().equals(oldCoil.getLength())) {
changedFields.add("长度: " + oldCoil.getLength() + "" + bo.getLength());
}
if (bo.getTemperGrade() != null && !bo.getTemperGrade().equals(oldCoil.getTemperGrade())) {
changedFields.add("调制度: " + oldCoil.getTemperGrade() + "" + bo.getTemperGrade());
}
if (bo.getCoatingType() != null && !bo.getCoatingType().equals(oldCoil.getCoatingType())) {
changedFields.add("镀层种类: " + oldCoil.getCoatingType() + "" + bo.getCoatingType());
}
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());
}
}
/**
* 更新二维码内容(自定义步骤类型)
* 用于退火完成等场景action和operation可以自定义
* @param oldCoil 旧钢卷记录
* @param bo 更新数据
* @param newCoilId 新钢卷ID
* @param action 自定义action如"更新"、"退火"
* @param operation 自定义operation如"信息更新"、"退火操作"
*/
private void updateQrcodeContentForCustomStep(WmsMaterialCoil oldCoil, WmsMaterialCoilBo bo, Long newCoilId, String action, String operation) {
try {
WmsGenerateRecordVo oldRecord = generateRecordService.queryById(oldCoil.getQrcodeRecordId());
if (oldRecord == null) {
throw new RuntimeException("二维码记录不存在");
}
ObjectMapper objectMapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> contentMap = objectMapper.readValue(oldRecord.getContent(), Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null) {
steps = new ArrayList<>();
}
Map<String, Object> newStep = new HashMap<>();
newStep.put("step", steps.size() + 1);
newStep.put("action", action);
newStep.put("operation", operation);
newStep.put("old_current_coil_no", oldCoil.getCurrentCoilNo());
newStep.put("new_current_coil_no", bo.getCurrentCoilNo() != null ? bo.getCurrentCoilNo() : oldCoil.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.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());
}
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());
}
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 做一些业务上的校验,判断是否需要校验
}
// 检查独占状态
for (Long id : ids) {
validateCoilOperationPermission(id, "删除");
}
// 获取要删除的钢卷记录
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);
}
}
}
// 删除钢卷的时候要把钢卷对应的二维码状态改成 0 表示二维码失效
if (coilList != null) {
for (WmsMaterialCoil coil : coilList) {
if (coil.getQrcodeRecordId() != null) {
WmsGenerateRecordBo qrBo = new WmsGenerateRecordBo();
qrBo.setRecordId(coil.getQrcodeRecordId());
qrBo.setStatus(0); // 0=失效
generateRecordService.updateByBo(qrBo);
}
}
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 钢卷溯源查询
* 根据钢卷ID查询二维码解析content中的steps然后根据steps中的钢卷号反向查询数据库
*
* @param coilId 钢卷ID
* @param currentCoilNo 当前钢卷号(可选,用于查询特定子钢卷)
* @return 溯源结果(包含二维码信息和数据库记录)
*/
@Override
public Map<String, Object> queryTrace(Long coilId, String currentCoilNo) {
try {
WmsMaterialCoilVo coilVo = baseMapper.selectVoById(coilId);
if (coilVo == null) {
throw new RuntimeException("未找到对应的钢卷记录");
}
Long qrcodeRecordId = coilVo.getQrcodeRecordId();
if (qrcodeRecordId == null) {
throw new RuntimeException("该钢卷未关联二维码记录");
}
WmsGenerateRecordVo qrRecord = generateRecordService.queryById(qrcodeRecordId);
if (qrRecord == null) {
throw new RuntimeException("未找到对应的二维码记录");
}
String enterCoilNo = coilVo.getEnterCoilNo();
ObjectMapper objectMapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> contentMap = objectMapper.readValue(qrRecord.getContent(), Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
Set<String> allCoilNos = new HashSet<>();
Set<String> operatorUsernames = new HashSet<>();
if (steps != null) {
for (Map<String, Object> step : steps) {
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);
extractCoilId(step, "child_coil_ids", allCoilNos);
Object operator = step.get("operator");
if (operator != null) {
operatorUsernames.add(operator.toString());
}
}
}
Map<String, String> operatorNicknameMap = getOperatorNicknames(operatorUsernames);
List<Map<String, Object>> allSteps = new ArrayList<>(steps != null ? steps : new ArrayList<>());
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"));
allSteps.get(i).put("qrcode_serial", qrRecord.getSerialNumber());
allSteps.get(i).put("qrcode_id", qrRecord.getRecordId());
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);
}
}
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());
}
List<WmsMaterialCoilVo> result = new ArrayList<>();
if (!filteredCoilNos.isEmpty() && StringUtils.isNotBlank(enterCoilNo)) {
List<String> coilNoList = new ArrayList<>(filteredCoilNos);
if (coilNoList.size() <= 1000) {
LambdaQueryWrapper<WmsMaterialCoil> lqw = Wrappers.lambdaQuery();
lqw.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo);
lqw.in(WmsMaterialCoil::getCurrentCoilNo, coilNoList);
lqw.orderByAsc(WmsMaterialCoil::getCreateTime);
result = baseMapper.selectVoList(lqw);
} 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 (!result.isEmpty()) {
fillRelatedObjectsBatch(result);
}
}
if (result.isEmpty() && StringUtils.isNotBlank(enterCoilNo)) {
LambdaQueryWrapper<WmsMaterialCoil> lqw = Wrappers.lambdaQuery();
lqw.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo);
lqw.orderByAsc(WmsMaterialCoil::getCreateTime);
result = baseMapper.selectVoList(lqw);
if (!result.isEmpty()) {
fillRelatedObjectsBatch(result);
}
}
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("qrcode", qrRecord);
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;
}
/**
* 从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;
}
vo.setItemName(map.get("itemName") != null ? map.get("itemName").toString() : null);
vo.setItemCode(map.get("itemCode") != null ? map.get("itemCode").toString() : null);
vo.setSpecification(map.get("specification") != null ? map.get("specification").toString() : null);
vo.setMaterial(map.get("material") != null ? map.get("material").toString() : null);
vo.setSurfaceTreatmentDesc(map.get("surfaceTreatmentDesc") != null ? map.get("surfaceTreatmentDesc").toString() : null);
vo.setZincLayer(map.get("zincLayer") != null ? map.get("zincLayer").toString() : null);
vo.setManufacturer(map.get("manufacturer") != null ? map.get("manufacturer").toString() : null);
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) {
Map<Long, Date> coilIdCompleteTimeMap = new HashMap<>();
if ((bo.getCoilIds() == null || bo.getCoilIds().isEmpty())
&& bo.getActionIds() != null && !bo.getActionIds().isEmpty()) {
String[] actionIdArr = bo.getActionIds().split(",");
List<Long> actionIdList = Arrays.stream(actionIdArr)
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(Long::parseLong)
.collect(Collectors.toList());
LambdaQueryWrapper<WmsCoilPendingAction> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(WmsCoilPendingAction::getActionId, actionIdList);
List<WmsCoilPendingAction> actions = coilPendingActionMapper.selectList(queryWrapper);
if (actions != null && !actions.isEmpty()) {
for (WmsCoilPendingAction action : actions) {
coilIdCompleteTimeMap.put(action.getCoilId(), action.getCompleteTime());
}
bo.setCoilIds(actions.stream()
.map(a -> String.valueOf(a.getCoilId()))
.collect(Collectors.joining(",")));
}
}
QueryWrapper<WmsMaterialCoil> lqw = buildQueryWrapperPlus(bo);
List<WmsMaterialCoilExportVo> wmsMaterialCoilExportVos = baseMapper.selectExportList(lqw);
// 遍历数据,根据状态替换日期字段,并处理空值兜底
wmsMaterialCoilExportVos.stream().forEach(vo -> {
// 设置action完成时间
if (coilIdCompleteTimeMap.containsKey(vo.getCoilId())) {
vo.setActionCompleteTime(coilIdCompleteTimeMap.get(vo.getCoilId()));
}
// 判断查询条件中的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不变
// 如果是dataType=0的历史数据将实际库区设置为null
if (vo.getDataType() != null && vo.getDataType() == 0) {
vo.setActualWarehouseName(null);
}
});
return wmsMaterialCoilExportVos;
}
/**
* 查询钢卷导出数据列表(完整版)
*/
@Override
@Transactional(readOnly = true)
public List<WmsMaterialCoilAllExportVo> queryExportListAll(WmsMaterialCoilBo bo) {
QueryWrapper<WmsMaterialCoil> lqw = buildQueryWrapperPlus(bo);
List<WmsMaterialCoilExportVo> wmsMaterialCoilVos = baseMapper.selectExportList(lqw);
List<WmsMaterialCoilAllExportVo> exportVoList = BeanUtil.copyToList(wmsMaterialCoilVos, WmsMaterialCoilAllExportVo.class);
if (CollectionUtils.isNotEmpty(exportVoList)) {
for (WmsMaterialCoilAllExportVo vo : exportVoList) {
if (Objects.equals(vo.getDataType(), 0)) {
vo.setActualWarehouseName(null);
}
// 转换数据类型0=历史,1=现存
if (vo.getDataType() != null) {
String dataTypeText = vo.getDataType() == 0 ? "历史" : "现存";
vo.setDataTypeText(dataTypeText);
}
// 转换是否与订单相关0=否,1=是
if (vo.getIsRelatedToOrder() != null) {
String isRelatedText = vo.getIsRelatedToOrder() == 0 ? "" : "";
vo.setIsRelatedToOrderText(isRelatedText);
}
}
}
return exportVoList;
}
/**
* 发货报表导出:按 coilIds 查询钢卷 + 发货单明细/主表/计划联查数据
*/
@Override
public List<WmsMaterialCoilDeliveryExportVo> queryDeliveryExportList(WmsMaterialCoilBo bo) {
List<Long> coilIds = parseCsvLongs(bo == null ? null : bo.getCoilIds());
if (coilIds.isEmpty()) {
return Collections.emptyList();
}
List<WmsMaterialCoilDeliveryExportVo> wmsMaterialCoilDeliveryExportVos = baseMapper.selectDeliveryExportListByCoilIds(coilIds);
// 遍历数据,根据状态替换日期字段,并处理空值兜底
wmsMaterialCoilDeliveryExportVos.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不变
// 如果是dataType=0的历史数据将实际库区设置为null
if (vo.getDataType() != null && vo.getDataType() == 0) {
vo.setActualWarehouseName(null);
}
});
return wmsMaterialCoilDeliveryExportVos;
}
private List<Long> parseCsvLongs(String csv) {
if (StringUtils.isBlank(csv)) {
return Collections.emptyList();
}
String[] arr = csv.split(",");
List<Long> list = new ArrayList<>();
for (String s : arr) {
if (StringUtils.isBlank(s)) {
continue;
}
try {
list.add(Long.parseLong(s.trim()));
} catch (NumberFormatException ignore) {
}
}
return list.stream().distinct().collect(Collectors.toList());
}
/**
* 钢卷发货
* @param coilId
* @return
*/
@Override
public int exportCoil(Long coilId) {
// 检查独占状态
validateCoilOperationPermission(coilId, "发货");
// 查询当前钢卷信息记录原实际库区ID
WmsMaterialCoilVo wmsMaterialCoilVo = queryById(coilId);
Long oldActualWarehouseId = wmsMaterialCoilVo != null ? wmsMaterialCoilVo.getActualWarehouseId() : null;
// 如果质量状态为O的提示不能发货
if (wmsMaterialCoilVo != null && Objects.equals(wmsMaterialCoilVo.getQualityStatus(), "O")) {
throw new RuntimeException("当前钢卷为质检未通过,请勿发货!");
}
// 如果当前钢卷为历史数据应该抛异常
if (wmsMaterialCoilVo != null && wmsMaterialCoilVo.getDataType() == 0) {
throw new RuntimeException("当前数据为历史数据,请勿发货!");
}
// 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);
// 3. 仅当oldActualWarehouseId不为空且更新成功时记录操作日志
recordWarehouseOperationLog(coilId, oldActualWarehouseId, 4, 2, "钢卷发货出库");
}
return rows;
}
/**
* 记录钢卷库区操作日志
*/
private void recordWarehouseOperationLog(Long coilId, Long warehouseId, Integer operationType, Integer inOutType, String remark) {
try {
WmsCoilWarehouseOperationLog operationLog = new WmsCoilWarehouseOperationLog();
operationLog.setCoilId(coilId);
operationLog.setActualWarehouseId(warehouseId);
operationLog.setOperationType(operationType);
operationLog.setInOutType(inOutType);
operationLog.setRemark(remark);
wmsCoilWarehouseOperationLogMapper.insert(operationLog);
} catch (Exception e) {
log.warn("记录钢卷库区操作日志失败:{}", e.getMessage());
}
}
/**
* 合卷操作
* 在批量更新逻辑的基础上为newCoils中的每个被合的卷加入actionId和actionType
* 如果传递了actionId则更新操作记录状态为已完成并设置完成时间和合卷后的钢卷ID到processedCoilIds
* 如果未传actionId则创建新的操作记录
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean mergeCoils(WmsMaterialCoilBo bo) {
if (bo.getNewCoils() == null || bo.getNewCoils().isEmpty()) {
throw new RuntimeException("合卷操作需要提供参与合卷的钢卷列表");
}
Date now = new Date();
// 第一步:先为每个原始钢卷创建待操作记录(状态为处理中)
// 收集创建成功的actionId用于后续更新
Map<Long, Long> coilToActionIdMap = new HashMap<>();
for (WmsMaterialCoilBo originalCoilBo : bo.getNewCoils()) {
Long coilId = originalCoilBo.getCoilId();
Integer actionType = originalCoilBo.getActionType();
Long actionId = originalCoilBo.getActionId();
if (coilId == null || actionType == null) {
continue;
}
if (actionId != null) {
// 如果传递了actionId记录下来
coilToActionIdMap.put(coilId, actionId);
} else {
// 如果未传actionId则创建新的待操作记录
// 获取原始钢卷信息
WmsMaterialCoil originalCoil = baseMapper.selectById(coilId);
if (originalCoil == null) {
continue;
}
WmsCoilPendingActionBo pendingActionBo = new WmsCoilPendingActionBo();
pendingActionBo.setCoilId(coilId);
pendingActionBo.setCurrentCoilNo(originalCoil.getCurrentCoilNo());
pendingActionBo.setActionType(actionType);
pendingActionBo.setActionStatus(0); // 处理中
pendingActionBo.setSourceType("manual");
pendingActionBo.setPriority(0);
coilPendingActionService.insertByBo(pendingActionBo);
// insertByBo 会自动设置 actionId 到 bo 中
if (pendingActionBo.getActionId() != null) {
coilToActionIdMap.put(coilId, pendingActionBo.getActionId());
}
}
}
// 执行第二步操作之前要判断一下参与合卷的钢卷是否有重复,相同的钢卷不能进行合卷操作
Set<Long> coilIdSet = new HashSet<>();
for (WmsMaterialCoilBo originalCoilBo : bo.getNewCoils()) {
if (originalCoilBo.getCoilId() == null) {
continue;
}
if (!coilIdSet.add(originalCoilBo.getCoilId())) {
throw new RuntimeException("参与合卷的钢卷中存在重复,相同的钢卷不能进行合卷操作");
}
}
// 第二步:执行合卷操作
String coilIds = updateByBatch(bo);
if (coilIds == null || coilIds.isEmpty()) {
throw new RuntimeException("合卷操作失败");
}
// 直接从bo中获取合卷后的新钢卷ID
Long mergedCoilId = bo.getMergedCoilId();
if (mergedCoilId == null) {
throw new RuntimeException("未找到合卷后的新钢卷ID");
}
// 第三步更新所有待操作记录状态为已完成并设置processedCoilIds
for (Map.Entry<Long, Long> entry : coilToActionIdMap.entrySet()) {
Long actionId = entry.getValue();
WmsCoilPendingActionBo pendingActionBo = new WmsCoilPendingActionBo();
pendingActionBo.setActionId(actionId);
pendingActionBo.setActionStatus(2); // 已完成
pendingActionBo.setCompleteTime(now);
pendingActionBo.setProcessedCoilIds(mergedCoilId.toString());
coilPendingActionService.updateByBo(pendingActionBo);
}
return true;
}
/**
* 钢卷发货撤回
* @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);
}
/**
* 批量更新钢卷发货状态
*
* @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(Long coilId, String enterCoilNo, String currentCoilNo, String supplierCoilNo) {
Map<String, Object> result = new HashMap<>();
// 新增核心逻辑:先判断是否操作的是历史记录
// 1. 如果coilId不为空修改操作先查询该钢卷的dataType
if (coilId != null) {
WmsMaterialCoil coil = baseMapper.selectById(coilId);
// 2. 如果查询到钢卷且dataType!=1说明是历史记录直接返回无重复
if (coil != null && coil.getDataType() != 1) {
result.put("duplicateType", "none");
result.put("enterCoilNoDuplicate", false);
result.put("currentCoilNoDuplicate", false);
result.put("supplierCoilNoDuplicate", false);
return result; // 直接返回,不执行后续检查
}
}
boolean enterCoilNoDuplicate = false;
boolean currentCoilNoDuplicate = false;
boolean supplierCoilNoDuplicate = false;
// 检查入场钢卷号是否重复
if (StringUtils.isNotBlank(enterCoilNo)) {
LambdaQueryWrapper<WmsMaterialCoil> enterWrapper = Wrappers.lambdaQuery();
enterWrapper.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo)
.eq(WmsMaterialCoil::getDelFlag, 0)
.eq(WmsMaterialCoil::getDataType, 1); // 过滤历史数据
// 如果是修改操作,排除自身
if (coilId != null) {
enterWrapper.ne(WmsMaterialCoil::getCoilId, coilId);
}
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);
// 如果是修改操作,排除自身
if (coilId != null) {
currentWrapper.ne(WmsMaterialCoil::getCoilId, coilId);
}
long currentCount = baseMapper.selectCount(currentWrapper);
currentCoilNoDuplicate = currentCount > 0;
}
// 检查厂家原料卷号是否重复
if (StringUtils.isNotBlank(supplierCoilNo)) {
LambdaQueryWrapper<WmsMaterialCoil> supplierWrapper = Wrappers.lambdaQuery();
supplierWrapper.eq(WmsMaterialCoil::getSupplierCoilNo, supplierCoilNo)
.eq(WmsMaterialCoil::getDelFlag, 0)
.eq(WmsMaterialCoil::getDataType, 1);
// 如果是修改操作,排除自身
if (coilId != null) {
supplierWrapper.ne(WmsMaterialCoil::getCoilId, coilId);
}
long supplierCount = baseMapper.selectCount(supplierWrapper);
supplierCoilNoDuplicate = supplierCount > 0;
}
// 判断重复类型
String duplicateType;
if (enterCoilNoDuplicate && currentCoilNoDuplicate && supplierCoilNoDuplicate) {
duplicateType = "both";
} else if (enterCoilNoDuplicate) {
duplicateType = "enter";
} else if (currentCoilNoDuplicate) {
duplicateType = "current";
} else if (supplierCoilNoDuplicate) {
duplicateType = "supplier";
} else {
duplicateType = "none";
}
result.put("duplicateType", duplicateType);
result.put("enterCoilNoDuplicate", enterCoilNoDuplicate);
result.put("currentCoilNoDuplicate", currentCoilNoDuplicate);
result.put("supplierCoilNoDuplicate", supplierCoilNoDuplicate);
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;
}
/**
* 回滚钢卷操作
* 根据当前钢卷ID找到历史钢卷并恢复删除当前钢卷
* 用于撤销单个更新、合卷或分卷操作
*
* @param currentCoilId 当前钢卷ID需要回滚的钢卷
* @return 操作结果,包含恢复的历史钢卷信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> rollbackCoil(Long currentCoilId) {
Map<String, Object> result = new HashMap<>();
result.put("success", false);
try {
// 1. 查询当前钢卷信息
WmsMaterialCoil currentCoil = baseMapper.selectById(currentCoilId);
if (currentCoil == null) {
throw new RuntimeException("当前钢卷不存在");
}
// 检查独占状态
validateCoilOperationPermission(currentCoilId, "回滚");
// 2. 检查当前钢卷是否为当前数据(不能回滚历史数据)
if (currentCoil.getDataType() != 1) {
throw new RuntimeException("只能回滚当前数据,不能回滚历史数据");
}
if (!currentCoil.getDelFlag().equals(0)) {
throw new RuntimeException("当前钢卷已删除,无法回滚");
}
if (currentCoil.getStatus() == 1) {
throw new RuntimeException("只能回滚未发货数据,不能回滚已发货数据");
}
// 3. 检查是否有二维码记录
if (currentCoil.getQrcodeRecordId() == null) {
throw new RuntimeException("当前钢卷没有二维码记录,无法回滚");
}
// 4. 查询二维码记录并解析上一步钢卷ID
WmsGenerateRecordVo qrcodeRecord = generateRecordService.queryById(currentCoil.getQrcodeRecordId());
if (qrcodeRecord == null || StringUtils.isBlank(qrcodeRecord.getContent())) {
throw new RuntimeException("二维码记录不存在或内容为空,无法回滚");
}
// 5. 解析二维码内容,获取最后一步操作信息
// 直接从二维码解析最后一步操作,用于检查独占状态
Map<String, Object> lastStepInfo = getLastOperationStepFromQrcode(qrcodeRecord.getContent());
String lastOperation = (String) lastStepInfo.get("operation");
Long oldCoilIdFromLastStep = (Long) lastStepInfo.get("oldCoilId");
// 如果最后一步是分卷操作并且old_coil_id正在处于单步分卷则当前钢卷不能回滚
if ("分卷".equals(lastOperation) && oldCoilIdFromLastStep != null) {
WmsMaterialCoil motherCoil = baseMapper.selectById(oldCoilIdFromLastStep);
if (motherCoil != null && motherCoil.getExclusiveStatus() != null && motherCoil.getExclusiveStatus() == 1) {
throw new RuntimeException("母卷[" + motherCoil.getCurrentCoilNo() + "]正在进行分卷操作,当前钢卷无法回滚");
}
}
// 6. 解析二维码内容,判断是合卷、分卷还是普通更新
Map<String, Object> rollbackInfo = parseRollbackInfoFromQrcode(qrcodeRecord.getContent(), currentCoilId);
String operationType = (String) rollbackInfo.get("operationType");
// 7. 根据操作类型执行不同的回滚逻辑
if ("MERGE".equals(operationType)) {
// 合卷回滚
return rollbackMergeOperation(currentCoil, qrcodeRecord, rollbackInfo, result);
} else if ("SPLIT".equals(operationType)) {
// 分卷回滚
return rollbackSplitOperation(currentCoil, qrcodeRecord, rollbackInfo, result);
} else {
// 普通更新回滚(原有逻辑)
return rollbackNormalOperation(currentCoil, qrcodeRecord, result);
}
} catch (Exception e) {
log.error("钢卷回滚操作失败currentCoilId={}", currentCoilId, e);
result.put("success", false);
result.put("message", "回滚操作失败:" + e.getMessage());
throw e;
}
}
/**
* 从二维码内容中获取最后一步操作信息(用于检查独占状态)
* 只返回最后一步的操作类型和old_coil_id不进行复杂的回滚逻辑判断
*/
private Map<String, Object> getLastOperationStepFromQrcode(String content) {
Map<String, Object> result = new HashMap<>();
try {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> contentMap = objectMapper.readValue(content, Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null || steps.isEmpty()) {
return result;
}
// 从后往前找到最后一个非回滚的步骤
for (int i = steps.size() - 1; i >= 0; i--) {
Map<String, Object> step = steps.get(i);
String action = (String) step.get("action");
// 跳过回滚操作
if ("回滚".equals(action)) {
continue;
}
// 记录最后一步的操作类型
result.put("operation", step.get("operation"));
result.put("action", action);
// 获取old_coil_id
Object oldCoilIdObj = step.get("old_coil_id");
if (oldCoilIdObj != null) {
try {
result.put("oldCoilId", Long.parseLong(oldCoilIdObj.toString()));
} catch (NumberFormatException ignored) {
}
}
// 获取child_coil_ids用于分卷判断
Object childCoilIdsObj = step.get("child_coil_ids");
if (childCoilIdsObj != null) {
result.put("childCoilIds", childCoilIdsObj.toString());
}
// 获取parent_coil_ids用于合卷判断
Object parentCoilIdsObj = step.get("parent_coil_ids");
if (parentCoilIdsObj != null) {
result.put("parentCoilIds", parentCoilIdsObj.toString());
}
return result;
}
return result;
} catch (Exception e) {
log.error("解析二维码最后一步操作失败", e);
return result;
}
}
/**
* 解析二维码内容,获取回滚所需的信息
* @return 包含operationType和相应参数的Map
*/
private Map<String, Object> parseRollbackInfoFromQrcode(String content, Long currentCoilId) {
Map<String, Object> result = new HashMap<>();
try {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> contentMap = objectMapper.readValue(content, Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null || steps.isEmpty()) {
result.put("operationType", "NORMAL");
return result;
}
// 从后往前查找最新的操作
for (int i = steps.size() - 1; i >= 0; i--) {
Map<String, Object> step = steps.get(i);
String action = (String) step.get("action");
String operation = (String) step.get("operation");
// 跳过新增和回滚操作
if ("新增".equals(action) || "回滚".equals(action)) {
continue;
}
// 检查是否是合卷操作
if ("合卷".equals(operation)) {
Object parentCoilIdsObj = step.get("parent_coil_ids");
if (parentCoilIdsObj != null) {
String parentCoilIdsStr = parentCoilIdsObj.toString();
if (StringUtils.isNotBlank(parentCoilIdsStr)) {
List<Long> parentCoilIds = Arrays.stream(parentCoilIdsStr.split(","))
.map(String::trim)
.filter(s -> StringUtils.isNotBlank(s))
.map(Long::parseLong)
.collect(Collectors.toList());
result.put("operationType", "MERGE");
result.put("parentCoilIds", parentCoilIds);
result.put("targetStep", step);
return result;
}
}
}
// 检查是否是分卷操作
if ("分卷".equals(operation)) {
Object childCoilIdsObj = step.get("child_coil_ids");
if (childCoilIdsObj != null) {
String childCoilIdsStr = childCoilIdsObj.toString();
if (StringUtils.isNotBlank(childCoilIdsStr)) {
List<Long> childCoilIds = Arrays.stream(childCoilIdsStr.split(","))
.map(String::trim)
.filter(s -> StringUtils.isNotBlank(s))
.map(Long::parseLong)
.collect(Collectors.toList());
result.put("operationType", "SPLIT");
result.put("childCoilIds", childCoilIds);
result.put("targetStep", step);
return result;
}
}
result.put("operationType", "SPLIT");
return result;
}
// 这里不写更新是因为还有退货操作和退火操作,但是也和更新一样可以回滚所以直接找new_coil_id即可
// 如果找到普通更新操作
Object newCoilIdObj = step.get("new_coil_id");
if (newCoilIdObj != null && newCoilIdObj.toString().equals(currentCoilId.toString())) {
Object oldCoilIdObj = step.get("old_coil_id");
if (oldCoilIdObj != null) {
result.put("operationType", "NORMAL");
result.put("historyCoilId", Long.parseLong(oldCoilIdObj.toString()));
return result;
}
}
}
// 如果没找到任何操作,返回普通类型
result.put("operationType", "NORMAL");
return result;
} catch (Exception e) {
log.error("解析二维码内容失败", e);
result.put("operationType", "NORMAL");
return result;
}
}
/**
* 回滚合卷操作
* 获取所有被合并的钢卷ID将这些钢卷还原dataType改为1并删除当前合卷的钢卷
*/
private Map<String, Object> rollbackMergeOperation(WmsMaterialCoil currentCoil, WmsGenerateRecordVo qrcodeRecord,
Map<String, Object> rollbackInfo, Map<String, Object> result) {
List<Long> parentCoilIds = (List<Long>) rollbackInfo.get("parentCoilIds");
if (parentCoilIds == null || parentCoilIds.isEmpty()) {
throw new RuntimeException("无法获取合卷的原始钢卷ID无法回滚");
}
// 查询所有原始钢卷
List<WmsMaterialCoil> originalCoils = baseMapper.selectBatchIds(parentCoilIds);
// 检查所有原始钢卷是否存在
if (originalCoils.size() != parentCoilIds.size()) {
throw new RuntimeException("部分原始钢卷不存在,无法回滚");
}
// // 检查恢复的钢卷的当前钢卷号是否重复
// for (WmsMaterialCoil originalCoil : originalCoils) {
// Map<String, Object> duplicateCheck = checkCoilNoDuplicate(originalCoil.getCoilId(), originalCoil.getEnterCoilNo(), originalCoil.getCurrentCoilNo());
// boolean currentCoilNoDuplicate = (boolean) duplicateCheck.get("currentCoilNoDuplicate");
// if (currentCoilNoDuplicate) {
// String errorMsg = "无法恢复原始钢卷,存在重复的钢卷号:原始钢卷的当前钢卷号[" + originalCoil.getCurrentCoilNo() + "]重复。";
// errorMsg += "重复的钢卷无法进行回滚操作。";
// throw new RuntimeException(errorMsg);
// }
// }
// 释放当前合卷钢卷占用的实际库区
if (currentCoil.getActualWarehouseId() != null) {
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
releaseBo.setActualWarehouseId(currentCoil.getActualWarehouseId());
releaseBo.setIsEnabled(1);
actualWarehouseService.updateByBo(releaseBo);
}
// 将被删除的合卷钢卷的二维码设置为失效
if (currentCoil.getQrcodeRecordId() != null) {
WmsGenerateRecordBo deleteQrBo = new WmsGenerateRecordBo();
deleteQrBo.setRecordId(currentCoil.getQrcodeRecordId());
deleteQrBo.setStatus(0); // 0=失效
generateRecordService.updateByBo(deleteQrBo);
}
// 删除当前合卷钢卷
baseMapper.deleteById(currentCoil.getCoilId());
// 将所有原始钢卷恢复为当前数据,实际库区置空
for (WmsMaterialCoil originalCoil : originalCoils) {
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(WmsMaterialCoil::getCoilId, originalCoil.getCoilId())
.set(WmsMaterialCoil::getDataType, 1)
.set(WmsMaterialCoil::getActualWarehouseId, (Long) null);
baseMapper.update(null, updateWrapper);
// 删除操作记录
deletePendingActionForRollback(originalCoil.getCoilId());
// 恢复原始钢卷的二维码为生效状态并更新二维码记录
if (originalCoil.getQrcodeRecordId() != null) {
WmsGenerateRecordBo originalQrBo = new WmsGenerateRecordBo();
originalQrBo.setRecordId(originalCoil.getQrcodeRecordId());
originalQrBo.setStatus(1); // 1=生效
generateRecordService.updateByBo(originalQrBo);
// 更新原始钢卷的二维码记录(添加回滚步骤)
WmsGenerateRecordVo originalQrRecord = generateRecordService.queryById(originalCoil.getQrcodeRecordId());
if (originalQrRecord != null) {
updateQrcodeForMergeRollback(originalQrRecord, currentCoil, originalCoil.getCoilId());
}
}
}
result.put("success", true);
result.put("message", "合卷回滚成功,已恢复 " + parentCoilIds.size() + " 个原始钢卷,删除了合卷钢卷");
result.put("restoredCoilCount", parentCoilIds.size());
result.put("deletedCoilId", currentCoil.getCoilId());
log.info("合卷回滚操作成功删除的合卷钢卷ID={}, 恢复的原始钢卷ID={}", currentCoil.getCoilId(), parentCoilIds);
return result;
}
/**
* 回滚分卷操作
* 删除所有母卷生成的子钢卷,恢复母卷为当前数据
*/
private Map<String, Object> rollbackSplitOperation(WmsMaterialCoil currentCoil, WmsGenerateRecordVo qrcodeRecord,
Map<String, Object> rollbackInfo, Map<String, Object> result) {
List<Long> childCoilIds = (List<Long>) rollbackInfo.get("childCoilIds");
if (childCoilIds == null || childCoilIds.isEmpty()) {
throw new RuntimeException("无法获取分卷的子钢卷ID无法回滚");
}
// 检查当前钢卷ID是否在childCoilIds里面如果不在说明不是同一步骤则不能回滚
if (!childCoilIds.contains(currentCoil.getCoilId())) {
throw new RuntimeException("无可回滚的母卷");
}
// 查询所有子钢卷
List<WmsMaterialCoil> childCoils = baseMapper.selectBatchIds(childCoilIds);
// 检查所有子钢卷是否存在
if (childCoils.size() != childCoilIds.size()) {
throw new RuntimeException("部分子钢卷不存在,无法回滚");
}
// 找到母卷
Map<String, Object> targetStep = (Map<String, Object>) rollbackInfo.get("targetStep");
Object oldCoilIdObj = targetStep.get("old_coil_id");
Long motherCoilId = null;
if (oldCoilIdObj != null) {
motherCoilId = Long.parseLong(oldCoilIdObj.toString());
}
// 检查母卷的独占状态
if (motherCoilId != null) {
WmsMaterialCoil motherCoil = baseMapper.selectById(motherCoilId);
if (motherCoil != null && motherCoil.getExclusiveStatus() != null && motherCoil.getExclusiveStatus() == 1) {
throw new RuntimeException("母卷[" + motherCoil.getCurrentCoilNo() + "]正在进行单步分卷操作,无法回滚");
}
}
// 检查是否有子钢卷的dataType不等于1不是现存的钢卷
List<String> invalidCoilNos = new ArrayList<>();
for (WmsMaterialCoil childCoil : childCoils) {
if (childCoil.getDataType() != 1) {
invalidCoilNos.add(childCoil.getCurrentCoilNo() + "(历史钢卷)");
}
// 检查子钢卷的独占状态
if (childCoil.getExclusiveStatus() != null && childCoil.getExclusiveStatus() == 1) {
invalidCoilNos.add(childCoil.getCurrentCoilNo() + "(正在单步分卷)");
}
}
if (!invalidCoilNos.isEmpty()) {
throw new RuntimeException("以下钢卷不是现存钢卷,无法回滚:" + String.join(", ", invalidCoilNos));
}
// // 检查恢复的母卷的当前钢卷号是否重复
// if (motherCoilId != null) {
// WmsMaterialCoil motherCoil = baseMapper.selectById(motherCoilId);
// if (motherCoil != null) {
// Map<String, Object> duplicateCheck = checkCoilNoDuplicate(motherCoilId, motherCoil.getEnterCoilNo(), motherCoil.getCurrentCoilNo());
// boolean currentCoilNoDuplicate = (boolean) duplicateCheck.get("currentCoilNoDuplicate");
// if (currentCoilNoDuplicate) {
// String errorMsg = "无法恢复母卷,存在重复的钢卷号:母卷的当前钢卷号[" + motherCoil.getCurrentCoilNo() + "]重复。";
// errorMsg += "重复的钢卷无法进行回滚操作。";
// throw new RuntimeException(errorMsg);
// }
// }
// }
// 释放所有子钢卷占用的实际库区并删除所有子钢卷
for (WmsMaterialCoil childCoil : childCoils) {
if (childCoil.getActualWarehouseId() != null) {
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
releaseBo.setActualWarehouseId(childCoil.getActualWarehouseId());
releaseBo.setIsEnabled(1);
actualWarehouseService.updateByBo(releaseBo);
}
// 将被删除的子钢卷的二维码设置为失效
if (childCoil.getQrcodeRecordId() != null) {
WmsGenerateRecordBo deleteQrBo = new WmsGenerateRecordBo();
deleteQrBo.setRecordId(childCoil.getQrcodeRecordId());
deleteQrBo.setStatus(0); // 0=失效
generateRecordService.updateByBo(deleteQrBo);
}
baseMapper.deleteById(childCoil.getCoilId());
}
if (motherCoilId != null) {
// 恢复母卷为当前数据,实际库区置空
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(WmsMaterialCoil::getCoilId, motherCoilId)
.set(WmsMaterialCoil::getDataType, 1)
.set(WmsMaterialCoil::getActualWarehouseId, (Long) null);
baseMapper.update(null, updateWrapper);
// 删除母卷的操作记录
deletePendingActionForRollback(motherCoilId);
// 恢复母卷的二维码为生效状态
WmsMaterialCoil motherCoil = baseMapper.selectById(motherCoilId);
if (motherCoil != null && motherCoil.getQrcodeRecordId() != null) {
WmsGenerateRecordBo motherQrBo = new WmsGenerateRecordBo();
motherQrBo.setRecordId(motherCoil.getQrcodeRecordId());
motherQrBo.setStatus(1); // 1=生效
generateRecordService.updateByBo(motherQrBo);
// 更新母卷的二维码记录(添加回滚步骤)
WmsGenerateRecordVo motherQrRecord = generateRecordService.queryById(motherCoil.getQrcodeRecordId());
if (motherQrRecord != null) {
updateQrcodeForSplitRollback(motherQrRecord, currentCoil, motherCoilId);
}
}
}
result.put("success", true);
result.put("message", "分卷回滚成功,已恢复母卷,删除了 " + childCoilIds.size() + " 个子钢卷");
result.put("deletedCoilIds", childCoilIds);
result.put("restoredCoilId", motherCoilId);
log.info("分卷回滚操作成功删除的子卷ID={}, 恢复的母卷ID={}", childCoilIds, motherCoilId);
return result;
}
/**
* 删除回滚操作记录
* 删除最晚的一条且actionType不在401-405范围内并且coilId等于指定ID的操作记录
*/
private void deletePendingActionForRollback(Long coilId) {
LambdaQueryWrapper<WmsCoilPendingAction> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(WmsCoilPendingAction::getCoilId, coilId)
.notIn(WmsCoilPendingAction::getActionType, Arrays.asList(401, 402, 403, 404, 405))
.eq(WmsCoilPendingAction::getDelFlag, 0)
.eq(WmsCoilPendingAction::getActionStatus, 2)
.orderByDesc(WmsCoilPendingAction::getCreateTime)
.last("LIMIT 1");
WmsCoilPendingAction latestAction = coilPendingActionMapper.selectOne(queryWrapper);
if (latestAction != null) {
coilPendingActionMapper.deleteById(latestAction.getActionId());
}
}
/**
* 回滚普通更新操作(原有逻辑)
*/
private Map<String, Object> rollbackNormalOperation(WmsMaterialCoil currentCoil, WmsGenerateRecordVo qrcodeRecord, Map<String, Object> result) {
// 解析二维码内容找到上一步的历史钢卷ID
Long historyCoilId = parseHistoryCoilIdFromQrcode(qrcodeRecord.getContent(), currentCoil.getCoilId());
if (historyCoilId == null) {
// 检查无法回滚的具体原因
String rollbackBlockReason = getRollbackBlockReason(qrcodeRecord.getContent(), currentCoil.getCoilId());
switch (rollbackBlockReason) {
case "INITIAL_CREATE":
throw new RuntimeException("无法继续回滚:当前钢卷为最初的新增操作创建,如需删除请直接使用删除功能");
default:
throw new RuntimeException("无法从二维码记录中找到历史钢卷ID无法回滚");
}
}
// 查询历史钢卷信息
WmsMaterialCoil historyCoil = baseMapper.selectById(historyCoilId);
if (historyCoil == null) {
throw new RuntimeException("历史钢卷不存在,无法回滚");
}
// 检查历史钢卷是否为历史数据
if (historyCoil.getDataType() != 0) {
throw new RuntimeException("目标钢卷不是历史数据,无法恢复");
}
// // 检查历史钢卷的当前钢卷号是否重复
// Map<String, Object> duplicateCheck = checkCoilNoDuplicate(currentCoil.getCoilId(), historyCoil.getEnterCoilNo(), historyCoil.getCurrentCoilNo());
// boolean currentCoilNoDuplicate = (boolean) duplicateCheck.get("currentCoilNoDuplicate");
// if (currentCoilNoDuplicate) {
// String errorMsg = "无法恢复历史钢卷,存在重复的钢卷号:历史钢卷的当前钢卷号[" + historyCoil.getCurrentCoilNo() + "]重复。";
// errorMsg += "重复的钢卷无法进行回滚操作。";
// throw new RuntimeException(errorMsg);
// }
// 执行回滚操作
// 1. 释放当前钢卷占用的实际库区
if (currentCoil.getActualWarehouseId() != null) {
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
releaseBo.setActualWarehouseId(currentCoil.getActualWarehouseId());
releaseBo.setIsEnabled(1);
actualWarehouseService.updateByBo(releaseBo);
}
// 2. 将被删除的当前钢卷的二维码设置为失效
if (currentCoil.getQrcodeRecordId() != null) {
WmsGenerateRecordBo deleteQrBo = new WmsGenerateRecordBo();
deleteQrBo.setRecordId(currentCoil.getQrcodeRecordId());
deleteQrBo.setStatus(0); // 0=失效
generateRecordService.updateByBo(deleteQrBo);
}
// 3. 删除当前钢卷
baseMapper.deleteById(currentCoil.getCoilId());
// 3. 删除操作记录
LambdaQueryWrapper<WmsCoilPendingAction> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(WmsCoilPendingAction::getCoilId, historyCoilId)
.notIn(WmsCoilPendingAction::getActionType, Arrays.asList(401, 402, 403, 404, 405))
.eq(WmsCoilPendingAction::getDelFlag, 0)
.eq(WmsCoilPendingAction::getActionStatus, 2)
.orderByDesc(WmsCoilPendingAction::getCreateTime)
.last("LIMIT 1");
WmsCoilPendingAction latestAction = coilPendingActionMapper.selectOne(queryWrapper);
if (latestAction != null) {
coilPendingActionMapper.deleteById(latestAction.getActionId());
}
// 4. 恢复历史钢卷为当前数据
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(WmsMaterialCoil::getCoilId, historyCoilId)
.set(WmsMaterialCoil::getDataType, 1)
.set(WmsMaterialCoil::getActualWarehouseId, (Long) null);
baseMapper.update(null, updateWrapper);
// 5. 更新二维码记录
updateQrcodeForRollback(qrcodeRecord, currentCoil, historyCoil);
// 6. 重新查询恢复后的历史钢卷信息
WmsMaterialCoilVo restoredCoil = queryById(historyCoilId);
// 7. 返回操作结果
result.put("success", true);
result.put("restoredCoil", restoredCoil);
result.put("deletedCoilId", currentCoil.getCoilId());
result.put("message", "回滚操作成功,已恢复历史钢卷,删除了当前钢卷");
log.info("钢卷回滚操作成功历史钢卷ID={}, 删除的钢卷ID={}", historyCoilId, currentCoil.getCoilId());
return result;
}
/**
* 更新合卷回滚后的二维码记录
*/
private void updateQrcodeForMergeRollback(WmsGenerateRecordVo qrcodeRecord, WmsMaterialCoil currentCoil, Long restoredCoilId) {
try {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> contentMap = objectMapper.readValue(qrcodeRecord.getContent(), Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null) {
steps = new ArrayList<>();
}
// 添加回滚步骤
Map<String, Object> rollbackStep = new HashMap<>();
rollbackStep.put("step", steps.size() + 1);
rollbackStep.put("action", "回滚");
rollbackStep.put("operation", "合卷回滚");
rollbackStep.put("deleted_coil_id", String.valueOf(currentCoil.getCoilId()));
rollbackStep.put("deleted_current_coil_no", currentCoil.getCurrentCoilNo());
rollbackStep.put("restored_coil_id", String.valueOf(restoredCoilId));
rollbackStep.put("operator", LoginHelper.getUsername());
rollbackStep.put("rollback_time", new java.util.Date());
steps.add(rollbackStep);
contentMap.put("steps", steps);
// 更新current_coil_id为恢复的原始钢卷ID
contentMap.put("current_coil_id", String.valueOf(restoredCoilId));
String newContentJson = objectMapper.writeValueAsString(contentMap);
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
updateBo.setRecordId(qrcodeRecord.getRecordId());
updateBo.setContent(newContentJson);
generateRecordService.updateByBo(updateBo);
} catch (Exception e) {
log.error("更新合卷回滚二维码记录失败", e);
throw new RuntimeException("更新二维码记录失败: " + e.getMessage());
}
}
/**
* 更新分卷回滚后的二维码记录
*/
private void updateQrcodeForSplitRollback(WmsGenerateRecordVo qrcodeRecord, WmsMaterialCoil currentCoil, Long motherCoilId) {
try {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> contentMap = objectMapper.readValue(qrcodeRecord.getContent(), Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null) {
steps = new ArrayList<>();
}
// 添加回滚步骤
Map<String, Object> rollbackStep = new HashMap<>();
rollbackStep.put("step", steps.size() + 1);
rollbackStep.put("action", "回滚");
rollbackStep.put("operation", "分卷回滚");
rollbackStep.put("deleted_coil_id", String.valueOf(currentCoil.getCoilId()));
rollbackStep.put("deleted_current_coil_no", currentCoil.getCurrentCoilNo());
rollbackStep.put("restored_coil_id", String.valueOf(motherCoilId));
rollbackStep.put("operator", LoginHelper.getUsername());
rollbackStep.put("rollback_time", new java.util.Date());
steps.add(rollbackStep);
contentMap.put("steps", steps);
// 更新current_coil_id为恢复的母卷ID
contentMap.put("current_coil_id", String.valueOf(motherCoilId));
String newContentJson = objectMapper.writeValueAsString(contentMap);
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
updateBo.setRecordId(qrcodeRecord.getRecordId());
updateBo.setContent(newContentJson);
generateRecordService.updateByBo(updateBo);
} catch (Exception e) {
log.error("更新分卷回滚二维码记录失败", e);
throw new RuntimeException("更新二维码记录失败: " + e.getMessage());
}
}
/**
* 从二维码内容中解析历史钢卷ID
* 根据当前钢卷ID找到对应的上一步操作中的old_coil_id
*/
private Long parseHistoryCoilIdFromQrcode(String content, Long currentCoilId) {
try {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> contentMap = objectMapper.readValue(content, Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null || steps.isEmpty()) {
return null;
}
String currentCoilIdStr = currentCoilId.toString();
// 从后往前查找找到当前钢卷ID对应的上一步操作
for (int i = steps.size() - 1; i >= 0; i--) {
Map<String, Object> step = steps.get(i);
String action = (String) step.get("action");
// 如果是新增操作,说明已经回滚到最初状态
if ("新增".equals(action)) {
return null; // 无法继续回滚
}
// 如果是回滚操作,跳过(回滚步骤不应该影响历史查找)
if ("回滚".equals(action)) {
continue;
}
// 检查这个步骤是否产生了当前钢卷
Object newCoilIdObj = step.get("new_coil_id");
if (newCoilIdObj != null) {
String newCoilIdStr = newCoilIdObj.toString();
if (newCoilIdStr.equals(currentCoilIdStr)) {
// 找到了产生当前钢卷的步骤获取old_coil_id
Object oldCoilIdObj = step.get("old_coil_id");
if (oldCoilIdObj != null) {
String oldCoilIdStr = oldCoilIdObj.toString();
if (!"null".equals(oldCoilIdStr) && !oldCoilIdStr.isEmpty()) {
try {
return Long.parseLong(oldCoilIdStr);
} catch (NumberFormatException e) {
log.warn("解析old_coil_id失败{}", oldCoilIdStr);
}
}
}
break;
}
}
// 无论是分卷还是合卷 当前逻辑应该是可以继续的 此处先进行跳过 只回滚当前工序的
// 对于合卷操作检查parent_coil_ids
if ("合卷".equals(step.get("operation"))) {
// 当前钢卷是合卷的产物返回null表示无法继续回滚
return null;
}
// 对于分卷操作检查new_current_coil_nos是否包含多个钢卷号
if ("分卷".equals(step.get("operation"))) {
// 当前钢卷是分卷操作产生的子钢卷,无法回滚
// 分卷操作会产生多个钢卷,回滚其中一个会导致其他钢卷异常
return null;
}
}
} catch (Exception e) {
log.error("解析二维码内容失败", e);
}
return null;
}
/**
* 获取无法回滚的具体原因
* @return "INITIAL_CREATE" - 初始新增操作
* "MERGE_PRODUCT" - 合卷产物
* "SPLIT_PRODUCT" - 分卷产物
* "UNKNOWN" - 其他未知原因
*/
private String getRollbackBlockReason(String content, Long currentCoilId) {
try {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> contentMap = objectMapper.readValue(content, Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null || steps.isEmpty()) {
return "UNKNOWN";
}
String currentCoilIdStr = currentCoilId.toString();
boolean isInitialCreate = false; // 标记是否为初始新增
// 倒序遍历所有步骤,整合所有判断逻辑
for (int i = steps.size() - 1; i >= 0; i--) {
Map<String, Object> step = steps.get(i);
String operation = (String) step.get("operation");
String action = (String) step.get("action");
// 1. 优先判断初始新增(仅第一个步骤可能是新增)
if (i == 0 && "新增".equals(action)) {
Object coilIdObj = contentMap.get("coil_id");
Object currentCoilIdObj = contentMap.get("current_coil_id");
if (coilIdObj != null && currentCoilIdObj != null) {
String coilIdStr = coilIdObj.toString();
String currentCoilIdStrInContent = currentCoilIdObj.toString();
if (coilIdStr.equals(currentCoilIdStrInContent) && coilIdStr.equals(currentCoilIdStr)) {
isInitialCreate = true;
break; // 确认是初始新增,直接退出循环
}
}
}
// 2. 检查合卷操作(最新操作优先)
if ("合卷".equals(operation)) {
return "MERGE_PRODUCT";
}
// 3. 检查分卷操作
if ("分卷".equals(operation)) {
return "SPLIT_PRODUCT";
}
}
// 最终判断如果是初始新增则返回否则返回UNKNOWN
return isInitialCreate ? "INITIAL_CREATE" : "UNKNOWN";
} catch (Exception e) {
log.error("检查回滚阻止原因失败, currentCoilId:{}", currentCoilId, e);
return "UNKNOWN";
}
}
/**
* 更新分卷子钢卷的二维码记录中的child_coil_ids字段
* 将child_coil_ids设置到对应的分卷步骤中而不是最外层
* 支持多次分卷每次的child_coil_ids都会记录在各自的分卷步骤中
*
* @param qrcodeRecordId 二维码记录ID
* @param currentCoilId 当前子钢卷ID
* @param childCoilIdsStr 本次分卷产生的所有子钢卷ID的逗号分隔字符串
*/
private void updateChildCoilIdsQrcodeCoilId(Long oldCoilId, Long qrcodeRecordId, Long currentCoilId, String childCoilIdsStr) {
log.info("开始更新子钢卷二维码的分卷步骤child_coil_idsqrcodeRecordId: {}, currentCoilId: {}, childCoilIdsStr: {}",
qrcodeRecordId, currentCoilId, childCoilIdsStr);
try {
// 参数校验
if (qrcodeRecordId == null || currentCoilId == null) {
throw new RuntimeException("二维码记录ID和当前钢卷ID不能为空");
}
if (StringUtils.isBlank(childCoilIdsStr)) {
log.warn("childCoilIdsStr为空跳过更新qrcodeRecordId: {}, currentCoilId: {}", qrcodeRecordId, currentCoilId);
return;
}
// 获取二维码记录
WmsGenerateRecordVo record = generateRecordService.queryById(qrcodeRecordId);
if (record == null) {
throw new RuntimeException("二维码记录不存在recordId: " + qrcodeRecordId);
}
log.debug("获取到二维码记录recordId: {}, 原始content长度: {}", qrcodeRecordId, record.getContent().length());
// 解析现有content
ObjectMapper objectMapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> contentMap = objectMapper.readValue(record.getContent(), Map.class);
// 获取steps数组
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null || steps.isEmpty()) {
log.warn("二维码记录中没有steps信息跳过child_coil_ids更新qrcodeRecordId: {}", qrcodeRecordId);
return;
}
// 找到最后一个分卷步骤(最新的分卷操作)
Map<String, Object> lastSplitStep = null;
for (int i = steps.size() - 1; i >= 0; i--) {
Map<String, Object> step = steps.get(i);
String operation = (String) step.get("operation");
Long parentCoilId = null;
Object oldCoilIdObj = step.get("old_coil_id");
if (oldCoilIdObj != null) {
parentCoilId = Long.valueOf(oldCoilIdObj.toString());
}
if ("分卷".equals(operation) && oldCoilId.equals(parentCoilId)) {
lastSplitStep = step;
break; // 找到最新的分卷步骤就停止
}
}
if (lastSplitStep == null) {
log.warn("未找到分卷步骤无法设置child_coil_idsqrcodeRecordId: {}", qrcodeRecordId);
return;
}
// 在分卷步骤中设置child_coil_ids
lastSplitStep.put("child_coil_ids", childCoilIdsStr);
log.debug("已在分卷步骤中设置child_coil_ids: {}", childCoilIdsStr);
// 确保current_coil_id已设置虽然在前面应该已经设置过了但这里再确认一下
if (!contentMap.containsKey("current_coil_id") || "null".equals(contentMap.get("current_coil_id"))) {
contentMap.put("current_coil_id", String.valueOf(currentCoilId));
log.debug("已设置current_coil_id字段: {}", currentCoilId);
}
// 更新二维码记录
String newContentJson = objectMapper.writeValueAsString(contentMap);
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
updateBo.setRecordId(qrcodeRecordId);
updateBo.setContent(newContentJson);
generateRecordService.updateByBo(updateBo);
log.info("成功更新子钢卷二维码的分卷步骤child_coil_idsqrcodeRecordId: {}, currentCoilId: {}, childCoilIdsStr: {}",
qrcodeRecordId, currentCoilId, childCoilIdsStr);
} catch (Exception e) {
log.error("更新子钢卷二维码的分卷步骤child_coil_ids失败qrcodeRecordId: {}, currentCoilId: {}, childCoilIdsStr: {}",
qrcodeRecordId, currentCoilId, childCoilIdsStr, e);
throw new RuntimeException("更新子钢卷二维码失败: " + e.getMessage());
}
}
/**
* 更新二维码记录以记录回滚操作
*/
private void updateQrcodeForRollback(WmsGenerateRecordVo qrcodeRecord, WmsMaterialCoil currentCoil, WmsMaterialCoil historyCoil) {
try {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> contentMap = objectMapper.readValue(qrcodeRecord.getContent(), Map.class);
// 获取现有steps
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null) {
steps = new ArrayList<>();
}
// 添加回滚步骤
Map<String, Object> rollbackStep = new HashMap<>();
rollbackStep.put("step", steps.size() + 1);
rollbackStep.put("action", "回滚");
rollbackStep.put("operation", "撤销操作");
rollbackStep.put("deleted_coil_id", String.valueOf(currentCoil.getCoilId()));
rollbackStep.put("restored_coil_id", String.valueOf(historyCoil.getCoilId()));
rollbackStep.put("deleted_current_coil_no", currentCoil.getCurrentCoilNo());
rollbackStep.put("restored_current_coil_no", historyCoil.getCurrentCoilNo());
rollbackStep.put("operator", LoginHelper.getUsername());
rollbackStep.put("rollback_time", new java.util.Date());
steps.add(rollbackStep);
contentMap.put("steps", steps);
// 更新二维码中的当前钢卷信息指向恢复的历史钢卷
contentMap.put("current_coil_id", String.valueOf(historyCoil.getCoilId()));
contentMap.put("current_coil_no", historyCoil.getCurrentCoilNo());
// enter_coil_no 保持不变
// 更新二维码记录
String newContentJson = objectMapper.writeValueAsString(contentMap);
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
updateBo.setRecordId(qrcodeRecord.getRecordId());
updateBo.setContent(newContentJson);
generateRecordService.updateByBo(updateBo);
} catch (Exception e) {
log.error("更新二维码记录失败", e);
throw new RuntimeException("更新二维码记录失败: " + e.getMessage());
}
}
/**
* 单步分卷 - 第一步:领料并锁定钢卷
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean startSpecialSplit(Long coilId, Integer actionType) {
// 1. 查询钢卷信息
WmsMaterialCoil coil = baseMapper.selectById(coilId);
if (coil == null) {
throw new RuntimeException("钢卷不存在");
}
// 2. 检查钢卷状态
if (coil.getDataType() != 1) {
throw new RuntimeException("当前钢卷是历史卷");
}
// 3. 检查是否已被锁定或已有相同类型的待处理操作
if (coil.getExclusiveStatus() != null && coil.getExclusiveStatus() != 0) {
throw new RuntimeException("钢卷正在进行操作");
}
// // 3.1 检查是否已经有相同类型的待处理操作(防止重复领料)
// LambdaQueryWrapper<WmsCoilPendingAction> pendingActionQuery = new LambdaQueryWrapper<>();
// pendingActionQuery.eq(WmsCoilPendingAction::getCoilId, coilId)
// .eq(WmsCoilPendingAction::getActionType, 501) // 领料操作类型
// .ne(WmsCoilPendingAction::getActionStatus, 2); // 不是已完成的状态
// List<WmsCoilPendingAction> existingActions = coilPendingActionMapper.selectList(pendingActionQuery);
// if (!existingActions.isEmpty()) {
// throw new RuntimeException("该钢卷已有待处理的领料操作,无法重复领料");
// }
// 4. 设置独占状态(锁定钢卷)
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(WmsMaterialCoil::getCoilId, coilId)
.set(WmsMaterialCoil::getExclusiveStatus, 1); // 1表示单步分卷中
baseMapper.update(null, updateWrapper);
// 5. 创建待操作记录(领料操作)
WmsCoilPendingActionBo pendingActionBo = new WmsCoilPendingActionBo();
pendingActionBo.setCoilId(coilId);
pendingActionBo.setCurrentCoilNo(coil.getCurrentCoilNo());
pendingActionBo.setActionType(actionType); // 领料操作类型
pendingActionBo.setActionStatus(0); // 待处理
pendingActionBo.setSourceType("manual"); // 手动创建
pendingActionBo.setPriority(0); // 默认普通优先级
Boolean insertResult = coilPendingActionService.insertByBo(pendingActionBo);
if (!insertResult) {
throw new RuntimeException("创建待操作失败");
}
return true;
}
/**
* 单步分卷 - 第二步:逐个创建子钢卷
*/
@Override
@Transactional(rollbackFor = Exception.class)
public WmsMaterialCoilVo createChildCoilInSpecialSplit(Long parentCoilId, WmsMaterialCoilBo childCoilBo, Long pendingActionId) {
// 1. 查询母卷信息
WmsMaterialCoil parentCoil = baseMapper.selectById(parentCoilId);
if (parentCoil == null) {
throw new RuntimeException("钢卷不存在");
}
// 2. 验证独占状态
if (parentCoil.getExclusiveStatus() == null || parentCoil.getExclusiveStatus() != 1) {
throw new RuntimeException("钢卷未处于分卷状态");
}
// 3. 查询待操作记录
WmsCoilPendingActionVo pendingAction = coilPendingActionService.queryById(pendingActionId);
if (pendingAction == null || !pendingAction.getCoilId().equals(parentCoilId)) {
throw new RuntimeException("待操作记录不存在或不匹配");
}
// // 4. 检查子钢卷号是否重复
// Map<String, Object> duplicateCheck = checkCoilNoDuplicate(null, parentCoil.getEnterCoilNo(), childCoilBo.getCurrentCoilNo());
// if ("current".equals(duplicateCheck.get("duplicateType")) || "both".equals(duplicateCheck.get("duplicateType"))) {
// throw new RuntimeException("子钢卷号[" + childCoilBo.getCurrentCoilNo() + "]已存在");
// }
// 校验每个子卷的实际库位
if (childCoilBo.getActualWarehouseId() != null) {
validateActualWarehouseForAssign(childCoilBo.getActualWarehouseId(), null);
}
// 5. 创建子钢卷(参考普通分卷逻辑,但不设置母卷为历史数据)
WmsMaterialCoil childCoil = BeanUtil.toBean(childCoilBo, WmsMaterialCoil.class);
childCoil.setCoilId(null);
childCoil.setDataType(1); // 当前数据
childCoil.setTransferType(null);
childCoil.setEnterCoilNo(parentCoil.getEnterCoilNo());
childCoil.setSupplierCoilNo(parentCoil.getSupplierCoilNo());
childCoil.setExclusiveStatus(0); // 子卷不被锁定
// 继承母卷的基本信息
if (childCoil.getItemType() == null) {
childCoil.setItemType(parentCoil.getItemType());
}
if (childCoil.getItemId() == null) {
childCoil.setItemId(parentCoil.getItemId());
}
if (childCoil.getWarehouseId() == null) {
childCoil.setWarehouseId(parentCoil.getWarehouseId());
}
if (childCoil.getTeam() == null) {
childCoil.setTeam(parentCoil.getTeam());
}
// 在子卷的 parent_coil_nos 字段中记录母卷号
childCoil.setParentCoilNos(parentCoil.getCurrentCoilNo());
// 6. 生成子钢卷的二维码(复用分卷二维码生成逻辑)
List<String> allNewCoilNos = Arrays.asList(childCoilBo.getCurrentCoilNo());
Long childQrcodeId = generateQrcodeForSplit(parentCoil, childCoilBo, allNewCoilNos);
childCoil.setQrcodeRecordId(childQrcodeId);
// 7. 校验并插入子钢卷
validEntityBeforeSave(childCoil);
childCoil.setParentCoilId(String.valueOf(parentCoilId));
baseMapper.insert(childCoil);
// 插入子钢卷的异常信息
if (childCoilBo.getAbnormals() != null && !childCoilBo.getAbnormals().isEmpty()) {
for (WmsCoilAbnormalBo abnormalBo : childCoilBo.getAbnormals()) {
abnormalBo.setCoilId(childCoil.getCoilId());
coilAbnormalService.insertByBo(abnormalBo);
}
}
updateActualWarehouseEnableStatus(null, childCoilBo.getActualWarehouseId());
// 8. 更新二维码中的coilId
updateQrcodeCoilId(childQrcodeId, childCoil.getCoilId());
// 9. 更新待操作记录的备注字段记录已创建的子钢卷ID
String existingChildCoilIds = pendingAction.getRemark() != null ? pendingAction.getRemark() : "";
String updatedChildCoilIds = existingChildCoilIds.isEmpty() ?
String.valueOf(childCoil.getCoilId()) :
existingChildCoilIds + "," + childCoil.getCoilId();
coilPendingActionMapper.update(
null,
Wrappers.<WmsCoilPendingAction>lambdaUpdate()
.eq(WmsCoilPendingAction::getActionId, pendingActionId)
.set(WmsCoilPendingAction::getRemark, updatedChildCoilIds)
);
// 10. 返回创建的子钢卷信息
WmsMaterialCoilVo result = queryById(childCoil.getCoilId());
log.info("创建特殊分卷子钢卷母卷ID={}, 子钢卷ID={}, 待操作记录ID={}", parentCoilId, childCoil.getCoilId(), pendingActionId);
return result;
}
/**
* 单步分卷 - 第三步:完成分卷操作
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> completeSpecialSplit(Long pendingActionId) {
Map<String, Object> result = new HashMap<>();
result.put("success", false);
try {
// 1. 查询待操作记录
WmsCoilPendingActionVo pendingAction = coilPendingActionService.queryById(pendingActionId);
if (pendingAction == null) {
throw new RuntimeException("待操作记录不存在");
}
if (pendingAction.getActionStatus() == 2) {
throw new RuntimeException("该分卷操作已完成");
}
// 2. 获取母卷和子卷信息
Long parentCoilId = pendingAction.getCoilId();
WmsMaterialCoil parentCoil = baseMapper.selectById(parentCoilId);
if (parentCoil == null) {
throw new RuntimeException("钢卷不存在");
}
// 3. 获取所有子卷ID
String childCoilIdsStr = pendingAction.getRemark();
if (StringUtils.isBlank(childCoilIdsStr)) {
throw new RuntimeException("没有找到已创建的子钢卷,无法完成分卷");
}
List<Long> childCoilIds = Arrays.stream(childCoilIdsStr.split(","))
.map(String::trim)
.filter(StringUtils::isNotBlank)
.map(Long::parseLong)
.collect(Collectors.toList());
QueryWrapper<WmsMaterialCoil> queryWrapper = new QueryWrapper<>();
queryWrapper.in("coil_id", childCoilIds);
queryWrapper.eq("del_flag", 0); // 确保只查询未删除的记录
List<WmsMaterialCoil> childCoils = baseMapper.selectList(queryWrapper);
List<String> childCoilNos = childCoils.stream()
.map(WmsMaterialCoil::getCurrentCoilNo)
.collect(Collectors.toList());
// 如果母卷即将成为历史卷 证明已经加工完成则释放库位
if (parentCoil.getActualWarehouseId() != null){
updateActualWarehouseEnableStatus(parentCoil.getActualWarehouseId(), null);
}
// 4. 将母卷标记为历史数据
LambdaUpdateWrapper<WmsMaterialCoil> parentUpdateWrapper = new LambdaUpdateWrapper<>();
parentUpdateWrapper.eq(WmsMaterialCoil::getCoilId, parentCoilId)
.set(WmsMaterialCoil::getDataType, 0) // 历史数据
.set(WmsMaterialCoil::getParentCoilNos, String.join(",", childCoilNos)); // 记录子卷号
baseMapper.update(null, parentUpdateWrapper);
// 4.1 将母卷的二维码标记为失效
if (parentCoil.getQrcodeRecordId() != null) {
WmsGenerateRecordBo parentQrBo = new WmsGenerateRecordBo();
parentQrBo.setRecordId(parentCoil.getQrcodeRecordId());
parentQrBo.setStatus(0); // 0=失效
generateRecordService.updateByBo(parentQrBo);
}
// 5. 批量更新所有子钢卷的二维码中的child_coil_ids
String allChildCoilIdsStr = childCoilIds.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
for (WmsMaterialCoil childCoil : childCoils) {
if (childCoil.getQrcodeRecordId() != null) {
updateChildCoilIdsQrcodeCoilId(parentCoilId, childCoil.getQrcodeRecordId(), childCoil.getCoilId(), allChildCoilIdsStr);
}
}
// 6. 解除母卷的独占状态
LambdaUpdateWrapper<WmsMaterialCoil> unlockWrapper = new LambdaUpdateWrapper<>();
unlockWrapper.eq(WmsMaterialCoil::getCoilId, parentCoilId)
.set(WmsMaterialCoil::getExclusiveStatus, 0); // 解除锁定
baseMapper.update(null, unlockWrapper);
// 7. 更新待操作记录状态为已完成
WmsCoilPendingActionBo completePendingBo = new WmsCoilPendingActionBo();
completePendingBo.setActionId(pendingActionId);
completePendingBo.setActionStatus(2); // 2=已完成
completePendingBo.setCompleteTime(new Date());
coilPendingActionService.updateByBo(completePendingBo);
// 7.1 单步分卷完成记录加工日志operationType=2
// 老的实际库区存在则加一条加工出库记录
Long parentActualWarehouseId = parentCoil.getActualWarehouseId();
if (parentActualWarehouseId != null && parentActualWarehouseId != -1L) {
recordWarehouseOperationLog(parentCoilId, parentActualWarehouseId, 2, 2, "单步分卷完成-加工出库");
}
// 为每个子钢卷的实际库区加一条加工入库记录(有的加,没有不加)
for (WmsMaterialCoil childCoil : childCoils) {
Long childActualWarehouseId = childCoil.getActualWarehouseId();
if (childActualWarehouseId != null && childActualWarehouseId != -1L) {
recordWarehouseOperationLog(childCoil.getCoilId(), childActualWarehouseId, 2, 1, "单步分卷完成-加工入库");
}
}
// 8. 返回结果
result.put("success", true);
result.put("parentCoilId", parentCoilId);
result.put("childCoilIds", childCoilIds);
result.put("message", "单步分卷操作已完成");
log.info("完成特殊分卷母卷ID={}, 子钢卷数量={}, 待操作记录ID={}", parentCoilId, childCoilIds.size(), pendingActionId);
} catch (Exception e) {
log.error("完成特殊分卷失败pendingActionId={}", pendingActionId, e);
result.put("success", false);
result.put("message", "完成分卷操作失败:" + e.getMessage());
throw e;
}
return result;
}
//Map<String, Object> cancelSpecialSplit(@NotNull(message = "待操作记录ID不能为空") Long pendingActionId);
/**
* 单步分卷 - 取消分卷(释放锁 + 回滚子卷)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> cancelSpecialSplit(Long pendingActionId) {
Map<String, Object> result = new HashMap<>();
result.put("success", false);
// 1. 查询待操作记录
WmsCoilPendingActionVo pendingAction = coilPendingActionService.queryById(pendingActionId);
if (pendingAction == null) {
throw new RuntimeException("待操作记录不存在");
}
// 2. 已完成的分卷不允许取消
if (Objects.equals(pendingAction.getActionStatus(), 2)) {
throw new RuntimeException("该分卷操作已完成,无法取消");
}
if (StringUtils.isNotBlank(pendingAction.getRemark())) {
throw new RuntimeException("该分卷操作已开始,请勿取消");
}
// 删除待操作记录
coilPendingActionService.deleteWithValidByIds(Arrays.asList(pendingActionId), false);
// 3. 查询母卷
WmsMaterialCoil parentCoil = baseMapper.selectById(pendingAction.getCoilId());
if (parentCoil == null) {
throw new RuntimeException("钢卷不存在");
}
if (parentCoil.getDataType() == 0) {
throw new RuntimeException("该钢卷已成为历史数据,无法取消分卷");
}
// 4. 校验母卷是否处于“单步分卷中”
if (Objects.equals(parentCoil.getExclusiveStatus(), 0)) {
throw new RuntimeException("钢卷未处于分卷状态,无法取消");
}
// 释放母卷独占锁
if (Objects.equals(parentCoil.getExclusiveStatus(), 1)) {
LambdaUpdateWrapper<WmsMaterialCoil> parentUpdate = new LambdaUpdateWrapper<>();
parentUpdate.eq(WmsMaterialCoil::getCoilId, parentCoil.getCoilId())
.set(WmsMaterialCoil::getExclusiveStatus, 0);
baseMapper.update(null, parentUpdate);
}
result.put("success", true);
result.put("message", "取消分卷成功");
return result;
}
/**
* 钢卷退货操作
* 将钢卷退货到退货仓,创建新钢卷记录,将原钢卷设置为历史钢卷
*/
@Override
@Transactional(rollbackFor = Exception.class)
public WmsMaterialCoilVo returnCoil(Long coilId) {
if (coilId == null) {
throw new RuntimeException("钢卷ID不能为空");
}
validateCoilOperationPermission(coilId, "退货");
WmsMaterialCoil oldCoil = baseMapper.selectById(coilId);
if (oldCoil == null) {
throw new RuntimeException("原钢卷不存在");
}
if (oldCoil.getDataType() == 0) {
throw new RuntimeException("该钢卷是历史钢卷");
}
WmsWarehouseBo warehouseBo = new WmsWarehouseBo();
warehouseBo.setWarehouseName("退货仓");
warehouseBo.setWarehouseCode("return");
warehouseBo.setWarehouseType(1L);
warehouseBo.setIsEnabled(1);
List<WmsWarehouseVo> warehouseList = warehouseService.queryList(warehouseBo);
if (warehouseList == null || warehouseList.isEmpty()) {
throw new RuntimeException("退货仓不存在,请先配置退货仓");
}
Long returnWarehouseId = warehouseList.get(0).getWarehouseId();
if (oldCoil.getActualWarehouseId() != null) {
updateActualWarehouseEnableStatus(oldCoil.getActualWarehouseId(), null);
}
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(WmsMaterialCoil::getCoilId, oldCoil.getCoilId())
.set(WmsMaterialCoil::getDataType, 0);
baseMapper.update(null, updateWrapper);
WmsMaterialCoil newCoil = new WmsMaterialCoil();
BeanUtils.copyProperties(oldCoil, newCoil);
newCoil.setCoilId(null);
newCoil.setDataType(1);
newCoil.setWarehouseId(returnWarehouseId);
newCoil.setNextWarehouseId(null);
newCoil.setQrcodeRecordId(oldCoil.getQrcodeRecordId());
newCoil.setStatus(0);
newCoil.setExportBy(null);
newCoil.setExportTime(null);
newCoil.setCreateTime(new Date());
newCoil.setUpdateTime(new Date());
// 创建人
newCoil.setCreateBy(LoginHelper.getUsername());
newCoil.setUpdateBy(LoginHelper.getUsername());
validEntityBeforeSave(newCoil);
boolean flag = baseMapper.insert(newCoil) > 0;
if (flag && oldCoil.getQrcodeRecordId() != null) {
updateQrcodeContentForReturn(oldCoil, newCoil.getCoilId());
}
// 退货操作记录加工日志operationType=2
// 老的实际库区存在,加一条加工出库记录
Long oldActualWarehouseId = oldCoil.getActualWarehouseId();
if (oldActualWarehouseId != null && oldActualWarehouseId != -1L) {
recordWarehouseOperationLog(oldCoil.getCoilId(), oldActualWarehouseId, 2, 2, "退货操作-加工出库");
}
return queryById(newCoil.getCoilId());
}
/**
* 更新二维码内容(退货操作)
*/
private void updateQrcodeContentForReturn(WmsMaterialCoil oldCoil, Long newCoilId) {
try {
WmsGenerateRecordVo oldRecord = generateRecordService.queryById(oldCoil.getQrcodeRecordId());
if (oldRecord == null) {
throw new RuntimeException("二维码记录不存在");
}
ObjectMapper objectMapper = new ObjectMapper();
@SuppressWarnings("unchecked")
Map<String, Object> contentMap = objectMapper.readValue(oldRecord.getContent(), Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null) {
steps = new ArrayList<>();
}
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", oldCoil.getCurrentCoilNo());
newStep.put("old_coil_id", String.valueOf(oldCoil.getCoilId()));
newStep.put("new_coil_id", String.valueOf(newCoilId));
newStep.put("operator", LoginHelper.getUsername());
newStep.put("update_time", new java.util.Date());
steps.add(newStep);
contentMap.put("steps", steps);
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 (JsonProcessingException e) {
throw new RuntimeException("更新二维码内容失败", e);
}
}
@Override
public List<CoilTrimStatisticsVo> getCoilTrimStatistics() {
List<CoilTrimRawVo> rawList = baseMapper.selectCoilTrimStatistics();
Map<String, List<CoilTrimRawVo>> byThickness = rawList.stream()
.collect(Collectors.groupingBy(v -> v.getThickness() == null ? "空置" : v.getThickness()));
List<CoilTrimStatisticsVo> result = new ArrayList<>();
for (Map.Entry<String, List<CoilTrimRawVo>> thicknessEntry : byThickness.entrySet()) {
CoilTrimStatisticsVo vo = new CoilTrimStatisticsVo();
vo.setThickness(thicknessEntry.getKey());
List<CoilTrimRawVo> thicknessList = thicknessEntry.getValue();
Map<String, List<CoilTrimRawVo>> byTrim = thicknessList.stream()
.collect(Collectors.groupingBy(v -> v.getTrimmingRequirement() == null ? "未知" : v.getTrimmingRequirement()));
List<CoilTrimRawVo> trimmedList = byTrim.getOrDefault("净边", new ArrayList<>());
List<CoilTrimRawVo> untrimmedList = byTrim.getOrDefault("毛边", new ArrayList<>());
List<TrimWidthStatisticsVo> trimmedWidthList = new ArrayList<>();
for (CoilTrimRawVo raw : trimmedList) {
TrimWidthStatisticsVo widthVo = new TrimWidthStatisticsVo();
widthVo.setWidth(raw.getWidth() == null ? "空置" : raw.getWidth());
widthVo.setCoilCount(raw.getCoilCount() == null ? 0 : raw.getCoilCount());
widthVo.setTotalWeight(raw.getTotalWeight() == null ? BigDecimal.ZERO : raw.getTotalWeight());
trimmedWidthList.add(widthVo);
}
List<TrimWidthStatisticsVo> untrimmedWidthList = new ArrayList<>();
for (CoilTrimRawVo raw : untrimmedList) {
TrimWidthStatisticsVo widthVo = new TrimWidthStatisticsVo();
widthVo.setWidth(raw.getWidth() == null ? "空置" : raw.getWidth());
widthVo.setCoilCount(raw.getCoilCount() == null ? 0 : raw.getCoilCount());
widthVo.setTotalWeight(raw.getTotalWeight() == null ? BigDecimal.ZERO : raw.getTotalWeight());
untrimmedWidthList.add(widthVo);
}
vo.setTrimmedList(trimmedWidthList);
vo.setUntrimmedList(untrimmedWidthList);
result.add(vo);
}
result.sort(Comparator.comparing(v -> {
try {
return new BigDecimal(v.getThickness());
} catch (Exception e) {
return new BigDecimal("999999");
}
}));
return result;
}
@Override
public List<CategoryWidthStatisticsVo> getCategoryWidthStatistics() {
List<CategoryWidthRawVo> rawList = baseMapper.selectCategoryWidthStatistics();
Map<String, List<CategoryWidthRawVo>> byCategory = rawList.stream()
.collect(Collectors.groupingBy(v -> v.getCategory() == null ? "其他" : v.getCategory()));
List<CategoryWidthStatisticsVo> result = new ArrayList<>();
for (Map.Entry<String, List<CategoryWidthRawVo>> categoryEntry : byCategory.entrySet()) {
CategoryWidthStatisticsVo vo = new CategoryWidthStatisticsVo();
vo.setCategory(categoryEntry.getKey());
BigDecimal total = BigDecimal.ZERO;
BigDecimal width1000 = BigDecimal.ZERO;
BigDecimal width1200 = BigDecimal.ZERO;
BigDecimal width1220 = BigDecimal.ZERO;
BigDecimal width1250 = BigDecimal.ZERO;
BigDecimal otherWidth = BigDecimal.ZERO;
for (CategoryWidthRawVo raw : categoryEntry.getValue()) {
BigDecimal weight = raw.getTotalWeight() == null ? BigDecimal.ZERO : raw.getTotalWeight();
String width = raw.getWidth();
String isTrimmed = raw.getIsTrimmed();
if ("净边".equals(isTrimmed)) {
if ("1000".equals(width)) {
width1000 = width1000.add(weight);
} else if ("1200".equals(width)) {
width1200 = width1200.add(weight);
} else if ("1220".equals(width)) {
width1220 = width1220.add(weight);
} else if ("1250".equals(width)) {
width1250 = width1250.add(weight);
} else {
otherWidth = otherWidth.add(weight);
}
} else {
otherWidth = otherWidth.add(weight);
}
total = total.add(weight);
}
vo.setWidth1000(width1000);
vo.setWidth1200(width1200);
vo.setWidth1220(width1220);
vo.setWidth1250(width1250);
vo.setOtherWidth(otherWidth);
vo.setTotal(total);
result.add(vo);
}
return result;
}
@Override
public WmsMaterialCoilReportSummaryVo reportSummary(WmsMaterialCoilReportSummaryBo req) {
WmsMaterialCoilBo coilFilter = req != null && req.getMaterialCoilFilter() != null
? req.getMaterialCoilFilter() : new WmsMaterialCoilBo();
WmsCoilPendingActionBo pendingFilter = req == null ? null : req.getPendingActionFilter();
if (hasAnyPendingFilter(pendingFilter)) {
List<Long> pendingCoilIds = queryCoilIdsByPendingFilter(pendingFilter);
if (CollectionUtils.isEmpty(pendingCoilIds)) {
return buildEmptySummary();
}
coilFilter.setCoilIds(mergeCoilIds(coilFilter.getCoilIds(), pendingCoilIds));
}
List<WmsMaterialCoilVo> list = queryList(coilFilter);
return buildSummaryFromList(list);
}
private boolean hasAnyPendingFilter(WmsCoilPendingActionBo bo) {
if (bo == null) {
return false;
}
return bo.getCoilId() != null
|| StringUtils.isNotBlank(bo.getCurrentCoilNo())
|| bo.getActionType() != null
|| CollectionUtils.isNotEmpty(bo.getActionTypes())
|| bo.getActionStatus() != null
|| bo.getWarehouseId() != null
|| bo.getPriority() != null
|| StringUtils.isNotBlank(bo.getSourceType())
|| bo.getStartTime() != null
|| bo.getEndTime() != null
|| StringUtils.isNotBlank(bo.getCreateBy())
|| StringUtils.isNotBlank(bo.getUpdateBy())
|| StringUtils.isNotBlank(bo.getProcessedCoilIds())
|| bo.getIncludeDeleted() != null;
}
private List<Long> queryCoilIdsByPendingFilter(WmsCoilPendingActionBo bo) {
LambdaQueryWrapper<WmsCoilPendingAction> qw = Wrappers.lambdaQuery();
qw.select(WmsCoilPendingAction::getCoilId);
qw.eq(bo.getCoilId() != null, WmsCoilPendingAction::getCoilId, bo.getCoilId());
qw.like(StringUtils.isNotBlank(bo.getCurrentCoilNo()), WmsCoilPendingAction::getCurrentCoilNo, bo.getCurrentCoilNo());
if (CollectionUtils.isNotEmpty(bo.getActionTypes())) {
qw.in(WmsCoilPendingAction::getActionType, bo.getActionTypes());
} else {
qw.eq(bo.getActionType() != null, WmsCoilPendingAction::getActionType, bo.getActionType());
}
if (bo.getActionStatus() != null) {
if (bo.getActionStatus() == -1) {
qw.ne(WmsCoilPendingAction::getActionStatus, 2);
} else {
qw.eq(WmsCoilPendingAction::getActionStatus, bo.getActionStatus());
}
}
qw.eq(bo.getWarehouseId() != null, WmsCoilPendingAction::getWarehouseId, bo.getWarehouseId());
qw.eq(bo.getPriority() != null, WmsCoilPendingAction::getPriority, bo.getPriority());
qw.like(StringUtils.isNotBlank(bo.getSourceType()), WmsCoilPendingAction::getSourceType, bo.getSourceType());
qw.ge(bo.getStartTime() != null, WmsCoilPendingAction::getCompleteTime, bo.getStartTime());
qw.le(bo.getEndTime() != null, WmsCoilPendingAction::getCompleteTime, bo.getEndTime());
qw.eq(StringUtils.isNotBlank(bo.getCreateBy()), WmsCoilPendingAction::getCreateBy, bo.getCreateBy());
qw.eq(StringUtils.isNotBlank(bo.getUpdateBy()), WmsCoilPendingAction::getUpdateBy, bo.getUpdateBy());
qw.like(StringUtils.isNotBlank(bo.getProcessedCoilIds()), WmsCoilPendingAction::getProcessedCoilIds, bo.getProcessedCoilIds());
if (bo.getIncludeDeleted() != null) {
if (bo.getIncludeDeleted() == 2) {
qw.eq(WmsCoilPendingAction::getDelFlag, 2);
} else if (bo.getIncludeDeleted() == 0) {
qw.eq(WmsCoilPendingAction::getDelFlag, 0);
}
} else {
qw.eq(WmsCoilPendingAction::getDelFlag, 0);
}
List<WmsCoilPendingAction> actions = coilPendingActionMapper.selectList(qw);
if (CollectionUtils.isEmpty(actions)) {
return Collections.emptyList();
}
return actions.stream()
.map(WmsCoilPendingAction::getCoilId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
}
private String mergeCoilIds(String coilIdsCsv, List<Long> pendingCoilIds) {
Set<Long> pendingSet = new LinkedHashSet<>(pendingCoilIds);
if (StringUtils.isBlank(coilIdsCsv)) {
return pendingSet.stream().map(String::valueOf).collect(Collectors.joining(","));
}
List<Long> existing = parseCsvLongs(coilIdsCsv);
if (CollectionUtils.isEmpty(existing)) {
return pendingSet.stream().map(String::valueOf).collect(Collectors.joining(","));
}
Set<Long> existingSet = new LinkedHashSet<>(existing);
existingSet.retainAll(pendingSet);
if (existingSet.isEmpty()) {
return "-1";
}
return existingSet.stream().map(String::valueOf).collect(Collectors.joining(","));
}
private WmsMaterialCoilReportSummaryVo buildEmptySummary() {
WmsMaterialCoilReportSummaryVo vo = new WmsMaterialCoilReportSummaryVo();
vo.setTotalCount(0);
vo.setTotalWeight("0.00");
vo.setAvgWeight("0");
vo.setAbnormalSummary(buildAbnormalSummary(Collections.emptyList()));
vo.setTeamSummary(Collections.emptyMap());
return vo;
}
private WmsMaterialCoilReportSummaryVo buildSummaryFromList(List<WmsMaterialCoilVo> list) {
List<WmsMaterialCoilVo> safeList = list == null ? Collections.emptyList() : list;
int totalCount = safeList.size();
BigDecimal totalWeight = safeList.stream()
.map(WmsMaterialCoilVo::getNetWeight)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
String avgWeight = totalCount > 0
? totalWeight.divide(BigDecimal.valueOf(totalCount), 2, java.math.RoundingMode.HALF_UP).toPlainString()
: "0";
WmsMaterialCoilReportSummaryVo vo = new WmsMaterialCoilReportSummaryVo();
vo.setTotalCount(totalCount);
vo.setTotalWeight(totalWeight.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString());
vo.setAvgWeight(avgWeight);
vo.setAbnormalSummary(buildAbnormalSummary(safeList));
vo.setTeamSummary(buildTeamSummary(safeList));
return vo;
}
private List<Map<String, String>> buildAbnormalSummary(List<WmsMaterialCoilVo> list) {
int totalCount = list.size();
int jishuCount = 0;
int miniCount = 0;
int rubbishCount = 0;
int returnCount = 0;
BigDecimal jishuWeight = BigDecimal.ZERO;
BigDecimal miniWeight = BigDecimal.ZERO;
BigDecimal rubbishWeight = BigDecimal.ZERO;
BigDecimal returnWeight = BigDecimal.ZERO;
for (WmsMaterialCoilVo coil : list) {
String warehouseId = coil.getWarehouseId() == null ? null : String.valueOf(coil.getWarehouseId());
String qualityStatus = coil.getQualityStatus();
BigDecimal netWeight = coil.getNetWeight() == null ? BigDecimal.ZERO : coil.getNetWeight();
if ("2019583656787259393".equals(warehouseId) || "O".equals(qualityStatus)) {
jishuCount++;
jishuWeight = jishuWeight.add(netWeight);
} else if ("2019583325311414274".equals(warehouseId)) {
miniCount++;
miniWeight = miniWeight.add(netWeight);
} else if ("2019583429955104769".equals(warehouseId)
|| "D-".equals(qualityStatus)
|| "D".equals(qualityStatus)
|| "D+".equals(qualityStatus)
|| "C-".equals(qualityStatus)
|| "C".equals(qualityStatus)
|| "C+".equals(qualityStatus)) {
rubbishCount++;
rubbishWeight = rubbishWeight.add(netWeight);
} else if ("2019583137616310273".equals(warehouseId)) {
returnCount++;
returnWeight = returnWeight.add(netWeight);
}
}
List<Map<String, String>> result = new ArrayList<>();
result.add(buildLabelValue("技术部钢卷数", String.valueOf(jishuCount)));
result.add(buildLabelValue("小钢卷库钢卷数", String.valueOf(miniCount)));
result.add(buildLabelValue("废品库钢卷数", String.valueOf(rubbishCount)));
result.add(buildLabelValue("退货库钢卷数", String.valueOf(returnCount)));
result.add(buildLabelValue("技术部钢卷重量", jishuWeight.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString()));
result.add(buildLabelValue("小钢卷库钢卷重量", miniWeight.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString()));
result.add(buildLabelValue("废品库钢卷重量", rubbishWeight.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString()));
result.add(buildLabelValue("退货库钢卷重量", returnWeight.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString()));
result.add(buildLabelValue("技术部占比", percent(jishuCount, totalCount)));
result.add(buildLabelValue("小钢卷库占比", percent(miniCount, totalCount)));
result.add(buildLabelValue("废品库占比", percent(rubbishCount, totalCount)));
result.add(buildLabelValue("退货库占比", percent(returnCount, totalCount)));
return result;
}
private Map<String, WmsMaterialCoilReportSummaryVo.TeamSummaryItem> buildTeamSummary(List<WmsMaterialCoilVo> list) {
Map<String, WmsMaterialCoilReportSummaryVo.TeamSummaryItem> teamSummary = new LinkedHashMap<>();
for (WmsMaterialCoilVo coil : list) {
String team = StringUtils.isNotBlank(coil.getTeam()) ? coil.getTeam() : "未分配";
WmsMaterialCoilReportSummaryVo.TeamSummaryItem item = teamSummary.computeIfAbsent(team, k -> {
WmsMaterialCoilReportSummaryVo.TeamSummaryItem summaryItem = new WmsMaterialCoilReportSummaryVo.TeamSummaryItem();
summaryItem.setCount(0);
summaryItem.setWeight("0.00");
return summaryItem;
});
int nextCount = item.getCount() == null ? 1 : item.getCount() + 1;
BigDecimal currWeight = StringUtils.isBlank(item.getWeight()) ? BigDecimal.ZERO : new BigDecimal(item.getWeight());
BigDecimal netWeight = coil.getNetWeight() == null ? BigDecimal.ZERO : coil.getNetWeight();
item.setCount(nextCount);
item.setWeight(currWeight.add(netWeight).setScale(2, java.math.RoundingMode.HALF_UP).toPlainString());
}
return teamSummary;
}
private Map<String, String> buildLabelValue(String label, String value) {
Map<String, String> map = new LinkedHashMap<>(2);
map.put("label", label);
map.put("value", value);
return map;
}
private String percent(int part, int total) {
if (total <= 0) {
return "0.00%";
}
return BigDecimal.valueOf(part)
.multiply(BigDecimal.valueOf(100))
.divide(BigDecimal.valueOf(total), 2, java.math.RoundingMode.HALF_UP)
.toPlainString() + "%";
}
/**
* 查询itemId和itemType不匹配的钢卷
* 检查所有钢卷的itemId是否存在于对应的表中根据itemType
*/
@Override
public List<WmsMaterialCoilVo> queryMismatchedItemCoils() {
// 用SQL子查询直接找出itemId和itemType不匹配的钢卷
List<WmsMaterialCoil> mismatchedCoils = baseMapper.selectMismatchedItemCoils();
if (mismatchedCoils == null || mismatchedCoils.isEmpty()) {
return new ArrayList<>();
}
// 批量填充关联信息
List<WmsMaterialCoilVo> voList = mismatchedCoils.stream()
.map(coil -> BeanUtil.toBean(coil, WmsMaterialCoilVo.class))
.collect(Collectors.toList());
fillRelatedObjectsBatch(voList);
return voList;
}
}