feat(schProdScheduleItem): 新增明细下发功能支持逐条明细独立下发

- 在 ISchProdScheduleItemService 中新增 receiveFromDetail 方法接口
- 在 SchProdScheduleItemController 中新增 /receiveFromDetail 接口
- 在 SchProdScheduleItemServiceImpl 中实现明细下发业务逻辑
- 新增 SchProdScheduleItemDispatchBo 数据传输对象
- 支持按明细+工序步骤维度删除重复记录后再重新插入
- 使用明细自身 scheduleDate 作为生产日期确保多天排产准确性
- 实现批量插入排产单明细项功能
This commit is contained in:
2026-07-10 11:15:30 +08:00
parent df89a088ee
commit c6c77294af
4 changed files with 188 additions and 0 deletions

View File

@@ -24,6 +24,7 @@ import com.klp.flow.domain.bo.SchProdScheduleItemMergeBo;
import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo; import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo;
import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo; import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo;
import com.klp.flow.domain.bo.SchProdScheduleItemSortBo; import com.klp.flow.domain.bo.SchProdScheduleItemSortBo;
import com.klp.flow.domain.bo.SchProdScheduleItemDispatchBo;
import com.klp.flow.service.ISchProdScheduleItemService; import com.klp.flow.service.ISchProdScheduleItemService;
import com.klp.common.core.page.TableDataInfo; import com.klp.common.core.page.TableDataInfo;
@@ -132,6 +133,18 @@ public class SchProdScheduleItemController extends BaseController {
return toAjax(iSchProdScheduleItemService.receiveByBo(receiveBo)); return toAjax(iSchProdScheduleItemService.receiveByBo(receiveBo));
} }
/**
* 明细下发:逐条从 sch_prod_schedule_detail 下发到 sch_prod_schedule_item
* 生产日期使用明细的 scheduleDate支持一条一条明细独立下发
* 若已下发过(同明细+同工序),先删除旧记录再重新插入
*/
@Log(title = "排产单主加明细可合并", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/receiveFromDetail")
public R<Void> receiveFromDetail(@Validated @RequestBody SchProdScheduleItemDispatchBo dispatchBo) {
return toAjax(iSchProdScheduleItemService.receiveFromDetail(dispatchBo));
}
/** /**
* 合并校验:检查待合并记录来源产需单的 header 字段是否一致 * 合并校验:检查待合并记录来源产需单的 header 字段是否一致
*/ */

View File

@@ -0,0 +1,69 @@
package com.klp.flow.domain.bo;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 排产明细下发请求对象 —— 从 sch_prod_schedule_detail 逐条下发到 sch_prod_schedule_item
*
* @author klp
* @date 2026-07-10
*/
@Data
public class SchProdScheduleItemDispatchBo {
/**
* 明细下发配置列表(每条明细独立下发,各自携带工序步骤)
*/
@NotEmpty(message = "明细下发配置不能为空")
private List<DetailDispatchConfig> detailList;
/**
* 单条明细的下发配置
*/
@Data
public static class DetailDispatchConfig {
/**
* 排产明细IDsch_prod_schedule_detail.schedule_detail_id
*/
@NotNull(message = "明细ID不能为空")
private Long scheduleDetailId;
/**
* 工序IDsch_prod_process.process_id用于区分下发后按工序分组
*/
@NotNull(message = "工序ID不能为空")
private Long processId;
/**
* 工序步骤列表
*/
@NotEmpty(message = "工序步骤不能为空")
private List<StepConfig> stepList;
}
/**
* 工序步骤配置
*/
@Data
public static class StepConfig {
/**
* 步骤IDsch_prod_process_step.step_id下发后存为 actionId
*/
@NotNull(message = "步骤ID不能为空")
private Long stepId;
/**
* 步骤顺序号
*/
private Long stepOrder;
/**
* 步骤名称
*/
private String stepName;
}
}

View File

@@ -8,6 +8,7 @@ import com.klp.flow.domain.bo.SchProdScheduleItemMergeBo;
import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo; import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo;
import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo; import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo;
import com.klp.flow.domain.bo.SchProdScheduleItemSortBo; import com.klp.flow.domain.bo.SchProdScheduleItemSortBo;
import com.klp.flow.domain.bo.SchProdScheduleItemDispatchBo;
import com.klp.common.core.page.TableDataInfo; import com.klp.common.core.page.TableDataInfo;
import com.klp.common.core.domain.PageQuery; import com.klp.common.core.domain.PageQuery;
@@ -76,4 +77,11 @@ public interface ISchProdScheduleItemService {
* 保存已排产拖拽排序 * 保存已排产拖拽排序
*/ */
Boolean saveSort(SchProdScheduleItemSortBo sortBo); Boolean saveSort(SchProdScheduleItemSortBo sortBo);
/**
* 明细下发:逐条从 sch_prod_schedule_detail 下发到 sch_prod_schedule_item
* 若已下发过(同明细+同工序),先删除再重新插入
* 生产日期使用明细自身的 scheduleDate而非主表的 prodDate
*/
Boolean receiveFromDetail(SchProdScheduleItemDispatchBo dispatchBo);
} }

View File

@@ -14,6 +14,7 @@ import com.klp.flow.domain.bo.SchProdScheduleItemMergeBo;
import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo; import com.klp.flow.domain.bo.SchProdScheduleItemReceiveBo;
import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo; import com.klp.flow.domain.bo.SchProdScheduleItemMergeValidateBo;
import com.klp.flow.domain.bo.SchProdScheduleItemSortBo; import com.klp.flow.domain.bo.SchProdScheduleItemSortBo;
import com.klp.flow.domain.bo.SchProdScheduleItemDispatchBo;
import com.klp.flow.domain.vo.SchProdScheduleItemVo; import com.klp.flow.domain.vo.SchProdScheduleItemVo;
import com.klp.flow.domain.vo.SchProdScheduleItemMergeValidateVo; import com.klp.flow.domain.vo.SchProdScheduleItemMergeValidateVo;
import com.klp.flow.domain.SchProdScheduleItem; import com.klp.flow.domain.SchProdScheduleItem;
@@ -480,6 +481,103 @@ public class SchProdScheduleItemServiceImpl implements ISchProdScheduleItemServi
return true; return true;
} }
/**
* 明细下发:逐条从 sch_prod_schedule_detail 下发到 sch_prod_schedule_item
* 若已下发过(同明细+同工序步骤),先删除再重新插入,避免重复
* 生产日期使用明细自身的 scheduleDate保证多天排产的时间准确性
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean receiveFromDetail(SchProdScheduleItemDispatchBo dispatchBo) {
List<SchProdScheduleItemDispatchBo.DetailDispatchConfig> detailList = dispatchBo.getDetailList();
for (SchProdScheduleItemDispatchBo.DetailDispatchConfig config : detailList) {
Long detailId = config.getScheduleDetailId();
// 1. 查询排产明细
SchProdScheduleDetail detail = schProdScheduleDetailMapper.selectById(detailId);
if (detail == null) {
throw new RuntimeException("排产明细不存在scheduleDetailId=" + detailId);
}
// 2. 查询产需单主表
SchProdSchedule header = schProdScheduleMapper.selectById(detail.getScheduleId());
if (header == null) {
throw new RuntimeException("产需单不存在scheduleId=" + detail.getScheduleId());
}
// 3. 收集本次下发的步骤ID列表
List<Long> stepIds = config.getStepList().stream()
.map(SchProdScheduleItemDispatchBo.StepConfig::getStepId)
.collect(Collectors.toList());
// 4. 删除该明细上一次下发的内容(同明细 + 同工序步骤),区分工序避免误删
baseMapper.delete(
Wrappers.<SchProdScheduleItem>lambdaQuery()
.eq(SchProdScheduleItem::getScheduleDetailIds, String.valueOf(detailId))
.in(SchProdScheduleItem::getActionId, stepIds)
);
// 5. 根据工序步骤逐条生成新的排产单明细项
List<SchProdScheduleItem> addList = new ArrayList<>();
for (SchProdScheduleItemDispatchBo.StepConfig step : config.getStepList()) {
SchProdScheduleItem item = new SchProdScheduleItem();
// —— 从 header 复制主表字段 ——
item.setScheduleNo(header.getScheduleNo());
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());
// —— 生产日期使用明细自身的 scheduleDate实现逐条明细独立时间 ——
item.setProdDate(detail.getScheduleDate());
// —— 从 detail 复制明细字段 ——
item.setSpec(detail.getSpec());
item.setMaterial(detail.getMaterial());
item.setScheduleWeight(detail.getScheduleWeight());
item.setProductItem(detail.getProductType());
item.setRowRemark(detail.getRemark());
// —— 工序步骤 ——
item.setActionId(step.getStepId());
// —— 来源追溯未合并存单明细ID ——
item.setScheduleDetailIds(String.valueOf(detailId));
validEntityBeforeSave(item);
addList.add(item);
}
// 6. 批量插入新记录
if (!addList.isEmpty()) {
baseMapper.insertBatch(addList);
}
}
return true;
}
/** /**
* 合并校验:检查待合并记录来源产需单的 header 字段是否一致 * 合并校验:检查待合并记录来源产需单的 header 字段是否一致
*/ */