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 voList) { if (voList == null || voList.isEmpty()) { return; } // 收集所有需要查询的ID Set warehouseIds = new HashSet<>(); Set nextWarehouseIds = new HashSet<>(); Set actualWarehouseIds = new HashSet<>(); Set qrcodeRecordIds = new HashSet<>(); Set rawMaterialIds = new HashSet<>(); Set productIds = new HashSet<>(); Set 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 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 actualWarehouseMap = new HashMap<>(); if (!actualWarehouseIds.isEmpty()) { for (Long id : actualWarehouseIds) { WmsActualWarehouseVo actualWarehouse = actualWarehouseService.queryById(id); if (actualWarehouse != null) { actualWarehouseMap.put(id, actualWarehouse); } } } // 批量查询二维码信息 Map qrcodeMap = new HashMap<>(); if (!qrcodeRecordIds.isEmpty()) { for (Long id : qrcodeRecordIds) { WmsGenerateRecordVo qrcode = generateRecordService.queryById(id); if (qrcode != null) { qrcodeMap.put(id, qrcode); } } } // 批量查询原材料信息 Map 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 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 queryPageList(WmsMaterialCoilBo bo, PageQuery pageQuery) { Page result = queryMaterialCoilPage(bo, pageQuery); List records = result.getRecords(); if (records == null || records.isEmpty()) { return TableDataInfo.build(result); } fillPageCommonFields(records); return TableDataInfo.build(result); } @Override public TableDataInfo queryPageListWithBindInfo(WmsMaterialCoilBo bo, PageQuery pageQuery) { Page result = queryMaterialCoilPage(bo, pageQuery); List baseRecords = result.getRecords(); if (baseRecords == null || baseRecords.isEmpty()) { return TableDataInfo.build(new Page<>(result.getCurrent(), result.getSize(), result.getTotal())); } List bindRecords = baseRecords.stream().map(item -> { WmsMaterialCoilBindVo bindVo = new WmsMaterialCoilBindVo(); BeanUtil.copyProperties(item, bindVo); return bindVo; }).collect(Collectors.toList()); fillBindInfoForPage(bindRecords); fillPageCommonFields(bindRecords); Page bindResult = new Page<>(result.getCurrent(), result.getSize(), result.getTotal()); bindResult.setRecords(bindRecords); return TableDataInfo.build(bindResult); } /** * 统计筛选条件下的全量汇总数据(高性能:只查sum/count) * 独立的统计接口,不影响分页查询 */ @Override public Map getStatistics(WmsMaterialCoilBo bo) { QueryWrapper qw = buildQueryWrapperPlus(bo); return selectMaterialCoilStatistics(qw); } private Page queryMaterialCoilPage(WmsMaterialCoilBo bo, PageQuery pageQuery) { QueryWrapper qw = buildQueryWrapperPlus(bo); Page 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 records) { Set userNames = records.stream() .flatMap(v -> java.util.stream.Stream.of(v.getCreateBy(), v.getUpdateBy(), v.getExportBy())) .filter(StringUtils::isNotBlank) .collect(Collectors.toSet()); Map 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 selectMaterialCoilStatistics(QueryWrapper qw) { Map 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 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 records) { // 绑定信息仅用于专用接口,避免污染通用分页结构 try { List coilIds = records.stream() .map(WmsMaterialCoilVo::getCoilId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); if (coilIds.isEmpty()) { return; } Map 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 buildQueryWrapperPlus(WmsMaterialCoilBo bo) { QueryWrapper 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 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 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 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 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 explicitItemIds = null; if (StringUtils.isNotBlank(bo.getItemIds())) { String[] itemIdArray = bo.getItemIds().split(","); List 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 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 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 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 getDuplicateCoilGroups() { // 使用优化的数据库查询方法,直接获取重复入场卷号的钢卷信息 List enterDuplicates = baseMapper.selectDuplicateEnterCoilNoList(); // 使用优化的数据库查询方法,直接获取重复当前卷号的钢卷信息 List currentDuplicates = baseMapper.selectDuplicateCurrentCoilNoList(); // 按入场卷号分组重复项 Map> enterGrouped = enterDuplicates.stream() .filter(e -> StringUtils.isNotBlank(e.getEnterCoilNo())) .collect(Collectors.groupingBy(WmsMaterialCoilVo::getEnterCoilNo)); // 按当前卷号分组重复项 Map> currentGrouped = currentDuplicates.stream() .filter(e -> StringUtils.isNotBlank(e.getCurrentCoilNo())) .collect(Collectors.groupingBy(WmsMaterialCoilVo::getCurrentCoilNo)); // 构建入场卷号重复组 List> enterGroups = enterGrouped.entrySet().stream() .filter(entry -> entry.getValue() != null && entry.getValue().size() > 1) .map(entry -> { Map group = new HashMap<>(); group.put("enterCoilNo", entry.getKey()); group.put("coils", entry.getValue()); return group; }) .collect(Collectors.toList()); // 构建当前卷号重复组 List> currentGroups = currentGrouped.entrySet().stream() .filter(entry -> entry.getValue() != null && entry.getValue().size() > 1) .map(entry -> { Map group = new HashMap<>(); group.put("currentCoilNo", entry.getKey()); group.put("coils", entry.getValue()); return group; }) .collect(Collectors.toList()); Map 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 args) { if (StringUtils.isBlank(csvValues)) { return ""; } String[] vals = csvValues.split(","); List 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 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 level2List = actualWarehouseService.queryList(level2Bo); if (level2List == null || level2List.isEmpty()) { return new ArrayList<>(); } List resultIds = new ArrayList<>(); for (WmsActualWarehouseVo level2 : level2List) { if (level2 == null || level2.getActualWarehouseId() == null) { continue; } WmsActualWarehouseBo childBo = new WmsActualWarehouseBo(); childBo.setParentId(level2.getActualWarehouseId()); List 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 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 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 coils = this.queryList(coilBo); result.setWarehouses(warehouses); result.setCoils(coils); result.setTotal(coils.size()); return result; } /** * 查询钢卷物料表列表 */ @Override public List queryList(WmsMaterialCoilBo bo) { QueryWrapper lqw = buildQueryWrapperPlus(bo); List list = baseMapper.selectVoListWithDynamicJoin(lqw); return list; } @Override public String queryQualityStatusByWarehouseIdAndCurrentCoilNo(Long warehouseId, String currentCoilNo) { if (warehouseId == null || StringUtils.isBlank(currentCoilNo)) { return null; } LambdaQueryWrapper 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 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> steps = new ArrayList<>(); Map 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 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 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 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 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 updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(WmsMaterialCoil::getCoilId, bo.getCoilId()) .set(WmsMaterialCoil::getDataType, 0); // 设置为历史数据 baseMapper.update(null, updateWrapper); } // 2. 插入多条新的当前数据(data_type=1) List newCoils = new ArrayList<>(); List 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 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 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 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 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 originalCoils) { if (CollectionUtils.isEmpty(originalCoils)) { return; } // 收集所有需要验证的coilId List coilIds = originalCoils.stream() .map(WmsMaterialCoilBo::getCoilId) .filter(Objects::nonNull) .collect(Collectors.toList()); if (CollectionUtils.isEmpty(coilIds)) { return; } // 批量查询钢卷数据,避免N+1查询问题 List originalCoilsData = baseMapper.selectBatchIds(coilIds); // 检查是否有历史数据 List 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 allNewCoilNos) { try { Map 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> steps = new ArrayList<>(); if (oldCoil.getQrcodeRecordId() != null) { WmsGenerateRecordVo oldRecord = generateRecordService.queryById(oldCoil.getQrcodeRecordId()); if (oldRecord != null) { ObjectMapper objectMapper = new ObjectMapper(); @SuppressWarnings("unchecked") Map oldContentMap = objectMapper.readValue(oldRecord.getContent(), Map.class); @SuppressWarnings("unchecked") List> oldSteps = (List>) oldContentMap.get("steps"); if (oldSteps != null) { steps.addAll(oldSteps); } } } // 添加分卷步骤 Map 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 originalCoils) { try { if (mergedCoilBo == null) { throw new RuntimeException("合卷后的钢卷数据不能为空"); } Map 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> 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 originalContentMap = objectMapper.readValue(originalQr.getContent(), Map.class); @SuppressWarnings("unchecked") List> originalSteps = (List>) originalContentMap.get("steps"); if (originalSteps != null) { steps.addAll(originalSteps); } } } } } } // 添加合卷步骤 Map mergeStep = new HashMap<>(); mergeStep.put("step", steps.size() + 1); mergeStep.put("action", "更新"); mergeStep.put("operation", "合卷"); // 收集参与合卷的原始钢卷号和ID List originalCoilNos = new ArrayList<>(); List 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 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 contentMap = objectMapper.readValue(oldRecord.getContent(), Map.class); // 获取现有steps @SuppressWarnings("unchecked") List> steps = (List>) contentMap.get("steps"); if (steps == null) { steps = new ArrayList<>(); } // 添加新的step,记录更新信息 Map 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 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 contentMap = objectMapper.readValue(oldRecord.getContent(), Map.class); @SuppressWarnings("unchecked") List> steps = (List>) contentMap.get("steps"); if (steps == null) { steps = new ArrayList<>(); } Map 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 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 ids, Boolean isValid) { if (isValid) { //TODO 做一些业务上的校验,判断是否需要校验 } // 检查独占状态 for (Long id : ids) { validateCoilOperationPermission(id, "删除"); } // 获取要删除的钢卷记录 List 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 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 contentMap = objectMapper.readValue(qrRecord.getContent(), Map.class); @SuppressWarnings("unchecked") List> steps = (List>) contentMap.get("steps"); Set allCoilNos = new HashSet<>(); Set operatorUsernames = new HashSet<>(); if (steps != null) { for (Map 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 operatorNicknameMap = getOperatorNicknames(operatorUsernames); List> 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 filteredCoilNos = allCoilNos; if (currentCoilNo != null && !currentCoilNo.trim().isEmpty()) { final String filterValue = currentCoilNo; filteredCoilNos = allCoilNos.stream() .filter(coilNo -> coilNo.contains(filterValue)) .collect(Collectors.toSet()); } List result = new ArrayList<>(); if (!filteredCoilNos.isEmpty() && StringUtils.isNotBlank(enterCoilNo)) { List coilNoList = new ArrayList<>(filteredCoilNos); if (coilNoList.size() <= 1000) { LambdaQueryWrapper 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 batch = coilNoList.subList(i, end); LambdaQueryWrapper batchLqw = Wrappers.lambdaQuery(); batchLqw.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo); batchLqw.in(WmsMaterialCoil::getCurrentCoilNo, batch); batchLqw.orderByAsc(WmsMaterialCoil::getCreateTime); List batchResult = baseMapper.selectVoList(batchLqw); result.addAll(batchResult); } } if (!result.isEmpty()) { fillRelatedObjectsBatch(result); } } if (result.isEmpty() && StringUtils.isNotBlank(enterCoilNo)) { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo); lqw.orderByAsc(WmsMaterialCoil::getCreateTime); result = baseMapper.selectVoList(lqw); if (!result.isEmpty()) { fillRelatedObjectsBatch(result); } } Map 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 getOperatorNicknames(Set usernames) { Map 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 step, String fieldName, Set 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 step, String fieldName, Set 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 getDistributionByWarehouse(String itemType, Long itemId) { List> mapList = baseMapper.getDistributionByWarehouse(itemType, itemId); return convertMapListToVoList(mapList); } @Override public List getDistributionByActualWarehouse(String itemType, Long itemId) { List> mapList = baseMapper.getDistributionByActualWarehouse(itemType, itemId); return convertMapListToVoListActual(mapList); } private List convertMapListToVoListActual(List> mapList) { List voList = new ArrayList<>(); for (Map 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 getDistributionByItemType(String itemType, Long itemId) { List> mapList = baseMapper.getDistributionByItemType(itemType, itemId); return convertMapListToVoList(mapList); } /** * 将Map列表转换为WmsMaterialCoilVo列表 */ private List convertMapListToVoList(List> mapList) { List voList = new ArrayList<>(); for (Map 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 map, List 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 getDistributionByActualItemType(String itemType, Long itemId) { List> mapList = baseMapper.getDistributionByActualItemType(itemType, itemId); return convertMapListToVoListActual(mapList); } /** * 查询钢卷导出数据列表 */ @Override public List queryExportList(WmsMaterialCoilBo bo) { Map coilIdCompleteTimeMap = new HashMap<>(); if ((bo.getCoilIds() == null || bo.getCoilIds().isEmpty()) && bo.getActionIds() != null && !bo.getActionIds().isEmpty()) { String[] actionIdArr = bo.getActionIds().split(","); List actionIdList = Arrays.stream(actionIdArr) .map(String::trim) .filter(s -> !s.isEmpty()) .map(Long::parseLong) .collect(Collectors.toList()); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.in(WmsCoilPendingAction::getActionId, actionIdList); List 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 lqw = buildQueryWrapperPlus(bo); List 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 queryExportListAll(WmsMaterialCoilBo bo) { QueryWrapper lqw = buildQueryWrapperPlus(bo); List wmsMaterialCoilVos = baseMapper.selectExportList(lqw); List 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 queryDeliveryExportList(WmsMaterialCoilBo bo) { List coilIds = parseCsvLongs(bo == null ? null : bo.getCoilIds()); if (coilIds.isEmpty()) { return Collections.emptyList(); } List 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 parseCsvLongs(String csv) { if (StringUtils.isBlank(csv)) { return Collections.emptyList(); } String[] arr = csv.split(","); List 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 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 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 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 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 coilIds, Integer status) { if (coilIds == null || coilIds.isEmpty()) { return false; } if (status == null || status < 0 || status > 2) { throw new RuntimeException("无效的状态值,状态必须在0-2之间"); } // ********** 批量查询钢卷原库区ID(仅当发货状态时需要)********** Map coilWarehouseMap = new HashMap<>(); // key: coilId, value: oldActualWarehouseId if (status == 1) { // 仅当设置为"已发货/在途"状态时,才处理库区释放逻辑 // 批量查询钢卷信息,获取原实际库区ID List coilVoList = queryBatchByIds(coilIds); for (WmsMaterialCoilVo vo : coilVoList) { if (vo != null && vo.getActualWarehouseId() != null) { coilWarehouseMap.put(vo.getCoilId(), vo.getActualWarehouseId()); } } } // 构造更新条件 LambdaUpdateWrapper 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 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 queryBatchByIds(List coilIds) { if (coilIds == null || coilIds.isEmpty()) { return Collections.emptyList(); } // 这里替换成你实际的批量查询逻辑(比如用IN条件查询) LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); queryWrapper.in(WmsMaterialCoil::getCoilId, coilIds); List 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 getUpdatedCoilIdsByOldCoilIds(List oldCoilIds) { // 初始化返回的Map,key=旧ID,value=下一步ID字符串(逗号分隔) Map 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 qrWrapper = Wrappers.lambdaQuery(); qrWrapper.eq(WmsGenerateRecord::getSerialNumber, oldCoil.getEnterCoilNo()) .or() .like(WmsGenerateRecord::getSerialNumber, oldCoil.getEnterCoilNo() + "-"); List 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 contentMap; try { contentMap = objectMapper.readValue(qrRecord.getContent(), Map.class); } catch (JsonProcessingException e) { log.warn("解析二维码记录[{}]的content失败: {}", qrRecord.getRecordId(), e.getMessage()); continue; } List> steps = (List>) contentMap.get("steps"); if (steps == null || steps.isEmpty()) { continue; } String oldCoilIdStr = oldCoilId.toString(); // 4. 遍历步骤找下一步ID for (Map 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 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 childCoilNos = null; // 解析子卷号列表 if (newCurrentCoilNos != null && !newCurrentCoilNos.isEmpty()) { childCoilNos = Arrays.asList(newCurrentCoilNos.split(",")); } else if (step.get("child_coils") instanceof List) { childCoilNos = (List) step.get("child_coils"); } // 批量查询所有子卷ID并拼接为逗号分隔的字符串 if (childCoilNos != null && !childCoilNos.isEmpty()) { // 去重+trim,避免空字符串/重复卷号 List validChildCoilNos = childCoilNos.stream() .map(String::trim) .filter(no -> !no.isEmpty()) .distinct() .collect(Collectors.toList()); if (!validChildCoilNos.isEmpty()) { // 批量查询(性能优化,避免循环查询) LambdaQueryWrapper coilWrapper = Wrappers.lambdaQuery(); coilWrapper.in(WmsMaterialCoil::getCurrentCoilNo, validChildCoilNos) .eq(WmsMaterialCoil::getDataType, 1); List 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 checkCoilNoDuplicate(Long coilId, String enterCoilNo, String currentCoilNo, String supplierCoilNo) { Map 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 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 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 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 包含最大钢卷号和前缀的Map,key分别为maxEnterCoilNo、prefix */ @Override public Map getMaxEnterCoilNoByPrefix(String enterCoilNoPrefix) { Map 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 wrapper = Wrappers.lambdaQuery(); wrapper.likeRight(WmsMaterialCoil::getEnterCoilNo, enterCoilNoPrefix) .eq(WmsMaterialCoil::getDelFlag, 0) .select(WmsMaterialCoil::getEnterCoilNo); // 仅查询需要的字段,提升性能 // 4. 查询所有匹配的钢卷记录 List 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 rollbackCoil(Long currentCoilId) { Map 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 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 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 getLastOperationStepFromQrcode(String content) { Map result = new HashMap<>(); try { ObjectMapper objectMapper = new ObjectMapper(); Map contentMap = objectMapper.readValue(content, Map.class); @SuppressWarnings("unchecked") List> steps = (List>) contentMap.get("steps"); if (steps == null || steps.isEmpty()) { return result; } // 从后往前找到最后一个非回滚的步骤 for (int i = steps.size() - 1; i >= 0; i--) { Map 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 parseRollbackInfoFromQrcode(String content, Long currentCoilId) { Map result = new HashMap<>(); try { ObjectMapper objectMapper = new ObjectMapper(); Map contentMap = objectMapper.readValue(content, Map.class); @SuppressWarnings("unchecked") List> steps = (List>) contentMap.get("steps"); if (steps == null || steps.isEmpty()) { result.put("operationType", "NORMAL"); return result; } // 从后往前查找最新的操作 for (int i = steps.size() - 1; i >= 0; i--) { Map 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 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 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 rollbackMergeOperation(WmsMaterialCoil currentCoil, WmsGenerateRecordVo qrcodeRecord, Map rollbackInfo, Map result) { List parentCoilIds = (List) rollbackInfo.get("parentCoilIds"); if (parentCoilIds == null || parentCoilIds.isEmpty()) { throw new RuntimeException("无法获取合卷的原始钢卷ID,无法回滚"); } // 查询所有原始钢卷 List originalCoils = baseMapper.selectBatchIds(parentCoilIds); // 检查所有原始钢卷是否存在 if (originalCoils.size() != parentCoilIds.size()) { throw new RuntimeException("部分原始钢卷不存在,无法回滚"); } // // 检查恢复的钢卷的当前钢卷号是否重复 // for (WmsMaterialCoil originalCoil : originalCoils) { // Map 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 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 rollbackSplitOperation(WmsMaterialCoil currentCoil, WmsGenerateRecordVo qrcodeRecord, Map rollbackInfo, Map result) { List childCoilIds = (List) rollbackInfo.get("childCoilIds"); if (childCoilIds == null || childCoilIds.isEmpty()) { throw new RuntimeException("无法获取分卷的子钢卷ID,无法回滚"); } // 检查当前钢卷ID是否在childCoilIds里面,如果不在说明不是同一步骤则不能回滚 if (!childCoilIds.contains(currentCoil.getCoilId())) { throw new RuntimeException("无可回滚的母卷"); } // 查询所有子钢卷 List childCoils = baseMapper.selectBatchIds(childCoilIds); // 检查所有子钢卷是否存在 if (childCoils.size() != childCoilIds.size()) { throw new RuntimeException("部分子钢卷不存在,无法回滚"); } // 找到母卷 Map targetStep = (Map) 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 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 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 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 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 rollbackNormalOperation(WmsMaterialCoil currentCoil, WmsGenerateRecordVo qrcodeRecord, Map 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 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 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 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 contentMap = objectMapper.readValue(qrcodeRecord.getContent(), Map.class); @SuppressWarnings("unchecked") List> steps = (List>) contentMap.get("steps"); if (steps == null) { steps = new ArrayList<>(); } // 添加回滚步骤 Map 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 contentMap = objectMapper.readValue(qrcodeRecord.getContent(), Map.class); @SuppressWarnings("unchecked") List> steps = (List>) contentMap.get("steps"); if (steps == null) { steps = new ArrayList<>(); } // 添加回滚步骤 Map 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 contentMap = objectMapper.readValue(content, Map.class); @SuppressWarnings("unchecked") List> steps = (List>) 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 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 contentMap = objectMapper.readValue(content, Map.class); @SuppressWarnings("unchecked") List> steps = (List>) 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 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_ids,qrcodeRecordId: {}, 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 contentMap = objectMapper.readValue(record.getContent(), Map.class); // 获取steps数组 @SuppressWarnings("unchecked") List> steps = (List>) contentMap.get("steps"); if (steps == null || steps.isEmpty()) { log.warn("二维码记录中没有steps信息,跳过child_coil_ids更新,qrcodeRecordId: {}", qrcodeRecordId); return; } // 找到最后一个分卷步骤(最新的分卷操作) Map lastSplitStep = null; for (int i = steps.size() - 1; i >= 0; i--) { Map 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_ids,qrcodeRecordId: {}", 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_ids,qrcodeRecordId: {}, 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 contentMap = objectMapper.readValue(qrcodeRecord.getContent(), Map.class); // 获取现有steps @SuppressWarnings("unchecked") List> steps = (List>) contentMap.get("steps"); if (steps == null) { steps = new ArrayList<>(); } // 添加回滚步骤 Map 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 pendingActionQuery = new LambdaQueryWrapper<>(); // pendingActionQuery.eq(WmsCoilPendingAction::getCoilId, coilId) // .eq(WmsCoilPendingAction::getActionType, 501) // 领料操作类型 // .ne(WmsCoilPendingAction::getActionStatus, 2); // 不是已完成的状态 // List existingActions = coilPendingActionMapper.selectList(pendingActionQuery); // if (!existingActions.isEmpty()) { // throw new RuntimeException("该钢卷已有待处理的领料操作,无法重复领料"); // } // 4. 设置独占状态(锁定钢卷) LambdaUpdateWrapper 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 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 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.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 completeSpecialSplit(Long pendingActionId) { Map 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 childCoilIds = Arrays.stream(childCoilIdsStr.split(",")) .map(String::trim) .filter(StringUtils::isNotBlank) .map(Long::parseLong) .collect(Collectors.toList()); QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.in("coil_id", childCoilIds); queryWrapper.eq("del_flag", 0); // 确保只查询未删除的记录 List childCoils = baseMapper.selectList(queryWrapper); List childCoilNos = childCoils.stream() .map(WmsMaterialCoil::getCurrentCoilNo) .collect(Collectors.toList()); // 如果母卷即将成为历史卷 证明已经加工完成则释放库位 if (parentCoil.getActualWarehouseId() != null){ updateActualWarehouseEnableStatus(parentCoil.getActualWarehouseId(), null); } // 4. 将母卷标记为历史数据 LambdaUpdateWrapper 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 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 cancelSpecialSplit(@NotNull(message = "待操作记录ID不能为空") Long pendingActionId); /** * 单步分卷 - 取消分卷(释放锁 + 回滚子卷) */ @Override @Transactional(rollbackFor = Exception.class) public Map cancelSpecialSplit(Long pendingActionId) { Map 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 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 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 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 contentMap = objectMapper.readValue(oldRecord.getContent(), Map.class); @SuppressWarnings("unchecked") List> steps = (List>) contentMap.get("steps"); if (steps == null) { steps = new ArrayList<>(); } Map 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 getCoilTrimStatistics() { List rawList = baseMapper.selectCoilTrimStatistics(); Map> byThickness = rawList.stream() .collect(Collectors.groupingBy(v -> v.getThickness() == null ? "空置" : v.getThickness())); List result = new ArrayList<>(); for (Map.Entry> thicknessEntry : byThickness.entrySet()) { CoilTrimStatisticsVo vo = new CoilTrimStatisticsVo(); vo.setThickness(thicknessEntry.getKey()); List thicknessList = thicknessEntry.getValue(); Map> byTrim = thicknessList.stream() .collect(Collectors.groupingBy(v -> v.getTrimmingRequirement() == null ? "未知" : v.getTrimmingRequirement())); List trimmedList = byTrim.getOrDefault("净边", new ArrayList<>()); List untrimmedList = byTrim.getOrDefault("毛边", new ArrayList<>()); List 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 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 getCategoryWidthStatistics() { List rawList = baseMapper.selectCategoryWidthStatistics(); Map> byCategory = rawList.stream() .collect(Collectors.groupingBy(v -> v.getCategory() == null ? "其他" : v.getCategory())); List result = new ArrayList<>(); for (Map.Entry> 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 pendingCoilIds = queryCoilIdsByPendingFilter(pendingFilter); if (CollectionUtils.isEmpty(pendingCoilIds)) { return buildEmptySummary(); } coilFilter.setCoilIds(mergeCoilIds(coilFilter.getCoilIds(), pendingCoilIds)); } List 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 queryCoilIdsByPendingFilter(WmsCoilPendingActionBo bo) { LambdaQueryWrapper 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 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 pendingCoilIds) { Set pendingSet = new LinkedHashSet<>(pendingCoilIds); if (StringUtils.isBlank(coilIdsCsv)) { return pendingSet.stream().map(String::valueOf).collect(Collectors.joining(",")); } List existing = parseCsvLongs(coilIdsCsv); if (CollectionUtils.isEmpty(existing)) { return pendingSet.stream().map(String::valueOf).collect(Collectors.joining(",")); } Set 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 list) { List 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> buildAbnormalSummary(List 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> 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 buildTeamSummary(List list) { Map 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 buildLabelValue(String label, String value) { Map 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 queryMismatchedItemCoils() { // 用SQL子查询直接找出itemId和itemType不匹配的钢卷 List mismatchedCoils = baseMapper.selectMismatchedItemCoils(); if (mismatchedCoils == null || mismatchedCoils.isEmpty()) { return new ArrayList<>(); } // 批量填充关联信息 List voList = mismatchedCoils.stream() .map(coil -> BeanUtil.toBean(coil, WmsMaterialCoilVo.class)) .collect(Collectors.toList()); fillRelatedObjectsBatch(voList); return voList; } }