5081 lines
237 KiB
Java
5081 lines
237 KiB
Java
package com.klp.service.impl;
|
||
|
||
import cn.hutool.core.bean.BeanUtil;
|
||
import com.baomidou.dynamic.datasource.annotation.DS;
|
||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||
import com.klp.common.core.domain.entity.SysUser;
|
||
import com.klp.common.core.page.TableDataInfo;
|
||
import com.klp.common.core.domain.PageQuery;
|
||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||
import com.klp.common.helper.LoginHelper;
|
||
import com.klp.common.utils.DateUtils;
|
||
import com.klp.common.utils.StringUtils;
|
||
import com.klp.domain.*;
|
||
import com.klp.domain.bo.*;
|
||
import com.klp.domain.vo.*;
|
||
import com.klp.domain.vo.dashboard.CoilTrimStatisticsVo;
|
||
import com.klp.domain.vo.dashboard.CoilTrimRawVo;
|
||
import com.klp.domain.vo.dashboard.TrimWidthStatisticsVo;
|
||
import com.klp.domain.vo.dashboard.CategoryWidthStatisticsVo;
|
||
import com.klp.domain.vo.dashboard.CategoryWidthRawVo;
|
||
import com.klp.domain.WmsCoilPendingAction;
|
||
import com.klp.domain.bo.WmsCoilPendingActionBo;
|
||
import com.klp.mapper.*;
|
||
import com.klp.system.service.ISysUserService;
|
||
import lombok.RequiredArgsConstructor;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.apache.commons.collections4.CollectionUtils;
|
||
import org.springframework.beans.BeanUtils;
|
||
import org.springframework.stereotype.Service;
|
||
import org.springframework.transaction.annotation.Transactional;
|
||
import com.klp.service.IWmsMaterialCoilService;
|
||
import com.klp.service.IWmsStockService;
|
||
import com.klp.service.IWmsGenerateRecordService;
|
||
import com.klp.service.IWmsWarehouseService;
|
||
import com.klp.service.IWmsActualWarehouseService;
|
||
import com.klp.service.IWmsRawMaterialService;
|
||
import com.klp.service.IWmsBomItemService;
|
||
import com.klp.service.IWmsCoilPendingActionService;
|
||
import com.klp.service.IWmsProductService;
|
||
import com.klp.service.IWmsCoilAbnormalService;
|
||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||
|
||
import java.util.*;
|
||
import java.util.stream.Collectors;
|
||
import java.math.BigDecimal;
|
||
import java.util.Arrays;
|
||
|
||
/**
|
||
* 钢卷物料表Service业务层处理
|
||
*
|
||
* @author Joshi
|
||
* @date 2025-07-18
|
||
*/
|
||
@Slf4j
|
||
@RequiredArgsConstructor
|
||
@Service
|
||
@DS("master")
|
||
public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
|
||
|
||
private final WmsMaterialCoilMapper baseMapper;
|
||
private final IWmsStockService stockService;
|
||
private final IWmsGenerateRecordService generateRecordService;
|
||
private final WmsGenerateRecordMapper generateRecordMapper;
|
||
private final IWmsWarehouseService warehouseService;
|
||
private final IWmsActualWarehouseService actualWarehouseService;
|
||
private final IWmsRawMaterialService rawMaterialService;
|
||
// private final IWmsBomItemService bomItemService;
|
||
private final IWmsCoilPendingActionService coilPendingActionService;
|
||
private final WmsCoilPendingActionMapper coilPendingActionMapper;
|
||
private final IWmsProductService productService;
|
||
private final ISysUserService userService;
|
||
// private final WmsDeliveryPlanMapper deliveryPlanMapper;
|
||
private final WmsProductMapper productMapper;
|
||
private final WmsRawMaterialMapper rawMaterialMapper;
|
||
private final WmsDeliveryWaybillDetailMapper deliveryWaybillDetailMapper;
|
||
private final WmsCoilWarehouseOperationLogMapper wmsCoilWarehouseOperationLogMapper;
|
||
private final IWmsCoilAbnormalService coilAbnormalService;
|
||
|
||
/**
|
||
* 查询钢卷物料表
|
||
*/
|
||
@Override
|
||
public WmsMaterialCoilVo queryById(Long coilId) {
|
||
WmsMaterialCoilVo vo = baseMapper.selectVoById(coilId);
|
||
if (vo == null) {
|
||
return null;
|
||
}
|
||
|
||
// 查询关联对象
|
||
fillRelatedObjects(vo);
|
||
|
||
return vo;
|
||
}
|
||
|
||
/**
|
||
* 批量填充关联对象信息(优化版本,避免N+1查询)
|
||
*/
|
||
private void fillRelatedObjectsBatch(List<WmsMaterialCoilVo> voList) {
|
||
if (voList == null || voList.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
// 收集所有需要查询的ID
|
||
Set<Long> warehouseIds = new HashSet<>();
|
||
Set<Long> nextWarehouseIds = new HashSet<>();
|
||
Set<Long> actualWarehouseIds = new HashSet<>();
|
||
Set<Long> qrcodeRecordIds = new HashSet<>();
|
||
Set<Long> rawMaterialIds = new HashSet<>();
|
||
Set<Long> productIds = new HashSet<>();
|
||
Set<Long> bomIds = new HashSet<>();
|
||
|
||
for (WmsMaterialCoilVo vo : voList) {
|
||
if (vo.getWarehouseId() != null) warehouseIds.add(vo.getWarehouseId());
|
||
if (vo.getNextWarehouseId() != null) nextWarehouseIds.add(vo.getNextWarehouseId());
|
||
if (vo.getActualWarehouseId() != null) actualWarehouseIds.add(vo.getActualWarehouseId());
|
||
if (vo.getQrcodeRecordId() != null) qrcodeRecordIds.add(vo.getQrcodeRecordId());
|
||
if ("raw_material".equals(vo.getItemType()) && vo.getItemId() != null) {
|
||
rawMaterialIds.add(vo.getItemId());
|
||
}
|
||
if ("product".equals(vo.getItemType()) && vo.getItemId() != null) {
|
||
productIds.add(vo.getItemId());
|
||
}
|
||
}
|
||
|
||
// 批量查询库区信息
|
||
Map<Long, WmsWarehouseVo> warehouseMap = new HashMap<>();
|
||
if (!warehouseIds.isEmpty()) {
|
||
for (Long id : warehouseIds) {
|
||
WmsWarehouseVo warehouse = warehouseService.queryById(id);
|
||
if (warehouse != null) {
|
||
warehouseMap.put(id, warehouse);
|
||
}
|
||
}
|
||
}
|
||
if (!nextWarehouseIds.isEmpty()) {
|
||
for (Long id : nextWarehouseIds) {
|
||
if (!warehouseMap.containsKey(id)) {
|
||
WmsWarehouseVo warehouse = warehouseService.queryById(id);
|
||
if (warehouse != null) {
|
||
warehouseMap.put(id, warehouse);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 批量查询实际库区信息
|
||
Map<Long, WmsActualWarehouseVo> actualWarehouseMap = new HashMap<>();
|
||
if (!actualWarehouseIds.isEmpty()) {
|
||
for (Long id : actualWarehouseIds) {
|
||
WmsActualWarehouseVo actualWarehouse = actualWarehouseService.queryById(id);
|
||
if (actualWarehouse != null) {
|
||
actualWarehouseMap.put(id, actualWarehouse);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 批量查询二维码信息
|
||
Map<Long, WmsGenerateRecordVo> qrcodeMap = new HashMap<>();
|
||
if (!qrcodeRecordIds.isEmpty()) {
|
||
for (Long id : qrcodeRecordIds) {
|
||
WmsGenerateRecordVo qrcode = generateRecordService.queryById(id);
|
||
if (qrcode != null) {
|
||
qrcodeMap.put(id, qrcode);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 批量查询原材料信息
|
||
Map<Long, WmsRawMaterialVo> rawMaterialMap = new HashMap<>();
|
||
if (!rawMaterialIds.isEmpty()) {
|
||
for (Long id : rawMaterialIds) {
|
||
WmsRawMaterialVo rawMaterial = rawMaterialService.queryById(id);
|
||
if (rawMaterial != null) {
|
||
rawMaterialMap.put(id, rawMaterial);
|
||
if (rawMaterial.getBomId() != null) {
|
||
bomIds.add(rawMaterial.getBomId());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 批量查询产品信息
|
||
Map<Long, WmsProductVo> productMap = new HashMap<>();
|
||
if (!productIds.isEmpty()) {
|
||
for (Long id : productIds) {
|
||
WmsProductVo product = productService.queryById(id);
|
||
if (product != null) {
|
||
productMap.put(id, product);
|
||
if (product.getBomId() != null) {
|
||
bomIds.add(product.getBomId());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// 填充到VO对象中
|
||
for (WmsMaterialCoilVo vo : voList) {
|
||
if (vo.getWarehouseId() != null && warehouseMap.containsKey(vo.getWarehouseId())) {
|
||
vo.setWarehouse(warehouseMap.get(vo.getWarehouseId()));
|
||
}
|
||
if (vo.getNextWarehouseId() != null && warehouseMap.containsKey(vo.getNextWarehouseId())) {
|
||
vo.setNextWarehouse(warehouseMap.get(vo.getNextWarehouseId()));
|
||
}
|
||
if (vo.getActualWarehouseId() != null && actualWarehouseMap.containsKey(vo.getActualWarehouseId())) {
|
||
WmsActualWarehouseVo actualWarehouse = actualWarehouseMap.get(vo.getActualWarehouseId());
|
||
vo.setActualWarehouseName(actualWarehouse.getActualWarehouseName());
|
||
}
|
||
if (vo.getQrcodeRecordId() != null && qrcodeMap.containsKey(vo.getQrcodeRecordId())) {
|
||
vo.setQrcodeRecord(qrcodeMap.get(vo.getQrcodeRecordId()));
|
||
}
|
||
if ("raw_material".equals(vo.getItemType()) && vo.getItemId() != null && rawMaterialMap.containsKey(vo.getItemId())) {
|
||
WmsRawMaterialVo rawMaterial = rawMaterialMap.get(vo.getItemId());
|
||
vo.setItemName(rawMaterial.getRawMaterialName());
|
||
vo.setItemCode(rawMaterial.getRawMaterialCode());
|
||
vo.setSpecification(rawMaterial.getSpecification());
|
||
vo.setMaterial(rawMaterial.getMaterial());
|
||
vo.setManufacturer(rawMaterial.getManufacturer());
|
||
vo.setSurfaceTreatmentDesc(rawMaterial.getSurfaceTreatmentDesc());
|
||
vo.setZincLayer(rawMaterial.getZincLayer());
|
||
}
|
||
if ("product".equals(vo.getItemType()) && vo.getItemId() != null && productMap.containsKey(vo.getItemId())) {
|
||
WmsProductVo product = productMap.get(vo.getItemId());
|
||
vo.setItemId(product.getProductId());
|
||
vo.setItemName(product.getProductName());
|
||
vo.setItemCode(product.getProductCode());
|
||
vo.setSpecification(product.getSpecification());
|
||
vo.setMaterial(product.getMaterial());
|
||
vo.setManufacturer(product.getManufacturer());
|
||
vo.setSurfaceTreatmentDesc(product.getSurfaceTreatmentDesc());
|
||
vo.setZincLayer(product.getZincLayer());
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 填充关联对象信息(单个对象,保留用于兼容性)
|
||
*/
|
||
private void fillRelatedObjects(WmsMaterialCoilVo vo) {
|
||
// 查询所在库区信息
|
||
if (vo.getWarehouseId() != null) {
|
||
WmsWarehouseVo warehouse = warehouseService.queryById(vo.getWarehouseId());
|
||
vo.setWarehouse(warehouse);
|
||
vo.setWarehouseName(warehouse.getWarehouseName());
|
||
}
|
||
|
||
// 查询下一库区信息
|
||
if (vo.getNextWarehouseId() != null) {
|
||
WmsWarehouseVo nextWarehouse = warehouseService.queryById(vo.getNextWarehouseId());
|
||
vo.setNextWarehouse(nextWarehouse);
|
||
}
|
||
|
||
// 查询实际库区信息
|
||
if (vo.getActualWarehouseId() != null) {
|
||
WmsActualWarehouseVo actualWarehouse = actualWarehouseService.queryById(vo.getActualWarehouseId());
|
||
if (actualWarehouse != null) {
|
||
vo.setActualWarehouseName(actualWarehouse.getActualWarehouseName());
|
||
vo.setActualWarehouse(actualWarehouse);
|
||
}
|
||
}
|
||
|
||
// 查询二维码信息
|
||
if (vo.getQrcodeRecordId() != null) {
|
||
WmsGenerateRecordVo qrcodeRecord = generateRecordService.queryById(vo.getQrcodeRecordId());
|
||
vo.setQrcodeRecord(qrcodeRecord);
|
||
}
|
||
|
||
// 查询原材料并直接回填扁平字段(避免返回嵌套对象)
|
||
if ("raw_material".equals(vo.getItemType()) && vo.getItemId() != null) {
|
||
WmsRawMaterialVo rawMaterial = rawMaterialService.queryById(vo.getItemId());
|
||
if (rawMaterial != null) {
|
||
vo.setItemName(rawMaterial.getRawMaterialName());
|
||
vo.setItemCode(rawMaterial.getRawMaterialCode());
|
||
vo.setSpecification(rawMaterial.getSpecification());
|
||
vo.setMaterial(rawMaterial.getMaterial());
|
||
vo.setManufacturer(rawMaterial.getManufacturer());
|
||
vo.setSurfaceTreatmentDesc(rawMaterial.getSurfaceTreatmentDesc());
|
||
// 锌层
|
||
if (rawMaterial.getZincLayer() != null) {
|
||
vo.setZincLayer(rawMaterial.getZincLayer());
|
||
}
|
||
}
|
||
}
|
||
|
||
// 查询产品并直接回填扁平字段(避免返回嵌套对象)
|
||
if ("product".equals(vo.getItemType()) && vo.getItemId() != null) {
|
||
WmsProductVo product = productService.queryById(vo.getItemId());
|
||
if (product != null) {
|
||
vo.setItemId(product.getProductId());
|
||
vo.setItemName(product.getProductName());
|
||
vo.setItemCode(product.getProductCode());
|
||
vo.setSpecification(product.getSpecification());
|
||
vo.setMaterial(product.getMaterial());
|
||
vo.setManufacturer(product.getManufacturer());
|
||
vo.setSurfaceTreatmentDesc(product.getSurfaceTreatmentDesc());
|
||
// 锌层
|
||
if (product.getZincLayer() != null) {
|
||
vo.setZincLayer(product.getZincLayer());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 查询钢卷物料表列表
|
||
*/
|
||
@Override
|
||
public TableDataInfo<WmsMaterialCoilVo> queryPageList(WmsMaterialCoilBo bo, PageQuery pageQuery) {
|
||
Page<WmsMaterialCoilVo> result = queryMaterialCoilPage(bo, pageQuery);
|
||
List<WmsMaterialCoilVo> records = result.getRecords();
|
||
if (records == null || records.isEmpty()) {
|
||
return TableDataInfo.build(result);
|
||
}
|
||
fillPageCommonFields(records);
|
||
return TableDataInfo.build(result);
|
||
}
|
||
|
||
@Override
|
||
public TableDataInfo<WmsMaterialCoilBindVo> queryPageListWithBindInfo(WmsMaterialCoilBo bo, PageQuery pageQuery) {
|
||
Page<WmsMaterialCoilVo> result = queryMaterialCoilPage(bo, pageQuery);
|
||
List<WmsMaterialCoilVo> baseRecords = result.getRecords();
|
||
if (baseRecords == null || baseRecords.isEmpty()) {
|
||
return TableDataInfo.build(new Page<>(result.getCurrent(), result.getSize(), result.getTotal()));
|
||
}
|
||
|
||
List<WmsMaterialCoilBindVo> bindRecords = baseRecords.stream().map(item -> {
|
||
WmsMaterialCoilBindVo bindVo = new WmsMaterialCoilBindVo();
|
||
BeanUtil.copyProperties(item, bindVo);
|
||
return bindVo;
|
||
}).collect(Collectors.toList());
|
||
|
||
fillBindInfoForPage(bindRecords);
|
||
fillPageCommonFields(bindRecords);
|
||
|
||
Page<WmsMaterialCoilBindVo> bindResult = new Page<>(result.getCurrent(), result.getSize(), result.getTotal());
|
||
bindResult.setRecords(bindRecords);
|
||
return TableDataInfo.build(bindResult);
|
||
}
|
||
|
||
/**
|
||
* 统计筛选条件下的全量汇总数据(高性能:只查sum/count)
|
||
* 独立的统计接口,不影响分页查询
|
||
*/
|
||
@Override
|
||
public Map<String, BigDecimal> getStatistics(WmsMaterialCoilBo bo) {
|
||
QueryWrapper<WmsMaterialCoil> qw = buildQueryWrapperPlus(bo);
|
||
return selectMaterialCoilStatistics(qw);
|
||
}
|
||
|
||
private Page<WmsMaterialCoilVo> queryMaterialCoilPage(WmsMaterialCoilBo bo, PageQuery pageQuery) {
|
||
QueryWrapper<WmsMaterialCoil> qw = buildQueryWrapperPlus(bo);
|
||
Page<WmsMaterialCoilVo> result;
|
||
if (Boolean.TRUE.equals(bo.getOrderBy())) {
|
||
result = baseMapper.selectVoPagePlusOrderBy(pageQuery.build(), qw);
|
||
} else {
|
||
result = baseMapper.selectVoPagePlus(pageQuery.build(), qw);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
private void fillPageCommonFields(List<? extends WmsMaterialCoilVo> records) {
|
||
Set<String> userNames = records.stream()
|
||
.flatMap(v -> java.util.stream.Stream.of(v.getCreateBy(), v.getUpdateBy(), v.getExportBy()))
|
||
.filter(StringUtils::isNotBlank)
|
||
.collect(Collectors.toSet());
|
||
|
||
Map<String, String> nickMap = Collections.emptyMap();
|
||
if (!userNames.isEmpty()) {
|
||
nickMap = userService.selectNickNameMapByUserNames(new ArrayList<>(userNames));
|
||
}
|
||
|
||
// 单次遍历:填充创建/更新/发货人昵称,并构建物料/产品对象
|
||
for (WmsMaterialCoilVo vo : records) {
|
||
if (!nickMap.isEmpty()) {
|
||
if (StringUtils.isNotBlank(vo.getCreateBy())) {
|
||
vo.setCreateByName(nickMap.getOrDefault(vo.getCreateBy(), vo.getCreateBy()));
|
||
}
|
||
if (StringUtils.isNotBlank(vo.getUpdateBy())) {
|
||
vo.setUpdateByName(nickMap.getOrDefault(vo.getUpdateBy(), vo.getUpdateBy()));
|
||
}
|
||
if (StringUtils.isNotBlank(vo.getExportBy())) {
|
||
vo.setExportByName(nickMap.getOrDefault(vo.getExportBy(), vo.getExportBy()));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 统计筛选条件下的全量汇总数据(高性能:只查sum/count)
|
||
*/
|
||
private Map<String, BigDecimal> selectMaterialCoilStatistics(QueryWrapper<WmsMaterialCoil> qw) {
|
||
Map<String, BigDecimal> result = new HashMap<>();
|
||
result.put("total_gross_weight", BigDecimal.ZERO);
|
||
result.put("total_net_weight", BigDecimal.ZERO);
|
||
result.put("total_count", BigDecimal.ZERO);
|
||
|
||
try {
|
||
Map<String, Object> stats = baseMapper.selectStatistics(qw);
|
||
if (stats != null) {
|
||
Object grossWeight = stats.get("total_gross_weight");
|
||
Object netWeight = stats.get("total_net_weight");
|
||
result.put("total_gross_weight", grossWeight != null ? new BigDecimal(grossWeight.toString()) : BigDecimal.ZERO);
|
||
result.put("total_net_weight", netWeight != null ? new BigDecimal(netWeight.toString()) : BigDecimal.ZERO);
|
||
}
|
||
} catch (Exception e) {
|
||
log.warn("统计查询失败: {}", e.getMessage());
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
private void fillBindInfoForPage(List<WmsMaterialCoilBindVo> records) {
|
||
// 绑定信息仅用于专用接口,避免污染通用分页结构
|
||
try {
|
||
List<Long> coilIds = records.stream()
|
||
.map(WmsMaterialCoilVo::getCoilId)
|
||
.filter(Objects::nonNull)
|
||
.distinct()
|
||
.collect(Collectors.toList());
|
||
|
||
if (coilIds.isEmpty()) {
|
||
return;
|
||
}
|
||
|
||
Map<Long, WmsCoilBindInfoVo> bindMap = deliveryWaybillDetailMapper
|
||
.selectBindInfoByCoilIds(coilIds)
|
||
.stream()
|
||
.collect(Collectors.toMap(
|
||
WmsCoilBindInfoVo::getCoilId,
|
||
v -> v,
|
||
(a, b) -> a
|
||
));
|
||
|
||
for (WmsMaterialCoilBindVo vo : records) {
|
||
WmsCoilBindInfoVo bind = bindMap.get(vo.getCoilId());
|
||
if (bind != null) {
|
||
vo.setBound(Boolean.TRUE);
|
||
vo.setBindDetailId(bind.getDetailId());
|
||
vo.setBindWaybillId(bind.getWaybillId());
|
||
vo.setBindWaybillNo(bind.getWaybillNo());
|
||
vo.setBindWaybillName(bind.getWaybillName());
|
||
vo.setBindPlanId(bind.getPlanId());
|
||
vo.setBindPlanName(bind.getPlanName());
|
||
vo.setBindPlanDate(bind.getPlanDate());
|
||
vo.setBindLicensePlate(bind.getLicensePlate());
|
||
vo.setBindConsigneeUnit(bind.getConsigneeUnit());
|
||
vo.setBindSenderUnit(bind.getSenderUnit());
|
||
vo.setBindDeliveryTime(bind.getDeliveryTime());
|
||
vo.setBindWeighbridge(bind.getWeighbridge());
|
||
vo.setBindSalesPerson(bind.getSalesPerson());
|
||
vo.setBindPrincipal(bind.getPrincipal());
|
||
vo.setBindPrincipalPhone(bind.getPrincipalPhone());
|
||
vo.setBindWaybillStatus(bind.getWaybillStatus());
|
||
vo.setBindWaybillRemark(bind.getWaybillRemark());
|
||
vo.setBindDetailSettlementType(bind.getDetailSettlementType());
|
||
vo.setBindDetailUnitPrice(bind.getDetailUnitPrice());
|
||
} else {
|
||
vo.setBound(Boolean.FALSE);
|
||
}
|
||
}
|
||
} catch (Exception ignore) {
|
||
// 绑定信息属于增强字段,查询失败时不影响钢卷列表主流程
|
||
}
|
||
}
|
||
|
||
private QueryWrapper<WmsMaterialCoil> buildQueryWrapperPlus(WmsMaterialCoilBo bo) {
|
||
QueryWrapper<WmsMaterialCoil> qw = Wrappers.query();
|
||
qw.like(StringUtils.isNotBlank(bo.getEnterCoilNo()), "mc.enter_coil_no", bo.getEnterCoilNo());
|
||
qw.like(StringUtils.isNotBlank(bo.getCurrentCoilNo()), "mc.current_coil_no", bo.getCurrentCoilNo());
|
||
qw.like(StringUtils.isNotBlank(bo.getSupplierCoilNo()), "mc.supplier_coil_no", bo.getSupplierCoilNo());
|
||
qw.eq(bo.getDataType() != null, "mc.data_type", bo.getDataType());
|
||
qw.eq(bo.getMaterialType() != null, "mc.material_type", bo.getMaterialType());
|
||
qw.eq(bo.getHasMergeSplit() != null, "mc.has_merge_split", bo.getHasMergeSplit());
|
||
qw.eq(bo.getStatus() != null, "mc.status", bo.getStatus());
|
||
qw.eq(StringUtils.isNotBlank(bo.getItemType()), "mc.item_type", bo.getItemType());
|
||
qw.eq(StringUtils.isNotBlank(bo.getCreateBy()), "mc.create_by", bo.getCreateBy());
|
||
qw.eq(StringUtils.isNotBlank(bo.getUpdateBy()), "mc.update_by", bo.getUpdateBy());
|
||
// 切边要求
|
||
qw.eq(StringUtils.isNotBlank(bo.getTrimmingRequirement()), "mc.trimming_requirement", bo.getTrimmingRequirement());
|
||
// 打包状态
|
||
qw.eq(StringUtils.isNotBlank(bo.getPackingStatus()), "mc.packing_status", bo.getPackingStatus());
|
||
// 质量状态
|
||
qw.eq(StringUtils.isNotBlank(bo.getQualityStatus()), "mc.quality_status", bo.getQualityStatus());
|
||
// 包装
|
||
qw.eq(StringUtils.isNotBlank(bo.getPackagingRequirement()), "mc.packaging_requirement", bo.getPackagingRequirement());
|
||
// 锌层种类和调制度
|
||
qw.eq(StringUtils.isNotBlank(bo.getCoatingType()), "mc.coating_type", bo.getCoatingType());
|
||
qw.eq(StringUtils.isNotBlank(bo.getTemperGrade()), "mc.temper_grade", bo.getTemperGrade());
|
||
// 独占状态
|
||
qw.eq(bo.getExclusiveStatus() != null, "mc.exclusive_status", bo.getExclusiveStatus());
|
||
// 业务用途
|
||
qw.eq(StringUtils.isNotBlank(bo.getBusinessPurpose()), "mc.business_purpose", bo.getBusinessPurpose());
|
||
// 是否与订单相关(0=否,1=是)
|
||
qw.eq(bo.getIsRelatedToOrder() != null, "mc.is_related_to_order", bo.getIsRelatedToOrder());
|
||
//逻辑删除
|
||
qw.eq("mc.del_flag", 0);
|
||
// 统一处理 warehouseId 与 warehouseIds:
|
||
List<Long> warehouseIdList = new ArrayList<>();
|
||
if (bo.getWarehouseId() != null) {
|
||
warehouseIdList.add(bo.getWarehouseId());
|
||
}
|
||
if (StringUtils.isNotBlank(bo.getWarehouseIds())) {
|
||
String[] warehouseIdArray = bo.getWarehouseIds().split(",");
|
||
for (String warehouseIdStr : warehouseIdArray) {
|
||
if (StringUtils.isNotBlank(warehouseIdStr)) {
|
||
try {
|
||
warehouseIdList.add(Long.parseLong(warehouseIdStr.trim()));
|
||
} catch (NumberFormatException ignore) {
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!warehouseIdList.isEmpty()) {
|
||
qw.in("mc.warehouse_id", warehouseIdList.stream().distinct().collect(Collectors.toList()));
|
||
}
|
||
|
||
List<String> qualityStatusList = new ArrayList<>();
|
||
if (StringUtils.isNotBlank(bo.getQualityStatusCsv())) {
|
||
String[] qualityStatusArray = bo.getQualityStatusCsv().split(",");
|
||
for (String qualityStatusStr : qualityStatusArray) {
|
||
if (StringUtils.isNotBlank(qualityStatusStr)) {
|
||
try {
|
||
qualityStatusList.add(qualityStatusStr.trim());
|
||
} catch (NumberFormatException ignore) {
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!qualityStatusList.isEmpty()) {
|
||
qw.in("mc.quality_status", qualityStatusList.stream().distinct().collect(Collectors.toList()));
|
||
}
|
||
|
||
// 统一处理 nextWarehouseId 与 nextWarehouseIds:
|
||
List<Long> nextWarehouseIdList = new ArrayList<>();
|
||
if (bo.getNextWarehouseId() != null) {
|
||
nextWarehouseIdList.add(bo.getNextWarehouseId());
|
||
}
|
||
if (StringUtils.isNotBlank(bo.getNextWarehouseIds())) {
|
||
String[] nextWarehouseIdArray = bo.getNextWarehouseIds().split(",");
|
||
for (String nextWarehouseIdStr : nextWarehouseIdArray) {
|
||
if (StringUtils.isNotBlank(nextWarehouseIdStr)) {
|
||
try {
|
||
nextWarehouseIdList.add(Long.parseLong(nextWarehouseIdStr.trim()));
|
||
} catch (NumberFormatException ignore) {
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!nextWarehouseIdList.isEmpty()) {
|
||
qw.in("mc.next_warehouse_id", nextWarehouseIdList.stream().distinct().collect(Collectors.toList()));
|
||
}
|
||
// 新增长度
|
||
qw.eq(bo.getLength() != null, "mc.length", bo.getLength());
|
||
// 实测长度
|
||
qw.eq(bo.getActualLength() != null, "mc.actual_length", bo.getActualLength());
|
||
// 实测宽度
|
||
qw.eq(bo.getActualWidth() != null, "mc.actual_width", bo.getActualWidth());
|
||
// 实测厚度
|
||
qw.eq(StringUtils.isNotBlank(bo.getActualThickness()), "mc.actual_thickness", bo.getActualThickness());
|
||
// 生产开始时间
|
||
qw.eq(bo.getProductionStartTime() != null, "mc.production_start_time", bo.getProductionStartTime());
|
||
// 生产结束时间
|
||
qw.eq(bo.getProductionEndTime() != null, "mc.production_end_time", bo.getProductionEndTime());
|
||
// 生产耗时
|
||
qw.eq(bo.getProductionDuration() != null, "mc.production_duration", bo.getProductionDuration());
|
||
// 预留宽度
|
||
qw.eq(bo.getReservedWidth() != null, "mc.reserved_width", bo.getReservedWidth());
|
||
// 钢卷表面处理
|
||
qw.eq(bo.getCoilSurfaceTreatment() != null, "mc.coil_surface_treatment", bo.getCoilSurfaceTreatment());
|
||
// 调拨类型
|
||
qw.eq(StringUtils.isNotBlank(bo.getTransferType()), "mc.transfer_type", bo.getTransferType());
|
||
// 如果actualWarehouseId不为空,则根据实际库区ID进行查询 如果为-1,则查询无库区的数据
|
||
if (bo.getActualWarehouseId() != null) {
|
||
if (bo.getActualWarehouseId() == -1) {
|
||
// 当actualWarehouseId为-1时,查询actual_warehouse_id为空的记录(无库区)
|
||
qw.isNull("mc.actual_warehouse_id");
|
||
} else {
|
||
// 正常传值时,需要处理库位的层级关系
|
||
// 如果传入的是二级库位,需要查询其下所有的三级/四级库位
|
||
List<Long> warehouseIds = getWarehouseIdsIncludingChildren(bo.getActualWarehouseId());
|
||
if (warehouseIds.isEmpty()) {
|
||
// 如果没有找到任何库位,则直接匹配该ID
|
||
qw.eq("mc.actual_warehouse_id", bo.getActualWarehouseId());
|
||
} else {
|
||
// 使用IN查询包含该库位及其所有子库位的钢卷
|
||
qw.in("mc.actual_warehouse_id", warehouseIds);
|
||
}
|
||
}
|
||
}
|
||
// 新增查询逻辑也就是当saleId未空时候
|
||
if (bo.getSaleId() != null) {
|
||
if (bo.getSaleId() == -1) {
|
||
// 当saleId为-1时查询sale_id为空的记录
|
||
qw.isNull("mc.sale_id");
|
||
} else if (bo.getSaleId() > 0) {
|
||
// 正常传值时依据saleId作为查询条件
|
||
qw.eq("mc.sale_id", bo.getSaleId());
|
||
}
|
||
}
|
||
|
||
// 销售人员姓名模糊查询
|
||
if (bo.getSaleName() != null && !bo.getSaleName().isEmpty()) {
|
||
qw.like("su.nick_name", bo.getSaleName());
|
||
}
|
||
|
||
// 仅查询废品:质量状态为 D+、D、D-
|
||
if (Boolean.TRUE.equals(bo.getOnlyScrap())) {
|
||
qw.in("mc.quality_status", java.util.Arrays.asList("C+", "C", "C-", "D+", "D", "D-"));
|
||
}
|
||
|
||
// 排除已被发货单明细绑定的钢卷(SQL级过滤,分页总数准确)
|
||
if (Boolean.TRUE.equals(bo.getExcludeBound())) {
|
||
qw.apply("NOT EXISTS (SELECT 1 FROM wms_delivery_waybill_detail d WHERE d.del_flag = 0 AND d.coil_id = mc.coil_id)");
|
||
}
|
||
|
||
// 排除已打包的钢卷(防止重复打包)
|
||
if (Boolean.TRUE.equals(bo.getExcludePacked())) {
|
||
qw.apply("NOT EXISTS (SELECT 1 FROM wms_coil_packing_detail pd WHERE pd.del_flag = 0 AND pd.coil_id = mc.coil_id)");
|
||
}
|
||
|
||
// 组合 item_id 条件:改为使用 EXISTS 子查询,替代预查询 + IN
|
||
boolean hasSelectType = StringUtils.isNotBlank(bo.getSelectType());
|
||
boolean hasAnyItemFilter = StringUtils.isNotBlank(bo.getItemMaterial())
|
||
|| StringUtils.isNotBlank(bo.getItemManufacturer())
|
||
|| StringUtils.isNotBlank(bo.getItemSurfaceTreatmentDesc())
|
||
|| StringUtils.isNotBlank(bo.getItemZincLayer())
|
||
|| StringUtils.isNotBlank(bo.getItemName())
|
||
|| StringUtils.isNotBlank(bo.getItemSpecification());
|
||
|
||
// 解析显式 itemIds 或单个 itemId
|
||
List<Long> explicitItemIds = null;
|
||
if (StringUtils.isNotBlank(bo.getItemIds())) {
|
||
String[] itemIdArray = bo.getItemIds().split(",");
|
||
List<Long> tmp = new ArrayList<>();
|
||
for (String itemIdStr : itemIdArray) {
|
||
if (StringUtils.isNotBlank(itemIdStr)) {
|
||
try {
|
||
tmp.add(Long.parseLong(itemIdStr.trim()));
|
||
} catch (NumberFormatException ignore) {}
|
||
}
|
||
}
|
||
if (!tmp.isEmpty()) {
|
||
explicitItemIds = tmp.stream().distinct().collect(Collectors.toList());
|
||
}
|
||
} else if (bo.getItemId() != null) {
|
||
explicitItemIds = Collections.singletonList(bo.getItemId());
|
||
}
|
||
|
||
// 使用 EXISTS 针对 selectType 的细粒度筛选(使用参数占位符防注入)
|
||
if (hasSelectType && hasAnyItemFilter) {
|
||
// 执行筛选逻辑(和上面完全一样)
|
||
qw.eq("mc.item_type", bo.getSelectType());
|
||
StringBuilder existsSql = new StringBuilder();
|
||
List<Object> existsArgs = new ArrayList<>();
|
||
if ("product".equals(bo.getSelectType())) {
|
||
existsSql.append(" EXISTS (SELECT 1 FROM wms_product p WHERE p.del_flag = 0 AND p.product_id = mc.item_id");
|
||
String clause;
|
||
clause = buildOrLikeClause("p.product_name", bo.getItemName(), existsArgs);
|
||
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
|
||
clause = buildOrLikeClause("p.material", bo.getItemMaterial(), existsArgs);
|
||
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
|
||
clause = buildOrLikeClause("p.manufacturer", bo.getItemManufacturer(), existsArgs);
|
||
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
|
||
clause = buildOrLikeClause("p.surface_treatment_desc", bo.getItemSurfaceTreatmentDesc(), existsArgs);
|
||
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
|
||
clause = buildOrLikeClause("p.zinc_layer", bo.getItemZincLayer(), existsArgs);
|
||
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
|
||
clause = buildOrLikeClause("p.specification", bo.getItemSpecification(), existsArgs);
|
||
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
|
||
existsSql.append(")");
|
||
} else if ("raw_material".equals(bo.getSelectType())) {
|
||
existsSql.append(" EXISTS (SELECT 1 FROM wms_raw_material r WHERE r.del_flag = 0 AND r.raw_material_id = mc.item_id");
|
||
String clause;
|
||
clause = buildOrLikeClause("r.raw_material_name", bo.getItemName(), existsArgs);
|
||
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
|
||
clause = buildOrLikeClause("r.material", bo.getItemMaterial(), existsArgs);
|
||
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
|
||
clause = buildOrLikeClause("r.manufacturer", bo.getItemManufacturer(), existsArgs);
|
||
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
|
||
clause = buildOrLikeClause("r.surface_treatment_desc", bo.getItemSurfaceTreatmentDesc(), existsArgs);
|
||
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
|
||
clause = buildOrLikeClause("r.zinc_layer", bo.getItemZincLayer(), existsArgs);
|
||
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
|
||
clause = buildOrLikeClause("r.specification", bo.getItemSpecification(), existsArgs);
|
||
if (!clause.isEmpty()) existsSql.append(" AND ").append(clause);
|
||
existsSql.append(")");
|
||
}
|
||
if (existsSql.length() > 0) {
|
||
qw.apply(existsSql.toString(), existsArgs.toArray());
|
||
}
|
||
}
|
||
|
||
// 显式 itemId 条件:与 EXISTS 共存时,语义为交集
|
||
if (CollectionUtils.isNotEmpty(explicitItemIds)) {
|
||
qw.isNotNull("mc.item_id");
|
||
qw.in("mc.item_id", explicitItemIds);
|
||
} else if (explicitItemIds != null && explicitItemIds.isEmpty()) {
|
||
qw.apply("1 = 0");
|
||
return qw;
|
||
}
|
||
// 添加coilIds查询条件,支持逗号分隔的多个coilId查询
|
||
if (StringUtils.isNotBlank(bo.getCoilIds())) {
|
||
String[] coilIdArray = bo.getCoilIds().split(",");
|
||
List<Long> coilIdList = new ArrayList<>();
|
||
for (String coilIdStr : coilIdArray) {
|
||
if (StringUtils.isNotBlank(coilIdStr)) {
|
||
try {
|
||
coilIdList.add(Long.parseLong(coilIdStr.trim()));
|
||
} catch (NumberFormatException e) {
|
||
// 忽略无效的ID格式
|
||
}
|
||
}
|
||
}
|
||
if (!coilIdList.isEmpty()) {
|
||
qw.in("mc.coil_id", coilIdList);
|
||
}
|
||
}
|
||
// // 仅查询未发货且未绑定在发货计划里的钢卷
|
||
// if (Boolean.TRUE.equals(bo.getOnlyUnshippedAndUnplanned())) {
|
||
// // 未发货:排除状态=1(已出库/已发货)
|
||
// qw.ne("mc.status", 1);
|
||
// // 未绑定在任一有效发货计划:计划未删除,coil 字段包含当前 coil_id
|
||
// // 这里使用 NOT EXISTS + FIND_IN_SET,避免将所有计划加载到内存
|
||
// qw.apply("NOT EXISTS (SELECT 1 FROM wms_delivery_plan dp " +
|
||
// "WHERE dp.del_flag = 0 AND dp.coil IS NOT NULL AND dp.coil <> '' " +
|
||
// "AND FIND_IN_SET(CAST(mc.coil_id AS CHAR), dp.coil))");
|
||
// }
|
||
//把team字段作为筛选条件
|
||
qw.eq(StringUtils.isNotBlank(bo.getTeam()), "mc.team", bo.getTeam());
|
||
//根据开始时间和结束时间筛选修改时间
|
||
qw.ge(bo.getStartTime() != null, "mc.update_time", bo.getStartTime());
|
||
qw.le(bo.getEndTime() != null, "mc.update_time", bo.getEndTime());
|
||
|
||
qw.ge(bo.getByCreateTimeStart() != null, "mc.create_time", bo.getByCreateTimeStart());
|
||
qw.le(bo.getByCreateTimeEnd() != null, "mc.create_time", bo.getByCreateTimeEnd());
|
||
|
||
// 处理发货时间筛选逻辑(核心修改部分)
|
||
if (bo.getByExportTimeStart() != null || bo.getByExportTimeEnd() != null) {
|
||
// 开启OR条件分组:满足情况1 或 情况2
|
||
qw.and(w -> {
|
||
// 情况1:发货时间不为null且满足时间范围
|
||
w.nested(n -> {
|
||
n.isNotNull("mc.export_time");
|
||
if (bo.getByExportTimeStart() != null) {
|
||
n.ge("mc.export_time", bo.getByExportTimeStart());
|
||
}
|
||
if (bo.getByExportTimeEnd() != null) {
|
||
n.le("mc.export_time", bo.getByExportTimeEnd());
|
||
}
|
||
});
|
||
// 情况2:状态为1且发货时间为null,用更新时间匹配发货时间范围
|
||
w.or();
|
||
w.nested(n -> {
|
||
n.eq("mc.status", 1);
|
||
n.isNull("mc.export_time");
|
||
if (bo.getByExportTimeStart() != null) {
|
||
n.ge("mc.update_time", bo.getByExportTimeStart());
|
||
}
|
||
if (bo.getByExportTimeEnd() != null) {
|
||
n.le("mc.update_time", bo.getByExportTimeEnd());
|
||
}
|
||
});
|
||
});
|
||
}
|
||
// 根据创建人筛选(支持逗号分隔的多个创建人)
|
||
if (StringUtils.isNotBlank(bo.getCreateBys())) {
|
||
List<String> createByList = Arrays.stream(bo.getCreateBys().split(","))
|
||
.filter(StringUtils::isNotBlank)
|
||
.map(String::trim)
|
||
.collect(Collectors.toList());
|
||
if (!createByList.isEmpty()) {
|
||
qw.in("mc.create_by", createByList);
|
||
}
|
||
}
|
||
//根据异常数量筛选(大于等于指定值)
|
||
if (bo.getMinAbnormalCount() != null) {
|
||
qw.apply("COALESCE(ca.abnormal_count, 0) >= {0}", bo.getMinAbnormalCount());
|
||
}
|
||
// 排序:
|
||
// 已绑定钢卷列表中,未发货(status=0)的排在前面
|
||
if (Boolean.TRUE.equals(bo.getStatusFirst())) {
|
||
qw.orderByAsc("mc.status = 1");
|
||
qw.orderByAsc("mc.status");
|
||
} else if (Boolean.TRUE.equals(bo.getOrderBy())) {
|
||
// 按库位全局排序号排序
|
||
qw.orderByAsc("mc.actual_warehouse_id IS NULL");
|
||
// 全局交错排序(仅三/四级):使用 SQL 中计算好的辅助字段,避免 MP 注入拦截
|
||
qw.orderByAsc("aw_sort_key");
|
||
qw.orderByAsc("aw_layer_key");
|
||
qw.orderByAsc("aw_id_key");
|
||
} else if (Boolean.TRUE.equals(bo.getOrderByAbnormal())) {
|
||
// 按异常排序:异常的钢卷在前按创建时间倒序,没异常的钢卷在后按创建时间倒序
|
||
qw.orderByAsc("CASE WHEN COALESCE(ca.abnormal_count, 0) > 0 THEN 0 ELSE 1 END");
|
||
qw.orderByDesc("mc.create_time");
|
||
} else {
|
||
//根据创建时间倒叙
|
||
qw.orderByDesc("mc.create_time");
|
||
}
|
||
return qw;
|
||
}
|
||
|
||
@Override
|
||
public Map<String, Object> getDuplicateCoilGroups() {
|
||
// 使用优化的数据库查询方法,直接获取重复入场卷号的钢卷信息
|
||
List<WmsMaterialCoilVo> enterDuplicates = baseMapper.selectDuplicateEnterCoilNoList();
|
||
|
||
// 使用优化的数据库查询方法,直接获取重复当前卷号的钢卷信息
|
||
List<WmsMaterialCoilVo> currentDuplicates = baseMapper.selectDuplicateCurrentCoilNoList();
|
||
|
||
// 按入场卷号分组重复项
|
||
Map<String, List<WmsMaterialCoilVo>> enterGrouped = enterDuplicates.stream()
|
||
.filter(e -> StringUtils.isNotBlank(e.getEnterCoilNo()))
|
||
.collect(Collectors.groupingBy(WmsMaterialCoilVo::getEnterCoilNo));
|
||
|
||
// 按当前卷号分组重复项
|
||
Map<String, List<WmsMaterialCoilVo>> currentGrouped = currentDuplicates.stream()
|
||
.filter(e -> StringUtils.isNotBlank(e.getCurrentCoilNo()))
|
||
.collect(Collectors.groupingBy(WmsMaterialCoilVo::getCurrentCoilNo));
|
||
|
||
// 构建入场卷号重复组
|
||
List<Map<String, Object>> enterGroups = enterGrouped.entrySet().stream()
|
||
.filter(entry -> entry.getValue() != null && entry.getValue().size() > 1)
|
||
.map(entry -> {
|
||
Map<String, Object> group = new HashMap<>();
|
||
group.put("enterCoilNo", entry.getKey());
|
||
group.put("coils", entry.getValue());
|
||
return group;
|
||
})
|
||
.collect(Collectors.toList());
|
||
|
||
// 构建当前卷号重复组
|
||
List<Map<String, Object>> currentGroups = currentGrouped.entrySet().stream()
|
||
.filter(entry -> entry.getValue() != null && entry.getValue().size() > 1)
|
||
.map(entry -> {
|
||
Map<String, Object> group = new HashMap<>();
|
||
group.put("currentCoilNo", entry.getKey());
|
||
group.put("coils", entry.getValue());
|
||
return group;
|
||
})
|
||
.collect(Collectors.toList());
|
||
|
||
Map<String, Object> result = new HashMap<>();
|
||
result.put("enterGroups", enterGroups);
|
||
result.put("currentGroups", currentGroups);
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 构建 OR 连接的 LIKE 子句,使用 MyBatis-Plus apply 的 {index} 占位符并将参数加入 args。
|
||
* 例如:column = "p.product_name", values = "A,B" -> 返回 "(p.product_name LIKE {0} OR p.product_name LIKE {1})"
|
||
* 同时往 args 追加 "%A%", "%B%"。
|
||
*/
|
||
private String buildOrLikeClause(String column, String csvValues, List<Object> args) {
|
||
if (StringUtils.isBlank(csvValues)) {
|
||
return "";
|
||
}
|
||
String[] vals = csvValues.split(",");
|
||
List<String> parts = new ArrayList<>();
|
||
for (String raw : vals) {
|
||
if (raw == null) continue;
|
||
String v = raw.trim();
|
||
if (v.isEmpty()) continue;
|
||
int idx = args.size();
|
||
parts.add(column + " LIKE {" + idx + "}");
|
||
args.add('%' + v + '%');
|
||
}
|
||
if (parts.isEmpty()) {
|
||
return "";
|
||
}
|
||
return '(' + String.join(" OR ", parts) + ')';
|
||
}
|
||
|
||
/**
|
||
* 获取库位及其所有子库位的ID列表
|
||
* 逻辑参考 WmsActualWarehouseServiceImpl.queryList 中的拆分处理逻辑:
|
||
* 1. 如果是一级/三级/四级库位,直接返回该ID
|
||
* 2. 如果是二级库位:
|
||
* 查询其下的子库位(通常是四级库位)
|
||
*
|
||
* @param actualWarehouseId 库位ID
|
||
* @return 包含该库位及其所有子库位的ID列表
|
||
*/
|
||
private List<Long> getWarehouseIdsIncludingChildren(Long actualWarehouseId) {
|
||
if (actualWarehouseId == null || actualWarehouseId <= 0) {
|
||
return new ArrayList<>();
|
||
}
|
||
|
||
// 第一步:查询当前库位的基础信息,判断类型
|
||
WmsActualWarehouseVo warehouse = actualWarehouseService.queryById(actualWarehouseId);
|
||
if (warehouse == null) {
|
||
return Collections.singletonList(actualWarehouseId);
|
||
}
|
||
|
||
Long warehouseType = warehouse.getActualWarehouseType();
|
||
// 当为一级库位:查询其所有二级子节点,然后对每个二级节点调用 queryList 获取有效的最小层级库位(三级未拆分或四级)
|
||
if (warehouseType != null && warehouseType == 1L) {
|
||
WmsActualWarehouseBo level2Bo = new WmsActualWarehouseBo();
|
||
level2Bo.setParentId(actualWarehouseId);
|
||
List<WmsActualWarehouseVo> level2List = actualWarehouseService.queryList(level2Bo);
|
||
if (level2List == null || level2List.isEmpty()) {
|
||
return new ArrayList<>();
|
||
}
|
||
List<Long> resultIds = new ArrayList<>();
|
||
for (WmsActualWarehouseVo level2 : level2List) {
|
||
if (level2 == null || level2.getActualWarehouseId() == null) {
|
||
continue;
|
||
}
|
||
WmsActualWarehouseBo childBo = new WmsActualWarehouseBo();
|
||
childBo.setParentId(level2.getActualWarehouseId());
|
||
List<WmsActualWarehouseVo> children = actualWarehouseService.queryList(childBo);
|
||
if (children != null && !children.isEmpty()) {
|
||
for (WmsActualWarehouseVo ch : children) {
|
||
Long id = ch.getActualWarehouseId();
|
||
if (id != null) {
|
||
resultIds.add(id);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return resultIds;
|
||
}
|
||
// 三级/四级库位:直接返回自身ID(无下级)
|
||
if (warehouseType != null && (warehouseType == 3L || warehouseType == 4L)) {
|
||
return Collections.singletonList(actualWarehouseId);
|
||
}
|
||
|
||
// 二级库位:复用 queryList 已实现的“子库位+拆分替换”逻辑
|
||
if (warehouseType != null && warehouseType == 2L) {
|
||
WmsActualWarehouseBo bo = new WmsActualWarehouseBo();
|
||
bo.setParentId(actualWarehouseId);
|
||
// 这里 queryList 已经处理了:拆分库位替换为子库位、返回有效子库位列表
|
||
List<WmsActualWarehouseVo> children = actualWarehouseService.queryList(bo);
|
||
// 提取子库位ID即可
|
||
return children.stream()
|
||
.map(WmsActualWarehouseVo::getActualWarehouseId)
|
||
.filter(Objects::nonNull)
|
||
.collect(Collectors.toList());
|
||
}
|
||
|
||
// 一级库位或其他类型:返回自身ID
|
||
return Collections.singletonList(actualWarehouseId);
|
||
}
|
||
|
||
/**
|
||
* 校验实际库位是否可用于分配给钢卷:
|
||
* - 若库位被占用(isEnabled=0)则禁止(除非本次操作是继续使用同一已占用库位的场景,可通过 ignoreOccupiedId 放行占用校验)。
|
||
* - 若为三级库位且已拆分(splitStatus=1),禁止直接在三级库位上新增/修改。
|
||
* - 若为四级库位,且其父级三级库位未拆分(splitStatus!=1),禁止使用该四级库位。
|
||
*/
|
||
private void validateActualWarehouseForAssign(Long actualWarehouseId, Long ignoreOccupiedId) {
|
||
if (actualWarehouseId == null || actualWarehouseId.equals(-1L)) {
|
||
return;
|
||
}
|
||
WmsActualWarehouseVo aw = actualWarehouseService.queryById(actualWarehouseId);
|
||
if (aw == null) {
|
||
throw new RuntimeException("实际库区不存在");
|
||
}
|
||
// 占用校验(isEnabled=0 视为已被占用)
|
||
if (aw.getIsEnabled() != null && aw.getIsEnabled() == 0
|
||
&& (ignoreOccupiedId == null || !actualWarehouseId.equals(ignoreOccupiedId))) {
|
||
throw new RuntimeException("实际库区已被占用,请选择其他库区");
|
||
}
|
||
|
||
Long type = aw.getActualWarehouseType();
|
||
Integer splitStatus = aw.getSplitStatus();
|
||
// 三级库位且已拆分,禁止直接在三级层级使用
|
||
if (type != null && type == 3L && splitStatus != null && splitStatus == 1) {
|
||
throw new RuntimeException("该库位已被拆分,刷新后重试");
|
||
}
|
||
// 四级库位:父级必须是已拆分的三级库位
|
||
if (type != null && type == 4L) {
|
||
Long parentId = aw.getParentId();
|
||
if (parentId != null) {
|
||
WmsActualWarehouseVo parent = actualWarehouseService.queryById(parentId);
|
||
if (parent != null) {
|
||
Integer pSplit = parent.getSplitStatus();
|
||
Long pType = parent.getActualWarehouseType();
|
||
if (pType != null && pType == 3L && (pSplit == null || pSplit != 1)) {
|
||
throw new RuntimeException("该库位已被合并,刷新后重试");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
@Override
|
||
public WmsMaterialCoilLocationGridVo queryLocationGrid(Long actualWarehouseId,
|
||
String itemType,
|
||
String enterCoilNo,
|
||
String currentCoilNo,
|
||
String manufacturer) {
|
||
WmsMaterialCoilLocationGridVo result = new WmsMaterialCoilLocationGridVo();
|
||
if (actualWarehouseId == null) {
|
||
result.setWarehouses(Collections.emptyList());
|
||
result.setCoils(Collections.emptyList());
|
||
result.setTotal(0);
|
||
return result;
|
||
}
|
||
|
||
WmsActualWarehouseBo warehouseBo = new WmsActualWarehouseBo();
|
||
warehouseBo.setParentId(actualWarehouseId);
|
||
List<WmsActualWarehouseVo> warehouses = actualWarehouseService.queryList(warehouseBo);
|
||
|
||
WmsMaterialCoilBo coilBo = new WmsMaterialCoilBo();
|
||
coilBo.setActualWarehouseId(actualWarehouseId);
|
||
coilBo.setItemType(StringUtils.isBlank(itemType) ? "raw_material" : itemType);
|
||
coilBo.setEnterCoilNo(enterCoilNo);
|
||
coilBo.setCurrentCoilNo(currentCoilNo);
|
||
coilBo.setItemManufacturer(manufacturer);
|
||
coilBo.setDataType(1);
|
||
List<WmsMaterialCoilVo> coils = this.queryList(coilBo);
|
||
|
||
result.setWarehouses(warehouses);
|
||
result.setCoils(coils);
|
||
result.setTotal(coils.size());
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 查询钢卷物料表列表
|
||
*/
|
||
@Override
|
||
public List<WmsMaterialCoilVo> queryList(WmsMaterialCoilBo bo) {
|
||
QueryWrapper<WmsMaterialCoil> lqw = buildQueryWrapperPlus(bo);
|
||
List<WmsMaterialCoilVo> list = baseMapper.selectVoListWithDynamicJoin(lqw);
|
||
return list;
|
||
}
|
||
|
||
@Override
|
||
public String queryQualityStatusByWarehouseIdAndCurrentCoilNo(Long warehouseId, String currentCoilNo) {
|
||
if (warehouseId == null || StringUtils.isBlank(currentCoilNo)) {
|
||
return null;
|
||
}
|
||
LambdaQueryWrapper<WmsMaterialCoil> lqw = Wrappers.lambdaQuery();
|
||
lqw.select(WmsMaterialCoil::getQualityStatus);
|
||
lqw.eq(WmsMaterialCoil::getDelFlag, 0);
|
||
lqw.eq(WmsMaterialCoil::getDataType, 1);
|
||
lqw.eq(WmsMaterialCoil::getWarehouseId, warehouseId);
|
||
lqw.eq(WmsMaterialCoil::getCurrentCoilNo, currentCoilNo);
|
||
lqw.last("LIMIT 1");
|
||
WmsMaterialCoil one = baseMapper.selectOne(lqw);
|
||
return one == null ? null : one.getQualityStatus();
|
||
}
|
||
|
||
/**
|
||
* 新增钢卷物料表
|
||
*/
|
||
@Override
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public WmsMaterialCoilVo insertByBo(WmsMaterialCoilBo bo) {
|
||
// 1. 生成二维码
|
||
Long qrcodeRecordId = generateQrcodeForInsert(bo);
|
||
bo.setQrcodeRecordId(qrcodeRecordId);
|
||
|
||
// 2. 查找或创建stock
|
||
findOrCreateStock(bo);
|
||
|
||
// 处理实际库区:-1 表示空置库,统一转 NULL
|
||
if (bo.getActualWarehouseId() != null && bo.getActualWarehouseId() == -1) {
|
||
bo.setActualWarehouseId(null);
|
||
}
|
||
// 3. 插入钢卷数据
|
||
WmsMaterialCoil add = BeanUtil.toBean(bo, WmsMaterialCoil.class);
|
||
if(bo.getDataType() != null && bo.getDataType() == 10){
|
||
add.setDataType(10);
|
||
}else {
|
||
add.setDataType(1); // 新增的钢卷默认为当前数据
|
||
}
|
||
// 实际库位校验:占用与拆分/层级规则
|
||
if (bo.getActualWarehouseId() != null) {
|
||
validateActualWarehouseForAssign(bo.getActualWarehouseId(), null);
|
||
}
|
||
validEntityBeforeSave(add);
|
||
// 在新增钢卷数据之前需要先看库区id是否被占用如果被占用则不能新增抛异常为实际库区已被占用 不能再当前库区修改或者新增
|
||
int rows = baseMapper.insert(add);
|
||
if (rows <= 0) {
|
||
throw new RuntimeException("新增钢卷失败");
|
||
}
|
||
|
||
// 设置返回用的ID并更新二维码内容中的coilId
|
||
bo.setCoilId(add.getCoilId());
|
||
updateQrcodeCoilId(qrcodeRecordId, add.getCoilId());
|
||
|
||
// 如果提供了actualWarehouseId,则更新对应的实际库区为禁用状态
|
||
if (bo.getActualWarehouseId() != null) {
|
||
WmsActualWarehouseBo actualWarehouseBo = new WmsActualWarehouseBo();
|
||
actualWarehouseBo.setActualWarehouseId(bo.getActualWarehouseId());
|
||
actualWarehouseBo.setIsEnabled(0); // 设置为禁用状态
|
||
actualWarehouseService.updateByBo(actualWarehouseBo);
|
||
}
|
||
// 4. 返回完整的 VO(包含关联对象)
|
||
return queryById(add.getCoilId());
|
||
}
|
||
|
||
/**
|
||
* 生成二维码(新增)
|
||
*/
|
||
private Long generateQrcodeForInsert(WmsMaterialCoilBo bo) {
|
||
try {
|
||
Map<String, Object> contentMap = new HashMap<>();
|
||
String currentCoilNo = bo.getCurrentCoilNo() != null ? bo.getCurrentCoilNo() : bo.getEnterCoilNo();
|
||
|
||
contentMap.put("enter_coil_no", bo.getEnterCoilNo()); // 入场钢卷号(唯一不变)
|
||
contentMap.put("current_coil_no", currentCoilNo); // 当前钢卷号(可变)
|
||
contentMap.put("coil_id", "null"); // 初始钢卷ID(新增时暂时为null,插入后更新)
|
||
contentMap.put("current_coil_id", "null"); // 当前有效的钢卷ID(新增时暂时为null,插入后更新)
|
||
|
||
// 创建steps数组
|
||
List<Map<String, Object>> steps = new ArrayList<>();
|
||
Map<String, Object> step1 = new HashMap<>();
|
||
step1.put("step", 1);
|
||
step1.put("action", "新增");
|
||
step1.put("current_coil_no", currentCoilNo);
|
||
// 新增时间
|
||
step1.put("create_time", DateUtils.getNowDate());
|
||
// 新增创建人
|
||
step1.put("create_by", LoginHelper.getNickName());
|
||
|
||
|
||
// 判断是合卷还是分卷
|
||
if (bo.getHasMergeSplit() != null && bo.getHasMergeSplit() == 2) {
|
||
// 合卷:父编号字符串用逗号分隔
|
||
step1.put("operation", "合卷");
|
||
step1.put("parent_coil_nos", bo.getParentCoilNos());
|
||
} else if (bo.getHasMergeSplit() != null && bo.getHasMergeSplit() == 1) {
|
||
// 分卷:多个当前钢卷号用逗号分隔
|
||
step1.put("operation", "分卷");
|
||
step1.put("current_coil_nos", currentCoilNo);
|
||
} else {
|
||
// 默认:当前钢卷号
|
||
step1.put("operation", "新增");
|
||
}
|
||
|
||
steps.add(step1);
|
||
contentMap.put("steps", steps);
|
||
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
String contentJson = objectMapper.writeValueAsString(contentMap);
|
||
|
||
WmsGenerateRecordBo recordBo = new WmsGenerateRecordBo();
|
||
recordBo.setContent(contentJson);
|
||
recordBo.setSerialNumber(bo.getEnterCoilNo()); // 使用入场钢卷号作为编号
|
||
recordBo.setQrcodeType(0L);
|
||
recordBo.setIsEnabled(0L);
|
||
recordBo.setSize(200L);
|
||
recordBo.setStatus(1); // 1=当前有效码
|
||
|
||
WmsGenerateRecordVo record = generateRecordService.insertByBo(recordBo);
|
||
return record.getRecordId();
|
||
} catch (Exception e) {
|
||
throw new RuntimeException("生成二维码失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 查找或创建stock
|
||
*/
|
||
private void findOrCreateStock(WmsMaterialCoilBo bo) {
|
||
if (bo.getItemType() == null || bo.getItemId() == null) {
|
||
throw new RuntimeException("物品类型和物品ID不能为空");
|
||
}
|
||
|
||
// 查询是否存在相同的stock(匹配itemType和itemId)
|
||
WmsStockBo stockBo = new WmsStockBo();
|
||
stockBo.setItemType(bo.getItemType());
|
||
stockBo.setItemId(bo.getItemId());
|
||
List<WmsStockVo> stockList = stockService.queryList(stockBo);
|
||
|
||
if (stockList.isEmpty()) {
|
||
// 如果没有找到匹配的stock,新增一条stock记录
|
||
WmsStockBo newStockBo = new WmsStockBo();
|
||
newStockBo.setItemType(bo.getItemType());
|
||
newStockBo.setItemId(bo.getItemId());
|
||
|
||
|
||
// 调用stockService新增stock
|
||
Boolean insertResult = stockService.insertByBo(newStockBo);
|
||
if (!insertResult) {
|
||
throw new RuntimeException("新增库存记录失败");
|
||
}
|
||
}
|
||
// 如果已存在stock记录,则不需要重复创建
|
||
}
|
||
|
||
/**
|
||
* 修改钢卷物料表
|
||
* 如果newCoils不为空,则进行批量更新(分卷/合卷)
|
||
* 如果newCoils为空,则进行单个更新
|
||
*/
|
||
@Override
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public String updateByBo(WmsMaterialCoilBo bo, String qrcodeStepType) {
|
||
// 判断是否批量更新
|
||
if (bo.getNewCoils() != null && !bo.getNewCoils().isEmpty()) {
|
||
// 批量更新逻辑(分卷/合卷)
|
||
return updateByBatch(bo); // 分卷返回逗号分隔的ID,合卷返回单个ID
|
||
} else {
|
||
// 单个更新逻辑,需要coilId
|
||
if (bo.getCoilId() == null) {
|
||
throw new RuntimeException("钢卷ID不能为空");
|
||
}
|
||
return updateBySingle(bo, qrcodeStepType); // 返回新钢卷ID字符串
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 简单更新钢卷物料表
|
||
* 直接更新属性内容,不进行历史记录处理
|
||
*/
|
||
@Override
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public Boolean updateSimple(WmsMaterialCoilBo bo) {
|
||
if (bo.getCoilId() == null) {
|
||
throw new RuntimeException("钢卷ID不能为空");
|
||
}
|
||
|
||
// 检查独占状态
|
||
validateCoilOperationPermission(bo.getCoilId(), "简单更新");
|
||
|
||
// 查询原钢卷是否存在
|
||
WmsMaterialCoil oldCoil = baseMapper.selectById(bo.getCoilId());
|
||
if (oldCoil == null) {
|
||
throw new RuntimeException("钢卷不存在");
|
||
}
|
||
|
||
// 校验itemId是否存在(根据itemType)
|
||
if (bo.getItemId() != null && StringUtils.isNotBlank(bo.getItemType())) {
|
||
if ("raw_material".equals(bo.getItemType())) {
|
||
WmsRawMaterial rawMaterial = rawMaterialMapper.selectById(bo.getItemId());
|
||
if (rawMaterial == null) {
|
||
throw new RuntimeException("原材料不存在,ID: " + bo.getItemId());
|
||
}
|
||
} else if ("product".equals(bo.getItemType())) {
|
||
WmsProduct product = productMapper.selectById(bo.getItemId());
|
||
if (product == null) {
|
||
throw new RuntimeException("产品不存在,ID: " + bo.getItemId());
|
||
}
|
||
} else {
|
||
throw new RuntimeException("无效的物品类型: " + bo.getItemType());
|
||
}
|
||
}
|
||
|
||
// 若修改实际库位,先进行校验
|
||
if (bo.getActualWarehouseId() != null) {
|
||
// 若与原库位不同,常规占用校验;若相同,仅校验拆分/层级
|
||
Long ignoreOccupiedId = Objects.equals(bo.getActualWarehouseId(), oldCoil.getActualWarehouseId())
|
||
? bo.getActualWarehouseId() : null;
|
||
validateActualWarehouseForAssign(bo.getActualWarehouseId(), ignoreOccupiedId);
|
||
}
|
||
|
||
// 1. 历史钢卷(dataType=0)禁止修改实际库区(actualWarehouseId非空且不等于-1)
|
||
if (0 == oldCoil.getDataType()) { // 原钢卷是历史钢卷
|
||
// 场景1:传入了新的实际库区ID,且不是置空(-1),禁止操作
|
||
if (bo.getActualWarehouseId() != null && !bo.getActualWarehouseId().equals(-1L)) {
|
||
throw new RuntimeException("历史钢卷不允许占用实际库区!");
|
||
}
|
||
}
|
||
|
||
// 2. 已发货的钢卷不能占用实际库区,给出提醒
|
||
if (bo.getStatus() != null && bo.getStatus().equals(1)) {
|
||
if (bo.getActualWarehouseId() != null && !bo.getActualWarehouseId().equals(-1L)) {
|
||
throw new RuntimeException("已发货的钢卷不允许占用实际库区!");
|
||
}
|
||
}
|
||
// 直接更新钢卷属性
|
||
WmsMaterialCoil updateCoil = BeanUtil.toBean(bo, WmsMaterialCoil.class);
|
||
validEntityBeforeSave(updateCoil);
|
||
|
||
// 使用MyBatis-Plus的updateById方法直接更新
|
||
boolean flag = baseMapper.updateById(updateCoil) > 0;
|
||
// 特殊处理saleId字段,确保null值也能被正确更新
|
||
if (bo.getSaleId() == null) {
|
||
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
|
||
updateWrapper.eq(WmsMaterialCoil::getCoilId, bo.getCoilId());
|
||
updateWrapper.set(WmsMaterialCoil::getSaleId, (Long)null);
|
||
baseMapper.update(null, updateWrapper);
|
||
}
|
||
// 如果实际库区id为-1或状态为1,则清空钢卷上的实际库区绑定
|
||
if ((bo.getActualWarehouseId() != null && bo.getActualWarehouseId().equals(-1L))
|
||
|| (bo.getStatus() != null && bo.getStatus().equals(1))) {
|
||
clearActualWarehouseBinding(oldCoil.getActualWarehouseId(), bo.getCoilId());
|
||
}
|
||
|
||
// 更新实际库区的启用状态
|
||
if (flag) {
|
||
// 已发货的钢卷不能占用实际库区,释放旧库区
|
||
if (bo.getStatus() != null && bo.getStatus().equals(1)) {
|
||
if (oldCoil.getActualWarehouseId() != null) {
|
||
updateActualWarehouseEnableStatus(oldCoil.getActualWarehouseId(), null);
|
||
}
|
||
} else if (bo.getActualWarehouseId() != null && !bo.getActualWarehouseId().equals(-1L)
|
||
&& !bo.getActualWarehouseId().equals(oldCoil.getActualWarehouseId())) {
|
||
// 只有当新的库区ID不为空(且不为-1)且与原库区ID不同时才更新库区状态
|
||
updateActualWarehouseEnableStatus(oldCoil.getActualWarehouseId(), bo.getActualWarehouseId());
|
||
}
|
||
}
|
||
|
||
// 简单更新:记录调拨日志(operationType=3)
|
||
// 如果老的和新的实际库区相等则不用创建记录
|
||
Long oldActualWarehouseId = oldCoil.getActualWarehouseId();
|
||
Long newActualWarehouseId = bo.getActualWarehouseId();
|
||
if (!Objects.equals(oldActualWarehouseId, newActualWarehouseId)) {
|
||
// 如果以前有真实库区,加一条调拨的出库记录
|
||
if (oldActualWarehouseId != null && oldActualWarehouseId != -1L) {
|
||
recordWarehouseOperationLog(bo.getCoilId(), oldActualWarehouseId, 3, 2, "简单更新-调拨出库");
|
||
}
|
||
// 如果前端传了新的(非-1表示入库,-1表示置空出库)
|
||
if (newActualWarehouseId != null && newActualWarehouseId != -1L) {
|
||
// 有新库区,算入库
|
||
recordWarehouseOperationLog(bo.getCoilId(), newActualWarehouseId, 3, 1, "简单更新-调拨入库");
|
||
}
|
||
}
|
||
|
||
return flag;
|
||
}
|
||
|
||
/**
|
||
* 单个更新
|
||
* @param bo 更新数据
|
||
* @param qrcodeStepType 二维码步骤类型:null/空-默认"更新", "annealing"-退火
|
||
* @return 新钢卷ID字符串
|
||
*/
|
||
private String updateBySingle(WmsMaterialCoilBo bo, String qrcodeStepType) {
|
||
if (StringUtils.isNotBlank(qrcodeStepType) && !"annealing".equals(qrcodeStepType)){
|
||
// 检查独占状态
|
||
validateCoilOperationPermission(bo.getCoilId(), "单个更新");
|
||
}
|
||
|
||
// 查询原钢卷
|
||
WmsMaterialCoil oldCoil = baseMapper.selectById(bo.getCoilId());
|
||
if (oldCoil == null) {
|
||
throw new RuntimeException("原钢卷不存在");
|
||
}
|
||
|
||
// oldCoil 如果是历史卷也就是date_type=0 的时候就是历史钢卷
|
||
if (oldCoil.getDataType() == 0) {
|
||
throw new RuntimeException("原钢卷已被更新");
|
||
}
|
||
|
||
// 若修改实际库位,先进行校验
|
||
if (bo.getActualWarehouseId() != null) {
|
||
Long ignoreOccupiedId = Objects.equals(bo.getActualWarehouseId(), oldCoil.getActualWarehouseId())
|
||
? bo.getActualWarehouseId() : null;
|
||
validateActualWarehouseForAssign(bo.getActualWarehouseId(), ignoreOccupiedId);
|
||
}
|
||
|
||
if (oldCoil.getActualWarehouseId() != null){
|
||
updateActualWarehouseEnableStatus(oldCoil.getActualWarehouseId(), null);
|
||
}
|
||
// 1. 将原钢卷标记为历史数据(dataType = 0)
|
||
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
|
||
updateWrapper.eq(WmsMaterialCoil::getCoilId, oldCoil.getCoilId())
|
||
.set(WmsMaterialCoil::getDataType, 0); // 设置为历史数据
|
||
baseMapper.update(null, updateWrapper);
|
||
|
||
// 2. 创建新记录
|
||
WmsMaterialCoil newCoil = BeanUtil.toBean(bo, WmsMaterialCoil.class);
|
||
newCoil.setCoilId(null); // 清空ID,让数据库自动生成新ID
|
||
newCoil.setDataType(1); // 设置为当前数据
|
||
newCoil.setTransferType(null);
|
||
newCoil.setQrcodeRecordId(oldCoil.getQrcodeRecordId()); // 继续使用原二维码
|
||
|
||
// 继承原记录的关键字段
|
||
if (newCoil.getEnterCoilNo() == null) {
|
||
newCoil.setEnterCoilNo(oldCoil.getEnterCoilNo());
|
||
}
|
||
if (newCoil.getSupplierCoilNo() == null) {
|
||
newCoil.setSupplierCoilNo(oldCoil.getSupplierCoilNo());
|
||
}
|
||
|
||
validEntityBeforeSave(newCoil);
|
||
|
||
// 把老记录的coilId赋值给新纪录的parentCoilId
|
||
newCoil.setParentCoilId(String.valueOf(oldCoil.getCoilId()));
|
||
// 插入新记录
|
||
boolean flag = baseMapper.insert(newCoil) > 0;
|
||
|
||
if (!flag) {
|
||
throw new RuntimeException("创建新钢卷失败");
|
||
}
|
||
|
||
// 如果实际库区id为-1,则清空钢卷上的实际库区绑定
|
||
if (bo.getActualWarehouseId() != null && bo.getActualWarehouseId().equals(-1L)) {
|
||
clearActualWarehouseBinding(newCoil.getActualWarehouseId(), newCoil.getCoilId());
|
||
}
|
||
|
||
// 3. 更新二维码内容(添加更新步骤并更新current_coil_id)
|
||
if (oldCoil.getQrcodeRecordId() != null) {
|
||
if (StringUtils.isNotBlank(qrcodeStepType) && "annealing".equals(qrcodeStepType)) {
|
||
updateQrcodeContentForCustomStep(oldCoil, bo, newCoil.getCoilId(), "退火", "退火操作");
|
||
} else {
|
||
updateQrcodeContentForNormalUpdate(oldCoil, bo, newCoil.getCoilId());
|
||
}
|
||
}
|
||
|
||
// 只有当新的库区ID不为空时更新库区状态
|
||
if (bo.getActualWarehouseId() != null) {
|
||
updateActualWarehouseEnableStatus(null, bo.getActualWarehouseId());
|
||
}
|
||
|
||
// 单个更新:记录加工日志(operationType=2)
|
||
// 老的实际库区存在则加一条加工出库记录(使用老钢卷ID)
|
||
// 新的实际库区存在则加一条加工入库记录(使用新钢卷ID)
|
||
// 新的传-1置空则算一条出库记录
|
||
// 就算老的和新的实际库区相等也应该建两条,因为钢卷ID不一样
|
||
Long oldActualWarehouseId = oldCoil.getActualWarehouseId();
|
||
Long newActualWarehouseId = bo.getActualWarehouseId();
|
||
|
||
// 老的实际库区存在,加一条加工出库记录
|
||
if (oldActualWarehouseId != null && oldActualWarehouseId != -1L) {
|
||
recordWarehouseOperationLog(oldCoil.getCoilId(), oldActualWarehouseId, 2, 2, "单个更新-加工出库");
|
||
}
|
||
// 新的实际库区存在,加一条加工入库记录
|
||
if (newActualWarehouseId != null && newActualWarehouseId != -1L) {
|
||
// 有新库区,算入库
|
||
recordWarehouseOperationLog(newCoil.getCoilId(), newActualWarehouseId, 2, 1, "单个更新-加工入库");
|
||
}
|
||
|
||
// 插入钢卷异常信息
|
||
if (bo.getAbnormals() != null && !bo.getAbnormals().isEmpty()) {
|
||
for (WmsCoilAbnormalBo abnormalBo : bo.getAbnormals()) {
|
||
abnormalBo.setCoilId(newCoil.getCoilId());
|
||
coilAbnormalService.insertByBo(abnormalBo);
|
||
}
|
||
}
|
||
|
||
// 返回新钢卷ID字符串
|
||
return String.valueOf(newCoil.getCoilId());
|
||
}
|
||
/**
|
||
* 更新实际库区的启用状态
|
||
* @param oldActualWarehouseId 原来的实际库区ID
|
||
* @param newActualWarehouseId 新的实际库区ID
|
||
*/
|
||
private void updateActualWarehouseEnableStatus(Long oldActualWarehouseId, Long newActualWarehouseId) {
|
||
// 如果实际库区没有变化,则无需更新
|
||
if (Objects.equals(oldActualWarehouseId, newActualWarehouseId)) {
|
||
return;
|
||
}
|
||
|
||
// 启用原来的库区
|
||
if (oldActualWarehouseId != null) {
|
||
WmsActualWarehouseBo oldWarehouseBo = new WmsActualWarehouseBo();
|
||
oldWarehouseBo.setActualWarehouseId(oldActualWarehouseId);
|
||
oldWarehouseBo.setIsEnabled(1); // 设置为启用状态
|
||
actualWarehouseService.updateByBo(oldWarehouseBo);
|
||
}
|
||
|
||
// 禁用新的库区
|
||
if (newActualWarehouseId != null && newActualWarehouseId != -1L) {
|
||
WmsActualWarehouseBo newWarehouseBo = new WmsActualWarehouseBo();
|
||
newWarehouseBo.setActualWarehouseId(newActualWarehouseId);
|
||
newWarehouseBo.setIsEnabled(0); // 设置为禁用状态
|
||
actualWarehouseService.updateByBo(newWarehouseBo);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 清空钢卷上的实际库区绑定
|
||
* @param actualWarehouseId 实际库区ID
|
||
* @param coilId 钢卷ID
|
||
*/
|
||
private void clearActualWarehouseBinding(Long actualWarehouseId, Long coilId) {
|
||
// 启用原来的库区
|
||
if (actualWarehouseId != null && actualWarehouseId != -1L) {
|
||
WmsActualWarehouseBo warehouseBo = new WmsActualWarehouseBo();
|
||
warehouseBo.setActualWarehouseId(actualWarehouseId);
|
||
warehouseBo.setIsEnabled(1); // 设置为启用状态
|
||
actualWarehouseService.updateByBo(warehouseBo);
|
||
}
|
||
|
||
// 清空钢卷上的实际库区绑定
|
||
LambdaUpdateWrapper<WmsMaterialCoil> clearAwWrapper = new LambdaUpdateWrapper<>();
|
||
clearAwWrapper.eq(WmsMaterialCoil::getCoilId, coilId);
|
||
clearAwWrapper.set(WmsMaterialCoil::getActualWarehouseId, (Long) null);
|
||
baseMapper.update(null, clearAwWrapper);
|
||
}
|
||
|
||
|
||
/**
|
||
* 获取钢卷的独占状态
|
||
* @param coilId 钢卷ID
|
||
* @return exclusiveStatus值,null或0表示未独占
|
||
*/
|
||
private Integer getExclusiveStatus(Long coilId) {
|
||
if (coilId == null) {
|
||
return null;
|
||
}
|
||
WmsMaterialCoil coil = baseMapper.selectById(coilId);
|
||
return coil == null ? null : coil.getExclusiveStatus();
|
||
}
|
||
|
||
/**
|
||
* 验证钢卷操作权限(检查独占状态)
|
||
* @param coilId 钢卷ID
|
||
* @param operation 操作名称(用于错误信息)
|
||
*/
|
||
private void validateCoilOperationPermission(Long coilId, String operation) {
|
||
Integer status = getExclusiveStatus(coilId);
|
||
if (status == null || status == 0) {
|
||
return;
|
||
}
|
||
if (status == 1) {
|
||
throw new RuntimeException("钢卷正在进行单步分卷操作中,无法执行" + operation + "操作");
|
||
}
|
||
if (status == 2) {
|
||
throw new RuntimeException("钢卷正在进行退火操作中,无法执行" + operation + "操作");
|
||
}
|
||
throw new RuntimeException("钢卷已被独占,无法执行" + operation + "操作");
|
||
}
|
||
|
||
/**
|
||
* 批量更新(分卷/合卷)
|
||
*/
|
||
/**
|
||
* 批量更新(分卷/合卷)
|
||
* @return 分卷时返回逗号分隔的新钢卷ID字符串,合卷时返回单个新钢卷ID字符串
|
||
*/
|
||
private String updateByBatch(WmsMaterialCoilBo bo) {
|
||
// 检查独占状态
|
||
if (bo.getCoilId() != null) {
|
||
validateCoilOperationPermission(bo.getCoilId(), "批量更新");
|
||
}
|
||
|
||
// 查询原钢卷(分卷时需要,合卷时可能不需要)
|
||
WmsMaterialCoil oldCoil = null;
|
||
if (bo.getCoilId() != null) {
|
||
oldCoil = baseMapper.selectById(bo.getCoilId());
|
||
if (oldCoil == null) {
|
||
throw new RuntimeException("原钢卷不存在");
|
||
}
|
||
}
|
||
// oldCoil 如果是历史卷也就是date_type=0 的时候就不能分卷
|
||
if (oldCoil != null && oldCoil.getDataType() == 0) {
|
||
throw new RuntimeException("原钢卷已分卷,不能再分卷");
|
||
}
|
||
|
||
// 判断是分卷还是合卷
|
||
boolean isSplit = false;
|
||
boolean isMerge = false;
|
||
|
||
// 检查bo本身是否为合卷
|
||
if (bo.getHasMergeSplit() != null && bo.getHasMergeSplit() == 2) {
|
||
isMerge = true;
|
||
} else if (bo.getNewCoils() != null && !bo.getNewCoils().isEmpty()) {
|
||
// 检查newCoils中是否有分卷
|
||
for (WmsMaterialCoilBo newCoilBo : bo.getNewCoils()) {
|
||
if (newCoilBo.getHasMergeSplit() != null && newCoilBo.getHasMergeSplit() == 1) {
|
||
isSplit = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 1. 将原数据更新为历史数据(data_type=0)
|
||
// 注意:合卷时bo的coilId可能为空,因为bo是合卷后的新钢卷
|
||
if (bo.getCoilId() != null) {
|
||
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
|
||
updateWrapper.eq(WmsMaterialCoil::getCoilId, bo.getCoilId())
|
||
.set(WmsMaterialCoil::getDataType, 0); // 设置为历史数据
|
||
baseMapper.update(null, updateWrapper);
|
||
}
|
||
|
||
// 2. 插入多条新的当前数据(data_type=1)
|
||
List<WmsMaterialCoil> newCoils = new ArrayList<>();
|
||
List<String> allNewCoilNos = new ArrayList<>();
|
||
|
||
// 收集所有新钢卷号
|
||
for (WmsMaterialCoilBo newCoilBo : bo.getNewCoils()) {
|
||
allNewCoilNos.add(newCoilBo.getCurrentCoilNo());
|
||
}
|
||
|
||
if (isSplit) {
|
||
// 分卷:将bo作为被分卷的原始对象,newCoils中的对象作为分卷后产生的新钢卷
|
||
if (oldCoil == null) {
|
||
throw new RuntimeException("分卷操作需要原钢卷信息");
|
||
}
|
||
|
||
// 1. 将原始钢卷的二维码标记为失效(status=0)
|
||
if (oldCoil.getQrcodeRecordId() != null) {
|
||
WmsGenerateRecordBo oldQrBo = new WmsGenerateRecordBo();
|
||
oldQrBo.setRecordId(oldCoil.getQrcodeRecordId());
|
||
oldQrBo.setStatus(0); // 0=失效
|
||
generateRecordService.updateByBo(oldQrBo);
|
||
}
|
||
|
||
// 2. 将原始钢卷标记为历史数据,并记录所有子卷号
|
||
// 在母卷的 parent_coil_nos 字段中记录所有子卷号(用逗号分隔)
|
||
String childCoilNos = String.join(",", allNewCoilNos);
|
||
LambdaUpdateWrapper<WmsMaterialCoil> motherUpdateWrapper = new LambdaUpdateWrapper<>();
|
||
motherUpdateWrapper.eq(WmsMaterialCoil::getCoilId, oldCoil.getCoilId())
|
||
.set(WmsMaterialCoil::getParentCoilNos, childCoilNos); // 记录子卷号
|
||
baseMapper.update(null, motherUpdateWrapper);
|
||
|
||
// 3. 为每个分卷后的子钢卷生成独立的二维码并插入数据库
|
||
for (WmsMaterialCoilBo newCoilBo : bo.getNewCoils()) {
|
||
// 校验每个子卷的实际库位
|
||
if (newCoilBo.getActualWarehouseId() != null) {
|
||
Long ignoreOccupiedId = (oldCoil != null && Objects.equals(newCoilBo.getActualWarehouseId(), oldCoil.getActualWarehouseId()))
|
||
? newCoilBo.getActualWarehouseId() : null;
|
||
validateActualWarehouseForAssign(newCoilBo.getActualWarehouseId(), ignoreOccupiedId);
|
||
}
|
||
WmsMaterialCoil newCoil = BeanUtil.toBean(newCoilBo, WmsMaterialCoil.class);
|
||
newCoil.setCoilId(null);
|
||
newCoil.setDataType(1);
|
||
newCoil.setTransferType(null);
|
||
// 继承原钢卷的基本信息(强制继承,不能修改的字段)
|
||
newCoil.setEnterCoilNo(oldCoil.getEnterCoilNo());
|
||
newCoil.setSupplierCoilNo(oldCoil.getSupplierCoilNo()); // 保留厂家原料卷号
|
||
// materialType, itemType 和 itemId 使用前端传递的值,不强制继承
|
||
if (newCoil.getItemType() == null) {
|
||
newCoil.setItemType(oldCoil.getItemType());
|
||
}
|
||
if (newCoil.getItemId() == null) {
|
||
newCoil.setItemId(oldCoil.getItemId());
|
||
}
|
||
// 如果前端没传team,使用原钢卷的team
|
||
if (newCoil.getTeam() == null) {
|
||
newCoil.setTeam(oldCoil.getTeam());
|
||
}
|
||
// 如果没有指定库区,使用原库区
|
||
if (newCoil.getWarehouseId() == null) {
|
||
newCoil.setWarehouseId(oldCoil.getWarehouseId());
|
||
}
|
||
|
||
// 在子卷的 parent_coil_nos 字段中记录母卷号
|
||
newCoil.setParentCoilNos(oldCoil.getCurrentCoilNo());
|
||
|
||
// 为每个子钢卷生成独立二维码
|
||
Long newQrcodeId = generateQrcodeForSplit(oldCoil, newCoilBo, allNewCoilNos);
|
||
newCoil.setQrcodeRecordId(newQrcodeId);
|
||
|
||
validEntityBeforeSave(newCoil);
|
||
// 把老记录的coilId赋值给新纪录的parentCoilId
|
||
newCoil.setParentCoilId(String.valueOf(oldCoil.getCoilId()));
|
||
baseMapper.insert(newCoil);
|
||
newCoils.add(newCoil);
|
||
|
||
// 更新二维码内容中的coilId
|
||
updateQrcodeCoilId(newQrcodeId, newCoil.getCoilId());
|
||
|
||
// 更新实际库区的启用状态
|
||
updateActualWarehouseEnableStatus(oldCoil.getActualWarehouseId(), newCoilBo.getActualWarehouseId());
|
||
|
||
// 插入子钢卷的异常信息
|
||
if (newCoilBo.getAbnormals() != null && !newCoilBo.getAbnormals().isEmpty()) {
|
||
for (WmsCoilAbnormalBo abnormalBo : newCoilBo.getAbnormals()) {
|
||
abnormalBo.setCoilId(newCoil.getCoilId());
|
||
coilAbnormalService.insertByBo(abnormalBo);
|
||
}
|
||
}
|
||
}
|
||
// 更新所有子钢卷二维码中的分卷步骤child_coil_ids
|
||
List<Long> newCoilIds = newCoils.stream()
|
||
.map(WmsMaterialCoil::getCoilId)
|
||
.collect(Collectors.toList());
|
||
String childCoilIdsStr = newCoilIds.stream()
|
||
.map(String::valueOf)
|
||
.collect(Collectors.joining(","));
|
||
|
||
|
||
for (WmsMaterialCoil newChildCoil : newCoils) {
|
||
if (newChildCoil.getQrcodeRecordId() != null) {
|
||
// 更新该子钢卷的分卷步骤中的child_coil_ids字段
|
||
updateChildCoilIdsQrcodeCoilId(oldCoil.getCoilId(), newChildCoil.getQrcodeRecordId(), newChildCoil.getCoilId(), childCoilIdsStr);
|
||
} else {
|
||
log.warn("子钢卷缺少二维码记录ID,跳过分卷步骤child_coil_ids更新,coilId: {}", newChildCoil.getCoilId());
|
||
}
|
||
}
|
||
|
||
// 分卷操作:记录加工日志(operationType=2)
|
||
// 老的实际库区存在则加一条加工出库记录
|
||
Long oldSplitActualWarehouseId = oldCoil.getActualWarehouseId();
|
||
if (oldSplitActualWarehouseId != null && oldSplitActualWarehouseId != -1L) {
|
||
recordWarehouseOperationLog(oldCoil.getCoilId(), oldSplitActualWarehouseId, 2, 2, "分卷操作-加工出库");
|
||
}
|
||
// 为每个子钢卷的实际库区加一条加工入库记录(有的加,没有不加)
|
||
for (WmsMaterialCoil childCoil : newCoils) {
|
||
Long childActualWarehouseId = childCoil.getActualWarehouseId();
|
||
if (childActualWarehouseId != null && childActualWarehouseId != -1L) {
|
||
recordWarehouseOperationLog(childCoil.getCoilId(), childActualWarehouseId, 2, 1, "分卷操作-加工入库");
|
||
}
|
||
}
|
||
|
||
// 返回分卷后的所有新钢卷ID(逗号分隔)
|
||
return childCoilIdsStr;
|
||
|
||
} else if (isMerge) {
|
||
// 合卷:将bo作为合卷后的新钢卷,newCoils中的对象作为参与合卷的原始钢卷
|
||
// 1. 将参与合卷的原始钢卷的二维码标记为失效,并将钢卷标记为历史数据
|
||
//在合卷之前需要判断前端传来的bo.getNewCoils()中的所有原始钢卷的coilId是否已经是历史卷
|
||
validateOriginalCoilsForMerge(bo.getNewCoils());
|
||
for (WmsMaterialCoilBo originalCoilBo : bo.getNewCoils()) {
|
||
if (originalCoilBo.getCoilId() != null) {
|
||
WmsMaterialCoil originalCoil = baseMapper.selectById(originalCoilBo.getCoilId());
|
||
if (originalCoil != null) {
|
||
// 标记二维码为失效
|
||
if (originalCoil.getQrcodeRecordId() != null) {
|
||
WmsGenerateRecordBo oldQrBo = new WmsGenerateRecordBo();
|
||
oldQrBo.setRecordId(originalCoil.getQrcodeRecordId());
|
||
oldQrBo.setStatus(0); // 0=失效
|
||
generateRecordService.updateByBo(oldQrBo);
|
||
}
|
||
|
||
// 标记钢卷为历史数据
|
||
LambdaUpdateWrapper<WmsMaterialCoil> originalUpdateWrapper = new LambdaUpdateWrapper<>();
|
||
originalUpdateWrapper.eq(WmsMaterialCoil::getCoilId, originalCoilBo.getCoilId())
|
||
.set(WmsMaterialCoil::getDataType, 0); // 设置为历史数据
|
||
baseMapper.update(null, originalUpdateWrapper);
|
||
|
||
// 启用原始钢卷的实际库区
|
||
if (originalCoil.getActualWarehouseId() != null) {
|
||
WmsActualWarehouseBo disableWarehouseBo = new WmsActualWarehouseBo();
|
||
disableWarehouseBo.setActualWarehouseId(originalCoil.getActualWarehouseId());
|
||
disableWarehouseBo.setIsEnabled(1); // 设置为启用状态
|
||
actualWarehouseService.updateByBo(disableWarehouseBo);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2. 生成合卷后的新钢卷二维码
|
||
Long mergedQrcodeId = generateQrcodeForMerge(bo, bo.getNewCoils());
|
||
|
||
// 3. 插入合卷后的新钢卷
|
||
// 合卷结果若指定了实际库位,先进行校验
|
||
if (bo.getActualWarehouseId() != null) {
|
||
validateActualWarehouseForAssign(bo.getActualWarehouseId(), null);
|
||
}
|
||
WmsMaterialCoil newCoil = BeanUtil.toBean(bo, WmsMaterialCoil.class);
|
||
newCoil.setCoilId(null);
|
||
newCoil.setDataType(1);
|
||
newCoil.setTransferType(null);
|
||
|
||
// 从第一个参与合卷的原始钢卷获取基本信息
|
||
if (!bo.getNewCoils().isEmpty()) {
|
||
WmsMaterialCoil firstOriginalCoil = baseMapper.selectById(bo.getNewCoils().get(0).getCoilId());
|
||
if (firstOriginalCoil != null) {
|
||
// 继承基本信息
|
||
if (newCoil.getEnterCoilNo() == null) {
|
||
newCoil.setEnterCoilNo(firstOriginalCoil.getEnterCoilNo());
|
||
}
|
||
if (newCoil.getSupplierCoilNo() == null) {
|
||
newCoil.setSupplierCoilNo(firstOriginalCoil.getSupplierCoilNo()); // 保留厂家原料卷号
|
||
}
|
||
if (newCoil.getItemType() == null) {
|
||
newCoil.setItemType(firstOriginalCoil.getItemType());
|
||
}
|
||
if (newCoil.getItemId() == null) {
|
||
newCoil.setItemId(firstOriginalCoil.getItemId());
|
||
}
|
||
if (newCoil.getTeam() == null) {
|
||
newCoil.setTeam(firstOriginalCoil.getTeam());
|
||
}
|
||
}
|
||
}
|
||
newCoil.setQrcodeRecordId(mergedQrcodeId);
|
||
|
||
validEntityBeforeSave(newCoil);
|
||
// 收集所有参与合卷的原始钢卷ID并用逗号分隔
|
||
List<Long> parentCoilIds = new ArrayList<>();
|
||
for (WmsMaterialCoilBo originalCoilBo : bo.getNewCoils()) {
|
||
if (originalCoilBo.getCoilId() != null) {
|
||
parentCoilIds.add(originalCoilBo.getCoilId());
|
||
}
|
||
}
|
||
String parentCoilIdsStr = parentCoilIds.stream()
|
||
.map(String::valueOf)
|
||
.collect(Collectors.joining(","));
|
||
newCoil.setParentCoilId(parentCoilIdsStr);
|
||
|
||
baseMapper.insert(newCoil);
|
||
newCoils.add(newCoil);
|
||
|
||
// 更新二维码内容中的coilId
|
||
updateQrcodeCoilId(mergedQrcodeId, newCoil.getCoilId());
|
||
|
||
// 禁用新钢卷的实际库区
|
||
if (bo.getActualWarehouseId() != null) {
|
||
WmsActualWarehouseBo enableWarehouseBo = new WmsActualWarehouseBo();
|
||
enableWarehouseBo.setActualWarehouseId(bo.getActualWarehouseId());
|
||
enableWarehouseBo.setIsEnabled(0); // 设置为禁用状态
|
||
actualWarehouseService.updateByBo(enableWarehouseBo);
|
||
}
|
||
|
||
// 合卷操作:记录加工日志(operationType=2)
|
||
// 为每个参与合卷的原始钢卷加一条加工出库记录
|
||
for (WmsMaterialCoilBo originalCoilBo : bo.getNewCoils()) {
|
||
if (originalCoilBo.getCoilId() != null) {
|
||
WmsMaterialCoil originalCoil = baseMapper.selectById(originalCoilBo.getCoilId());
|
||
if (originalCoil != null && originalCoil.getActualWarehouseId() != null
|
||
&& originalCoil.getActualWarehouseId() != -1L) {
|
||
recordWarehouseOperationLog(originalCoil.getCoilId(), originalCoil.getActualWarehouseId(), 2, 2, "合卷操作-加工出库");
|
||
}
|
||
}
|
||
}
|
||
// 为新合卷的钢卷加一条加工入库记录
|
||
Long mergeNewActualWarehouseId = bo.getActualWarehouseId();
|
||
if (mergeNewActualWarehouseId != null && mergeNewActualWarehouseId != -1L) {
|
||
recordWarehouseOperationLog(newCoil.getCoilId(), mergeNewActualWarehouseId, 2, 1, "合卷操作-加工入库");
|
||
}
|
||
|
||
// 插入合卷钢卷的异常信息
|
||
if (bo.getAbnormals() != null && !bo.getAbnormals().isEmpty()) {
|
||
for (WmsCoilAbnormalBo abnormalBo : bo.getAbnormals()) {
|
||
abnormalBo.setCoilId(newCoil.getCoilId());
|
||
coilAbnormalService.insertByBo(abnormalBo);
|
||
}
|
||
}
|
||
|
||
// 合卷完成后,设置新钢卷ID到bo对象中,方便外部获取
|
||
bo.setMergedCoilId(newCoil.getCoilId());
|
||
|
||
// 返回合卷后的新钢卷ID
|
||
return String.valueOf(newCoil.getCoilId());
|
||
}
|
||
|
||
throw new RuntimeException("未知的批量更新类型");
|
||
}
|
||
|
||
/**
|
||
* 验证参与合卷的原始钢卷是否都是当前数据(而非历史数据)
|
||
* @param originalCoils 参与合卷的原始钢卷列表
|
||
*/
|
||
private void validateOriginalCoilsForMerge(List<WmsMaterialCoilBo> originalCoils) {
|
||
if (CollectionUtils.isEmpty(originalCoils)) {
|
||
return;
|
||
}
|
||
|
||
// 收集所有需要验证的coilId
|
||
List<Long> coilIds = originalCoils.stream()
|
||
.map(WmsMaterialCoilBo::getCoilId)
|
||
.filter(Objects::nonNull)
|
||
.collect(Collectors.toList());
|
||
|
||
if (CollectionUtils.isEmpty(coilIds)) {
|
||
return;
|
||
}
|
||
|
||
// 批量查询钢卷数据,避免N+1查询问题
|
||
List<WmsMaterialCoil> originalCoilsData = baseMapper.selectBatchIds(coilIds);
|
||
|
||
// 检查是否有历史数据
|
||
List<String> historyCoils = originalCoilsData.stream()
|
||
.filter(coil -> coil.getDataType() != null && coil.getDataType() == 0) // dataType=0表示历史数据
|
||
.map(coil -> ": " + coil.getCurrentCoilNo())
|
||
.collect(Collectors.toList());
|
||
|
||
if (!historyCoils.isEmpty()) {
|
||
String errorDetails = String.join(", ", historyCoils);
|
||
throw new RuntimeException("合卷操作需要所有原始钢卷为当前数据,以下卷号已被合卷" + errorDetails);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 为分卷生成新二维码(每个子钢卷一个)
|
||
*/
|
||
private Long generateQrcodeForSplit(WmsMaterialCoil oldCoil, WmsMaterialCoilBo newCoilBo, List<String> allNewCoilNos) {
|
||
try {
|
||
Map<String, Object> contentMap = new HashMap<>();
|
||
contentMap.put("enter_coil_no", oldCoil.getEnterCoilNo());
|
||
contentMap.put("current_coil_no", newCoilBo.getCurrentCoilNo());
|
||
contentMap.put("coil_id", String.valueOf(oldCoil.getCoilId())); // 初始钢卷ID(记录原钢卷的ID)
|
||
contentMap.put("current_coil_id", "null"); // 当前钢卷ID(分卷时暂时为null,插入后更新)
|
||
|
||
// 复制原钢卷的历史steps
|
||
List<Map<String, Object>> steps = new ArrayList<>();
|
||
if (oldCoil.getQrcodeRecordId() != null) {
|
||
WmsGenerateRecordVo oldRecord = generateRecordService.queryById(oldCoil.getQrcodeRecordId());
|
||
if (oldRecord != null) {
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
@SuppressWarnings("unchecked")
|
||
Map<String, Object> oldContentMap = objectMapper.readValue(oldRecord.getContent(), Map.class);
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> oldSteps = (List<Map<String, Object>>) oldContentMap.get("steps");
|
||
if (oldSteps != null) {
|
||
steps.addAll(oldSteps);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加分卷步骤
|
||
Map<String, Object> splitStep = new HashMap<>();
|
||
splitStep.put("step", steps.size() + 1);
|
||
splitStep.put("action", "更新");
|
||
splitStep.put("operation", "分卷");
|
||
splitStep.put("old_current_coil_no", oldCoil.getCurrentCoilNo());
|
||
splitStep.put("old_coil_id", String.valueOf(oldCoil.getCoilId()));
|
||
splitStep.put("new_current_coil_nos", String.join(",", allNewCoilNos));
|
||
splitStep.put("child_coils", allNewCoilNos);
|
||
// 分卷产生的子钢卷ID将在后续步骤中设置
|
||
splitStep.put("operator", LoginHelper.getUsername()); // 操作者
|
||
// 分卷的操作时间
|
||
splitStep.put("create_time", DateUtils.getNowDate());
|
||
steps.add(splitStep);
|
||
|
||
contentMap.put("steps", steps);
|
||
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
String contentJson = objectMapper.writeValueAsString(contentMap);
|
||
|
||
WmsGenerateRecordBo recordBo = new WmsGenerateRecordBo();
|
||
recordBo.setContent(contentJson);
|
||
recordBo.setSerialNumber(oldCoil.getEnterCoilNo() + "-" + newCoilBo.getCurrentCoilNo());
|
||
recordBo.setQrcodeType(0L);
|
||
recordBo.setIsEnabled(0L);
|
||
recordBo.setSize(200L);
|
||
recordBo.setStatus(1); // 1=当前有效码
|
||
|
||
WmsGenerateRecordVo record = generateRecordService.insertByBo(recordBo);
|
||
return record.getRecordId();
|
||
} catch (Exception e) {
|
||
throw new RuntimeException("生成分卷二维码失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 为合卷生成新二维码(合并多个父钢卷的二维码信息)
|
||
*/
|
||
private Long generateQrcodeForMerge(WmsMaterialCoilBo mergedCoilBo, List<WmsMaterialCoilBo> originalCoils) {
|
||
try {
|
||
if (mergedCoilBo == null) {
|
||
throw new RuntimeException("合卷后的钢卷数据不能为空");
|
||
}
|
||
|
||
Map<String, Object> contentMap = new HashMap<>();
|
||
// 获取enterCoilNo,优先使用mergedCoilBo的,如果没有则从原始钢卷中获取
|
||
String enterCoilNo = mergedCoilBo.getEnterCoilNo();
|
||
if (enterCoilNo == null && originalCoils != null && !originalCoils.isEmpty()) {
|
||
WmsMaterialCoil firstOriginalCoil = baseMapper.selectById(originalCoils.get(0).getCoilId());
|
||
if (firstOriginalCoil != null) {
|
||
enterCoilNo = firstOriginalCoil.getEnterCoilNo();
|
||
}
|
||
}
|
||
contentMap.put("enter_coil_no", enterCoilNo);
|
||
contentMap.put("current_coil_no", mergedCoilBo.getCurrentCoilNo());
|
||
contentMap.put("coil_id", "null"); // 初始钢卷ID(合卷时为null)
|
||
contentMap.put("current_coil_id", "null"); // 当前钢卷ID(合卷时暂时为null,插入后更新)
|
||
|
||
// 合并所有参与合卷的原始钢卷的历史steps
|
||
List<Map<String, Object>> steps = new ArrayList<>();
|
||
|
||
// 从参与合卷的原始钢卷中获取二维码信息并合并
|
||
if (originalCoils != null && !originalCoils.isEmpty()) {
|
||
for (WmsMaterialCoilBo originalCoilBo : originalCoils) {
|
||
if (originalCoilBo.getCoilId() != null) {
|
||
// 查询原始钢卷的二维码信息
|
||
WmsMaterialCoil originalCoil = baseMapper.selectById(originalCoilBo.getCoilId());
|
||
if (originalCoil != null && originalCoil.getQrcodeRecordId() != null) {
|
||
WmsGenerateRecordVo originalQr = generateRecordService.queryById(originalCoil.getQrcodeRecordId());
|
||
if (originalQr != null) {
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
@SuppressWarnings("unchecked")
|
||
Map<String, Object> originalContentMap = objectMapper.readValue(originalQr.getContent(), Map.class);
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> originalSteps = (List<Map<String, Object>>) originalContentMap.get("steps");
|
||
if (originalSteps != null) {
|
||
steps.addAll(originalSteps);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加合卷步骤
|
||
Map<String, Object> mergeStep = new HashMap<>();
|
||
mergeStep.put("step", steps.size() + 1);
|
||
mergeStep.put("action", "更新");
|
||
mergeStep.put("operation", "合卷");
|
||
|
||
// 收集参与合卷的原始钢卷号和ID
|
||
List<String> originalCoilNos = new ArrayList<>();
|
||
List<String> originalCoilIds = new ArrayList<>();
|
||
if (originalCoils != null && !originalCoils.isEmpty()) {
|
||
for (WmsMaterialCoilBo originalCoilBo : originalCoils) {
|
||
if (originalCoilBo.getCurrentCoilNo() != null) {
|
||
originalCoilNos.add(originalCoilBo.getCurrentCoilNo());
|
||
}
|
||
if (originalCoilBo.getCoilId() != null) {
|
||
originalCoilIds.add(originalCoilBo.getCoilId().toString());
|
||
}
|
||
}
|
||
}
|
||
mergeStep.put("parent_coil_nos", String.join(",", originalCoilNos));
|
||
mergeStep.put("parent_coil_ids", String.join(",", originalCoilIds));
|
||
mergeStep.put("new_current_coil_no", mergedCoilBo.getCurrentCoilNo());
|
||
mergeStep.put("operator", LoginHelper.getUsername()); // 操作者
|
||
steps.add(mergeStep);
|
||
|
||
contentMap.put("steps", steps);
|
||
// 将父钢卷ID也存储到顶层,方便快速查询
|
||
contentMap.put("parent_coil_ids", String.join(",", originalCoilIds));
|
||
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
String contentJson = objectMapper.writeValueAsString(contentMap);
|
||
|
||
WmsGenerateRecordBo recordBo = new WmsGenerateRecordBo();
|
||
recordBo.setContent(contentJson);
|
||
recordBo.setSerialNumber(enterCoilNo + "-" + mergedCoilBo.getCurrentCoilNo());
|
||
recordBo.setQrcodeType(0L);
|
||
recordBo.setIsEnabled(0L);
|
||
recordBo.setSize(200L);
|
||
recordBo.setStatus(1); // 1=当前有效码
|
||
|
||
WmsGenerateRecordVo record = generateRecordService.insertByBo(recordBo);
|
||
return record.getRecordId();
|
||
} catch (Exception e) {
|
||
throw new RuntimeException("生成合卷二维码失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新二维码内容中的coilId
|
||
*/
|
||
private void updateQrcodeCoilId(Long qrcodeRecordId, Long coilId) {
|
||
try {
|
||
// 获取二维码记录
|
||
WmsGenerateRecordVo record = generateRecordService.queryById(qrcodeRecordId);
|
||
if (record == null) {
|
||
throw new RuntimeException("二维码记录不存在");
|
||
}
|
||
|
||
// 解析现有content
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
Map<String, Object> contentMap = objectMapper.readValue(record.getContent(), Map.class);
|
||
|
||
// 如果是第一次设置coilId(从"null"变为实际ID),则同时设置coil_id和current_coil_id
|
||
if ("null".equals(contentMap.get("coil_id"))) {
|
||
contentMap.put("coil_id", String.valueOf(coilId)); // 初始ID,不再改变
|
||
}
|
||
|
||
// 始终更新current_coil_id为最新的钢卷ID
|
||
contentMap.put("current_coil_id", String.valueOf(coilId));
|
||
|
||
// 更新二维码记录
|
||
String newContentJson = objectMapper.writeValueAsString(contentMap);
|
||
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
|
||
updateBo.setRecordId(qrcodeRecordId);
|
||
updateBo.setContent(newContentJson);
|
||
generateRecordService.updateByBo(updateBo);
|
||
} catch (Exception e) {
|
||
throw new RuntimeException("更新二维码coilId失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新二维码内容(正常更新,添加step)
|
||
* @param oldCoil 旧钢卷记录
|
||
* @param bo 更新数据
|
||
* @param newCoilId 新钢卷ID(如果创建了新记录)
|
||
*/
|
||
private void updateQrcodeContentForNormalUpdate(WmsMaterialCoil oldCoil, WmsMaterialCoilBo bo, Long newCoilId) {
|
||
try {
|
||
// 获取原二维码记录
|
||
WmsGenerateRecordVo oldRecord = generateRecordService.queryById(oldCoil.getQrcodeRecordId());
|
||
if (oldRecord == null) {
|
||
throw new RuntimeException("二维码记录不存在");
|
||
}
|
||
|
||
// 解析现有content
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
@SuppressWarnings("unchecked")
|
||
Map<String, Object> contentMap = objectMapper.readValue(oldRecord.getContent(), Map.class);
|
||
|
||
// 获取现有steps
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||
if (steps == null) {
|
||
steps = new ArrayList<>();
|
||
}
|
||
|
||
// 添加新的step,记录更新信息
|
||
Map<String, Object> newStep = new HashMap<>();
|
||
newStep.put("step", steps.size() + 1);
|
||
newStep.put("action", "更新");
|
||
newStep.put("operation", "信息更新");
|
||
newStep.put("old_current_coil_no", oldCoil.getCurrentCoilNo());
|
||
newStep.put("new_current_coil_no", bo.getCurrentCoilNo());
|
||
newStep.put("old_coil_id", String.valueOf(oldCoil.getCoilId()));
|
||
newStep.put("new_coil_id", String.valueOf(newCoilId));
|
||
newStep.put("operator", LoginHelper.getUsername());
|
||
|
||
// 记录具体的变更字段
|
||
List<String> changedFields = new ArrayList<>();
|
||
if (bo.getCurrentCoilNo() != null && !bo.getCurrentCoilNo().equals(oldCoil.getCurrentCoilNo())) {
|
||
changedFields.add("钢卷号: " + oldCoil.getCurrentCoilNo() + " → " + bo.getCurrentCoilNo());
|
||
}
|
||
if (bo.getTeam() != null && !bo.getTeam().equals(oldCoil.getTeam())) {
|
||
changedFields.add("班组: " + oldCoil.getTeam() + " → " + bo.getTeam());
|
||
}
|
||
if (bo.getWarehouseId() != null && !bo.getWarehouseId().equals(oldCoil.getWarehouseId())) {
|
||
changedFields.add("逻辑库区ID: " + oldCoil.getWarehouseId() + " → " + bo.getWarehouseId());
|
||
}
|
||
if (bo.getActualWarehouseId() != null && !bo.getActualWarehouseId().equals(oldCoil.getActualWarehouseId())) {
|
||
changedFields.add("真实库区ID: " + oldCoil.getActualWarehouseId() + " → " + bo.getActualWarehouseId());
|
||
}
|
||
if (bo.getGrossWeight() != null && !bo.getGrossWeight().equals(oldCoil.getGrossWeight())) {
|
||
changedFields.add("毛重: " + oldCoil.getGrossWeight() + " → " + bo.getGrossWeight());
|
||
}
|
||
if (bo.getNetWeight() != null && !bo.getNetWeight().equals(oldCoil.getNetWeight())) {
|
||
changedFields.add("净重: " + oldCoil.getNetWeight() + " → " + bo.getNetWeight());
|
||
}
|
||
if (bo.getRemark() != null && !bo.getRemark().equals(oldCoil.getRemark())) {
|
||
changedFields.add("备注: " + oldCoil.getRemark() + " → " + bo.getRemark());
|
||
}
|
||
if (bo.getQualityStatus() != null && !bo.getQualityStatus().equals(oldCoil.getQualityStatus())) {
|
||
changedFields.add("质量状态: " + oldCoil.getQualityStatus() + " → " + bo.getQualityStatus());
|
||
}
|
||
if (bo.getTrimmingRequirement() != null && !bo.getTrimmingRequirement().equals(oldCoil.getTrimmingRequirement())) {
|
||
changedFields.add("切边要求: " + oldCoil.getTrimmingRequirement() + " → " + bo.getTrimmingRequirement());
|
||
}
|
||
if (bo.getPackingStatus() != null && !bo.getPackingStatus().equals(oldCoil.getPackingStatus())) {
|
||
changedFields.add("打包状态: " + oldCoil.getPackingStatus() + " → " + bo.getPackingStatus());
|
||
}
|
||
if (bo.getPackagingRequirement() != null && !bo.getPackagingRequirement().equals(oldCoil.getPackagingRequirement())) {
|
||
changedFields.add("包装要求: " + oldCoil.getPackagingRequirement() + " → " + bo.getPackagingRequirement());
|
||
}
|
||
if (bo.getItemId() != null && !bo.getItemId().equals(oldCoil.getItemId())) {
|
||
changedFields.add("物品ID: " + oldCoil.getItemId() + " → " + bo.getItemId());
|
||
}
|
||
if (bo.getMaterialType() != null && !bo.getMaterialType().equals(oldCoil.getMaterialType())) {
|
||
changedFields.add("材料类型: " + oldCoil.getMaterialType() + " → " + bo.getMaterialType());
|
||
}
|
||
if (bo.getLength() != null && !bo.getLength().equals(oldCoil.getLength())) {
|
||
changedFields.add("长度: " + oldCoil.getLength() + " → " + bo.getLength());
|
||
}
|
||
if (bo.getTemperGrade() != null && !bo.getTemperGrade().equals(oldCoil.getTemperGrade())) {
|
||
changedFields.add("调制度: " + oldCoil.getTemperGrade() + " → " + bo.getTemperGrade());
|
||
}
|
||
if (bo.getCoatingType() != null && !bo.getCoatingType().equals(oldCoil.getCoatingType())) {
|
||
changedFields.add("镀层种类: " + oldCoil.getCoatingType() + " → " + bo.getCoatingType());
|
||
}
|
||
|
||
newStep.put("changed_fields", String.join("; ", changedFields));
|
||
newStep.put("update_time", new java.util.Date());
|
||
|
||
steps.add(newStep);
|
||
contentMap.put("steps", steps);
|
||
|
||
// 更新当前钢卷号
|
||
if (bo.getCurrentCoilNo() != null) {
|
||
contentMap.put("current_coil_no", bo.getCurrentCoilNo());
|
||
}
|
||
|
||
// 更新 current_coil_id 为新记录的ID
|
||
if (newCoilId != null) {
|
||
contentMap.put("current_coil_id", String.valueOf(newCoilId));
|
||
}
|
||
|
||
// 更新二维码记录
|
||
String newContentJson = objectMapper.writeValueAsString(contentMap);
|
||
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
|
||
updateBo.setRecordId(oldCoil.getQrcodeRecordId());
|
||
updateBo.setContent(newContentJson);
|
||
generateRecordService.updateByBo(updateBo);
|
||
} catch (Exception e) {
|
||
throw new RuntimeException("更新二维码失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新二维码内容(自定义步骤类型)
|
||
* 用于退火完成等场景,action和operation可以自定义
|
||
* @param oldCoil 旧钢卷记录
|
||
* @param bo 更新数据
|
||
* @param newCoilId 新钢卷ID
|
||
* @param action 自定义action(如"更新"、"退火")
|
||
* @param operation 自定义operation(如"信息更新"、"退火操作")
|
||
*/
|
||
private void updateQrcodeContentForCustomStep(WmsMaterialCoil oldCoil, WmsMaterialCoilBo bo, Long newCoilId, String action, String operation) {
|
||
try {
|
||
WmsGenerateRecordVo oldRecord = generateRecordService.queryById(oldCoil.getQrcodeRecordId());
|
||
if (oldRecord == null) {
|
||
throw new RuntimeException("二维码记录不存在");
|
||
}
|
||
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
@SuppressWarnings("unchecked")
|
||
Map<String, Object> contentMap = objectMapper.readValue(oldRecord.getContent(), Map.class);
|
||
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||
if (steps == null) {
|
||
steps = new ArrayList<>();
|
||
}
|
||
|
||
Map<String, Object> newStep = new HashMap<>();
|
||
newStep.put("step", steps.size() + 1);
|
||
newStep.put("action", action);
|
||
newStep.put("operation", operation);
|
||
newStep.put("old_current_coil_no", oldCoil.getCurrentCoilNo());
|
||
newStep.put("new_current_coil_no", bo.getCurrentCoilNo() != null ? bo.getCurrentCoilNo() : oldCoil.getCurrentCoilNo());
|
||
newStep.put("old_coil_id", String.valueOf(oldCoil.getCoilId()));
|
||
newStep.put("new_coil_id", String.valueOf(newCoilId));
|
||
newStep.put("operator", LoginHelper.getUsername());
|
||
|
||
List<String> changedFields = new ArrayList<>();
|
||
if (bo.getWarehouseId() != null && !bo.getWarehouseId().equals(oldCoil.getWarehouseId())) {
|
||
changedFields.add("逻辑库区ID: " + oldCoil.getWarehouseId() + " → " + bo.getWarehouseId());
|
||
}
|
||
if (bo.getActualWarehouseId() != null && !bo.getActualWarehouseId().equals(oldCoil.getActualWarehouseId())) {
|
||
changedFields.add("真实库区ID: " + oldCoil.getActualWarehouseId() + " → " + bo.getActualWarehouseId());
|
||
}
|
||
newStep.put("changed_fields", String.join("; ", changedFields));
|
||
newStep.put("update_time", new java.util.Date());
|
||
|
||
steps.add(newStep);
|
||
contentMap.put("steps", steps);
|
||
|
||
if (bo.getCurrentCoilNo() != null) {
|
||
contentMap.put("current_coil_no", bo.getCurrentCoilNo());
|
||
}
|
||
|
||
if (newCoilId != null) {
|
||
contentMap.put("current_coil_id", String.valueOf(newCoilId));
|
||
}
|
||
|
||
String newContentJson = objectMapper.writeValueAsString(contentMap);
|
||
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
|
||
updateBo.setRecordId(oldCoil.getQrcodeRecordId());
|
||
updateBo.setContent(newContentJson);
|
||
generateRecordService.updateByBo(updateBo);
|
||
} catch (Exception e) {
|
||
throw new RuntimeException("更新二维码失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* 保存前的数据校验
|
||
*/
|
||
private void validEntityBeforeSave(WmsMaterialCoil entity) {
|
||
//TODO 做一些数据校验,如唯一约束
|
||
}
|
||
|
||
/**
|
||
* 批量删除钢卷物料表
|
||
*/
|
||
@Override
|
||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||
if (isValid) {
|
||
//TODO 做一些业务上的校验,判断是否需要校验
|
||
}
|
||
|
||
// 检查独占状态
|
||
for (Long id : ids) {
|
||
validateCoilOperationPermission(id, "删除");
|
||
}
|
||
|
||
// 获取要删除的钢卷记录
|
||
List<WmsMaterialCoil> coilList = baseMapper.selectBatchIds(ids);
|
||
if (coilList != null && !coilList.isEmpty()) {
|
||
// 遍历每个要删除的钢卷,释放其占用的实际库区
|
||
for (WmsMaterialCoil coil : coilList) {
|
||
if (coil.getActualWarehouseId() != null) {
|
||
WmsActualWarehouseBo actualWarehouseBo = new WmsActualWarehouseBo();
|
||
actualWarehouseBo.setActualWarehouseId(coil.getActualWarehouseId());
|
||
actualWarehouseBo.setIsEnabled(1); // 设置为启用状态,释放库区
|
||
actualWarehouseService.updateByBo(actualWarehouseBo);
|
||
}
|
||
}
|
||
}
|
||
// 删除钢卷的时候要把钢卷对应的二维码状态改成 0 表示二维码失效
|
||
if (coilList != null) {
|
||
for (WmsMaterialCoil coil : coilList) {
|
||
if (coil.getQrcodeRecordId() != null) {
|
||
WmsGenerateRecordBo qrBo = new WmsGenerateRecordBo();
|
||
qrBo.setRecordId(coil.getQrcodeRecordId());
|
||
qrBo.setStatus(0); // 0=失效
|
||
generateRecordService.updateByBo(qrBo);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
return baseMapper.deleteBatchIds(ids) > 0;
|
||
}
|
||
|
||
/**
|
||
* 钢卷溯源查询
|
||
* 根据钢卷ID查询二维码,解析content中的steps,然后根据steps中的钢卷号反向查询数据库
|
||
*
|
||
* @param coilId 钢卷ID
|
||
* @param currentCoilNo 当前钢卷号(可选,用于查询特定子钢卷)
|
||
* @return 溯源结果(包含二维码信息和数据库记录)
|
||
*/
|
||
@Override
|
||
public Map<String, Object> queryTrace(Long coilId, String currentCoilNo) {
|
||
try {
|
||
WmsMaterialCoilVo coilVo = baseMapper.selectVoById(coilId);
|
||
if (coilVo == null) {
|
||
throw new RuntimeException("未找到对应的钢卷记录");
|
||
}
|
||
|
||
Long qrcodeRecordId = coilVo.getQrcodeRecordId();
|
||
if (qrcodeRecordId == null) {
|
||
throw new RuntimeException("该钢卷未关联二维码记录");
|
||
}
|
||
|
||
WmsGenerateRecordVo qrRecord = generateRecordService.queryById(qrcodeRecordId);
|
||
if (qrRecord == null) {
|
||
throw new RuntimeException("未找到对应的二维码记录");
|
||
}
|
||
|
||
String enterCoilNo = coilVo.getEnterCoilNo();
|
||
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
|
||
@SuppressWarnings("unchecked")
|
||
Map<String, Object> contentMap = objectMapper.readValue(qrRecord.getContent(), Map.class);
|
||
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||
|
||
Set<String> allCoilNos = new HashSet<>();
|
||
Set<String> operatorUsernames = new HashSet<>();
|
||
|
||
if (steps != null) {
|
||
for (Map<String, Object> step : steps) {
|
||
extractCoilNo(step, "current_coil_no", allCoilNos);
|
||
extractCoilNo(step, "new_current_coil_no", allCoilNos);
|
||
extractCoilNo(step, "old_current_coil_no", allCoilNos);
|
||
extractCoilNo(step, "new_current_coil_nos", allCoilNos);
|
||
extractCoilNo(step, "merged_from", allCoilNos);
|
||
extractCoilNo(step, "parent_coil_nos", allCoilNos);
|
||
extractCoilId(step, "coil_id", allCoilNos);
|
||
extractCoilId(step, "old_coil_id", allCoilNos);
|
||
extractCoilId(step, "parent_coil_ids", allCoilNos);
|
||
extractCoilId(step, "child_coil_ids", allCoilNos);
|
||
|
||
Object operator = step.get("operator");
|
||
if (operator != null) {
|
||
operatorUsernames.add(operator.toString());
|
||
}
|
||
}
|
||
}
|
||
|
||
Map<String, String> operatorNicknameMap = getOperatorNicknames(operatorUsernames);
|
||
|
||
List<Map<String, Object>> allSteps = new ArrayList<>(steps != null ? steps : new ArrayList<>());
|
||
|
||
allSteps.sort((a, b) -> {
|
||
Integer stepA = (Integer) a.get("step");
|
||
Integer stepB = (Integer) b.get("step");
|
||
if (stepA == null) stepA = 0;
|
||
if (stepB == null) stepB = 0;
|
||
return stepA.compareTo(stepB);
|
||
});
|
||
|
||
for (int i = 0; i < allSteps.size(); i++) {
|
||
allSteps.get(i).put("display_step", i + 1);
|
||
allSteps.get(i).put("original_step", allSteps.get(i).get("step"));
|
||
allSteps.get(i).put("qrcode_serial", qrRecord.getSerialNumber());
|
||
allSteps.get(i).put("qrcode_id", qrRecord.getRecordId());
|
||
|
||
Object operator = allSteps.get(i).get("operator");
|
||
if (operator != null) {
|
||
String username = operator.toString();
|
||
String nickname = operatorNicknameMap.get(username);
|
||
allSteps.get(i).put("operator_nickname", nickname != null ? nickname : username);
|
||
}
|
||
}
|
||
|
||
Set<String> filteredCoilNos = allCoilNos;
|
||
if (currentCoilNo != null && !currentCoilNo.trim().isEmpty()) {
|
||
final String filterValue = currentCoilNo;
|
||
filteredCoilNos = allCoilNos.stream()
|
||
.filter(coilNo -> coilNo.contains(filterValue))
|
||
.collect(Collectors.toSet());
|
||
}
|
||
|
||
List<WmsMaterialCoilVo> result = new ArrayList<>();
|
||
if (!filteredCoilNos.isEmpty() && StringUtils.isNotBlank(enterCoilNo)) {
|
||
List<String> coilNoList = new ArrayList<>(filteredCoilNos);
|
||
if (coilNoList.size() <= 1000) {
|
||
LambdaQueryWrapper<WmsMaterialCoil> lqw = Wrappers.lambdaQuery();
|
||
lqw.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo);
|
||
lqw.in(WmsMaterialCoil::getCurrentCoilNo, coilNoList);
|
||
lqw.orderByAsc(WmsMaterialCoil::getCreateTime);
|
||
result = baseMapper.selectVoList(lqw);
|
||
} else {
|
||
int batchSize = 1000;
|
||
for (int i = 0; i < coilNoList.size(); i += batchSize) {
|
||
int end = Math.min(i + batchSize, coilNoList.size());
|
||
List<String> batch = coilNoList.subList(i, end);
|
||
LambdaQueryWrapper<WmsMaterialCoil> batchLqw = Wrappers.lambdaQuery();
|
||
batchLqw.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo);
|
||
batchLqw.in(WmsMaterialCoil::getCurrentCoilNo, batch);
|
||
batchLqw.orderByAsc(WmsMaterialCoil::getCreateTime);
|
||
List<WmsMaterialCoilVo> batchResult = baseMapper.selectVoList(batchLqw);
|
||
result.addAll(batchResult);
|
||
}
|
||
}
|
||
|
||
if (!result.isEmpty()) {
|
||
fillRelatedObjectsBatch(result);
|
||
}
|
||
}
|
||
|
||
if (result.isEmpty() && StringUtils.isNotBlank(enterCoilNo)) {
|
||
LambdaQueryWrapper<WmsMaterialCoil> lqw = Wrappers.lambdaQuery();
|
||
lqw.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo);
|
||
lqw.orderByAsc(WmsMaterialCoil::getCreateTime);
|
||
result = baseMapper.selectVoList(lqw);
|
||
|
||
if (!result.isEmpty()) {
|
||
fillRelatedObjectsBatch(result);
|
||
}
|
||
}
|
||
|
||
Map<String, Object> resultMap = new HashMap<>();
|
||
resultMap.put("qrcode", qrRecord);
|
||
resultMap.put("steps", allSteps);
|
||
resultMap.put("records", result);
|
||
|
||
return resultMap;
|
||
} catch (Exception e) {
|
||
throw new RuntimeException("溯源查询失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 根据用户名获取用户昵称映射
|
||
* @param usernames 用户名集合
|
||
* @return 用户名到昵称的映射
|
||
*/
|
||
private Map<String, String> getOperatorNicknames(Set<String> usernames) {
|
||
Map<String, String> nicknameMap = new HashMap<>();
|
||
if (usernames.isEmpty()) {
|
||
return nicknameMap;
|
||
}
|
||
|
||
for (String username : usernames) {
|
||
try {
|
||
// 通过用户名查找用户
|
||
SysUser user = userService.selectUserByUserName(username);
|
||
if (user != null) {
|
||
nicknameMap.put(username, user.getNickName());
|
||
} else {
|
||
nicknameMap.put(username, username); // 找不到则使用原用户名
|
||
}
|
||
} catch (Exception e) {
|
||
// 出现异常时使用原用户名
|
||
nicknameMap.put(username, username);
|
||
}
|
||
}
|
||
|
||
return nicknameMap;
|
||
}
|
||
|
||
|
||
/**
|
||
* 从step中提取钢卷号
|
||
*/
|
||
private void extractCoilNo(Map<String, Object> step, String fieldName, Set<String> coilNos) {
|
||
Object value = step.get(fieldName);
|
||
if (value != null) {
|
||
String strValue = value.toString();
|
||
if (strValue.contains(",")) {
|
||
// 如果是逗号分隔的多个钢卷号,分割后添加
|
||
String[] coilArray = strValue.split(",");
|
||
for (String coilNo : coilArray) {
|
||
coilNos.add(coilNo.trim());
|
||
}
|
||
} else {
|
||
coilNos.add(strValue.trim());
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从step中提取钢卷ID
|
||
*/
|
||
private void extractCoilId(Map<String, Object> step, String fieldName, Set<String> coilNos) {
|
||
Object value = step.get(fieldName);
|
||
if (value != null) {
|
||
String strValue = value.toString();
|
||
if (strValue.contains(",")) {
|
||
// 如果是逗号分隔的多个钢卷ID,分割后添加
|
||
String[] coilArray = strValue.split(",");
|
||
for (String coilId : coilArray) {
|
||
coilNos.add(coilId.trim());
|
||
}
|
||
} else {
|
||
coilNos.add(strValue.trim());
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 查询各个库区中不同类型的钢卷分布情况
|
||
* 按库区分组,统计每种物品类型和物品ID的钢卷数量和重量
|
||
*
|
||
* @param itemType 物品类型(可选)
|
||
* @param itemId 物品ID(可选)
|
||
* @return 分布情况列表,包含库区信息、物品类型、物品ID、数量、重量等
|
||
*/
|
||
@Override
|
||
public List<WmsMaterialCoilVo> getDistributionByWarehouse(String itemType, Long itemId) {
|
||
List<Map<String, Object>> mapList = baseMapper.getDistributionByWarehouse(itemType, itemId);
|
||
return convertMapListToVoList(mapList);
|
||
}
|
||
|
||
@Override
|
||
public List<WmsMaterialCoilVo> getDistributionByActualWarehouse(String itemType, Long itemId) {
|
||
List<Map<String, Object>> mapList = baseMapper.getDistributionByActualWarehouse(itemType, itemId);
|
||
return convertMapListToVoListActual(mapList);
|
||
}
|
||
|
||
|
||
|
||
private List<WmsMaterialCoilVo> convertMapListToVoListActual(List<Map<String, Object>> mapList) {
|
||
List<WmsMaterialCoilVo> voList = new ArrayList<>();
|
||
for (Map<String, Object> map : mapList) {
|
||
WmsMaterialCoilVo vo = new WmsMaterialCoilVo();
|
||
vo.setActualWarehouseId(map.get("actual_warehouse_id") != null ? Long.valueOf(map.get("actual_warehouse_id").toString()) : null);
|
||
vo.setActualWarehouseName(map.get("actual_warehouse_name") != null ? map.get("actual_warehouse_name").toString() : null);
|
||
vo.setItemType(map.get("item_type") != null ? map.get("item_type").toString() : null);
|
||
vo.setItemId(map.get("item_id") != null ? Long.valueOf(map.get("item_id").toString()) : null);
|
||
vo.setCoilCount(map.get("coil_count") != null ? Long.valueOf(map.get("coil_count").toString()) : 0L);
|
||
vo.setTotalGrossWeight(map.get("total_gross_weight") != null ? new BigDecimal(map.get("total_gross_weight").toString()) : BigDecimal.ZERO);
|
||
vo.setTotalNetWeight(map.get("total_net_weight") != null ? new BigDecimal(map.get("total_net_weight").toString()) : BigDecimal.ZERO);
|
||
|
||
setSubMaterialVo(vo, map, voList);
|
||
}
|
||
return voList;
|
||
}
|
||
/**
|
||
* 查询不同类型的钢卷在不同库区的分布情况
|
||
* 按物品类型和物品ID分组,统计每个库区的钢卷数量和重量
|
||
*
|
||
* @param itemType 物品类型(可选)
|
||
* @param itemId 物品ID(可选)
|
||
* @return 分布情况列表,包含物品类型、物品ID、库区信息、数量、重量等
|
||
*/
|
||
@Override
|
||
public List<WmsMaterialCoilVo> getDistributionByItemType(String itemType, Long itemId) {
|
||
List<Map<String, Object>> mapList = baseMapper.getDistributionByItemType(itemType, itemId);
|
||
return convertMapListToVoList(mapList);
|
||
}
|
||
|
||
/**
|
||
* 将Map列表转换为WmsMaterialCoilVo列表
|
||
*/
|
||
private List<WmsMaterialCoilVo> convertMapListToVoList(List<Map<String, Object>> mapList) {
|
||
List<WmsMaterialCoilVo> voList = new ArrayList<>();
|
||
for (Map<String, Object> map : mapList) {
|
||
WmsMaterialCoilVo vo = new WmsMaterialCoilVo();
|
||
vo.setWarehouseId(map.get("warehouse_id") != null ? Long.valueOf(map.get("warehouse_id").toString()) : null);
|
||
vo.setWarehouseName(map.get("warehouse_name") != null ? map.get("warehouse_name").toString() : null);
|
||
vo.setItemType(map.get("item_type") != null ? map.get("item_type").toString() : null);
|
||
vo.setItemId(map.get("item_id") != null ? Long.valueOf(map.get("item_id").toString()) : null);
|
||
vo.setCoilCount(map.get("coil_count") != null ? Long.valueOf(map.get("coil_count").toString()) : 0L);
|
||
vo.setTotalGrossWeight(map.get("total_gross_weight") != null ? new BigDecimal(map.get("total_gross_weight").toString()) : BigDecimal.ZERO);
|
||
vo.setTotalNetWeight(map.get("total_net_weight") != null ? new BigDecimal(map.get("total_net_weight").toString()) : BigDecimal.ZERO);
|
||
setSubMaterialVo(vo, map, voList);
|
||
}
|
||
return voList;
|
||
}
|
||
private void setSubMaterialVo(WmsMaterialCoilVo vo, Map<String, Object> map, List<WmsMaterialCoilVo> voList) {
|
||
String itemType = vo.getItemType();
|
||
if (itemType == null) {
|
||
voList.add(vo);
|
||
return;
|
||
}
|
||
vo.setItemName(map.get("itemName") != null ? map.get("itemName").toString() : null);
|
||
vo.setItemCode(map.get("itemCode") != null ? map.get("itemCode").toString() : null);
|
||
vo.setSpecification(map.get("specification") != null ? map.get("specification").toString() : null);
|
||
vo.setMaterial(map.get("material") != null ? map.get("material").toString() : null);
|
||
vo.setSurfaceTreatmentDesc(map.get("surfaceTreatmentDesc") != null ? map.get("surfaceTreatmentDesc").toString() : null);
|
||
vo.setZincLayer(map.get("zincLayer") != null ? map.get("zincLayer").toString() : null);
|
||
vo.setManufacturer(map.get("manufacturer") != null ? map.get("manufacturer").toString() : null);
|
||
voList.add(vo);
|
||
}
|
||
@Override
|
||
public List<WmsMaterialCoilVo> getDistributionByActualItemType(String itemType, Long itemId) {
|
||
List<Map<String, Object>> mapList = baseMapper.getDistributionByActualItemType(itemType, itemId);
|
||
return convertMapListToVoListActual(mapList);
|
||
}
|
||
|
||
/**
|
||
* 查询钢卷导出数据列表
|
||
*/
|
||
@Override
|
||
public List<WmsMaterialCoilExportVo> queryExportList(WmsMaterialCoilBo bo) {
|
||
Map<Long, Date> coilIdCompleteTimeMap = new HashMap<>();
|
||
|
||
if ((bo.getCoilIds() == null || bo.getCoilIds().isEmpty())
|
||
&& bo.getActionIds() != null && !bo.getActionIds().isEmpty()) {
|
||
String[] actionIdArr = bo.getActionIds().split(",");
|
||
List<Long> actionIdList = Arrays.stream(actionIdArr)
|
||
.map(String::trim)
|
||
.filter(s -> !s.isEmpty())
|
||
.map(Long::parseLong)
|
||
.collect(Collectors.toList());
|
||
|
||
LambdaQueryWrapper<WmsCoilPendingAction> queryWrapper = new LambdaQueryWrapper<>();
|
||
queryWrapper.in(WmsCoilPendingAction::getActionId, actionIdList);
|
||
|
||
List<WmsCoilPendingAction> actions = coilPendingActionMapper.selectList(queryWrapper);
|
||
|
||
if (actions != null && !actions.isEmpty()) {
|
||
for (WmsCoilPendingAction action : actions) {
|
||
coilIdCompleteTimeMap.put(action.getCoilId(), action.getCompleteTime());
|
||
}
|
||
|
||
bo.setCoilIds(actions.stream()
|
||
.map(a -> String.valueOf(a.getCoilId()))
|
||
.collect(Collectors.joining(",")));
|
||
}
|
||
}
|
||
|
||
QueryWrapper<WmsMaterialCoil> lqw = buildQueryWrapperPlus(bo);
|
||
List<WmsMaterialCoilExportVo> wmsMaterialCoilExportVos = baseMapper.selectExportList(lqw);
|
||
// 遍历数据,根据状态替换日期字段,并处理空值兜底
|
||
wmsMaterialCoilExportVos.stream().forEach(vo -> {
|
||
// 设置action完成时间
|
||
if (coilIdCompleteTimeMap.containsKey(vo.getCoilId())) {
|
||
vo.setActionCompleteTime(coilIdCompleteTimeMap.get(vo.getCoilId()));
|
||
}
|
||
|
||
// 判断查询条件中的status是否为1(已发货)
|
||
if (bo.getStatus() != null && bo.getStatus() == 1) {
|
||
Date finalDate = null;
|
||
// 1. 优先使用发货时间
|
||
if (vo.getExportTime() != null) {
|
||
finalDate = vo.getExportTime();
|
||
}
|
||
// 2. 发货时间为空时,使用更新时间兜底
|
||
else if (vo.getUpdateTime() != null) {
|
||
finalDate = vo.getUpdateTime();
|
||
}
|
||
// 3. 若更新时间也为空,保留原有createTime(避免日期字段为空)
|
||
else {
|
||
finalDate = vo.getCreateTime();
|
||
}
|
||
// 赋值最终的日期
|
||
vo.setCreateTime(finalDate);
|
||
}
|
||
// 非1的情况,保持原有create_time不变
|
||
|
||
// 如果是dataType=0的历史数据,将实际库区设置为null
|
||
if (vo.getDataType() != null && vo.getDataType() == 0) {
|
||
vo.setActualWarehouseName(null);
|
||
}
|
||
});
|
||
return wmsMaterialCoilExportVos;
|
||
}
|
||
/**
|
||
* 查询钢卷导出数据列表(完整版)
|
||
*/
|
||
@Override
|
||
@Transactional(readOnly = true)
|
||
public List<WmsMaterialCoilAllExportVo> queryExportListAll(WmsMaterialCoilBo bo) {
|
||
QueryWrapper<WmsMaterialCoil> lqw = buildQueryWrapperPlus(bo);
|
||
List<WmsMaterialCoilExportVo> wmsMaterialCoilVos = baseMapper.selectExportList(lqw);
|
||
List<WmsMaterialCoilAllExportVo> exportVoList = BeanUtil.copyToList(wmsMaterialCoilVos, WmsMaterialCoilAllExportVo.class);
|
||
if (CollectionUtils.isNotEmpty(exportVoList)) {
|
||
for (WmsMaterialCoilAllExportVo vo : exportVoList) {
|
||
if (Objects.equals(vo.getDataType(), 0)) {
|
||
vo.setActualWarehouseName(null);
|
||
}
|
||
// 转换数据类型:0=历史,1=现存
|
||
if (vo.getDataType() != null) {
|
||
String dataTypeText = vo.getDataType() == 0 ? "历史" : "现存";
|
||
vo.setDataTypeText(dataTypeText);
|
||
}
|
||
|
||
// 转换是否与订单相关:0=否,1=是
|
||
if (vo.getIsRelatedToOrder() != null) {
|
||
String isRelatedText = vo.getIsRelatedToOrder() == 0 ? "否" : "是";
|
||
vo.setIsRelatedToOrderText(isRelatedText);
|
||
}
|
||
}
|
||
}
|
||
|
||
return exportVoList;
|
||
}
|
||
/**
|
||
* 发货报表导出:按 coilIds 查询钢卷 + 发货单明细/主表/计划联查数据
|
||
*/
|
||
@Override
|
||
public List<WmsMaterialCoilDeliveryExportVo> queryDeliveryExportList(WmsMaterialCoilBo bo) {
|
||
List<Long> coilIds = parseCsvLongs(bo == null ? null : bo.getCoilIds());
|
||
if (coilIds.isEmpty()) {
|
||
return Collections.emptyList();
|
||
}
|
||
List<WmsMaterialCoilDeliveryExportVo> wmsMaterialCoilDeliveryExportVos = baseMapper.selectDeliveryExportListByCoilIds(coilIds);
|
||
// 遍历数据,根据状态替换日期字段,并处理空值兜底
|
||
wmsMaterialCoilDeliveryExportVos.stream().forEach(vo -> {
|
||
// 判断查询条件中的status是否为1(已发货)
|
||
if (bo.getStatus() != null && bo.getStatus() == 1) {
|
||
Date finalDate = null;
|
||
// 1. 优先使用发货时间
|
||
if (vo.getExportTime() != null) {
|
||
finalDate = vo.getExportTime();
|
||
}
|
||
// 2. 发货时间为空时,使用更新时间兜底
|
||
else if (vo.getUpdateTime() != null) {
|
||
finalDate = vo.getUpdateTime();
|
||
}
|
||
// 3. 若更新时间也为空,保留原有createTime(避免日期字段为空)
|
||
else {
|
||
finalDate = vo.getCreateTime();
|
||
}
|
||
// 赋值最终的日期
|
||
vo.setCreateTime(finalDate);
|
||
}
|
||
// 非1的情况,保持原有create_time不变
|
||
|
||
// 如果是dataType=0的历史数据,将实际库区设置为null
|
||
if (vo.getDataType() != null && vo.getDataType() == 0) {
|
||
vo.setActualWarehouseName(null);
|
||
}
|
||
});
|
||
return wmsMaterialCoilDeliveryExportVos;
|
||
}
|
||
|
||
private List<Long> parseCsvLongs(String csv) {
|
||
if (StringUtils.isBlank(csv)) {
|
||
return Collections.emptyList();
|
||
}
|
||
String[] arr = csv.split(",");
|
||
List<Long> list = new ArrayList<>();
|
||
for (String s : arr) {
|
||
if (StringUtils.isBlank(s)) {
|
||
continue;
|
||
}
|
||
try {
|
||
list.add(Long.parseLong(s.trim()));
|
||
} catch (NumberFormatException ignore) {
|
||
}
|
||
}
|
||
return list.stream().distinct().collect(Collectors.toList());
|
||
}
|
||
|
||
/**
|
||
* 钢卷发货
|
||
* @param coilId
|
||
* @return
|
||
*/
|
||
@Override
|
||
public int exportCoil(Long coilId) {
|
||
// 检查独占状态
|
||
validateCoilOperationPermission(coilId, "发货");
|
||
|
||
// 查询当前钢卷信息,记录原实际库区ID
|
||
WmsMaterialCoilVo wmsMaterialCoilVo = queryById(coilId);
|
||
Long oldActualWarehouseId = wmsMaterialCoilVo != null ? wmsMaterialCoilVo.getActualWarehouseId() : null;
|
||
|
||
// 如果质量状态为O的提示不能发货
|
||
if (wmsMaterialCoilVo != null && Objects.equals(wmsMaterialCoilVo.getQualityStatus(), "O")) {
|
||
throw new RuntimeException("当前钢卷为质检未通过,请勿发货!");
|
||
}
|
||
// 如果当前钢卷为历史数据应该抛异常
|
||
if (wmsMaterialCoilVo != null && wmsMaterialCoilVo.getDataType() == 0) {
|
||
throw new RuntimeException("当前数据为历史数据,请勿发货!");
|
||
}
|
||
// 1. 更新钢卷为已发货,并记录发货时间,同时清空实际库区占用(改用Wrapper实现)
|
||
int rows = 0;
|
||
//获取当前调用接口的这个人的username
|
||
String username = LoginHelper.getUsername();
|
||
if (wmsMaterialCoilVo != null) {
|
||
UpdateWrapper<WmsMaterialCoil> updateWrapper = new UpdateWrapper<>();
|
||
// 设置更新条件:钢卷ID
|
||
updateWrapper.eq("coil_id", coilId);
|
||
// 设置要更新的字段
|
||
updateWrapper.set("export_time", new Date()) // 发货时间
|
||
.set("status", 1) // 已发货状态
|
||
.set("actual_warehouse_id", null) // 清空实际库区ID(关键)
|
||
.set("export_by", username); // 发货人
|
||
|
||
// 执行更新操作
|
||
rows = baseMapper.update(null, updateWrapper);
|
||
}
|
||
|
||
// 2. 释放原实际库区(若存在):将库位设置为可用(启用)
|
||
if (rows > 0 && oldActualWarehouseId != null) {
|
||
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
|
||
releaseBo.setActualWarehouseId(oldActualWarehouseId);
|
||
releaseBo.setIsEnabled(1); // 释放:设置为启用
|
||
actualWarehouseService.updateByBo(releaseBo);
|
||
|
||
// 3. 仅当oldActualWarehouseId不为空且更新成功时,记录操作日志
|
||
recordWarehouseOperationLog(coilId, oldActualWarehouseId, 4, 2, "钢卷发货出库");
|
||
}
|
||
|
||
return rows;
|
||
}
|
||
|
||
/**
|
||
* 记录钢卷库区操作日志
|
||
*/
|
||
private void recordWarehouseOperationLog(Long coilId, Long warehouseId, Integer operationType, Integer inOutType, String remark) {
|
||
try {
|
||
WmsCoilWarehouseOperationLog operationLog = new WmsCoilWarehouseOperationLog();
|
||
operationLog.setCoilId(coilId);
|
||
operationLog.setActualWarehouseId(warehouseId);
|
||
operationLog.setOperationType(operationType);
|
||
operationLog.setInOutType(inOutType);
|
||
operationLog.setRemark(remark);
|
||
wmsCoilWarehouseOperationLogMapper.insert(operationLog);
|
||
} catch (Exception e) {
|
||
log.warn("记录钢卷库区操作日志失败:{}", e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 合卷操作
|
||
* 在批量更新逻辑的基础上,为newCoils中的每个被合的卷加入actionId和actionType
|
||
* 如果传递了actionId,则更新操作记录状态为已完成,并设置完成时间和合卷后的钢卷ID到processedCoilIds
|
||
* 如果未传actionId,则创建新的操作记录
|
||
*/
|
||
@Override
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public Boolean mergeCoils(WmsMaterialCoilBo bo) {
|
||
if (bo.getNewCoils() == null || bo.getNewCoils().isEmpty()) {
|
||
throw new RuntimeException("合卷操作需要提供参与合卷的钢卷列表");
|
||
}
|
||
|
||
Date now = new Date();
|
||
|
||
// 第一步:先为每个原始钢卷创建待操作记录(状态为处理中)
|
||
// 收集创建成功的actionId,用于后续更新
|
||
Map<Long, Long> coilToActionIdMap = new HashMap<>();
|
||
|
||
for (WmsMaterialCoilBo originalCoilBo : bo.getNewCoils()) {
|
||
Long coilId = originalCoilBo.getCoilId();
|
||
Integer actionType = originalCoilBo.getActionType();
|
||
Long actionId = originalCoilBo.getActionId();
|
||
|
||
if (coilId == null || actionType == null) {
|
||
continue;
|
||
}
|
||
|
||
if (actionId != null) {
|
||
// 如果传递了actionId,记录下来
|
||
coilToActionIdMap.put(coilId, actionId);
|
||
} else {
|
||
// 如果未传actionId,则创建新的待操作记录
|
||
// 获取原始钢卷信息
|
||
WmsMaterialCoil originalCoil = baseMapper.selectById(coilId);
|
||
if (originalCoil == null) {
|
||
continue;
|
||
}
|
||
|
||
WmsCoilPendingActionBo pendingActionBo = new WmsCoilPendingActionBo();
|
||
pendingActionBo.setCoilId(coilId);
|
||
pendingActionBo.setCurrentCoilNo(originalCoil.getCurrentCoilNo());
|
||
pendingActionBo.setActionType(actionType);
|
||
pendingActionBo.setActionStatus(0); // 处理中
|
||
pendingActionBo.setSourceType("manual");
|
||
pendingActionBo.setPriority(0);
|
||
coilPendingActionService.insertByBo(pendingActionBo);
|
||
|
||
// insertByBo 会自动设置 actionId 到 bo 中
|
||
if (pendingActionBo.getActionId() != null) {
|
||
coilToActionIdMap.put(coilId, pendingActionBo.getActionId());
|
||
}
|
||
}
|
||
}
|
||
// 执行第二步操作之前要判断一下参与合卷的钢卷是否有重复,相同的钢卷不能进行合卷操作
|
||
Set<Long> coilIdSet = new HashSet<>();
|
||
for (WmsMaterialCoilBo originalCoilBo : bo.getNewCoils()) {
|
||
if (originalCoilBo.getCoilId() == null) {
|
||
continue;
|
||
}
|
||
if (!coilIdSet.add(originalCoilBo.getCoilId())) {
|
||
throw new RuntimeException("参与合卷的钢卷中存在重复,相同的钢卷不能进行合卷操作");
|
||
}
|
||
}
|
||
|
||
// 第二步:执行合卷操作
|
||
String coilIds = updateByBatch(bo);
|
||
if (coilIds == null || coilIds.isEmpty()) {
|
||
throw new RuntimeException("合卷操作失败");
|
||
}
|
||
|
||
// 直接从bo中获取合卷后的新钢卷ID
|
||
Long mergedCoilId = bo.getMergedCoilId();
|
||
if (mergedCoilId == null) {
|
||
throw new RuntimeException("未找到合卷后的新钢卷ID");
|
||
}
|
||
|
||
// 第三步:更新所有待操作记录状态为已完成,并设置processedCoilIds
|
||
for (Map.Entry<Long, Long> entry : coilToActionIdMap.entrySet()) {
|
||
Long actionId = entry.getValue();
|
||
|
||
WmsCoilPendingActionBo pendingActionBo = new WmsCoilPendingActionBo();
|
||
pendingActionBo.setActionId(actionId);
|
||
pendingActionBo.setActionStatus(2); // 已完成
|
||
pendingActionBo.setCompleteTime(now);
|
||
pendingActionBo.setProcessedCoilIds(mergedCoilId.toString());
|
||
coilPendingActionService.updateByBo(pendingActionBo);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 钢卷发货撤回
|
||
* @param coilId
|
||
* @return
|
||
*/
|
||
@Override
|
||
@Transactional
|
||
public int withdrawExportCoil(Long coilId) {
|
||
// 参数校验
|
||
if (coilId == null) {
|
||
throw new IllegalArgumentException("钢卷ID不能为空");
|
||
}
|
||
|
||
WmsMaterialCoilVo wmsMaterialCoilVo = queryById(coilId);
|
||
if (wmsMaterialCoilVo == null) {
|
||
throw new RuntimeException("钢卷不存在");
|
||
}
|
||
|
||
// 检查当前状态是否为已发货状态
|
||
if (wmsMaterialCoilVo.getStatus() != 1) {
|
||
throw new RuntimeException("该钢卷并没有发货,无法撤回");
|
||
}
|
||
|
||
// 调用自定义的Mapper方法,只更新发货时间和状态字段
|
||
return baseMapper.updateForWithdrawExport(coilId, 0);
|
||
}
|
||
|
||
|
||
/**
|
||
* 批量更新钢卷发货状态
|
||
*
|
||
* @param coilIds 钢卷ID列表
|
||
* @param status 目标状态 (0=在库, 1=在途/已发货, 2=已出库)
|
||
* @return 是否更新成功
|
||
*/
|
||
@Override
|
||
public Boolean batchUpdateDeliveryStatus(List<Long> coilIds, Integer status) {
|
||
if (coilIds == null || coilIds.isEmpty()) {
|
||
return false;
|
||
}
|
||
|
||
if (status == null || status < 0 || status > 2) {
|
||
throw new RuntimeException("无效的状态值,状态必须在0-2之间");
|
||
}
|
||
|
||
// ********** 批量查询钢卷原库区ID(仅当发货状态时需要)**********
|
||
Map<Long, Long> coilWarehouseMap = new HashMap<>(); // key: coilId, value: oldActualWarehouseId
|
||
if (status == 1) { // 仅当设置为"已发货/在途"状态时,才处理库区释放逻辑
|
||
// 批量查询钢卷信息,获取原实际库区ID
|
||
List<WmsMaterialCoilVo> coilVoList = queryBatchByIds(coilIds);
|
||
for (WmsMaterialCoilVo vo : coilVoList) {
|
||
if (vo != null && vo.getActualWarehouseId() != null) {
|
||
coilWarehouseMap.put(vo.getCoilId(), vo.getActualWarehouseId());
|
||
}
|
||
}
|
||
}
|
||
|
||
// 构造更新条件
|
||
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = Wrappers.lambdaUpdate();
|
||
updateWrapper.in(WmsMaterialCoil::getCoilId, coilIds);
|
||
updateWrapper.set(WmsMaterialCoil::getStatus, status);
|
||
|
||
// ********** 发货状态时清空库区ID + 更新出库时间 **********
|
||
if (status == 1) {
|
||
updateWrapper.set(WmsMaterialCoil::getExportTime, new Date());
|
||
updateWrapper.set(WmsMaterialCoil::getActualWarehouseId, null); // 清空实际库区ID
|
||
}
|
||
|
||
// 执行批量更新
|
||
int updateCount = baseMapper.update(null, updateWrapper);
|
||
boolean updateSuccess = updateCount > 0;
|
||
|
||
// ********** 批量释放原库区占用(仅当更新成功且有库区ID时)**********
|
||
if (updateSuccess && status == 1 && !coilWarehouseMap.isEmpty()) {
|
||
// 去重库区ID(避免重复更新同一个库区)
|
||
Set<Long> warehouseIds = new HashSet<>(coilWarehouseMap.values());
|
||
for (Long warehouseId : warehouseIds) {
|
||
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
|
||
releaseBo.setActualWarehouseId(warehouseId);
|
||
releaseBo.setIsEnabled(1); // 释放:设置为启用
|
||
actualWarehouseService.updateByBo(releaseBo);
|
||
}
|
||
}
|
||
|
||
return updateSuccess;
|
||
}
|
||
|
||
// 补充:批量查询钢卷信息的方法(需要确保你的Mapper里有对应的批量查询逻辑)
|
||
private List<WmsMaterialCoilVo> queryBatchByIds(List<Long> coilIds) {
|
||
if (coilIds == null || coilIds.isEmpty()) {
|
||
return Collections.emptyList();
|
||
}
|
||
// 这里替换成你实际的批量查询逻辑(比如用IN条件查询)
|
||
LambdaQueryWrapper<WmsMaterialCoil> queryWrapper = Wrappers.lambdaQuery();
|
||
queryWrapper.in(WmsMaterialCoil::getCoilId, coilIds);
|
||
List<WmsMaterialCoil> coilList = baseMapper.selectList(queryWrapper);
|
||
|
||
// 转换为Vo
|
||
return coilList.stream()
|
||
.map(coil -> {
|
||
WmsMaterialCoilVo vo = new WmsMaterialCoilVo();
|
||
BeanUtils.copyProperties(coil, vo);
|
||
return vo;
|
||
})
|
||
.collect(Collectors.toList());
|
||
}
|
||
|
||
/**
|
||
* 根据更新前的钢卷ID列表获取对应的直接下一步钢卷ID
|
||
* 分卷场景返回所有子卷ID(逗号分隔),合卷/普通更新返回单个ID
|
||
*
|
||
* @param oldCoilIds 更新前的钢卷ID列表
|
||
* @return Map<旧钢卷ID, 下一步钢卷ID字符串>:
|
||
* - 普通更新/合卷:value为单个ID字符串(如"1002")
|
||
* - 分卷:value为逗号分隔的子卷ID字符串(如"1003,1004,1005")
|
||
* - 无下一步:value为null
|
||
*/
|
||
public Map<Long, String> getUpdatedCoilIdsByOldCoilIds(List<Long> oldCoilIds) {
|
||
// 初始化返回的Map,key=旧ID,value=下一步ID字符串(逗号分隔)
|
||
Map<Long, String> old2NextCoilIdMap = new LinkedHashMap<>();
|
||
if (oldCoilIds == null || oldCoilIds.isEmpty()) {
|
||
return old2NextCoilIdMap;
|
||
}
|
||
|
||
// 先将所有旧ID放入Map,默认value为null
|
||
for (Long oldCoilId : oldCoilIds) {
|
||
old2NextCoilIdMap.put(oldCoilId, null);
|
||
}
|
||
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
|
||
// 遍历每个旧ID,解析对应的下一步ID
|
||
for (Long oldCoilId : oldCoilIds) {
|
||
String nextCoilIdsStr = null; // 存储最终返回的ID字符串(单个/逗号分隔)
|
||
try {
|
||
// 1. 查询旧钢卷记录
|
||
WmsMaterialCoil oldCoil = baseMapper.selectById(oldCoilId);
|
||
if (oldCoil == null || oldCoil.getQrcodeRecordId() == null) {
|
||
old2NextCoilIdMap.put(oldCoilId, null);
|
||
continue;
|
||
}
|
||
|
||
// 2. 查询关联的二维码记录
|
||
LambdaQueryWrapper<WmsGenerateRecord> qrWrapper = Wrappers.lambdaQuery();
|
||
qrWrapper.eq(WmsGenerateRecord::getSerialNumber, oldCoil.getEnterCoilNo())
|
||
.or()
|
||
.like(WmsGenerateRecord::getSerialNumber, oldCoil.getEnterCoilNo() + "-");
|
||
List<WmsGenerateRecordVo> qrRecords = generateRecordMapper.selectVoList(qrWrapper);
|
||
|
||
if (qrRecords.isEmpty()) {
|
||
old2NextCoilIdMap.put(oldCoilId, null);
|
||
continue;
|
||
}
|
||
|
||
// 3. 解析每个二维码的步骤
|
||
for (WmsGenerateRecordVo qrRecord : qrRecords) {
|
||
if (qrRecord.getContent() == null || qrRecord.getContent().trim().isEmpty()) {
|
||
continue;
|
||
}
|
||
|
||
Map<String, Object> contentMap;
|
||
try {
|
||
contentMap = objectMapper.readValue(qrRecord.getContent(), Map.class);
|
||
} catch (JsonProcessingException e) {
|
||
log.warn("解析二维码记录[{}]的content失败: {}", qrRecord.getRecordId(), e.getMessage());
|
||
continue;
|
||
}
|
||
|
||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||
if (steps == null || steps.isEmpty()) {
|
||
continue;
|
||
}
|
||
|
||
String oldCoilIdStr = oldCoilId.toString();
|
||
// 4. 遍历步骤找下一步ID
|
||
for (Map<String, Object> step : steps) {
|
||
String stepOldCoilIdStr = step.get("old_coil_id") != null ? step.get("old_coil_id").toString() : null;
|
||
String newCoilIdStr = step.get("new_coil_id") != null ? step.get("new_coil_id").toString() : null;
|
||
|
||
// 普通更新场景:单个ID
|
||
if (stepOldCoilIdStr != null && stepOldCoilIdStr.equals(oldCoilIdStr)
|
||
&& newCoilIdStr != null && !"null".equals(newCoilIdStr)) {
|
||
nextCoilIdsStr = newCoilIdStr;
|
||
break;
|
||
}
|
||
|
||
// 合卷场景:单个ID
|
||
String parentCoilIds = step.get("parent_coil_ids") != null ? step.get("parent_coil_ids").toString() : null;
|
||
if (parentCoilIds != null && parentCoilIds.contains(oldCoilIdStr)) {
|
||
String newCurrentCoilNo = step.get("new_current_coil_no") != null ? step.get("new_current_coil_no").toString() : null;
|
||
if (newCurrentCoilNo != null) {
|
||
LambdaQueryWrapper<WmsMaterialCoil> coilWrapper = Wrappers.lambdaQuery();
|
||
coilWrapper.eq(WmsMaterialCoil::getCurrentCoilNo, newCurrentCoilNo)
|
||
.eq(WmsMaterialCoil::getDataType, 1);
|
||
WmsMaterialCoil nextCoil = baseMapper.selectOne(coilWrapper);
|
||
if (nextCoil != null) {
|
||
nextCoilIdsStr = nextCoil.getCoilId().toString();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 分卷场景:所有子卷ID(逗号分隔)
|
||
String operation = step.get("operation") != null ? step.get("operation").toString() : "";
|
||
if ("分卷".equals(operation) && oldCoilIdStr.equals(stepOldCoilIdStr)) {
|
||
String newCurrentCoilNos = step.get("new_current_coil_nos") != null ? step.get("new_current_coil_nos").toString() : null;
|
||
List<String> childCoilNos = null;
|
||
|
||
// 解析子卷号列表
|
||
if (newCurrentCoilNos != null && !newCurrentCoilNos.isEmpty()) {
|
||
childCoilNos = Arrays.asList(newCurrentCoilNos.split(","));
|
||
} else if (step.get("child_coils") instanceof List) {
|
||
childCoilNos = (List<String>) step.get("child_coils");
|
||
}
|
||
|
||
// 批量查询所有子卷ID并拼接为逗号分隔的字符串
|
||
if (childCoilNos != null && !childCoilNos.isEmpty()) {
|
||
// 去重+trim,避免空字符串/重复卷号
|
||
List<String> validChildCoilNos = childCoilNos.stream()
|
||
.map(String::trim)
|
||
.filter(no -> !no.isEmpty())
|
||
.distinct()
|
||
.collect(Collectors.toList());
|
||
|
||
if (!validChildCoilNos.isEmpty()) {
|
||
// 批量查询(性能优化,避免循环查询)
|
||
LambdaQueryWrapper<WmsMaterialCoil> coilWrapper = Wrappers.lambdaQuery();
|
||
coilWrapper.in(WmsMaterialCoil::getCurrentCoilNo, validChildCoilNos)
|
||
.eq(WmsMaterialCoil::getDataType, 1);
|
||
List<WmsMaterialCoil> childCoils = baseMapper.selectList(coilWrapper);
|
||
|
||
// 拼接子卷ID为逗号分隔的字符串
|
||
if (!childCoils.isEmpty()) {
|
||
nextCoilIdsStr = childCoils.stream()
|
||
.map(coil -> coil.getCoilId().toString())
|
||
.collect(Collectors.joining(","));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (nextCoilIdsStr != null) {
|
||
break; // 找到结果,跳出二维码循环
|
||
}
|
||
}
|
||
|
||
// 5. 将结果放入Map
|
||
old2NextCoilIdMap.put(oldCoilId, nextCoilIdsStr);
|
||
|
||
} catch (Exception e) {
|
||
log.error("解析钢卷ID[{}]的下一步ID失败", oldCoilId, e);
|
||
old2NextCoilIdMap.put(oldCoilId, null);
|
||
}
|
||
}
|
||
|
||
return old2NextCoilIdMap;
|
||
}
|
||
|
||
/**
|
||
* 检查钢卷号是否重复
|
||
* 根据入场钢卷号、当前钢卷号和厂家原料卷号查询数据库,判断哪个钢卷号重复
|
||
* 新增逻辑:修改历史记录时不检查重复
|
||
*/
|
||
@Override
|
||
public Map<String, Object> checkCoilNoDuplicate(Long coilId, String enterCoilNo, String currentCoilNo, String supplierCoilNo) {
|
||
Map<String, Object> result = new HashMap<>();
|
||
|
||
// 新增核心逻辑:先判断是否操作的是历史记录
|
||
// 1. 如果coilId不为空(修改操作),先查询该钢卷的dataType
|
||
if (coilId != null) {
|
||
WmsMaterialCoil coil = baseMapper.selectById(coilId);
|
||
// 2. 如果查询到钢卷且dataType!=1(说明是历史记录),直接返回无重复
|
||
if (coil != null && coil.getDataType() != 1) {
|
||
result.put("duplicateType", "none");
|
||
result.put("enterCoilNoDuplicate", false);
|
||
result.put("currentCoilNoDuplicate", false);
|
||
result.put("supplierCoilNoDuplicate", false);
|
||
return result; // 直接返回,不执行后续检查
|
||
}
|
||
}
|
||
|
||
boolean enterCoilNoDuplicate = false;
|
||
boolean currentCoilNoDuplicate = false;
|
||
boolean supplierCoilNoDuplicate = false;
|
||
|
||
// 检查入场钢卷号是否重复
|
||
if (StringUtils.isNotBlank(enterCoilNo)) {
|
||
LambdaQueryWrapper<WmsMaterialCoil> enterWrapper = Wrappers.lambdaQuery();
|
||
enterWrapper.eq(WmsMaterialCoil::getEnterCoilNo, enterCoilNo)
|
||
.eq(WmsMaterialCoil::getDelFlag, 0)
|
||
.eq(WmsMaterialCoil::getDataType, 1); // 过滤历史数据
|
||
// 如果是修改操作,排除自身
|
||
if (coilId != null) {
|
||
enterWrapper.ne(WmsMaterialCoil::getCoilId, coilId);
|
||
}
|
||
long enterCount = baseMapper.selectCount(enterWrapper);
|
||
enterCoilNoDuplicate = enterCount > 0;
|
||
}
|
||
|
||
// 检查当前钢卷号是否重复
|
||
if (StringUtils.isNotBlank(currentCoilNo)) {
|
||
LambdaQueryWrapper<WmsMaterialCoil> currentWrapper = Wrappers.lambdaQuery();
|
||
currentWrapper.eq(WmsMaterialCoil::getCurrentCoilNo, currentCoilNo)
|
||
.eq(WmsMaterialCoil::getDelFlag, 0)
|
||
.eq(WmsMaterialCoil::getDataType, 1);
|
||
// 如果是修改操作,排除自身
|
||
if (coilId != null) {
|
||
currentWrapper.ne(WmsMaterialCoil::getCoilId, coilId);
|
||
}
|
||
|
||
long currentCount = baseMapper.selectCount(currentWrapper);
|
||
currentCoilNoDuplicate = currentCount > 0;
|
||
}
|
||
|
||
// 检查厂家原料卷号是否重复
|
||
if (StringUtils.isNotBlank(supplierCoilNo)) {
|
||
LambdaQueryWrapper<WmsMaterialCoil> supplierWrapper = Wrappers.lambdaQuery();
|
||
supplierWrapper.eq(WmsMaterialCoil::getSupplierCoilNo, supplierCoilNo)
|
||
.eq(WmsMaterialCoil::getDelFlag, 0)
|
||
.eq(WmsMaterialCoil::getDataType, 1);
|
||
// 如果是修改操作,排除自身
|
||
if (coilId != null) {
|
||
supplierWrapper.ne(WmsMaterialCoil::getCoilId, coilId);
|
||
}
|
||
|
||
long supplierCount = baseMapper.selectCount(supplierWrapper);
|
||
supplierCoilNoDuplicate = supplierCount > 0;
|
||
}
|
||
|
||
// 判断重复类型
|
||
String duplicateType;
|
||
if (enterCoilNoDuplicate && currentCoilNoDuplicate && supplierCoilNoDuplicate) {
|
||
duplicateType = "both";
|
||
} else if (enterCoilNoDuplicate) {
|
||
duplicateType = "enter";
|
||
} else if (currentCoilNoDuplicate) {
|
||
duplicateType = "current";
|
||
} else if (supplierCoilNoDuplicate) {
|
||
duplicateType = "supplier";
|
||
} else {
|
||
duplicateType = "none";
|
||
}
|
||
|
||
result.put("duplicateType", duplicateType);
|
||
result.put("enterCoilNoDuplicate", enterCoilNoDuplicate);
|
||
result.put("currentCoilNoDuplicate", currentCoilNoDuplicate);
|
||
result.put("supplierCoilNoDuplicate", supplierCoilNoDuplicate);
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 根据入场钢卷号前缀查询最大的入场钢卷号
|
||
* 前端传入入场钢卷号的前四位,查询所有符合的入场钢卷号,返回数值上的最大值
|
||
*
|
||
* @param enterCoilNoPrefix 入场钢卷号前缀(建议为4位)
|
||
* @return 包含最大钢卷号和前缀的Map,key分别为maxEnterCoilNo、prefix
|
||
*/
|
||
@Override
|
||
public Map<String, Object> getMaxEnterCoilNoByPrefix(String enterCoilNoPrefix) {
|
||
Map<String, Object> result = new HashMap<>(2); // 初始化固定容量,提升性能
|
||
result.put("prefix", enterCoilNoPrefix); // 先赋值前缀,避免重复代码
|
||
|
||
// 1. 边界校验:前缀为空/空白字符串,直接返回null
|
||
if (StringUtils.isBlank(enterCoilNoPrefix)) {
|
||
log.warn("查询最大入场钢卷号失败:前缀为空");
|
||
result.put("maxEnterCoilNo", null);
|
||
return result;
|
||
}
|
||
|
||
// 2. 前缀长度校验(可选,根据业务要求,比如强制4位)
|
||
if (enterCoilNoPrefix.length() != 4) {
|
||
log.warn("查询最大入场钢卷号失败:前缀长度不符合要求(需4位),当前前缀:{}", enterCoilNoPrefix);
|
||
result.put("maxEnterCoilNo", null);
|
||
return result;
|
||
}
|
||
|
||
try {
|
||
// 3. 构建查询条件:匹配前缀 + 未删除
|
||
LambdaQueryWrapper<WmsMaterialCoil> wrapper = Wrappers.lambdaQuery();
|
||
wrapper.likeRight(WmsMaterialCoil::getEnterCoilNo, enterCoilNoPrefix)
|
||
.eq(WmsMaterialCoil::getDelFlag, 0)
|
||
.select(WmsMaterialCoil::getEnterCoilNo); // 仅查询需要的字段,提升性能
|
||
|
||
// 4. 查询所有匹配的钢卷记录
|
||
List<WmsMaterialCoil> coilList = baseMapper.selectList(wrapper);
|
||
log.info("根据前缀{}查询到匹配的钢卷记录数:{}", enterCoilNoPrefix, coilList.size());
|
||
|
||
// 5. 手动筛选数值最大的钢卷号(解决字符串排序问题)
|
||
String maxEnterCoilNo = null;
|
||
long maxNum = -1;
|
||
for (WmsMaterialCoil coil : coilList) {
|
||
String coilNo = coil.getEnterCoilNo();
|
||
// 跳过空值、非纯数字的钢卷号
|
||
if (StringUtils.isBlank(coilNo) || !coilNo.matches("^\\d+$")) {
|
||
log.debug("跳过无效钢卷号:{}(空值或非纯数字)", coilNo);
|
||
continue;
|
||
}
|
||
|
||
// 处理大数溢出风险(如果钢卷号超过Long范围,可改用BigDecimal)
|
||
long currentNum;
|
||
try {
|
||
currentNum = Long.parseLong(coilNo);
|
||
} catch (NumberFormatException e) {
|
||
log.warn("钢卷号{}转换为数字失败:{}", coilNo, e.getMessage());
|
||
continue;
|
||
}
|
||
|
||
// 更新最大值
|
||
if (currentNum > maxNum) {
|
||
maxNum = currentNum;
|
||
maxEnterCoilNo = coilNo;
|
||
}
|
||
}
|
||
|
||
// 6. 结果赋值 + 日志记录
|
||
result.put("maxEnterCoilNo", maxEnterCoilNo);
|
||
log.info("前缀{}对应的最大入场钢卷号:{}", enterCoilNoPrefix, maxEnterCoilNo == null ? "无匹配记录" : maxEnterCoilNo);
|
||
|
||
} catch (Exception e) {
|
||
// 7. 全局异常捕获,避免接口报错
|
||
log.error("查询最大入场钢卷号失败,前缀:{}", enterCoilNoPrefix, e);
|
||
result.put("maxEnterCoilNo", null);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 回滚钢卷操作
|
||
* 根据当前钢卷ID找到历史钢卷并恢复,删除当前钢卷
|
||
* 用于撤销单个更新、合卷或分卷操作
|
||
*
|
||
* @param currentCoilId 当前钢卷ID(需要回滚的钢卷)
|
||
* @return 操作结果,包含恢复的历史钢卷信息
|
||
*/
|
||
@Override
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public Map<String, Object> rollbackCoil(Long currentCoilId) {
|
||
Map<String, Object> result = new HashMap<>();
|
||
result.put("success", false);
|
||
|
||
try {
|
||
// 1. 查询当前钢卷信息
|
||
WmsMaterialCoil currentCoil = baseMapper.selectById(currentCoilId);
|
||
if (currentCoil == null) {
|
||
throw new RuntimeException("当前钢卷不存在");
|
||
}
|
||
|
||
// 检查独占状态
|
||
validateCoilOperationPermission(currentCoilId, "回滚");
|
||
|
||
// 2. 检查当前钢卷是否为当前数据(不能回滚历史数据)
|
||
if (currentCoil.getDataType() != 1) {
|
||
throw new RuntimeException("只能回滚当前数据,不能回滚历史数据");
|
||
}
|
||
|
||
if (!currentCoil.getDelFlag().equals(0)) {
|
||
throw new RuntimeException("当前钢卷已删除,无法回滚");
|
||
}
|
||
|
||
if (currentCoil.getStatus() == 1) {
|
||
throw new RuntimeException("只能回滚未发货数据,不能回滚已发货数据");
|
||
}
|
||
|
||
// 3. 检查是否有二维码记录
|
||
if (currentCoil.getQrcodeRecordId() == null) {
|
||
throw new RuntimeException("当前钢卷没有二维码记录,无法回滚");
|
||
}
|
||
|
||
// 4. 查询二维码记录并解析上一步钢卷ID
|
||
WmsGenerateRecordVo qrcodeRecord = generateRecordService.queryById(currentCoil.getQrcodeRecordId());
|
||
if (qrcodeRecord == null || StringUtils.isBlank(qrcodeRecord.getContent())) {
|
||
throw new RuntimeException("二维码记录不存在或内容为空,无法回滚");
|
||
}
|
||
|
||
// 5. 解析二维码内容,获取最后一步操作信息
|
||
// 直接从二维码解析最后一步操作,用于检查独占状态
|
||
Map<String, Object> lastStepInfo = getLastOperationStepFromQrcode(qrcodeRecord.getContent());
|
||
String lastOperation = (String) lastStepInfo.get("operation");
|
||
Long oldCoilIdFromLastStep = (Long) lastStepInfo.get("oldCoilId");
|
||
|
||
// 如果最后一步是分卷操作并且old_coil_id正在处于单步分卷则当前钢卷不能回滚
|
||
if ("分卷".equals(lastOperation) && oldCoilIdFromLastStep != null) {
|
||
WmsMaterialCoil motherCoil = baseMapper.selectById(oldCoilIdFromLastStep);
|
||
if (motherCoil != null && motherCoil.getExclusiveStatus() != null && motherCoil.getExclusiveStatus() == 1) {
|
||
throw new RuntimeException("母卷[" + motherCoil.getCurrentCoilNo() + "]正在进行分卷操作,当前钢卷无法回滚");
|
||
}
|
||
}
|
||
|
||
// 6. 解析二维码内容,判断是合卷、分卷还是普通更新
|
||
Map<String, Object> rollbackInfo = parseRollbackInfoFromQrcode(qrcodeRecord.getContent(), currentCoilId);
|
||
String operationType = (String) rollbackInfo.get("operationType");
|
||
|
||
// 7. 根据操作类型执行不同的回滚逻辑
|
||
if ("MERGE".equals(operationType)) {
|
||
// 合卷回滚
|
||
return rollbackMergeOperation(currentCoil, qrcodeRecord, rollbackInfo, result);
|
||
} else if ("SPLIT".equals(operationType)) {
|
||
// 分卷回滚
|
||
return rollbackSplitOperation(currentCoil, qrcodeRecord, rollbackInfo, result);
|
||
} else {
|
||
// 普通更新回滚(原有逻辑)
|
||
return rollbackNormalOperation(currentCoil, qrcodeRecord, result);
|
||
}
|
||
|
||
} catch (Exception e) {
|
||
log.error("钢卷回滚操作失败:currentCoilId={}", currentCoilId, e);
|
||
result.put("success", false);
|
||
result.put("message", "回滚操作失败:" + e.getMessage());
|
||
throw e;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从二维码内容中获取最后一步操作信息(用于检查独占状态)
|
||
* 只返回最后一步的操作类型和old_coil_id,不进行复杂的回滚逻辑判断
|
||
*/
|
||
private Map<String, Object> getLastOperationStepFromQrcode(String content) {
|
||
Map<String, Object> result = new HashMap<>();
|
||
try {
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
Map<String, Object> contentMap = objectMapper.readValue(content, Map.class);
|
||
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||
if (steps == null || steps.isEmpty()) {
|
||
return result;
|
||
}
|
||
|
||
// 从后往前找到最后一个非回滚的步骤
|
||
for (int i = steps.size() - 1; i >= 0; i--) {
|
||
Map<String, Object> step = steps.get(i);
|
||
String action = (String) step.get("action");
|
||
|
||
// 跳过回滚操作
|
||
if ("回滚".equals(action)) {
|
||
continue;
|
||
}
|
||
|
||
// 记录最后一步的操作类型
|
||
result.put("operation", step.get("operation"));
|
||
result.put("action", action);
|
||
|
||
// 获取old_coil_id
|
||
Object oldCoilIdObj = step.get("old_coil_id");
|
||
if (oldCoilIdObj != null) {
|
||
try {
|
||
result.put("oldCoilId", Long.parseLong(oldCoilIdObj.toString()));
|
||
} catch (NumberFormatException ignored) {
|
||
}
|
||
}
|
||
|
||
// 获取child_coil_ids(用于分卷判断)
|
||
Object childCoilIdsObj = step.get("child_coil_ids");
|
||
if (childCoilIdsObj != null) {
|
||
result.put("childCoilIds", childCoilIdsObj.toString());
|
||
}
|
||
|
||
// 获取parent_coil_ids(用于合卷判断)
|
||
Object parentCoilIdsObj = step.get("parent_coil_ids");
|
||
if (parentCoilIdsObj != null) {
|
||
result.put("parentCoilIds", parentCoilIdsObj.toString());
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
return result;
|
||
|
||
} catch (Exception e) {
|
||
log.error("解析二维码最后一步操作失败", e);
|
||
return result;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 解析二维码内容,获取回滚所需的信息
|
||
* @return 包含operationType和相应参数的Map
|
||
*/
|
||
private Map<String, Object> parseRollbackInfoFromQrcode(String content, Long currentCoilId) {
|
||
Map<String, Object> result = new HashMap<>();
|
||
try {
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
Map<String, Object> contentMap = objectMapper.readValue(content, Map.class);
|
||
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||
if (steps == null || steps.isEmpty()) {
|
||
result.put("operationType", "NORMAL");
|
||
return result;
|
||
}
|
||
|
||
// 从后往前查找最新的操作
|
||
for (int i = steps.size() - 1; i >= 0; i--) {
|
||
Map<String, Object> step = steps.get(i);
|
||
String action = (String) step.get("action");
|
||
String operation = (String) step.get("operation");
|
||
|
||
// 跳过新增和回滚操作
|
||
if ("新增".equals(action) || "回滚".equals(action)) {
|
||
continue;
|
||
}
|
||
|
||
// 检查是否是合卷操作
|
||
if ("合卷".equals(operation)) {
|
||
Object parentCoilIdsObj = step.get("parent_coil_ids");
|
||
if (parentCoilIdsObj != null) {
|
||
String parentCoilIdsStr = parentCoilIdsObj.toString();
|
||
if (StringUtils.isNotBlank(parentCoilIdsStr)) {
|
||
List<Long> parentCoilIds = Arrays.stream(parentCoilIdsStr.split(","))
|
||
.map(String::trim)
|
||
.filter(s -> StringUtils.isNotBlank(s))
|
||
.map(Long::parseLong)
|
||
.collect(Collectors.toList());
|
||
result.put("operationType", "MERGE");
|
||
result.put("parentCoilIds", parentCoilIds);
|
||
result.put("targetStep", step);
|
||
return result;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检查是否是分卷操作
|
||
if ("分卷".equals(operation)) {
|
||
Object childCoilIdsObj = step.get("child_coil_ids");
|
||
if (childCoilIdsObj != null) {
|
||
String childCoilIdsStr = childCoilIdsObj.toString();
|
||
if (StringUtils.isNotBlank(childCoilIdsStr)) {
|
||
List<Long> childCoilIds = Arrays.stream(childCoilIdsStr.split(","))
|
||
.map(String::trim)
|
||
.filter(s -> StringUtils.isNotBlank(s))
|
||
.map(Long::parseLong)
|
||
.collect(Collectors.toList());
|
||
result.put("operationType", "SPLIT");
|
||
result.put("childCoilIds", childCoilIds);
|
||
result.put("targetStep", step);
|
||
return result;
|
||
}
|
||
}
|
||
result.put("operationType", "SPLIT");
|
||
return result;
|
||
}
|
||
// 这里不写更新是因为还有退货操作和退火操作,但是也和更新一样可以回滚所以直接找new_coil_id即可
|
||
// 如果找到普通更新操作
|
||
Object newCoilIdObj = step.get("new_coil_id");
|
||
if (newCoilIdObj != null && newCoilIdObj.toString().equals(currentCoilId.toString())) {
|
||
Object oldCoilIdObj = step.get("old_coil_id");
|
||
if (oldCoilIdObj != null) {
|
||
result.put("operationType", "NORMAL");
|
||
result.put("historyCoilId", Long.parseLong(oldCoilIdObj.toString()));
|
||
return result;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果没找到任何操作,返回普通类型
|
||
result.put("operationType", "NORMAL");
|
||
return result;
|
||
|
||
} catch (Exception e) {
|
||
log.error("解析二维码内容失败", e);
|
||
result.put("operationType", "NORMAL");
|
||
return result;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 回滚合卷操作
|
||
* 获取所有被合并的钢卷ID,将这些钢卷还原(dataType改为1),并删除当前合卷的钢卷
|
||
*/
|
||
private Map<String, Object> rollbackMergeOperation(WmsMaterialCoil currentCoil, WmsGenerateRecordVo qrcodeRecord,
|
||
Map<String, Object> rollbackInfo, Map<String, Object> result) {
|
||
List<Long> parentCoilIds = (List<Long>) rollbackInfo.get("parentCoilIds");
|
||
|
||
if (parentCoilIds == null || parentCoilIds.isEmpty()) {
|
||
throw new RuntimeException("无法获取合卷的原始钢卷ID,无法回滚");
|
||
}
|
||
|
||
// 查询所有原始钢卷
|
||
List<WmsMaterialCoil> originalCoils = baseMapper.selectBatchIds(parentCoilIds);
|
||
|
||
// 检查所有原始钢卷是否存在
|
||
if (originalCoils.size() != parentCoilIds.size()) {
|
||
throw new RuntimeException("部分原始钢卷不存在,无法回滚");
|
||
}
|
||
|
||
// // 检查恢复的钢卷的当前钢卷号是否重复
|
||
// for (WmsMaterialCoil originalCoil : originalCoils) {
|
||
// Map<String, Object> duplicateCheck = checkCoilNoDuplicate(originalCoil.getCoilId(), originalCoil.getEnterCoilNo(), originalCoil.getCurrentCoilNo());
|
||
// boolean currentCoilNoDuplicate = (boolean) duplicateCheck.get("currentCoilNoDuplicate");
|
||
// if (currentCoilNoDuplicate) {
|
||
// String errorMsg = "无法恢复原始钢卷,存在重复的钢卷号:原始钢卷的当前钢卷号[" + originalCoil.getCurrentCoilNo() + "]重复。";
|
||
// errorMsg += "重复的钢卷无法进行回滚操作。";
|
||
// throw new RuntimeException(errorMsg);
|
||
// }
|
||
// }
|
||
|
||
// 释放当前合卷钢卷占用的实际库区
|
||
if (currentCoil.getActualWarehouseId() != null) {
|
||
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
|
||
releaseBo.setActualWarehouseId(currentCoil.getActualWarehouseId());
|
||
releaseBo.setIsEnabled(1);
|
||
actualWarehouseService.updateByBo(releaseBo);
|
||
}
|
||
|
||
// 将被删除的合卷钢卷的二维码设置为失效
|
||
if (currentCoil.getQrcodeRecordId() != null) {
|
||
WmsGenerateRecordBo deleteQrBo = new WmsGenerateRecordBo();
|
||
deleteQrBo.setRecordId(currentCoil.getQrcodeRecordId());
|
||
deleteQrBo.setStatus(0); // 0=失效
|
||
generateRecordService.updateByBo(deleteQrBo);
|
||
}
|
||
|
||
// 删除当前合卷钢卷
|
||
baseMapper.deleteById(currentCoil.getCoilId());
|
||
|
||
// 将所有原始钢卷恢复为当前数据,实际库区置空
|
||
for (WmsMaterialCoil originalCoil : originalCoils) {
|
||
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
|
||
updateWrapper.eq(WmsMaterialCoil::getCoilId, originalCoil.getCoilId())
|
||
.set(WmsMaterialCoil::getDataType, 1)
|
||
.set(WmsMaterialCoil::getActualWarehouseId, (Long) null);
|
||
baseMapper.update(null, updateWrapper);
|
||
|
||
// 删除操作记录
|
||
deletePendingActionForRollback(originalCoil.getCoilId());
|
||
|
||
// 恢复原始钢卷的二维码为生效状态并更新二维码记录
|
||
if (originalCoil.getQrcodeRecordId() != null) {
|
||
WmsGenerateRecordBo originalQrBo = new WmsGenerateRecordBo();
|
||
originalQrBo.setRecordId(originalCoil.getQrcodeRecordId());
|
||
originalQrBo.setStatus(1); // 1=生效
|
||
generateRecordService.updateByBo(originalQrBo);
|
||
|
||
// 更新原始钢卷的二维码记录(添加回滚步骤)
|
||
WmsGenerateRecordVo originalQrRecord = generateRecordService.queryById(originalCoil.getQrcodeRecordId());
|
||
if (originalQrRecord != null) {
|
||
updateQrcodeForMergeRollback(originalQrRecord, currentCoil, originalCoil.getCoilId());
|
||
}
|
||
}
|
||
}
|
||
|
||
result.put("success", true);
|
||
result.put("message", "合卷回滚成功,已恢复 " + parentCoilIds.size() + " 个原始钢卷,删除了合卷钢卷");
|
||
result.put("restoredCoilCount", parentCoilIds.size());
|
||
result.put("deletedCoilId", currentCoil.getCoilId());
|
||
|
||
log.info("合卷回滚操作成功:删除的合卷钢卷ID={}, 恢复的原始钢卷ID={}", currentCoil.getCoilId(), parentCoilIds);
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 回滚分卷操作
|
||
* 删除所有母卷生成的子钢卷,恢复母卷为当前数据
|
||
*/
|
||
private Map<String, Object> rollbackSplitOperation(WmsMaterialCoil currentCoil, WmsGenerateRecordVo qrcodeRecord,
|
||
Map<String, Object> rollbackInfo, Map<String, Object> result) {
|
||
List<Long> childCoilIds = (List<Long>) rollbackInfo.get("childCoilIds");
|
||
|
||
if (childCoilIds == null || childCoilIds.isEmpty()) {
|
||
throw new RuntimeException("无法获取分卷的子钢卷ID,无法回滚");
|
||
}
|
||
|
||
// 检查当前钢卷ID是否在childCoilIds里面,如果不在说明不是同一步骤则不能回滚
|
||
if (!childCoilIds.contains(currentCoil.getCoilId())) {
|
||
throw new RuntimeException("无可回滚的母卷");
|
||
}
|
||
|
||
// 查询所有子钢卷
|
||
List<WmsMaterialCoil> childCoils = baseMapper.selectBatchIds(childCoilIds);
|
||
|
||
// 检查所有子钢卷是否存在
|
||
if (childCoils.size() != childCoilIds.size()) {
|
||
throw new RuntimeException("部分子钢卷不存在,无法回滚");
|
||
}
|
||
|
||
// 找到母卷
|
||
Map<String, Object> targetStep = (Map<String, Object>) rollbackInfo.get("targetStep");
|
||
Object oldCoilIdObj = targetStep.get("old_coil_id");
|
||
Long motherCoilId = null;
|
||
if (oldCoilIdObj != null) {
|
||
motherCoilId = Long.parseLong(oldCoilIdObj.toString());
|
||
}
|
||
|
||
// 检查母卷的独占状态
|
||
if (motherCoilId != null) {
|
||
WmsMaterialCoil motherCoil = baseMapper.selectById(motherCoilId);
|
||
if (motherCoil != null && motherCoil.getExclusiveStatus() != null && motherCoil.getExclusiveStatus() == 1) {
|
||
throw new RuntimeException("母卷[" + motherCoil.getCurrentCoilNo() + "]正在进行单步分卷操作,无法回滚");
|
||
}
|
||
}
|
||
|
||
// 检查是否有子钢卷的dataType不等于1(不是现存的钢卷)
|
||
List<String> invalidCoilNos = new ArrayList<>();
|
||
for (WmsMaterialCoil childCoil : childCoils) {
|
||
if (childCoil.getDataType() != 1) {
|
||
invalidCoilNos.add(childCoil.getCurrentCoilNo() + "(历史钢卷)");
|
||
}
|
||
// 检查子钢卷的独占状态
|
||
if (childCoil.getExclusiveStatus() != null && childCoil.getExclusiveStatus() == 1) {
|
||
invalidCoilNos.add(childCoil.getCurrentCoilNo() + "(正在单步分卷)");
|
||
}
|
||
}
|
||
|
||
if (!invalidCoilNos.isEmpty()) {
|
||
throw new RuntimeException("以下钢卷不是现存钢卷,无法回滚:" + String.join(", ", invalidCoilNos));
|
||
}
|
||
|
||
// // 检查恢复的母卷的当前钢卷号是否重复
|
||
// if (motherCoilId != null) {
|
||
// WmsMaterialCoil motherCoil = baseMapper.selectById(motherCoilId);
|
||
// if (motherCoil != null) {
|
||
// Map<String, Object> duplicateCheck = checkCoilNoDuplicate(motherCoilId, motherCoil.getEnterCoilNo(), motherCoil.getCurrentCoilNo());
|
||
// boolean currentCoilNoDuplicate = (boolean) duplicateCheck.get("currentCoilNoDuplicate");
|
||
// if (currentCoilNoDuplicate) {
|
||
// String errorMsg = "无法恢复母卷,存在重复的钢卷号:母卷的当前钢卷号[" + motherCoil.getCurrentCoilNo() + "]重复。";
|
||
// errorMsg += "重复的钢卷无法进行回滚操作。";
|
||
// throw new RuntimeException(errorMsg);
|
||
// }
|
||
// }
|
||
// }
|
||
|
||
// 释放所有子钢卷占用的实际库区并删除所有子钢卷
|
||
for (WmsMaterialCoil childCoil : childCoils) {
|
||
if (childCoil.getActualWarehouseId() != null) {
|
||
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
|
||
releaseBo.setActualWarehouseId(childCoil.getActualWarehouseId());
|
||
releaseBo.setIsEnabled(1);
|
||
actualWarehouseService.updateByBo(releaseBo);
|
||
}
|
||
// 将被删除的子钢卷的二维码设置为失效
|
||
if (childCoil.getQrcodeRecordId() != null) {
|
||
WmsGenerateRecordBo deleteQrBo = new WmsGenerateRecordBo();
|
||
deleteQrBo.setRecordId(childCoil.getQrcodeRecordId());
|
||
deleteQrBo.setStatus(0); // 0=失效
|
||
generateRecordService.updateByBo(deleteQrBo);
|
||
}
|
||
baseMapper.deleteById(childCoil.getCoilId());
|
||
}
|
||
|
||
if (motherCoilId != null) {
|
||
// 恢复母卷为当前数据,实际库区置空
|
||
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
|
||
updateWrapper.eq(WmsMaterialCoil::getCoilId, motherCoilId)
|
||
.set(WmsMaterialCoil::getDataType, 1)
|
||
.set(WmsMaterialCoil::getActualWarehouseId, (Long) null);
|
||
baseMapper.update(null, updateWrapper);
|
||
|
||
// 删除母卷的操作记录
|
||
deletePendingActionForRollback(motherCoilId);
|
||
|
||
// 恢复母卷的二维码为生效状态
|
||
WmsMaterialCoil motherCoil = baseMapper.selectById(motherCoilId);
|
||
if (motherCoil != null && motherCoil.getQrcodeRecordId() != null) {
|
||
WmsGenerateRecordBo motherQrBo = new WmsGenerateRecordBo();
|
||
motherQrBo.setRecordId(motherCoil.getQrcodeRecordId());
|
||
motherQrBo.setStatus(1); // 1=生效
|
||
generateRecordService.updateByBo(motherQrBo);
|
||
|
||
// 更新母卷的二维码记录(添加回滚步骤)
|
||
WmsGenerateRecordVo motherQrRecord = generateRecordService.queryById(motherCoil.getQrcodeRecordId());
|
||
if (motherQrRecord != null) {
|
||
updateQrcodeForSplitRollback(motherQrRecord, currentCoil, motherCoilId);
|
||
}
|
||
}
|
||
}
|
||
|
||
result.put("success", true);
|
||
result.put("message", "分卷回滚成功,已恢复母卷,删除了 " + childCoilIds.size() + " 个子钢卷");
|
||
result.put("deletedCoilIds", childCoilIds);
|
||
result.put("restoredCoilId", motherCoilId);
|
||
|
||
log.info("分卷回滚操作成功:删除的子卷ID={}, 恢复的母卷ID={}", childCoilIds, motherCoilId);
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 删除回滚操作记录
|
||
* 删除最晚的一条且actionType不在401-405范围内,并且coilId等于指定ID的操作记录
|
||
*/
|
||
private void deletePendingActionForRollback(Long coilId) {
|
||
LambdaQueryWrapper<WmsCoilPendingAction> queryWrapper = new LambdaQueryWrapper<>();
|
||
queryWrapper.eq(WmsCoilPendingAction::getCoilId, coilId)
|
||
.notIn(WmsCoilPendingAction::getActionType, Arrays.asList(401, 402, 403, 404, 405))
|
||
.eq(WmsCoilPendingAction::getDelFlag, 0)
|
||
.eq(WmsCoilPendingAction::getActionStatus, 2)
|
||
.orderByDesc(WmsCoilPendingAction::getCreateTime)
|
||
.last("LIMIT 1");
|
||
|
||
WmsCoilPendingAction latestAction = coilPendingActionMapper.selectOne(queryWrapper);
|
||
if (latestAction != null) {
|
||
coilPendingActionMapper.deleteById(latestAction.getActionId());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 回滚普通更新操作(原有逻辑)
|
||
*/
|
||
private Map<String, Object> rollbackNormalOperation(WmsMaterialCoil currentCoil, WmsGenerateRecordVo qrcodeRecord, Map<String, Object> result) {
|
||
// 解析二维码内容,找到上一步的历史钢卷ID
|
||
Long historyCoilId = parseHistoryCoilIdFromQrcode(qrcodeRecord.getContent(), currentCoil.getCoilId());
|
||
if (historyCoilId == null) {
|
||
// 检查无法回滚的具体原因
|
||
String rollbackBlockReason = getRollbackBlockReason(qrcodeRecord.getContent(), currentCoil.getCoilId());
|
||
switch (rollbackBlockReason) {
|
||
case "INITIAL_CREATE":
|
||
throw new RuntimeException("无法继续回滚:当前钢卷为最初的新增操作创建,如需删除请直接使用删除功能");
|
||
default:
|
||
throw new RuntimeException("无法从二维码记录中找到历史钢卷ID,无法回滚");
|
||
}
|
||
}
|
||
|
||
// 查询历史钢卷信息
|
||
WmsMaterialCoil historyCoil = baseMapper.selectById(historyCoilId);
|
||
if (historyCoil == null) {
|
||
throw new RuntimeException("历史钢卷不存在,无法回滚");
|
||
}
|
||
|
||
// 检查历史钢卷是否为历史数据
|
||
if (historyCoil.getDataType() != 0) {
|
||
throw new RuntimeException("目标钢卷不是历史数据,无法恢复");
|
||
}
|
||
|
||
// // 检查历史钢卷的当前钢卷号是否重复
|
||
// Map<String, Object> duplicateCheck = checkCoilNoDuplicate(currentCoil.getCoilId(), historyCoil.getEnterCoilNo(), historyCoil.getCurrentCoilNo());
|
||
// boolean currentCoilNoDuplicate = (boolean) duplicateCheck.get("currentCoilNoDuplicate");
|
||
// if (currentCoilNoDuplicate) {
|
||
// String errorMsg = "无法恢复历史钢卷,存在重复的钢卷号:历史钢卷的当前钢卷号[" + historyCoil.getCurrentCoilNo() + "]重复。";
|
||
// errorMsg += "重复的钢卷无法进行回滚操作。";
|
||
// throw new RuntimeException(errorMsg);
|
||
// }
|
||
|
||
// 执行回滚操作
|
||
// 1. 释放当前钢卷占用的实际库区
|
||
if (currentCoil.getActualWarehouseId() != null) {
|
||
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
|
||
releaseBo.setActualWarehouseId(currentCoil.getActualWarehouseId());
|
||
releaseBo.setIsEnabled(1);
|
||
actualWarehouseService.updateByBo(releaseBo);
|
||
}
|
||
|
||
// 2. 将被删除的当前钢卷的二维码设置为失效
|
||
if (currentCoil.getQrcodeRecordId() != null) {
|
||
WmsGenerateRecordBo deleteQrBo = new WmsGenerateRecordBo();
|
||
deleteQrBo.setRecordId(currentCoil.getQrcodeRecordId());
|
||
deleteQrBo.setStatus(0); // 0=失效
|
||
generateRecordService.updateByBo(deleteQrBo);
|
||
}
|
||
|
||
// 3. 删除当前钢卷
|
||
baseMapper.deleteById(currentCoil.getCoilId());
|
||
|
||
// 3. 删除操作记录
|
||
LambdaQueryWrapper<WmsCoilPendingAction> queryWrapper = new LambdaQueryWrapper<>();
|
||
queryWrapper.eq(WmsCoilPendingAction::getCoilId, historyCoilId)
|
||
.notIn(WmsCoilPendingAction::getActionType, Arrays.asList(401, 402, 403, 404, 405))
|
||
.eq(WmsCoilPendingAction::getDelFlag, 0)
|
||
.eq(WmsCoilPendingAction::getActionStatus, 2)
|
||
.orderByDesc(WmsCoilPendingAction::getCreateTime)
|
||
.last("LIMIT 1");
|
||
|
||
WmsCoilPendingAction latestAction = coilPendingActionMapper.selectOne(queryWrapper);
|
||
if (latestAction != null) {
|
||
coilPendingActionMapper.deleteById(latestAction.getActionId());
|
||
}
|
||
|
||
// 4. 恢复历史钢卷为当前数据
|
||
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
|
||
updateWrapper.eq(WmsMaterialCoil::getCoilId, historyCoilId)
|
||
.set(WmsMaterialCoil::getDataType, 1)
|
||
.set(WmsMaterialCoil::getActualWarehouseId, (Long) null);
|
||
baseMapper.update(null, updateWrapper);
|
||
|
||
// 5. 更新二维码记录
|
||
updateQrcodeForRollback(qrcodeRecord, currentCoil, historyCoil);
|
||
|
||
// 6. 重新查询恢复后的历史钢卷信息
|
||
WmsMaterialCoilVo restoredCoil = queryById(historyCoilId);
|
||
|
||
// 7. 返回操作结果
|
||
result.put("success", true);
|
||
result.put("restoredCoil", restoredCoil);
|
||
result.put("deletedCoilId", currentCoil.getCoilId());
|
||
result.put("message", "回滚操作成功,已恢复历史钢卷,删除了当前钢卷");
|
||
|
||
log.info("钢卷回滚操作成功:历史钢卷ID={}, 删除的钢卷ID={}", historyCoilId, currentCoil.getCoilId());
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 更新合卷回滚后的二维码记录
|
||
*/
|
||
private void updateQrcodeForMergeRollback(WmsGenerateRecordVo qrcodeRecord, WmsMaterialCoil currentCoil, Long restoredCoilId) {
|
||
try {
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
Map<String, Object> contentMap = objectMapper.readValue(qrcodeRecord.getContent(), Map.class);
|
||
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||
if (steps == null) {
|
||
steps = new ArrayList<>();
|
||
}
|
||
|
||
// 添加回滚步骤
|
||
Map<String, Object> rollbackStep = new HashMap<>();
|
||
rollbackStep.put("step", steps.size() + 1);
|
||
rollbackStep.put("action", "回滚");
|
||
rollbackStep.put("operation", "合卷回滚");
|
||
rollbackStep.put("deleted_coil_id", String.valueOf(currentCoil.getCoilId()));
|
||
rollbackStep.put("deleted_current_coil_no", currentCoil.getCurrentCoilNo());
|
||
rollbackStep.put("restored_coil_id", String.valueOf(restoredCoilId));
|
||
rollbackStep.put("operator", LoginHelper.getUsername());
|
||
rollbackStep.put("rollback_time", new java.util.Date());
|
||
|
||
steps.add(rollbackStep);
|
||
contentMap.put("steps", steps);
|
||
|
||
// 更新current_coil_id为恢复的原始钢卷ID
|
||
contentMap.put("current_coil_id", String.valueOf(restoredCoilId));
|
||
|
||
String newContentJson = objectMapper.writeValueAsString(contentMap);
|
||
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
|
||
updateBo.setRecordId(qrcodeRecord.getRecordId());
|
||
updateBo.setContent(newContentJson);
|
||
generateRecordService.updateByBo(updateBo);
|
||
|
||
} catch (Exception e) {
|
||
log.error("更新合卷回滚二维码记录失败", e);
|
||
throw new RuntimeException("更新二维码记录失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新分卷回滚后的二维码记录
|
||
*/
|
||
private void updateQrcodeForSplitRollback(WmsGenerateRecordVo qrcodeRecord, WmsMaterialCoil currentCoil, Long motherCoilId) {
|
||
try {
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
Map<String, Object> contentMap = objectMapper.readValue(qrcodeRecord.getContent(), Map.class);
|
||
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||
if (steps == null) {
|
||
steps = new ArrayList<>();
|
||
}
|
||
|
||
// 添加回滚步骤
|
||
Map<String, Object> rollbackStep = new HashMap<>();
|
||
rollbackStep.put("step", steps.size() + 1);
|
||
rollbackStep.put("action", "回滚");
|
||
rollbackStep.put("operation", "分卷回滚");
|
||
rollbackStep.put("deleted_coil_id", String.valueOf(currentCoil.getCoilId()));
|
||
rollbackStep.put("deleted_current_coil_no", currentCoil.getCurrentCoilNo());
|
||
rollbackStep.put("restored_coil_id", String.valueOf(motherCoilId));
|
||
rollbackStep.put("operator", LoginHelper.getUsername());
|
||
rollbackStep.put("rollback_time", new java.util.Date());
|
||
|
||
steps.add(rollbackStep);
|
||
contentMap.put("steps", steps);
|
||
|
||
// 更新current_coil_id为恢复的母卷ID
|
||
contentMap.put("current_coil_id", String.valueOf(motherCoilId));
|
||
|
||
String newContentJson = objectMapper.writeValueAsString(contentMap);
|
||
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
|
||
updateBo.setRecordId(qrcodeRecord.getRecordId());
|
||
updateBo.setContent(newContentJson);
|
||
generateRecordService.updateByBo(updateBo);
|
||
|
||
} catch (Exception e) {
|
||
log.error("更新分卷回滚二维码记录失败", e);
|
||
throw new RuntimeException("更新二维码记录失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从二维码内容中解析历史钢卷ID
|
||
* 根据当前钢卷ID找到对应的上一步操作中的old_coil_id
|
||
*/
|
||
private Long parseHistoryCoilIdFromQrcode(String content, Long currentCoilId) {
|
||
try {
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
Map<String, Object> contentMap = objectMapper.readValue(content, Map.class);
|
||
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||
if (steps == null || steps.isEmpty()) {
|
||
return null;
|
||
}
|
||
|
||
String currentCoilIdStr = currentCoilId.toString();
|
||
|
||
// 从后往前查找,找到当前钢卷ID对应的上一步操作
|
||
for (int i = steps.size() - 1; i >= 0; i--) {
|
||
Map<String, Object> step = steps.get(i);
|
||
String action = (String) step.get("action");
|
||
|
||
// 如果是新增操作,说明已经回滚到最初状态
|
||
if ("新增".equals(action)) {
|
||
return null; // 无法继续回滚
|
||
}
|
||
|
||
// 如果是回滚操作,跳过(回滚步骤不应该影响历史查找)
|
||
if ("回滚".equals(action)) {
|
||
continue;
|
||
}
|
||
|
||
// 检查这个步骤是否产生了当前钢卷
|
||
Object newCoilIdObj = step.get("new_coil_id");
|
||
if (newCoilIdObj != null) {
|
||
String newCoilIdStr = newCoilIdObj.toString();
|
||
if (newCoilIdStr.equals(currentCoilIdStr)) {
|
||
// 找到了产生当前钢卷的步骤,获取old_coil_id
|
||
Object oldCoilIdObj = step.get("old_coil_id");
|
||
if (oldCoilIdObj != null) {
|
||
String oldCoilIdStr = oldCoilIdObj.toString();
|
||
if (!"null".equals(oldCoilIdStr) && !oldCoilIdStr.isEmpty()) {
|
||
try {
|
||
return Long.parseLong(oldCoilIdStr);
|
||
} catch (NumberFormatException e) {
|
||
log.warn("解析old_coil_id失败:{}", oldCoilIdStr);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 无论是分卷还是合卷 当前逻辑应该是可以继续的 此处先进行跳过 只回滚当前工序的
|
||
// 对于合卷操作,检查parent_coil_ids
|
||
if ("合卷".equals(step.get("operation"))) {
|
||
// 当前钢卷是合卷的产物,返回null表示无法继续回滚
|
||
return null;
|
||
}
|
||
|
||
|
||
// 对于分卷操作,检查new_current_coil_nos是否包含多个钢卷号
|
||
if ("分卷".equals(step.get("operation"))) {
|
||
// 当前钢卷是分卷操作产生的子钢卷,无法回滚
|
||
// 分卷操作会产生多个钢卷,回滚其中一个会导致其他钢卷异常
|
||
return null;
|
||
}
|
||
}
|
||
|
||
} catch (Exception e) {
|
||
log.error("解析二维码内容失败", e);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 获取无法回滚的具体原因
|
||
* @return "INITIAL_CREATE" - 初始新增操作
|
||
* "MERGE_PRODUCT" - 合卷产物
|
||
* "SPLIT_PRODUCT" - 分卷产物
|
||
* "UNKNOWN" - 其他未知原因
|
||
*/
|
||
private String getRollbackBlockReason(String content, Long currentCoilId) {
|
||
try {
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
Map<String, Object> contentMap = objectMapper.readValue(content, Map.class);
|
||
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||
if (steps == null || steps.isEmpty()) {
|
||
return "UNKNOWN";
|
||
}
|
||
|
||
String currentCoilIdStr = currentCoilId.toString();
|
||
boolean isInitialCreate = false; // 标记是否为初始新增
|
||
|
||
// 倒序遍历所有步骤,整合所有判断逻辑
|
||
for (int i = steps.size() - 1; i >= 0; i--) {
|
||
Map<String, Object> step = steps.get(i);
|
||
String operation = (String) step.get("operation");
|
||
String action = (String) step.get("action");
|
||
|
||
// 1. 优先判断初始新增(仅第一个步骤可能是新增)
|
||
if (i == 0 && "新增".equals(action)) {
|
||
Object coilIdObj = contentMap.get("coil_id");
|
||
Object currentCoilIdObj = contentMap.get("current_coil_id");
|
||
|
||
if (coilIdObj != null && currentCoilIdObj != null) {
|
||
String coilIdStr = coilIdObj.toString();
|
||
String currentCoilIdStrInContent = currentCoilIdObj.toString();
|
||
|
||
if (coilIdStr.equals(currentCoilIdStrInContent) && coilIdStr.equals(currentCoilIdStr)) {
|
||
isInitialCreate = true;
|
||
break; // 确认是初始新增,直接退出循环
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2. 检查合卷操作(最新操作优先)
|
||
if ("合卷".equals(operation)) {
|
||
return "MERGE_PRODUCT";
|
||
}
|
||
|
||
// 3. 检查分卷操作
|
||
if ("分卷".equals(operation)) {
|
||
return "SPLIT_PRODUCT";
|
||
}
|
||
}
|
||
|
||
// 最终判断:如果是初始新增则返回,否则返回UNKNOWN
|
||
return isInitialCreate ? "INITIAL_CREATE" : "UNKNOWN";
|
||
} catch (Exception e) {
|
||
log.error("检查回滚阻止原因失败, currentCoilId:{}", currentCoilId, e);
|
||
return "UNKNOWN";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新分卷子钢卷的二维码记录中的child_coil_ids字段
|
||
* 将child_coil_ids设置到对应的分卷步骤中,而不是最外层
|
||
* 支持多次分卷,每次的child_coil_ids都会记录在各自的分卷步骤中
|
||
*
|
||
* @param qrcodeRecordId 二维码记录ID
|
||
* @param currentCoilId 当前子钢卷ID
|
||
* @param childCoilIdsStr 本次分卷产生的所有子钢卷ID的逗号分隔字符串
|
||
*/
|
||
private void updateChildCoilIdsQrcodeCoilId(Long oldCoilId, Long qrcodeRecordId, Long currentCoilId, String childCoilIdsStr) {
|
||
log.info("开始更新子钢卷二维码的分卷步骤child_coil_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<String, Object> contentMap = objectMapper.readValue(record.getContent(), Map.class);
|
||
|
||
// 获取steps数组
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||
if (steps == null || steps.isEmpty()) {
|
||
log.warn("二维码记录中没有steps信息,跳过child_coil_ids更新,qrcodeRecordId: {}", qrcodeRecordId);
|
||
return;
|
||
}
|
||
|
||
// 找到最后一个分卷步骤(最新的分卷操作)
|
||
Map<String, Object> lastSplitStep = null;
|
||
for (int i = steps.size() - 1; i >= 0; i--) {
|
||
Map<String, Object> step = steps.get(i);
|
||
String operation = (String) step.get("operation");
|
||
Long parentCoilId = null;
|
||
Object oldCoilIdObj = step.get("old_coil_id");
|
||
if (oldCoilIdObj != null) {
|
||
parentCoilId = Long.valueOf(oldCoilIdObj.toString());
|
||
}
|
||
if ("分卷".equals(operation) && oldCoilId.equals(parentCoilId)) {
|
||
lastSplitStep = step;
|
||
break; // 找到最新的分卷步骤就停止
|
||
}
|
||
}
|
||
|
||
if (lastSplitStep == null) {
|
||
log.warn("未找到分卷步骤,无法设置child_coil_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<String, Object> contentMap = objectMapper.readValue(qrcodeRecord.getContent(), Map.class);
|
||
|
||
// 获取现有steps
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||
if (steps == null) {
|
||
steps = new ArrayList<>();
|
||
}
|
||
|
||
// 添加回滚步骤
|
||
Map<String, Object> rollbackStep = new HashMap<>();
|
||
rollbackStep.put("step", steps.size() + 1);
|
||
rollbackStep.put("action", "回滚");
|
||
rollbackStep.put("operation", "撤销操作");
|
||
rollbackStep.put("deleted_coil_id", String.valueOf(currentCoil.getCoilId()));
|
||
rollbackStep.put("restored_coil_id", String.valueOf(historyCoil.getCoilId()));
|
||
rollbackStep.put("deleted_current_coil_no", currentCoil.getCurrentCoilNo());
|
||
rollbackStep.put("restored_current_coil_no", historyCoil.getCurrentCoilNo());
|
||
rollbackStep.put("operator", LoginHelper.getUsername());
|
||
rollbackStep.put("rollback_time", new java.util.Date());
|
||
|
||
steps.add(rollbackStep);
|
||
contentMap.put("steps", steps);
|
||
|
||
// 更新二维码中的当前钢卷信息指向恢复的历史钢卷
|
||
contentMap.put("current_coil_id", String.valueOf(historyCoil.getCoilId()));
|
||
contentMap.put("current_coil_no", historyCoil.getCurrentCoilNo());
|
||
// enter_coil_no 保持不变
|
||
|
||
// 更新二维码记录
|
||
String newContentJson = objectMapper.writeValueAsString(contentMap);
|
||
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
|
||
updateBo.setRecordId(qrcodeRecord.getRecordId());
|
||
updateBo.setContent(newContentJson);
|
||
generateRecordService.updateByBo(updateBo);
|
||
|
||
} catch (Exception e) {
|
||
log.error("更新二维码记录失败", e);
|
||
throw new RuntimeException("更新二维码记录失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 单步分卷 - 第一步:领料并锁定钢卷
|
||
*/
|
||
@Override
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public Boolean startSpecialSplit(Long coilId, Integer actionType) {
|
||
// 1. 查询钢卷信息
|
||
WmsMaterialCoil coil = baseMapper.selectById(coilId);
|
||
if (coil == null) {
|
||
throw new RuntimeException("钢卷不存在");
|
||
}
|
||
|
||
// 2. 检查钢卷状态
|
||
if (coil.getDataType() != 1) {
|
||
throw new RuntimeException("当前钢卷是历史卷");
|
||
}
|
||
|
||
// 3. 检查是否已被锁定或已有相同类型的待处理操作
|
||
if (coil.getExclusiveStatus() != null && coil.getExclusiveStatus() != 0) {
|
||
throw new RuntimeException("钢卷正在进行操作");
|
||
}
|
||
|
||
// // 3.1 检查是否已经有相同类型的待处理操作(防止重复领料)
|
||
// LambdaQueryWrapper<WmsCoilPendingAction> pendingActionQuery = new LambdaQueryWrapper<>();
|
||
// pendingActionQuery.eq(WmsCoilPendingAction::getCoilId, coilId)
|
||
// .eq(WmsCoilPendingAction::getActionType, 501) // 领料操作类型
|
||
// .ne(WmsCoilPendingAction::getActionStatus, 2); // 不是已完成的状态
|
||
// List<WmsCoilPendingAction> existingActions = coilPendingActionMapper.selectList(pendingActionQuery);
|
||
// if (!existingActions.isEmpty()) {
|
||
// throw new RuntimeException("该钢卷已有待处理的领料操作,无法重复领料");
|
||
// }
|
||
|
||
// 4. 设置独占状态(锁定钢卷)
|
||
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
|
||
updateWrapper.eq(WmsMaterialCoil::getCoilId, coilId)
|
||
.set(WmsMaterialCoil::getExclusiveStatus, 1); // 1表示单步分卷中
|
||
baseMapper.update(null, updateWrapper);
|
||
|
||
// 5. 创建待操作记录(领料操作)
|
||
WmsCoilPendingActionBo pendingActionBo = new WmsCoilPendingActionBo();
|
||
pendingActionBo.setCoilId(coilId);
|
||
pendingActionBo.setCurrentCoilNo(coil.getCurrentCoilNo());
|
||
pendingActionBo.setActionType(actionType); // 领料操作类型
|
||
pendingActionBo.setActionStatus(0); // 待处理
|
||
pendingActionBo.setSourceType("manual"); // 手动创建
|
||
pendingActionBo.setPriority(0); // 默认普通优先级
|
||
|
||
Boolean insertResult = coilPendingActionService.insertByBo(pendingActionBo);
|
||
if (!insertResult) {
|
||
throw new RuntimeException("创建待操作失败");
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 单步分卷 - 第二步:逐个创建子钢卷
|
||
*/
|
||
@Override
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public WmsMaterialCoilVo createChildCoilInSpecialSplit(Long parentCoilId, WmsMaterialCoilBo childCoilBo, Long pendingActionId) {
|
||
// 1. 查询母卷信息
|
||
WmsMaterialCoil parentCoil = baseMapper.selectById(parentCoilId);
|
||
if (parentCoil == null) {
|
||
throw new RuntimeException("钢卷不存在");
|
||
}
|
||
|
||
// 2. 验证独占状态
|
||
if (parentCoil.getExclusiveStatus() == null || parentCoil.getExclusiveStatus() != 1) {
|
||
throw new RuntimeException("钢卷未处于分卷状态");
|
||
}
|
||
|
||
// 3. 查询待操作记录
|
||
WmsCoilPendingActionVo pendingAction = coilPendingActionService.queryById(pendingActionId);
|
||
if (pendingAction == null || !pendingAction.getCoilId().equals(parentCoilId)) {
|
||
throw new RuntimeException("待操作记录不存在或不匹配");
|
||
}
|
||
|
||
// // 4. 检查子钢卷号是否重复
|
||
// Map<String, Object> duplicateCheck = checkCoilNoDuplicate(null, parentCoil.getEnterCoilNo(), childCoilBo.getCurrentCoilNo());
|
||
// if ("current".equals(duplicateCheck.get("duplicateType")) || "both".equals(duplicateCheck.get("duplicateType"))) {
|
||
// throw new RuntimeException("子钢卷号[" + childCoilBo.getCurrentCoilNo() + "]已存在");
|
||
// }
|
||
|
||
// 校验每个子卷的实际库位
|
||
if (childCoilBo.getActualWarehouseId() != null) {
|
||
validateActualWarehouseForAssign(childCoilBo.getActualWarehouseId(), null);
|
||
}
|
||
|
||
// 5. 创建子钢卷(参考普通分卷逻辑,但不设置母卷为历史数据)
|
||
WmsMaterialCoil childCoil = BeanUtil.toBean(childCoilBo, WmsMaterialCoil.class);
|
||
childCoil.setCoilId(null);
|
||
childCoil.setDataType(1); // 当前数据
|
||
childCoil.setTransferType(null);
|
||
childCoil.setEnterCoilNo(parentCoil.getEnterCoilNo());
|
||
childCoil.setSupplierCoilNo(parentCoil.getSupplierCoilNo());
|
||
childCoil.setExclusiveStatus(0); // 子卷不被锁定
|
||
|
||
// 继承母卷的基本信息
|
||
if (childCoil.getItemType() == null) {
|
||
childCoil.setItemType(parentCoil.getItemType());
|
||
}
|
||
if (childCoil.getItemId() == null) {
|
||
childCoil.setItemId(parentCoil.getItemId());
|
||
}
|
||
if (childCoil.getWarehouseId() == null) {
|
||
childCoil.setWarehouseId(parentCoil.getWarehouseId());
|
||
}
|
||
if (childCoil.getTeam() == null) {
|
||
childCoil.setTeam(parentCoil.getTeam());
|
||
}
|
||
|
||
// 在子卷的 parent_coil_nos 字段中记录母卷号
|
||
childCoil.setParentCoilNos(parentCoil.getCurrentCoilNo());
|
||
|
||
// 6. 生成子钢卷的二维码(复用分卷二维码生成逻辑)
|
||
List<String> allNewCoilNos = Arrays.asList(childCoilBo.getCurrentCoilNo());
|
||
Long childQrcodeId = generateQrcodeForSplit(parentCoil, childCoilBo, allNewCoilNos);
|
||
childCoil.setQrcodeRecordId(childQrcodeId);
|
||
|
||
// 7. 校验并插入子钢卷
|
||
validEntityBeforeSave(childCoil);
|
||
childCoil.setParentCoilId(String.valueOf(parentCoilId));
|
||
baseMapper.insert(childCoil);
|
||
|
||
// 插入子钢卷的异常信息
|
||
if (childCoilBo.getAbnormals() != null && !childCoilBo.getAbnormals().isEmpty()) {
|
||
for (WmsCoilAbnormalBo abnormalBo : childCoilBo.getAbnormals()) {
|
||
abnormalBo.setCoilId(childCoil.getCoilId());
|
||
coilAbnormalService.insertByBo(abnormalBo);
|
||
}
|
||
}
|
||
|
||
updateActualWarehouseEnableStatus(null, childCoilBo.getActualWarehouseId());
|
||
|
||
// 8. 更新二维码中的coilId
|
||
updateQrcodeCoilId(childQrcodeId, childCoil.getCoilId());
|
||
|
||
// 9. 更新待操作记录的备注字段,记录已创建的子钢卷ID
|
||
String existingChildCoilIds = pendingAction.getRemark() != null ? pendingAction.getRemark() : "";
|
||
String updatedChildCoilIds = existingChildCoilIds.isEmpty() ?
|
||
String.valueOf(childCoil.getCoilId()) :
|
||
existingChildCoilIds + "," + childCoil.getCoilId();
|
||
|
||
coilPendingActionMapper.update(
|
||
null,
|
||
Wrappers.<WmsCoilPendingAction>lambdaUpdate()
|
||
.eq(WmsCoilPendingAction::getActionId, pendingActionId)
|
||
.set(WmsCoilPendingAction::getRemark, updatedChildCoilIds)
|
||
);
|
||
|
||
// 10. 返回创建的子钢卷信息
|
||
WmsMaterialCoilVo result = queryById(childCoil.getCoilId());
|
||
log.info("创建特殊分卷子钢卷:母卷ID={}, 子钢卷ID={}, 待操作记录ID={}", parentCoilId, childCoil.getCoilId(), pendingActionId);
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 单步分卷 - 第三步:完成分卷操作
|
||
*/
|
||
@Override
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public Map<String, Object> completeSpecialSplit(Long pendingActionId) {
|
||
Map<String, Object> result = new HashMap<>();
|
||
result.put("success", false);
|
||
|
||
try {
|
||
// 1. 查询待操作记录
|
||
WmsCoilPendingActionVo pendingAction = coilPendingActionService.queryById(pendingActionId);
|
||
if (pendingAction == null) {
|
||
throw new RuntimeException("待操作记录不存在");
|
||
}
|
||
|
||
if (pendingAction.getActionStatus() == 2) {
|
||
throw new RuntimeException("该分卷操作已完成");
|
||
}
|
||
|
||
// 2. 获取母卷和子卷信息
|
||
Long parentCoilId = pendingAction.getCoilId();
|
||
WmsMaterialCoil parentCoil = baseMapper.selectById(parentCoilId);
|
||
if (parentCoil == null) {
|
||
throw new RuntimeException("钢卷不存在");
|
||
}
|
||
|
||
// 3. 获取所有子卷ID
|
||
String childCoilIdsStr = pendingAction.getRemark();
|
||
if (StringUtils.isBlank(childCoilIdsStr)) {
|
||
throw new RuntimeException("没有找到已创建的子钢卷,无法完成分卷");
|
||
}
|
||
|
||
List<Long> childCoilIds = Arrays.stream(childCoilIdsStr.split(","))
|
||
.map(String::trim)
|
||
.filter(StringUtils::isNotBlank)
|
||
.map(Long::parseLong)
|
||
.collect(Collectors.toList());
|
||
|
||
QueryWrapper<WmsMaterialCoil> queryWrapper = new QueryWrapper<>();
|
||
queryWrapper.in("coil_id", childCoilIds);
|
||
queryWrapper.eq("del_flag", 0); // 确保只查询未删除的记录
|
||
List<WmsMaterialCoil> childCoils = baseMapper.selectList(queryWrapper);
|
||
List<String> childCoilNos = childCoils.stream()
|
||
.map(WmsMaterialCoil::getCurrentCoilNo)
|
||
.collect(Collectors.toList());
|
||
|
||
// 如果母卷即将成为历史卷 证明已经加工完成则释放库位
|
||
if (parentCoil.getActualWarehouseId() != null){
|
||
updateActualWarehouseEnableStatus(parentCoil.getActualWarehouseId(), null);
|
||
}
|
||
// 4. 将母卷标记为历史数据
|
||
LambdaUpdateWrapper<WmsMaterialCoil> parentUpdateWrapper = new LambdaUpdateWrapper<>();
|
||
parentUpdateWrapper.eq(WmsMaterialCoil::getCoilId, parentCoilId)
|
||
.set(WmsMaterialCoil::getDataType, 0) // 历史数据
|
||
.set(WmsMaterialCoil::getParentCoilNos, String.join(",", childCoilNos)); // 记录子卷号
|
||
baseMapper.update(null, parentUpdateWrapper);
|
||
|
||
// 4.1 将母卷的二维码标记为失效
|
||
if (parentCoil.getQrcodeRecordId() != null) {
|
||
WmsGenerateRecordBo parentQrBo = new WmsGenerateRecordBo();
|
||
parentQrBo.setRecordId(parentCoil.getQrcodeRecordId());
|
||
parentQrBo.setStatus(0); // 0=失效
|
||
generateRecordService.updateByBo(parentQrBo);
|
||
}
|
||
|
||
|
||
// 5. 批量更新所有子钢卷的二维码中的child_coil_ids
|
||
String allChildCoilIdsStr = childCoilIds.stream()
|
||
.map(String::valueOf)
|
||
.collect(Collectors.joining(","));
|
||
|
||
for (WmsMaterialCoil childCoil : childCoils) {
|
||
if (childCoil.getQrcodeRecordId() != null) {
|
||
updateChildCoilIdsQrcodeCoilId(parentCoilId, childCoil.getQrcodeRecordId(), childCoil.getCoilId(), allChildCoilIdsStr);
|
||
}
|
||
}
|
||
|
||
// 6. 解除母卷的独占状态
|
||
LambdaUpdateWrapper<WmsMaterialCoil> unlockWrapper = new LambdaUpdateWrapper<>();
|
||
unlockWrapper.eq(WmsMaterialCoil::getCoilId, parentCoilId)
|
||
.set(WmsMaterialCoil::getExclusiveStatus, 0); // 解除锁定
|
||
baseMapper.update(null, unlockWrapper);
|
||
|
||
// 7. 更新待操作记录状态为已完成
|
||
WmsCoilPendingActionBo completePendingBo = new WmsCoilPendingActionBo();
|
||
completePendingBo.setActionId(pendingActionId);
|
||
completePendingBo.setActionStatus(2); // 2=已完成
|
||
completePendingBo.setCompleteTime(new Date());
|
||
coilPendingActionService.updateByBo(completePendingBo);
|
||
|
||
// 7.1 单步分卷完成:记录加工日志(operationType=2)
|
||
// 老的实际库区存在则加一条加工出库记录
|
||
Long parentActualWarehouseId = parentCoil.getActualWarehouseId();
|
||
if (parentActualWarehouseId != null && parentActualWarehouseId != -1L) {
|
||
recordWarehouseOperationLog(parentCoilId, parentActualWarehouseId, 2, 2, "单步分卷完成-加工出库");
|
||
}
|
||
// 为每个子钢卷的实际库区加一条加工入库记录(有的加,没有不加)
|
||
for (WmsMaterialCoil childCoil : childCoils) {
|
||
Long childActualWarehouseId = childCoil.getActualWarehouseId();
|
||
if (childActualWarehouseId != null && childActualWarehouseId != -1L) {
|
||
recordWarehouseOperationLog(childCoil.getCoilId(), childActualWarehouseId, 2, 1, "单步分卷完成-加工入库");
|
||
}
|
||
}
|
||
|
||
// 8. 返回结果
|
||
result.put("success", true);
|
||
result.put("parentCoilId", parentCoilId);
|
||
result.put("childCoilIds", childCoilIds);
|
||
result.put("message", "单步分卷操作已完成");
|
||
|
||
log.info("完成特殊分卷:母卷ID={}, 子钢卷数量={}, 待操作记录ID={}", parentCoilId, childCoilIds.size(), pendingActionId);
|
||
|
||
} catch (Exception e) {
|
||
log.error("完成特殊分卷失败:pendingActionId={}", pendingActionId, e);
|
||
result.put("success", false);
|
||
result.put("message", "完成分卷操作失败:" + e.getMessage());
|
||
throw e;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
//Map<String, Object> cancelSpecialSplit(@NotNull(message = "待操作记录ID不能为空") Long pendingActionId);
|
||
/**
|
||
* 单步分卷 - 取消分卷(释放锁 + 回滚子卷)
|
||
*/
|
||
@Override
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public Map<String, Object> cancelSpecialSplit(Long pendingActionId) {
|
||
|
||
Map<String, Object> result = new HashMap<>();
|
||
result.put("success", false);
|
||
|
||
// 1. 查询待操作记录
|
||
WmsCoilPendingActionVo pendingAction = coilPendingActionService.queryById(pendingActionId);
|
||
if (pendingAction == null) {
|
||
throw new RuntimeException("待操作记录不存在");
|
||
}
|
||
|
||
// 2. 已完成的分卷不允许取消
|
||
if (Objects.equals(pendingAction.getActionStatus(), 2)) {
|
||
throw new RuntimeException("该分卷操作已完成,无法取消");
|
||
}
|
||
|
||
if (StringUtils.isNotBlank(pendingAction.getRemark())) {
|
||
throw new RuntimeException("该分卷操作已开始,请勿取消");
|
||
}
|
||
// 删除待操作记录
|
||
coilPendingActionService.deleteWithValidByIds(Arrays.asList(pendingActionId), false);
|
||
// 3. 查询母卷
|
||
WmsMaterialCoil parentCoil = baseMapper.selectById(pendingAction.getCoilId());
|
||
if (parentCoil == null) {
|
||
throw new RuntimeException("钢卷不存在");
|
||
}
|
||
|
||
if (parentCoil.getDataType() == 0) {
|
||
throw new RuntimeException("该钢卷已成为历史数据,无法取消分卷");
|
||
}
|
||
|
||
// 4. 校验母卷是否处于“单步分卷中”
|
||
if (Objects.equals(parentCoil.getExclusiveStatus(), 0)) {
|
||
throw new RuntimeException("钢卷未处于分卷状态,无法取消");
|
||
}
|
||
|
||
// 释放母卷独占锁
|
||
if (Objects.equals(parentCoil.getExclusiveStatus(), 1)) {
|
||
LambdaUpdateWrapper<WmsMaterialCoil> parentUpdate = new LambdaUpdateWrapper<>();
|
||
parentUpdate.eq(WmsMaterialCoil::getCoilId, parentCoil.getCoilId())
|
||
.set(WmsMaterialCoil::getExclusiveStatus, 0);
|
||
baseMapper.update(null, parentUpdate);
|
||
}
|
||
|
||
result.put("success", true);
|
||
result.put("message", "取消分卷成功");
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 钢卷退货操作
|
||
* 将钢卷退货到退货仓,创建新钢卷记录,将原钢卷设置为历史钢卷
|
||
*/
|
||
@Override
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public WmsMaterialCoilVo returnCoil(Long coilId) {
|
||
if (coilId == null) {
|
||
throw new RuntimeException("钢卷ID不能为空");
|
||
}
|
||
|
||
validateCoilOperationPermission(coilId, "退货");
|
||
|
||
WmsMaterialCoil oldCoil = baseMapper.selectById(coilId);
|
||
if (oldCoil == null) {
|
||
throw new RuntimeException("原钢卷不存在");
|
||
}
|
||
|
||
if (oldCoil.getDataType() == 0) {
|
||
throw new RuntimeException("该钢卷是历史钢卷");
|
||
}
|
||
|
||
WmsWarehouseBo warehouseBo = new WmsWarehouseBo();
|
||
warehouseBo.setWarehouseName("退货仓");
|
||
warehouseBo.setWarehouseCode("return");
|
||
warehouseBo.setWarehouseType(1L);
|
||
warehouseBo.setIsEnabled(1);
|
||
List<WmsWarehouseVo> warehouseList = warehouseService.queryList(warehouseBo);
|
||
if (warehouseList == null || warehouseList.isEmpty()) {
|
||
throw new RuntimeException("退货仓不存在,请先配置退货仓");
|
||
}
|
||
Long returnWarehouseId = warehouseList.get(0).getWarehouseId();
|
||
|
||
if (oldCoil.getActualWarehouseId() != null) {
|
||
updateActualWarehouseEnableStatus(oldCoil.getActualWarehouseId(), null);
|
||
}
|
||
|
||
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
|
||
updateWrapper.eq(WmsMaterialCoil::getCoilId, oldCoil.getCoilId())
|
||
.set(WmsMaterialCoil::getDataType, 0);
|
||
baseMapper.update(null, updateWrapper);
|
||
|
||
WmsMaterialCoil newCoil = new WmsMaterialCoil();
|
||
BeanUtils.copyProperties(oldCoil, newCoil);
|
||
newCoil.setCoilId(null);
|
||
newCoil.setDataType(1);
|
||
newCoil.setWarehouseId(returnWarehouseId);
|
||
newCoil.setNextWarehouseId(null);
|
||
newCoil.setQrcodeRecordId(oldCoil.getQrcodeRecordId());
|
||
newCoil.setStatus(0);
|
||
newCoil.setExportBy(null);
|
||
newCoil.setExportTime(null);
|
||
newCoil.setCreateTime(new Date());
|
||
newCoil.setUpdateTime(new Date());
|
||
// 创建人
|
||
newCoil.setCreateBy(LoginHelper.getUsername());
|
||
newCoil.setUpdateBy(LoginHelper.getUsername());
|
||
|
||
validEntityBeforeSave(newCoil);
|
||
boolean flag = baseMapper.insert(newCoil) > 0;
|
||
|
||
if (flag && oldCoil.getQrcodeRecordId() != null) {
|
||
updateQrcodeContentForReturn(oldCoil, newCoil.getCoilId());
|
||
}
|
||
|
||
// 退货操作:记录加工日志(operationType=2)
|
||
// 老的实际库区存在,加一条加工出库记录
|
||
Long oldActualWarehouseId = oldCoil.getActualWarehouseId();
|
||
if (oldActualWarehouseId != null && oldActualWarehouseId != -1L) {
|
||
recordWarehouseOperationLog(oldCoil.getCoilId(), oldActualWarehouseId, 2, 2, "退货操作-加工出库");
|
||
}
|
||
|
||
return queryById(newCoil.getCoilId());
|
||
}
|
||
|
||
/**
|
||
* 更新二维码内容(退货操作)
|
||
*/
|
||
private void updateQrcodeContentForReturn(WmsMaterialCoil oldCoil, Long newCoilId) {
|
||
try {
|
||
WmsGenerateRecordVo oldRecord = generateRecordService.queryById(oldCoil.getQrcodeRecordId());
|
||
if (oldRecord == null) {
|
||
throw new RuntimeException("二维码记录不存在");
|
||
}
|
||
|
||
ObjectMapper objectMapper = new ObjectMapper();
|
||
@SuppressWarnings("unchecked")
|
||
Map<String, Object> contentMap = objectMapper.readValue(oldRecord.getContent(), Map.class);
|
||
|
||
@SuppressWarnings("unchecked")
|
||
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
|
||
if (steps == null) {
|
||
steps = new ArrayList<>();
|
||
}
|
||
|
||
Map<String, Object> newStep = new HashMap<>();
|
||
newStep.put("step", steps.size() + 1);
|
||
newStep.put("action", "退货");
|
||
newStep.put("operation", "退货操作");
|
||
newStep.put("old_current_coil_no", oldCoil.getCurrentCoilNo());
|
||
newStep.put("new_current_coil_no", oldCoil.getCurrentCoilNo());
|
||
newStep.put("old_coil_id", String.valueOf(oldCoil.getCoilId()));
|
||
newStep.put("new_coil_id", String.valueOf(newCoilId));
|
||
newStep.put("operator", LoginHelper.getUsername());
|
||
newStep.put("update_time", new java.util.Date());
|
||
|
||
steps.add(newStep);
|
||
contentMap.put("steps", steps);
|
||
|
||
contentMap.put("current_coil_id", String.valueOf(newCoilId));
|
||
|
||
String newContentJson = objectMapper.writeValueAsString(contentMap);
|
||
|
||
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
|
||
updateBo.setRecordId(oldCoil.getQrcodeRecordId());
|
||
updateBo.setContent(newContentJson);
|
||
generateRecordService.updateByBo(updateBo);
|
||
} catch (JsonProcessingException e) {
|
||
throw new RuntimeException("更新二维码内容失败", e);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public List<CoilTrimStatisticsVo> getCoilTrimStatistics() {
|
||
List<CoilTrimRawVo> rawList = baseMapper.selectCoilTrimStatistics();
|
||
|
||
Map<String, List<CoilTrimRawVo>> byThickness = rawList.stream()
|
||
.collect(Collectors.groupingBy(v -> v.getThickness() == null ? "空置" : v.getThickness()));
|
||
|
||
List<CoilTrimStatisticsVo> result = new ArrayList<>();
|
||
|
||
for (Map.Entry<String, List<CoilTrimRawVo>> thicknessEntry : byThickness.entrySet()) {
|
||
CoilTrimStatisticsVo vo = new CoilTrimStatisticsVo();
|
||
vo.setThickness(thicknessEntry.getKey());
|
||
|
||
List<CoilTrimRawVo> thicknessList = thicknessEntry.getValue();
|
||
|
||
Map<String, List<CoilTrimRawVo>> byTrim = thicknessList.stream()
|
||
.collect(Collectors.groupingBy(v -> v.getTrimmingRequirement() == null ? "未知" : v.getTrimmingRequirement()));
|
||
|
||
List<CoilTrimRawVo> trimmedList = byTrim.getOrDefault("净边", new ArrayList<>());
|
||
List<CoilTrimRawVo> untrimmedList = byTrim.getOrDefault("毛边", new ArrayList<>());
|
||
|
||
List<TrimWidthStatisticsVo> trimmedWidthList = new ArrayList<>();
|
||
for (CoilTrimRawVo raw : trimmedList) {
|
||
TrimWidthStatisticsVo widthVo = new TrimWidthStatisticsVo();
|
||
widthVo.setWidth(raw.getWidth() == null ? "空置" : raw.getWidth());
|
||
widthVo.setCoilCount(raw.getCoilCount() == null ? 0 : raw.getCoilCount());
|
||
widthVo.setTotalWeight(raw.getTotalWeight() == null ? BigDecimal.ZERO : raw.getTotalWeight());
|
||
trimmedWidthList.add(widthVo);
|
||
}
|
||
|
||
List<TrimWidthStatisticsVo> untrimmedWidthList = new ArrayList<>();
|
||
for (CoilTrimRawVo raw : untrimmedList) {
|
||
TrimWidthStatisticsVo widthVo = new TrimWidthStatisticsVo();
|
||
widthVo.setWidth(raw.getWidth() == null ? "空置" : raw.getWidth());
|
||
widthVo.setCoilCount(raw.getCoilCount() == null ? 0 : raw.getCoilCount());
|
||
widthVo.setTotalWeight(raw.getTotalWeight() == null ? BigDecimal.ZERO : raw.getTotalWeight());
|
||
untrimmedWidthList.add(widthVo);
|
||
}
|
||
|
||
vo.setTrimmedList(trimmedWidthList);
|
||
vo.setUntrimmedList(untrimmedWidthList);
|
||
result.add(vo);
|
||
}
|
||
|
||
result.sort(Comparator.comparing(v -> {
|
||
try {
|
||
return new BigDecimal(v.getThickness());
|
||
} catch (Exception e) {
|
||
return new BigDecimal("999999");
|
||
}
|
||
}));
|
||
|
||
return result;
|
||
}
|
||
|
||
@Override
|
||
public List<CategoryWidthStatisticsVo> getCategoryWidthStatistics() {
|
||
List<CategoryWidthRawVo> rawList = baseMapper.selectCategoryWidthStatistics();
|
||
|
||
Map<String, List<CategoryWidthRawVo>> byCategory = rawList.stream()
|
||
.collect(Collectors.groupingBy(v -> v.getCategory() == null ? "其他" : v.getCategory()));
|
||
|
||
List<CategoryWidthStatisticsVo> result = new ArrayList<>();
|
||
|
||
for (Map.Entry<String, List<CategoryWidthRawVo>> categoryEntry : byCategory.entrySet()) {
|
||
CategoryWidthStatisticsVo vo = new CategoryWidthStatisticsVo();
|
||
vo.setCategory(categoryEntry.getKey());
|
||
|
||
BigDecimal total = BigDecimal.ZERO;
|
||
BigDecimal width1000 = BigDecimal.ZERO;
|
||
BigDecimal width1200 = BigDecimal.ZERO;
|
||
BigDecimal width1220 = BigDecimal.ZERO;
|
||
BigDecimal width1250 = BigDecimal.ZERO;
|
||
BigDecimal otherWidth = BigDecimal.ZERO;
|
||
|
||
for (CategoryWidthRawVo raw : categoryEntry.getValue()) {
|
||
BigDecimal weight = raw.getTotalWeight() == null ? BigDecimal.ZERO : raw.getTotalWeight();
|
||
String width = raw.getWidth();
|
||
String isTrimmed = raw.getIsTrimmed();
|
||
|
||
if ("净边".equals(isTrimmed)) {
|
||
if ("1000".equals(width)) {
|
||
width1000 = width1000.add(weight);
|
||
} else if ("1200".equals(width)) {
|
||
width1200 = width1200.add(weight);
|
||
} else if ("1220".equals(width)) {
|
||
width1220 = width1220.add(weight);
|
||
} else if ("1250".equals(width)) {
|
||
width1250 = width1250.add(weight);
|
||
} else {
|
||
otherWidth = otherWidth.add(weight);
|
||
}
|
||
} else {
|
||
otherWidth = otherWidth.add(weight);
|
||
}
|
||
total = total.add(weight);
|
||
}
|
||
|
||
vo.setWidth1000(width1000);
|
||
vo.setWidth1200(width1200);
|
||
vo.setWidth1220(width1220);
|
||
vo.setWidth1250(width1250);
|
||
vo.setOtherWidth(otherWidth);
|
||
vo.setTotal(total);
|
||
result.add(vo);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
@Override
|
||
public WmsMaterialCoilReportSummaryVo reportSummary(WmsMaterialCoilReportSummaryBo req) {
|
||
WmsMaterialCoilBo coilFilter = req != null && req.getMaterialCoilFilter() != null
|
||
? req.getMaterialCoilFilter() : new WmsMaterialCoilBo();
|
||
WmsCoilPendingActionBo pendingFilter = req == null ? null : req.getPendingActionFilter();
|
||
|
||
if (hasAnyPendingFilter(pendingFilter)) {
|
||
List<Long> pendingCoilIds = queryCoilIdsByPendingFilter(pendingFilter);
|
||
if (CollectionUtils.isEmpty(pendingCoilIds)) {
|
||
return buildEmptySummary();
|
||
}
|
||
coilFilter.setCoilIds(mergeCoilIds(coilFilter.getCoilIds(), pendingCoilIds));
|
||
}
|
||
|
||
List<WmsMaterialCoilVo> list = queryList(coilFilter);
|
||
return buildSummaryFromList(list);
|
||
}
|
||
|
||
|
||
|
||
private boolean hasAnyPendingFilter(WmsCoilPendingActionBo bo) {
|
||
if (bo == null) {
|
||
return false;
|
||
}
|
||
return bo.getCoilId() != null
|
||
|| StringUtils.isNotBlank(bo.getCurrentCoilNo())
|
||
|| bo.getActionType() != null
|
||
|| CollectionUtils.isNotEmpty(bo.getActionTypes())
|
||
|| bo.getActionStatus() != null
|
||
|| bo.getWarehouseId() != null
|
||
|| bo.getPriority() != null
|
||
|| StringUtils.isNotBlank(bo.getSourceType())
|
||
|| bo.getStartTime() != null
|
||
|| bo.getEndTime() != null
|
||
|| StringUtils.isNotBlank(bo.getCreateBy())
|
||
|| StringUtils.isNotBlank(bo.getUpdateBy())
|
||
|| StringUtils.isNotBlank(bo.getProcessedCoilIds())
|
||
|| bo.getIncludeDeleted() != null;
|
||
}
|
||
|
||
private List<Long> queryCoilIdsByPendingFilter(WmsCoilPendingActionBo bo) {
|
||
LambdaQueryWrapper<WmsCoilPendingAction> qw = Wrappers.lambdaQuery();
|
||
qw.select(WmsCoilPendingAction::getCoilId);
|
||
qw.eq(bo.getCoilId() != null, WmsCoilPendingAction::getCoilId, bo.getCoilId());
|
||
qw.like(StringUtils.isNotBlank(bo.getCurrentCoilNo()), WmsCoilPendingAction::getCurrentCoilNo, bo.getCurrentCoilNo());
|
||
if (CollectionUtils.isNotEmpty(bo.getActionTypes())) {
|
||
qw.in(WmsCoilPendingAction::getActionType, bo.getActionTypes());
|
||
} else {
|
||
qw.eq(bo.getActionType() != null, WmsCoilPendingAction::getActionType, bo.getActionType());
|
||
}
|
||
if (bo.getActionStatus() != null) {
|
||
if (bo.getActionStatus() == -1) {
|
||
qw.ne(WmsCoilPendingAction::getActionStatus, 2);
|
||
} else {
|
||
qw.eq(WmsCoilPendingAction::getActionStatus, bo.getActionStatus());
|
||
}
|
||
}
|
||
qw.eq(bo.getWarehouseId() != null, WmsCoilPendingAction::getWarehouseId, bo.getWarehouseId());
|
||
qw.eq(bo.getPriority() != null, WmsCoilPendingAction::getPriority, bo.getPriority());
|
||
qw.like(StringUtils.isNotBlank(bo.getSourceType()), WmsCoilPendingAction::getSourceType, bo.getSourceType());
|
||
qw.ge(bo.getStartTime() != null, WmsCoilPendingAction::getCompleteTime, bo.getStartTime());
|
||
qw.le(bo.getEndTime() != null, WmsCoilPendingAction::getCompleteTime, bo.getEndTime());
|
||
qw.eq(StringUtils.isNotBlank(bo.getCreateBy()), WmsCoilPendingAction::getCreateBy, bo.getCreateBy());
|
||
qw.eq(StringUtils.isNotBlank(bo.getUpdateBy()), WmsCoilPendingAction::getUpdateBy, bo.getUpdateBy());
|
||
qw.like(StringUtils.isNotBlank(bo.getProcessedCoilIds()), WmsCoilPendingAction::getProcessedCoilIds, bo.getProcessedCoilIds());
|
||
|
||
if (bo.getIncludeDeleted() != null) {
|
||
if (bo.getIncludeDeleted() == 2) {
|
||
qw.eq(WmsCoilPendingAction::getDelFlag, 2);
|
||
} else if (bo.getIncludeDeleted() == 0) {
|
||
qw.eq(WmsCoilPendingAction::getDelFlag, 0);
|
||
}
|
||
} else {
|
||
qw.eq(WmsCoilPendingAction::getDelFlag, 0);
|
||
}
|
||
|
||
List<WmsCoilPendingAction> actions = coilPendingActionMapper.selectList(qw);
|
||
if (CollectionUtils.isEmpty(actions)) {
|
||
return Collections.emptyList();
|
||
}
|
||
return actions.stream()
|
||
.map(WmsCoilPendingAction::getCoilId)
|
||
.filter(Objects::nonNull)
|
||
.distinct()
|
||
.collect(Collectors.toList());
|
||
}
|
||
|
||
private String mergeCoilIds(String coilIdsCsv, List<Long> pendingCoilIds) {
|
||
Set<Long> pendingSet = new LinkedHashSet<>(pendingCoilIds);
|
||
if (StringUtils.isBlank(coilIdsCsv)) {
|
||
return pendingSet.stream().map(String::valueOf).collect(Collectors.joining(","));
|
||
}
|
||
|
||
List<Long> existing = parseCsvLongs(coilIdsCsv);
|
||
if (CollectionUtils.isEmpty(existing)) {
|
||
return pendingSet.stream().map(String::valueOf).collect(Collectors.joining(","));
|
||
}
|
||
|
||
Set<Long> existingSet = new LinkedHashSet<>(existing);
|
||
existingSet.retainAll(pendingSet);
|
||
if (existingSet.isEmpty()) {
|
||
return "-1";
|
||
}
|
||
return existingSet.stream().map(String::valueOf).collect(Collectors.joining(","));
|
||
}
|
||
|
||
private WmsMaterialCoilReportSummaryVo buildEmptySummary() {
|
||
WmsMaterialCoilReportSummaryVo vo = new WmsMaterialCoilReportSummaryVo();
|
||
vo.setTotalCount(0);
|
||
vo.setTotalWeight("0.00");
|
||
vo.setAvgWeight("0");
|
||
vo.setAbnormalSummary(buildAbnormalSummary(Collections.emptyList()));
|
||
vo.setTeamSummary(Collections.emptyMap());
|
||
return vo;
|
||
}
|
||
|
||
private WmsMaterialCoilReportSummaryVo buildSummaryFromList(List<WmsMaterialCoilVo> list) {
|
||
List<WmsMaterialCoilVo> safeList = list == null ? Collections.emptyList() : list;
|
||
int totalCount = safeList.size();
|
||
BigDecimal totalWeight = safeList.stream()
|
||
.map(WmsMaterialCoilVo::getNetWeight)
|
||
.filter(Objects::nonNull)
|
||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||
String avgWeight = totalCount > 0
|
||
? totalWeight.divide(BigDecimal.valueOf(totalCount), 2, java.math.RoundingMode.HALF_UP).toPlainString()
|
||
: "0";
|
||
|
||
WmsMaterialCoilReportSummaryVo vo = new WmsMaterialCoilReportSummaryVo();
|
||
vo.setTotalCount(totalCount);
|
||
vo.setTotalWeight(totalWeight.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString());
|
||
vo.setAvgWeight(avgWeight);
|
||
vo.setAbnormalSummary(buildAbnormalSummary(safeList));
|
||
vo.setTeamSummary(buildTeamSummary(safeList));
|
||
return vo;
|
||
}
|
||
|
||
private List<Map<String, String>> buildAbnormalSummary(List<WmsMaterialCoilVo> list) {
|
||
int totalCount = list.size();
|
||
int jishuCount = 0;
|
||
int miniCount = 0;
|
||
int rubbishCount = 0;
|
||
int returnCount = 0;
|
||
|
||
BigDecimal jishuWeight = BigDecimal.ZERO;
|
||
BigDecimal miniWeight = BigDecimal.ZERO;
|
||
BigDecimal rubbishWeight = BigDecimal.ZERO;
|
||
BigDecimal returnWeight = BigDecimal.ZERO;
|
||
|
||
for (WmsMaterialCoilVo coil : list) {
|
||
String warehouseId = coil.getWarehouseId() == null ? null : String.valueOf(coil.getWarehouseId());
|
||
String qualityStatus = coil.getQualityStatus();
|
||
BigDecimal netWeight = coil.getNetWeight() == null ? BigDecimal.ZERO : coil.getNetWeight();
|
||
|
||
if ("2019583656787259393".equals(warehouseId) || "O".equals(qualityStatus)) {
|
||
jishuCount++;
|
||
jishuWeight = jishuWeight.add(netWeight);
|
||
} else if ("2019583325311414274".equals(warehouseId)) {
|
||
miniCount++;
|
||
miniWeight = miniWeight.add(netWeight);
|
||
} else if ("2019583429955104769".equals(warehouseId)
|
||
|| "D-".equals(qualityStatus)
|
||
|| "D".equals(qualityStatus)
|
||
|| "D+".equals(qualityStatus)
|
||
|| "C-".equals(qualityStatus)
|
||
|| "C".equals(qualityStatus)
|
||
|| "C+".equals(qualityStatus)) {
|
||
rubbishCount++;
|
||
rubbishWeight = rubbishWeight.add(netWeight);
|
||
} else if ("2019583137616310273".equals(warehouseId)) {
|
||
returnCount++;
|
||
returnWeight = returnWeight.add(netWeight);
|
||
}
|
||
}
|
||
|
||
List<Map<String, String>> result = new ArrayList<>();
|
||
result.add(buildLabelValue("技术部钢卷数", String.valueOf(jishuCount)));
|
||
result.add(buildLabelValue("小钢卷库钢卷数", String.valueOf(miniCount)));
|
||
result.add(buildLabelValue("废品库钢卷数", String.valueOf(rubbishCount)));
|
||
result.add(buildLabelValue("退货库钢卷数", String.valueOf(returnCount)));
|
||
result.add(buildLabelValue("技术部钢卷重量", jishuWeight.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString()));
|
||
result.add(buildLabelValue("小钢卷库钢卷重量", miniWeight.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString()));
|
||
result.add(buildLabelValue("废品库钢卷重量", rubbishWeight.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString()));
|
||
result.add(buildLabelValue("退货库钢卷重量", returnWeight.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString()));
|
||
result.add(buildLabelValue("技术部占比", percent(jishuCount, totalCount)));
|
||
result.add(buildLabelValue("小钢卷库占比", percent(miniCount, totalCount)));
|
||
result.add(buildLabelValue("废品库占比", percent(rubbishCount, totalCount)));
|
||
result.add(buildLabelValue("退货库占比", percent(returnCount, totalCount)));
|
||
return result;
|
||
}
|
||
|
||
private Map<String, WmsMaterialCoilReportSummaryVo.TeamSummaryItem> buildTeamSummary(List<WmsMaterialCoilVo> list) {
|
||
Map<String, WmsMaterialCoilReportSummaryVo.TeamSummaryItem> teamSummary = new LinkedHashMap<>();
|
||
for (WmsMaterialCoilVo coil : list) {
|
||
String team = StringUtils.isNotBlank(coil.getTeam()) ? coil.getTeam() : "未分配";
|
||
WmsMaterialCoilReportSummaryVo.TeamSummaryItem item = teamSummary.computeIfAbsent(team, k -> {
|
||
WmsMaterialCoilReportSummaryVo.TeamSummaryItem summaryItem = new WmsMaterialCoilReportSummaryVo.TeamSummaryItem();
|
||
summaryItem.setCount(0);
|
||
summaryItem.setWeight("0.00");
|
||
return summaryItem;
|
||
});
|
||
int nextCount = item.getCount() == null ? 1 : item.getCount() + 1;
|
||
BigDecimal currWeight = StringUtils.isBlank(item.getWeight()) ? BigDecimal.ZERO : new BigDecimal(item.getWeight());
|
||
BigDecimal netWeight = coil.getNetWeight() == null ? BigDecimal.ZERO : coil.getNetWeight();
|
||
item.setCount(nextCount);
|
||
item.setWeight(currWeight.add(netWeight).setScale(2, java.math.RoundingMode.HALF_UP).toPlainString());
|
||
}
|
||
return teamSummary;
|
||
}
|
||
|
||
private Map<String, String> buildLabelValue(String label, String value) {
|
||
Map<String, String> map = new LinkedHashMap<>(2);
|
||
map.put("label", label);
|
||
map.put("value", value);
|
||
return map;
|
||
}
|
||
|
||
private String percent(int part, int total) {
|
||
if (total <= 0) {
|
||
return "0.00%";
|
||
}
|
||
return BigDecimal.valueOf(part)
|
||
.multiply(BigDecimal.valueOf(100))
|
||
.divide(BigDecimal.valueOf(total), 2, java.math.RoundingMode.HALF_UP)
|
||
.toPlainString() + "%";
|
||
}
|
||
|
||
/**
|
||
* 查询itemId和itemType不匹配的钢卷
|
||
* 检查所有钢卷的itemId是否存在于对应的表中(根据itemType)
|
||
*/
|
||
@Override
|
||
public List<WmsMaterialCoilVo> queryMismatchedItemCoils() {
|
||
// 用SQL子查询直接找出itemId和itemType不匹配的钢卷
|
||
List<WmsMaterialCoil> mismatchedCoils = baseMapper.selectMismatchedItemCoils();
|
||
|
||
if (mismatchedCoils == null || mismatchedCoils.isEmpty()) {
|
||
return new ArrayList<>();
|
||
}
|
||
|
||
// 批量填充关联信息
|
||
List<WmsMaterialCoilVo> voList = mismatchedCoils.stream()
|
||
.map(coil -> BeanUtil.toBean(coil, WmsMaterialCoilVo.class))
|
||
.collect(Collectors.toList());
|
||
fillRelatedObjectsBatch(voList);
|
||
|
||
return voList;
|
||
}
|
||
|
||
|
||
}
|