feat: 实现产需单按工序步骤生成排产明细功能

1. 重构接收产需单接口,支持按配置工序步骤生成明细
2. 新增工艺、工艺步骤CRUD接口与管理页面
3. 新增工序选择组件
4. 优化产需单页面,增加历史记录功能
5. 为排产明细添加工序步骤名称展示
This commit is contained in:
2026-07-04 15:43:05 +08:00
parent ce09ac9da3
commit 2fde9ec993
12 changed files with 1529 additions and 427 deletions

View File

@@ -2,6 +2,8 @@ package com.klp.flow.domain.bo;
import lombok.Data;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
* 产需单接收请求对象
@@ -18,4 +20,55 @@ public class SchProdScheduleItemReceiveBo {
@NotNull(message = "排产单主表ID不能为空")
private Long scheduleId;
/**
* 明细工序配置列表
*/
@NotEmpty(message = "明细工序配置不能为空")
private List<DetailProcessConfig> detailProcessList;
/**
* 明细工序配置
*/
@Data
public static class DetailProcessConfig {
/**
* 产需单明细ID
*/
@NotNull(message = "明细ID不能为空")
private Long scheduleDetailId;
/**
* 工序ID
*/
@NotNull(message = "工序ID不能为空")
private Long processId;
/**
* 工序步骤列表
*/
@NotEmpty(message = "工序步骤不能为空")
private List<StepConfig> stepList;
}
/**
* 工序步骤配置
*/
@Data
public static class StepConfig {
/**
* 步骤ID
*/
@NotNull(message = "步骤ID不能为空")
private Long stepId;
/**
* 步骤顺序号
*/
private Long stepOrder;
/**
* 步骤名称
*/
private String stepName;
}
}

View File

@@ -2,10 +2,13 @@ package com.klp.flow.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.TableField;
import com.klp.common.annotation.ExcelDictFormat;
import com.klp.common.convert.ExcelDictConvert;
import lombok.Data;
import java.util.List;
/**
* 工序定义主视图对象 sch_prod_process
@@ -44,5 +47,10 @@ public class SchProdProcessVo {
@ExcelProperty(value = "备注")
private String remark;
/**
* 工艺步骤列表
*/
@TableField(exist = false)
private List<SchProdProcessStepVo> stepList;
}

View File

@@ -2,6 +2,7 @@ package com.klp.flow.domain.vo;
import java.math.BigDecimal;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
@@ -195,6 +196,12 @@ public class SchProdScheduleItemVo {
@ExcelProperty(value = "工序ID")
private Long actionId;
/**
* 工序步骤名称
*/
@TableField(exist = false)
private String stepName;
/**
* 规格 例1.0X1250
*/

View File

@@ -11,13 +11,18 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.klp.flow.domain.bo.SchProdProcessBo;
import com.klp.flow.domain.vo.SchProdProcessVo;
import com.klp.flow.domain.vo.SchProdProcessStepVo;
import com.klp.flow.domain.SchProdProcess;
import com.klp.flow.domain.SchProdProcessStep;
import com.klp.flow.mapper.SchProdProcessMapper;
import com.klp.flow.mapper.SchProdProcessStepMapper;
import com.klp.flow.service.ISchProdProcessService;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.stream.Collectors;
/**
* 工序定义主Service业务层处理
@@ -30,13 +35,18 @@ import java.util.Collection;
public class SchProdProcessServiceImpl implements ISchProdProcessService {
private final SchProdProcessMapper baseMapper;
private final SchProdProcessStepMapper stepMapper;
/**
* 查询工序定义主
*/
@Override
public SchProdProcessVo queryById(Long processId){
return baseMapper.selectVoById(processId);
SchProdProcessVo vo = baseMapper.selectVoById(processId);
if (vo != null) {
fillStepList(Collections.singletonList(vo));
}
return vo;
}
/**
@@ -46,6 +56,9 @@ public class SchProdProcessServiceImpl implements ISchProdProcessService {
public TableDataInfo<SchProdProcessVo> queryPageList(SchProdProcessBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SchProdProcess> lqw = buildQueryWrapper(bo);
Page<SchProdProcessVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
if (result.getRecords() != null && !result.getRecords().isEmpty()) {
fillStepList(result.getRecords());
}
return TableDataInfo.build(result);
}
@@ -55,7 +68,34 @@ public class SchProdProcessServiceImpl implements ISchProdProcessService {
@Override
public List<SchProdProcessVo> queryList(SchProdProcessBo bo) {
LambdaQueryWrapper<SchProdProcess> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
List<SchProdProcessVo> list = baseMapper.selectVoList(lqw);
if (list != null && !list.isEmpty()) {
fillStepList(list);
}
return list;
}
/**
* 批量填充工艺步骤列表
*/
private void fillStepList(List<SchProdProcessVo> voList) {
List<Long> processIds = voList.stream()
.map(SchProdProcessVo::getProcessId)
.filter(id -> id != null)
.distinct()
.collect(Collectors.toList());
if (processIds.isEmpty()) {
return;
}
LambdaQueryWrapper<SchProdProcessStep> stepQw = Wrappers.lambdaQuery();
stepQw.in(SchProdProcessStep::getProcessId, processIds);
stepQw.orderByAsc(SchProdProcessStep::getStepOrder);
List<SchProdProcessStepVo> allSteps = stepMapper.selectVoList(stepQw);
Map<Long, List<SchProdProcessStepVo>> stepMap = allSteps.stream()
.collect(Collectors.groupingBy(SchProdProcessStepVo::getProcessId));
for (SchProdProcessVo vo : voList) {
vo.setStepList(stepMap.getOrDefault(vo.getProcessId(), Collections.emptyList()));
}
}
private LambdaQueryWrapper<SchProdProcess> buildQueryWrapper(SchProdProcessBo bo) {

View File

@@ -18,15 +18,18 @@ 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.domain.SchProdProcessStep;
import com.klp.flow.mapper.SchProdScheduleItemMapper;
import com.klp.flow.mapper.SchProdScheduleMapper;
import com.klp.flow.mapper.SchProdScheduleDetailMapper;
import com.klp.flow.mapper.SchProdProcessStepMapper;
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.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -46,6 +49,7 @@ public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemServi
private final SchProdScheduleItemMapper baseMapper;
private final SchProdScheduleMapper schProdScheduleMapper;
private final SchProdScheduleDetailMapper schProdScheduleDetailMapper;
private final SchProdProcessStepMapper stepMapper;
/**
* 查询排产单主加明细可合并
@@ -62,6 +66,9 @@ public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemServi
public TableDataInfo<SchProdScheduleItemVo> queryPageList(SchProdScheduleItemBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SchProdScheduleItem> lqw = buildQueryWrapper(bo);
Page<SchProdScheduleItemVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
if (result.getRecords() != null && !result.getRecords().isEmpty()) {
fillStepName(result.getRecords());
}
return TableDataInfo.build(result);
}
@@ -71,7 +78,11 @@ public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemServi
@Override
public List<SchProdScheduleItemVo> queryList(SchProdScheduleItemBo bo) {
LambdaQueryWrapper<SchProdScheduleItem> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
List<SchProdScheduleItemVo> list = baseMapper.selectVoList(lqw);
if (list != null && !list.isEmpty()) {
fillStepName(list);
}
return list;
}
private LambdaQueryWrapper<SchProdScheduleItem> buildQueryWrapper(SchProdScheduleItemBo bo) {
@@ -113,6 +124,30 @@ public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemServi
return lqw;
}
/**
* 批量填充工序步骤名称
*/
private void fillStepName(List<SchProdScheduleItemVo> voList) {
List<Long> stepIds = voList.stream()
.map(SchProdScheduleItemVo::getActionId)
.filter(id -> id != null)
.distinct()
.collect(Collectors.toList());
if (stepIds.isEmpty()) {
return;
}
LambdaQueryWrapper<SchProdProcessStep> stepQw = Wrappers.lambdaQuery();
stepQw.in(SchProdProcessStep::getStepId, stepIds);
List<SchProdProcessStep> steps = stepMapper.selectList(stepQw);
Map<Long, String> stepNameMap = steps.stream()
.collect(Collectors.toMap(SchProdProcessStep::getStepId, SchProdProcessStep::getStepName, (v1, v2) -> v1));
for (SchProdScheduleItemVo vo : voList) {
if (vo.getActionId() != null) {
vo.setStepName(stepNameMap.get(vo.getActionId()));
}
}
}
/**
* 新增排产单主加明细可合并
*/
@@ -195,12 +230,13 @@ public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemServi
}
/**
* 接收产需单:从 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();
List<SchProdScheduleItemReceiveBo.DetailProcessConfig> detailProcessList = receiveBo.getDetailProcessList();
// 1. 查询产需单主表
SchProdSchedule header = schProdScheduleMapper.selectById(scheduleId);
@@ -217,57 +253,74 @@ public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemServi
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);
// 3. 构建明细ID到工序配置的映射
Map<Long, SchProdScheduleItemReceiveBo.DetailProcessConfig> processConfigMap = new HashMap<>();
for (SchProdScheduleItemReceiveBo.DetailProcessConfig config : detailProcessList) {
processConfigMap.put(config.getScheduleDetailId(), config);
}
// 4. 批量插入
// 4. 遍历每条 detail根据工序步骤生成多条 SchProdScheduleItem
List<SchProdScheduleItem> addList = new ArrayList<>();
for (SchProdScheduleDetail detail : details) {
SchProdScheduleItemReceiveBo.DetailProcessConfig processConfig = processConfigMap.get(detail.getScheduleDetailId());
if (processConfig == null || processConfig.getStepList() == null || processConfig.getStepList().isEmpty()) {
throw new RuntimeException("明细ID=" + detail.getScheduleDetailId() + " 未配置工序步骤");
}
// 根据工序步骤数量生成多条排产单明细
for (SchProdScheduleItemReceiveBo.StepConfig step : processConfig.getStepList()) {
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为actionId
item.setActionId(step.getStepId());
// 来源追溯未合并各存明细ID
item.setScheduleDetailIds(String.valueOf(detail.getScheduleDetailId()));
validEntityBeforeSave(item);
addList.add(item);
}
}
// 5. 批量插入
baseMapper.insertBatch(addList);
// 5. 更新产需单状态为 2已下达
// 6. 更新产需单状态为 2已下达
header.setScheduleStatus(2L);
schProdScheduleMapper.updateById(header);