feat(aps): 新增排产单明细合并功能及优化界面展示
- 在排产单明细表格中添加多选合并功能 - 实现排产单明细合并对话框及合并逻辑 - 优化排产单明细表格列配置和表单布局 - 添加合并校验和接收产需单API接口 - 重构订单绑定解绑逻辑提升用户体验 - 添加ScheduleDetailCoilBind组件引入
This commit is contained in:
@@ -2,8 +2,11 @@ package com.klp.flow.service;
|
||||
|
||||
import com.klp.flow.domain.SchProdScheduleItem;
|
||||
import com.klp.flow.domain.vo.SchProdScheduleItemVo;
|
||||
import com.klp.flow.domain.vo.SchProdScheduleItemMergeValidateVo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemMergeBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import com.klp.common.core.domain.PageQuery;
|
||||
|
||||
@@ -57,4 +60,14 @@ public interface ISchProdScheduleItemService {
|
||||
* 合并多条排产单主加明细可合并记录:先删除被合并的记录,再插入合并后的新记录
|
||||
*/
|
||||
Boolean mergeByBo(SchProdScheduleItemMergeBo mergeBo);
|
||||
|
||||
/**
|
||||
* 接收产需单:从 sch_prod_schedule + sch_prod_schedule_detail 全字段复制到 sch_prod_schedule_item
|
||||
*/
|
||||
Boolean receiveByBo(SchProdScheduleItemReceiveBo receiveBo);
|
||||
|
||||
/**
|
||||
* 合并校验:检查待合并记录来源产需单的 header 字段是否一致
|
||||
*/
|
||||
SchProdScheduleItemMergeValidateVo validateMerge(SchProdScheduleItemMergeValidateBo validateBo);
|
||||
}
|
||||
|
||||
@@ -11,16 +11,27 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemMergeBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo;
|
||||
import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo;
|
||||
import com.klp.flow.domain.vo.SchProdScheduleItemVo;
|
||||
import com.klp.flow.domain.vo.SchProdScheduleItemMergeValidateVo;
|
||||
import com.klp.flow.domain.SchProdScheduleItem;
|
||||
import com.klp.flow.domain.SchProdSchedule;
|
||||
import com.klp.flow.domain.SchProdScheduleDetail;
|
||||
import com.klp.flow.mapper.SchProdScheduleItemMapper;
|
||||
import com.klp.flow.mapper.SchProdScheduleMapper;
|
||||
import com.klp.flow.mapper.SchProdScheduleDetailMapper;
|
||||
import com.klp.flow.service.ISchProdScheduleItemService;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 排产单主加明细可合并Service业务层处理
|
||||
@@ -33,6 +44,8 @@ import java.util.Collection;
|
||||
public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemService {
|
||||
|
||||
private final SchProdScheduleItemMapper baseMapper;
|
||||
private final SchProdScheduleMapper schProdScheduleMapper;
|
||||
private final SchProdScheduleDetailMapper schProdScheduleDetailMapper;
|
||||
|
||||
/**
|
||||
* 查询排产单主加明细可合并
|
||||
@@ -89,8 +102,7 @@ public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemServi
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getOtherTechReq()), SchProdScheduleItem::getOtherTechReq, bo.getOtherTechReq());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getPaymentDesc()), SchProdScheduleItem::getPaymentDesc, bo.getPaymentDesc());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getReturnReason()), SchProdScheduleItem::getReturnReason, bo.getReturnReason());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getScheduleIds()), SchProdScheduleItem::getScheduleIds, bo.getScheduleIds());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getOrderDetailIds()), SchProdScheduleItem::getOrderDetailIds, bo.getOrderDetailIds());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getScheduleDetailIds()), SchProdScheduleItem::getScheduleDetailIds, bo.getScheduleDetailIds());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getSpec()), SchProdScheduleItem::getSpec, bo.getSpec());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getMaterial()), SchProdScheduleItem::getMaterial, bo.getMaterial());
|
||||
lqw.eq(bo.getScheduleWeight() != null, SchProdScheduleItem::getScheduleWeight, bo.getScheduleWeight());
|
||||
@@ -179,4 +191,287 @@ public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemServi
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收产需单:从 sch_prod_schedule + sch_prod_schedule_detail 全字段复制到 sch_prod_schedule_item
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean receiveByBo(SchProdScheduleItemReceiveBo receiveBo) {
|
||||
Long scheduleId = receiveBo.getScheduleId();
|
||||
|
||||
// 1. 查询产需单主表
|
||||
SchProdSchedule header = schProdScheduleMapper.selectById(scheduleId);
|
||||
if (header == null) {
|
||||
throw new RuntimeException("产需单不存在,scheduleId=" + scheduleId);
|
||||
}
|
||||
|
||||
// 2. 查询产需单明细列表
|
||||
List<SchProdScheduleDetail> details = schProdScheduleDetailMapper.selectList(
|
||||
Wrappers.<SchProdScheduleDetail>lambdaQuery()
|
||||
.eq(SchProdScheduleDetail::getScheduleId, scheduleId)
|
||||
);
|
||||
if (details == null || details.isEmpty()) {
|
||||
throw new RuntimeException("产需单无明细数据,scheduleId=" + scheduleId);
|
||||
}
|
||||
|
||||
// 3. 遍历每条 detail,构建 SchProdScheduleItem 列表
|
||||
List<SchProdScheduleItem> addList = new ArrayList<>(details.size());
|
||||
for (SchProdScheduleDetail detail : details) {
|
||||
SchProdScheduleItem item = new SchProdScheduleItem();
|
||||
|
||||
// 从 header 复制所有字段
|
||||
item.setScheduleNo(header.getScheduleNo());
|
||||
item.setProdDate(header.getProdDate());
|
||||
item.setScheduleStatus(2L); // 已下达
|
||||
item.setTotalPlanWeight(header.getTotalPlanWeight());
|
||||
item.setRelContractNo(header.getRelContractNo());
|
||||
item.setBusinessUser(header.getBusinessUser());
|
||||
item.setBusinessPhone(header.getBusinessPhone());
|
||||
item.setOrderDate(header.getOrderDate());
|
||||
item.setCustomerName(header.getCustomerName());
|
||||
item.setDeliveryCycle(header.getDeliveryCycle());
|
||||
item.setUsePurpose(header.getUsePurpose());
|
||||
item.setProductType(header.getProductType());
|
||||
item.setThicknessTolerance(header.getThicknessTolerance());
|
||||
item.setWidthTolerance(header.getWidthTolerance());
|
||||
item.setSurfaceQuality(header.getSurfaceQuality());
|
||||
item.setSurfaceTreatment(header.getSurfaceTreatment());
|
||||
item.setInnerDiameter(header.getInnerDiameter());
|
||||
item.setOuterDiameter(header.getOuterDiameter());
|
||||
item.setPackReq(header.getPackReq());
|
||||
item.setCutEdgeReq(header.getCutEdgeReq());
|
||||
item.setSingleCoilWeight(header.getSingleCoilWeight());
|
||||
item.setWeightDeviation(header.getWeightDeviation());
|
||||
item.setOtherTechReq(header.getOtherTechReq());
|
||||
item.setPaymentDesc(header.getPaymentDesc());
|
||||
item.setRemark(header.getRemark());
|
||||
// 不复制 returnReason
|
||||
|
||||
// 从 detail 复制
|
||||
item.setSpec(detail.getSpec());
|
||||
item.setMaterial(detail.getMaterial());
|
||||
item.setScheduleWeight(detail.getScheduleWeight());
|
||||
item.setProductItem(detail.getProductType());
|
||||
item.setRowRemark(detail.getRemark());
|
||||
|
||||
// 来源追溯(未合并,各存明细ID)
|
||||
item.setScheduleDetailIds(String.valueOf(detail.getScheduleDetailId()));
|
||||
|
||||
validEntityBeforeSave(item);
|
||||
addList.add(item);
|
||||
}
|
||||
|
||||
// 4. 批量插入
|
||||
baseMapper.insertBatch(addList);
|
||||
|
||||
// 5. 更新产需单状态为 2(已下达)
|
||||
header.setScheduleStatus(2L);
|
||||
schProdScheduleMapper.updateById(header);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并校验:检查待合并记录来源产需单的 header 字段是否一致
|
||||
*/
|
||||
@Override
|
||||
public SchProdScheduleItemMergeValidateVo validateMerge(SchProdScheduleItemMergeValidateBo validateBo) {
|
||||
List<Long> ids = validateBo.getIds();
|
||||
|
||||
// 1. 查询所有待合并的 sch_prod_schedule_item 记录
|
||||
List<SchProdScheduleItem> items = baseMapper.selectBatchIds(ids);
|
||||
if (items == null || items.size() < 2) {
|
||||
throw new RuntimeException("待合并记录不足,至少需要2条");
|
||||
}
|
||||
|
||||
// 2. 解析每条记录的 scheduleIds(逗号分隔),取第一个作为源 schedule_id
|
||||
// 查询所有源 sch_prod_schedule 记录
|
||||
List<Long> sourceScheduleIds = new ArrayList<>();
|
||||
for (SchProdScheduleItem item : items) {
|
||||
String sids = item.getScheduleDetailIds();
|
||||
if (sids != null && !sids.isEmpty()) {
|
||||
String[] parts = sids.split(",");
|
||||
if (parts.length > 0) {
|
||||
String firstId = parts[0].trim();
|
||||
if (!firstId.isEmpty()) {
|
||||
sourceScheduleIds.add(Long.valueOf(firstId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
List<Long> distinctSourceIds = sourceScheduleIds.stream().distinct().collect(Collectors.toList());
|
||||
List<SchProdSchedule> sourceHeaders = schProdScheduleMapper.selectBatchIds(distinctSourceIds);
|
||||
Map<Long, SchProdSchedule> headerMap = new LinkedHashMap<>();
|
||||
for (SchProdSchedule h : sourceHeaders) {
|
||||
headerMap.put(h.getScheduleId(), h);
|
||||
}
|
||||
|
||||
// 3. 定义要比较的字段列表(字段名 → 中文标签)
|
||||
Map<String, String> compareFields = new LinkedHashMap<>();
|
||||
compareFields.put("scheduleNo", "排产单号");
|
||||
compareFields.put("customerName", "订货单位");
|
||||
compareFields.put("businessUser", "业务员");
|
||||
compareFields.put("businessPhone", "联系电话");
|
||||
compareFields.put("deliveryCycle", "交货期(天)");
|
||||
compareFields.put("usePurpose", "产品用途");
|
||||
compareFields.put("productType", "品名");
|
||||
compareFields.put("thicknessTolerance", "厚度公差");
|
||||
compareFields.put("widthTolerance", "宽度公差");
|
||||
compareFields.put("surfaceQuality", "表面质量");
|
||||
compareFields.put("surfaceTreatment", "表面处理");
|
||||
compareFields.put("innerDiameter", "内径尺寸");
|
||||
compareFields.put("outerDiameter", "外径要求");
|
||||
compareFields.put("packReq", "包装要求");
|
||||
compareFields.put("cutEdgeReq", "切边要求");
|
||||
compareFields.put("singleCoilWeight", "单件重量");
|
||||
compareFields.put("weightDeviation", "交货重量偏差");
|
||||
compareFields.put("otherTechReq", "其他技术要求");
|
||||
compareFields.put("paymentDesc", "付款情况说明");
|
||||
compareFields.put("relContractNo", "关联销售合同号");
|
||||
compareFields.put("totalPlanWeight", "排产总计划吨数");
|
||||
compareFields.put("orderDate", "订单日期");
|
||||
|
||||
// 4. 逐一比较字段
|
||||
List<SchProdScheduleItemMergeValidateVo.FieldDiff> diffs = new ArrayList<>();
|
||||
boolean allConsistent = true;
|
||||
|
||||
for (Map.Entry<String, String> entry : compareFields.entrySet()) {
|
||||
String fieldName = entry.getKey();
|
||||
String fieldLabel = entry.getValue();
|
||||
Map<String, Object> values = new LinkedHashMap<>();
|
||||
|
||||
for (SchProdSchedule header : sourceHeaders) {
|
||||
Long headerId = header.getScheduleId();
|
||||
Object val = getFieldValue(header, fieldName);
|
||||
values.put(String.valueOf(headerId), val);
|
||||
}
|
||||
|
||||
// 判断所有值是否一致
|
||||
boolean same = values.values().stream().distinct().count() <= 1;
|
||||
if (!same) {
|
||||
allConsistent = false;
|
||||
SchProdScheduleItemMergeValidateVo.FieldDiff diff = new SchProdScheduleItemMergeValidateVo.FieldDiff();
|
||||
diff.setFieldName(fieldName);
|
||||
diff.setFieldLabel(fieldLabel);
|
||||
diff.setValues(values);
|
||||
diffs.add(diff);
|
||||
}
|
||||
}
|
||||
|
||||
// 4.1 比对明细级字段(spec, material, productItem)—— 这些字段来自 item 自身,不来自 header
|
||||
Map<String, String> itemCompareFields = new LinkedHashMap<>();
|
||||
itemCompareFields.put("spec", "规格");
|
||||
itemCompareFields.put("material", "材质");
|
||||
itemCompareFields.put("productItem", "品名项");
|
||||
|
||||
for (Map.Entry<String, String> entry : itemCompareFields.entrySet()) {
|
||||
String fieldName = entry.getKey();
|
||||
String fieldLabel = entry.getValue();
|
||||
Map<String, Object> values = new LinkedHashMap<>();
|
||||
|
||||
for (SchProdScheduleItem item : items) {
|
||||
Object val = getItemFieldValue(item, fieldName);
|
||||
values.put(String.valueOf(item.getScheduleId()), val);
|
||||
}
|
||||
|
||||
boolean same = values.values().stream().distinct().count() <= 1;
|
||||
if (!same) {
|
||||
allConsistent = false;
|
||||
SchProdScheduleItemMergeValidateVo.FieldDiff diff = new SchProdScheduleItemMergeValidateVo.FieldDiff();
|
||||
diff.setFieldName(fieldName);
|
||||
diff.setFieldLabel(fieldLabel);
|
||||
diff.setValues(values);
|
||||
diffs.add(diff);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 构建 ItemInfo 快照列表
|
||||
List<SchProdScheduleItemMergeValidateVo.ItemInfo> itemInfos = new ArrayList<>();
|
||||
BigDecimal totalWeight = BigDecimal.ZERO;
|
||||
for (SchProdScheduleItem item : items) {
|
||||
SchProdScheduleItemMergeValidateVo.ItemInfo info = new SchProdScheduleItemMergeValidateVo.ItemInfo();
|
||||
info.setItemId(item.getScheduleId());
|
||||
// 解析第一个源 schedule_id
|
||||
String sids = item.getScheduleDetailIds();
|
||||
Long firstSourceId = null;
|
||||
if (sids != null && !sids.isEmpty()) {
|
||||
String[] parts = sids.split(",");
|
||||
if (parts.length > 0) {
|
||||
try {
|
||||
firstSourceId = Long.valueOf(parts[0].trim());
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
}
|
||||
info.setSourceScheduleId(firstSourceId);
|
||||
info.setScheduleNo(item.getScheduleNo());
|
||||
info.setCustomerName(item.getCustomerName());
|
||||
info.setProductType(item.getProductType());
|
||||
info.setSpec(item.getSpec());
|
||||
info.setMaterial(item.getMaterial());
|
||||
info.setScheduleWeight(item.getScheduleWeight());
|
||||
info.setScheduleDetailIds(item.getScheduleDetailIds());
|
||||
// header 字段
|
||||
info.setBusinessUser(item.getBusinessUser());
|
||||
info.setBusinessPhone(item.getBusinessPhone());
|
||||
info.setDeliveryCycle(item.getDeliveryCycle());
|
||||
info.setUsePurpose(item.getUsePurpose());
|
||||
info.setThicknessTolerance(item.getThicknessTolerance());
|
||||
info.setWidthTolerance(item.getWidthTolerance());
|
||||
info.setSurfaceQuality(item.getSurfaceQuality());
|
||||
info.setSurfaceTreatment(item.getSurfaceTreatment());
|
||||
info.setInnerDiameter(item.getInnerDiameter());
|
||||
info.setOuterDiameter(item.getOuterDiameter());
|
||||
info.setPackReq(item.getPackReq());
|
||||
info.setCutEdgeReq(item.getCutEdgeReq());
|
||||
info.setSingleCoilWeight(item.getSingleCoilWeight());
|
||||
info.setWeightDeviation(item.getWeightDeviation());
|
||||
info.setOtherTechReq(item.getOtherTechReq());
|
||||
info.setPaymentDesc(item.getPaymentDesc());
|
||||
info.setRelContractNo(item.getRelContractNo());
|
||||
info.setTotalPlanWeight(item.getTotalPlanWeight());
|
||||
info.setOrderDate(item.getOrderDate());
|
||||
itemInfos.add(info);
|
||||
totalWeight = totalWeight.add(item.getScheduleWeight() != null ? item.getScheduleWeight() : BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
// 6. 构建汇总
|
||||
SchProdScheduleItemMergeValidateVo.MergedSummary summary = new SchProdScheduleItemMergeValidateVo.MergedSummary();
|
||||
summary.setItemCount(items.size());
|
||||
summary.setTotalScheduleWeight(totalWeight);
|
||||
|
||||
// 7. 组装返回
|
||||
SchProdScheduleItemMergeValidateVo vo = new SchProdScheduleItemMergeValidateVo();
|
||||
vo.setConsistent(allConsistent);
|
||||
vo.setDiffs(diffs);
|
||||
vo.setItems(itemInfos);
|
||||
vo.setSummary(summary);
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反射获取字段值(用于动态字段比较 — SchProdSchedule header)
|
||||
*/
|
||||
private Object getFieldValue(SchProdSchedule header, String fieldName) {
|
||||
try {
|
||||
java.lang.reflect.Field field = SchProdSchedule.class.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(header);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 反射获取字段值(用于动态字段比较 — SchProdScheduleItem 明细)
|
||||
*/
|
||||
private Object getItemFieldValue(SchProdScheduleItem item, String fieldName) {
|
||||
try {
|
||||
java.lang.reflect.Field field = SchProdScheduleItem.class.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(item);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +196,8 @@ public class SchProdScheduleServiceImpl implements ISchProdScheduleService {
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getOtherTechReq()), SchProdSchedule::getOtherTechReq, bo.getOtherTechReq());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getPaymentDesc()), SchProdSchedule::getPaymentDesc, bo.getPaymentDesc());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getReturnReason()), SchProdSchedule::getReturnReason, bo.getReturnReason());
|
||||
// 根据创建时间倒叙排序
|
||||
lqw.orderByDesc(SchProdSchedule::getCreateTime);
|
||||
return lqw;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user