2026-04-14
供应商交货计划完整流程分析
一、流程概述
采购订单(PO) → 供应商交货计划 → 提货计划 → 送货单 → 签收单 → 质检指令单 → 质检单 → 入库单
注意: 供应商交货计划(supp_delivery_plan)和提货计划(pick_goods_plan)是两个独立的模块,交货计划主要用于供应商协同平台展示,实际业务流转通过提货计划进行。
二、各环节详细代码分析
1. 供应商交货计划 (supp_delivery_plan)
1.1 模块位置
erp-services-suppcooperate/biz/src/main/java/com/idelamu/erp/suppcooperate/deliveryplan/
1.2 数据库表
supp_delivery_plan -- 供应商交货计划主表supp_delivery_plan_detail -- 供应商交货计划明细表
1.3 核心接口
Controller: SuppDeliveryPlanController.java
@RestController@RequestMapping("supp/deliveryplan")public class SuppDeliveryPlanController { // 生成交货计划详情(根据采购订单) @PostMapping(value = "createDeliveryPlanDetail") public Result<SuppDeliveryPlanDetailVo> createDeliveryPlanDetail( @RequestBody @Validated CreateDeliveryPlanDetailDto detailDto) // 创建并保存 @PostMapping(value = "createAndSave") public Result<DeliveryCreateVo> createAndSave( @RequestBody @Validated SuppDeliveryPlanCreateAndSaveDto addDto) // 更新并生效 @PostMapping(value = "updateAndEffect") public Result<Long> updateAndEffect( @RequestBody @Validated SuppDeliveryPlanUpdateAndEffectDto effectDto) // 生效 @PostMapping(value = "effect") public Result effect(@RequestBody @Validated SuppDeliveryPlanEffectDto effectDto)}
Service: SuppDeliveryPlanService.java
// 生成交货计划详情 - 根据采购订单信息生成public SuppDeliveryPlanDetailVo createDeliveryPlanDetail(CreateDeliveryPlanDetailDto detailDto) { // 查询采购订单信息 SuppPoApplyVo detail = suppPoApplyService.getDetail(poDetailDto); // 将采购订单下的详情,通过sku进行分组聚合 Map<String, List<SuppPoApplyDetailVo>> map = detail.getSuppPoApplyDetailVos().stream() .collect(Collectors.groupingBy(SuppPoApplyDetailVo::getSku)); // 构建交货计划详情 for (Map.Entry<String, List<SuppPoApplyDetailVo>> entry : map.entrySet()) { // 设置SKU、采购量等信息 } return detailVo;}// 创建并保存交货计划public DeliveryCreateVo createAndSave(SuppDeliveryPlanCreateAndSaveDto addDto) { Long suppId = suppRelateService.getSupp(); // 获取当前供应商 String newBillNo = "JHJH" + LocalDate.now().toString().replaceAll("-", "") + iSequenceApiService.nextSeq(SequenceNameTypeEnum.SUPP_DELIVERY_PLAN.getValue(), 5); // 构建主表实体 SuppDeliveryPlanEntity entity = buildAddEntity(addDto, newBillNo, id, suppId); suppDeliveryPlanMapper.insert(entity); // 构建明细表 List<SuppDeliveryPlanDetailEntity> detailEntities = ...; suppDeliveryPlanDetailMapper.insertBatch(detailEntities); return vo;}
1.4 状态流转
DRAFT(草稿) → EFFECTIVE(生效)
2. 提货计划 (pick_goods_plan)
2.1 模块位置
erp-services-purchase/biz/src/main/java/com/idelamu/erp/purchase/pickgoodsplan/
2.2 数据库表
pick_goods_plan -- 提货计划表
2.3 核心接口
Controller: PickGoodsPlanController.java
@RestController@RequestMapping("pgPlan")public class PickGoodsPlanController { // 导入提货计划 @PostMapping(value = "importFile") public Result<ConvertErrorVo> importFile(MultipartFile file, @RequestParam("planVersionBillNo") String planVersionBillNo) // 提交 @PostMapping(value = "/submit") public Result submit(@RequestBody List<Long> ids) // 撤提 @PostMapping(value = "/cancelAudit") public Result cancelAudit(@RequestBody List<Long> ids) // 完结 @PostMapping(value = "/end") public Result end(@RequestBody List<Long> ids) // 关联提货计划下拉列表(送货单使用) @PostMapping(value = "/listPgPlanRelate") public Result<ListPgPlanRelateVo> listPgPlanRelate(@RequestBody ListPgPlanRelateDto dto)}
Service: PickGoodsPlanService.java
// 导入提货计划public ConvertErrorVo importFile(MultipartFile multipartFile, String planVersionBillNo) { // 解析Excel List<ConvertPgPlan> convertPgPlan = ExcelUtils.parseExcel(...); // 查询采购订单已通过、未完结状态下的单据,按sku聚合,统计未入库数量 List<SkuAndNoStockCntBo> skuAndNoStockCntBo = poApplyService.getNoStockCntBySku(skus); // 校验并构建提货计划数据 for (ConvertPgPlanVo item : pgPlanVos) { // 校验SKU、FNSKU、计划员等 // 构建PickGoodsPlanEntity } // 批量插入 pickGoodsPlanMapper.insertBatch(entities);}// 更新提货计划关联送货单public void updatePgPlanRelateShOrder(PgPlanRelateShOrderDto dto) { // 更新关联送货单编号、已关联数量}
2.4 状态流转
DRAFT(草稿) → SUBMITTED(已提交) → COMPLETED(已完结)
3. 送货单 (cg_sh_order)
3.1 模块位置
erp-services-purchase/biz/src/main/java/com/idelamu/erp/purchase/sh/
3.2 数据库表
cg_sh_order -- 送货单主表cg_sh_order_detail -- 送货单明细表cg_sh_order_detail_distribute -- 送货单明细分配表(关联提货计划)
3.3 核心接口
Controller: SendGoodsOrderController.java
@RestController@RequestMapping("sh")public class SendGoodsOrderController { // 新增送货单 @PostMapping(value = "add") public Result<Long> add(@RequestBody @Validated SendGoodsAddDto dto) // 送货 @PostMapping(value = "sendGoods") public Result sendGoods(@RequestBody @Validated List<Long> ids) // 签收 - 返回签收单数据 @PostMapping(value = "sign") public Result<QsOrderVo> sign(@RequestBody @Validated QsSaveDto signDto)}
Service: SendGoodsOrderService.java
// 新增送货单public Long add(SendGoodsAddDto dto) { // 获取采购订单明细信息 List<Long> poDetailIds = dto.getDetails().stream() .map(SendGoodsDetailAddDto::getPoDetailId).distinct().collect(Collectors.toList()); SendGoodsOrderVo sendGoodsOrderVo = poApplyService.genShOrder(poDetailIds); // 获取关联的提货计划 List<String> pickGoodsPlanBillNos = dto.getrelateAllPgPlan(); List<PickGoodsPlanEntity> PickGoodsPlans = pickGoodsPlanService.listPgPlanByPickGoodsPlanBillNo(pickGoodsPlanBillNos); // 校验提货计划状态和数量 checkParam(dto.getDetails(), detailsMap, PickGoodsPlans, dto); // 构建送货单实体 SendGoodsOrderEntity entity = SendGoodsOrderEntity.initSendGoodsOrderEntity(); entity.setBillNo("SH" + dateString + iSequenceApiService.nextSeq(...)); // 构建明细和分配表 List<SendGoodsOrderDetailEntity> details = initShOrderDetails(...); List<SendGoodsOrderDetailDistributeEntity> shDistributeEntities = ...; sendGoodsOrderMapper.insert(entity); sendGoodsOrderDetailMapper.insertBatch(details); sendGoodsOrderDetailDistributeMapper.insertBatch(shDistributeEntities); // 关联提货计划 - 更新提货计划的已关联数量 for (SendGoodsOrderDetailDistributeEntity detail : shDistributeEntities) { if(!StringUtils.isEmpty(detail.getPickGoodsPlanBillNo())) { PgPlanRelateShOrderDto pgPlanRelateShOrderDto = new PgPlanRelateShOrderDto(); pgPlanRelateShOrderDto.setBillNoRelate(Boolean.TRUE); pgPlanRelateShOrderDto.setBillNo(detail.getPickGoodsPlanBillNo()); pgPlanRelateShOrderDto.setSendGoodsBillNo(entity.getBillNo()); pgPlanRelateShOrderDto.setCorrelatedQty(detail.getDistributeQty()); pickGoodsPlanService.updatePgPlanRelateShOrder(pgPlanRelateShOrderDto); } } return entity.getId();}// 签收 - 生成签收单数据public QsOrderVo sign(Long shId) { SendGoodsOrderEntity shOrder = getShOrderById(shId); if(!SendGoodsStatusEnum.SHED.name().equals(shOrder.getStatus())) { throw new BusinessException(SysErrCode.PARAM_ERROR, "仅'已送货'状态的送货单可以签收!"); } // 构建签收单数据 QsOrderVo vo = new QsOrderVo() .setShOrderId(shOrder.getId()) .setShBillNo(shOrder.getBillNo()) .setSuppId(shOrder.getSuppId()) ...; List<QsDetailVo> qsDetails = sendGoodsOrderDetailMapper.sign(shId); vo.setQsDetails(qsDetails); return vo;}
3.4 状态流转
DRAFT(草稿) → SHED(已送货)
4. 签收单 (cg_qs_order)
4.1 模块位置
erp-services-purchase/biz/src/main/java/com/idelamu/erp/purchase/qs/
4.2 数据库表
cg_qs_order -- 签收单主表cg_qs_order_detail -- 签收单明细表
4.3 核心接口
Controller: QsOrderController.java
@RestController@RequestMapping("qs")public class QsOrderController { // 待签收列表 @PostMapping(value = "needQs") public Result<NeedQsVo> needQs(@RequestBody @Validated NeedQsDto needQsDto) // 保存签收单 @PostMapping(value = "save") public Result<Long> save(@RequestBody @Validated QsSaveDto signDto) // 确认签收 @PostMapping(value = "confirm") public Result confirm(@RequestBody @Validated QsConfirmDto confirmDto)}
Service: QsOrderService.java
// 保存签收单public Long save(QsSaveDto signDto) { // 从送货单获取签收数据 QsOrderVo qsOrderVo = sign(signDto.getShOrderId()); saveCheckParam(signDto, qsOrderVo); // 构建签收单实体 QsOrderEntity qsOrderEntity = new QsOrderEntity(); qsOrderEntity.setBillNo("QS" + dateString + iSequenceApiService.nextSeq(...)); qsOrderEntity.setStatus(QsStatusEnum.DRAFT.toString()); // 构建明细 List<QsOrderDetailEntity> qsOrderDetailEntities = initQsOrderDetails(...); qsOrderMapper.insert(qsOrderEntity); qsOrderDetailMapper.insertBatch(qsOrderDetailEntities); return qsOrderEntity.getId();}
4.4 状态流转
DRAFT(草稿) → CONFIRMED(已确认)
5. 质检指令单 (qc_cmd_order)
5.1 模块位置
erp-services-purchase/biz/src/main/java/com/idelamu/erp/purchase/qccmd/
5.2 数据库表
qc_cmd_order -- 质检指令单主表qc_cmd_order_detail -- 质检指令单明细表
5.3 核心接口
Controller: QcCmdOrderController.java
@RestController@RequestMapping("qccmd")public class QcCmdOrderController { // 生成质检指令单 @PostMapping(value = "genQcCmdAdd") public Result genQcCmdAdd(@RequestBody @Valid List<GenQcCmdAddDto> dtos) // 确认 @PostMapping(value = "/confirmByIds") public Result confirmByIds(@RequestBody List<Long> ids) // 生成质检单-弹窗 @PostMapping(value = "/initQcCmdPopUp") public Result<List<initQcCmdPopUpVo>> initQcCmdPopUp(@RequestBody List<Long> ids) // 生成质检单 @PostMapping(value = "/initQcCmd") public Result initQcCmd(@RequestBody @Validated List<SaveEstInspectionTimeDto> dtos) // 免检 @PostMapping(value = "/exemptionQc") public Result exemptionQc(@RequestBody @Validated ExemptionQcDto dto)}
Service: QcCmdOrderService.java
// 生成质检指令单public void genQcCmdAdd(List<GenQcCmdAddDto> dtos) { // 从采购订单明细获取信息 Map<Long, QcCmdOrderVo> detailVoMap = ...; // 按供应商分组 Map<Long, List<GenQcCmdAddDto>> suppGroup = dtos.stream() .collect(Collectors.groupingBy(dto -> detailVoMap.get(dto.getPoDetailId()).getSuppId())); for (Map.Entry<Long, List<GenQcCmdAddDto>> entry : suppGroup.entrySet()) { // 构建质检指令单 QcCmdOrderEntity entity = QcCmdOrderEntity.initQcCmdOrderEntity(); entity.setBillNo("QCCMD" + dateString + iSequenceApiService.nextSeq(...)); // 构建明细 List<QcCmdOrderDetailEntity> details = initQcCmdOrderDetails(...); qcCmdOrderMapper.insert(entity); qcCmdOrderDetailMapper.insertBatch(details); // 回写采购订单明细的已申请质检数量 poApplyService.refreshPoDetailRelate(relatePoDtos, "QCCMD"); }}// 生成质检单public void initQcCmd(List<SaveEstInspectionTimeDto> dtos, Boolean needCheckStatus) { // 获取已确认状态的质检指令单明细 List<QcCmdOrderDetailEntity> qcCmdOrderDetails = qcCmdOrderDetailMapper.selectList( new LambdaQueryWrapper<QcCmdOrderDetailEntity>() .in(QcCmdOrderDetailEntity::getId, detailIds) .eq(QcCmdOrderDetailEntity::getDetailStatus, QcCmdStatusEnum.CONFIRMED.name())); // 构建质检单数据 PgQcAddDto addPgQcDto = new PgQcAddDto(); addPgQcDto.setSuppId(qcCmdOrders.get(0).getSuppId()); addPgQcDto.setSuppCode(qcCmdOrders.get(0).getSuppCode()); addPgQcDto.setSuppName(qcCmdOrders.get(0).getSuppName()); ... // 调用质检单Service创建 pgQcService.add(addPgQcDto);}
5.4 状态流转
DRAFT(草稿) → CONFIRMED(已确认) → QCED(已质检)
6. 供应商质检单 (pg_qc_order)
6.1 模块位置
erp-services-purchase/biz/src/main/java/com/idelamu/erp/purchase/pgqc/
6.2 数据库表
pg_qc_order -- 质检单主表pg_qc_order_detail -- 质检单明细表pg_qc_order_detail_desc -- 质检单明细描述表(质检详情JSON)
6.3 核心接口
Service: PgQcService.java
// 新增质检单public Long add(PgQcAddDto dto) { addCheckParam(dto); // 构建质检单实体 PgQcOrderEntity entity = PgQcOrderEntity.initPgQcOrderEntity(); entity.setBillNo("QC" + dateString + iSequenceApiService.nextSeq(...)); entity.setStatus(QcCmdStatusEnum.DRAFT.name()); // 构建明细 List<PgQcOrderDetailEntity> details = initPgQcOrderDetails(entity.getId(), dto.getDetails()); pgQcMapper.insert(entity); pgQcOrderDetailMapper.insertBatch(details); // 更新质检指令单明细状态为已质检 List<Long> qcCmdDetailIds = dto.getDetails().stream() .map(PgQcDetailAddDto::getQcCmdOrderDetailId).distinct().collect(Collectors.toList()); qcCmdOrderService.updateStatus(qcCmdDetailIds, QcCmdStatusEnum.QCED, entity.getBillNo()); return entity.getId();}// 保存质检详情public void saveQcDesc(PgQcOrderDetailDescDto dto) { // 保存质检详情JSON(包含良品数量、不良品数量、质检结果等) PgQcOrderDetailDescEntity descEntity = new PgQcOrderDetailDescEntity(); descEntity.setQcDesc(dto.getQcDesc()); // JSON格式 descEntity.setQcFileIds(JSONArray.toJSONString(dto.getQcFileIdList())); descEntity.setBlpFileIds(JSONArray.toJSONString(dto.getBlpFileIdList())); pgQcOrderDetailDescMapper.insert(descEntity); // 更新明细的质检数量 pgQcOrderDetailEntity.setActualQcQty(dto.getActualQcQty()); pgQcOrderDetailEntity.setQualifiedQty(dto.getQualifiedQty()); pgQcOrderDetailEntity.setUnqualifiedQty(dto.getUnqualifiedQty()); pgQcOrderDetailEntity.setQcStatus(dto.getQcStatus()); pgQcOrderDetailMapper.updateById(pgQcOrderDetailEntity);}// 确认质检单public void comfire(Long id) { PgQcOrderEntity pgQcOrder = getPgQcOrderById(id); if(!PgQcStatusEnum.DRAFT.name().equals(pgQcOrder.getStatus())) { throw new BusinessException(SysErrCode.PARAM_ERROR, "仅"草稿"状态下数据可以确认!"); } // 校验质检详情是否填写 List<PgQcOrderDetailEntity> pgQcDetails = getPgQcDetailByOrderId(id); for (PgQcOrderDetailEntity detail : pgQcDetails) { if(detail.getHasQcDesc() == null || detail.getHasQcDesc().intValue() == 0) { throw new BusinessException(SysErrCode.PARAM_ERROR, "质检详情未填写!"); } } pgQcOrder.setStatus(PgQcStatusEnum.COMFIRE.name()); pgQcMapper.updateById(pgQcOrder);}// 完成质检public void finishQc(Long id) { PgQcOrderEntity pgQcOrder = getPgQcOrderById(id); List<PgQcOrderDetailEntity> pgQcDetails = getPgQcDetailByOrderId(id); // 回写采购订单明细 List<RelatePoDto> relatePoDtos = new ArrayList<>(); for (PgQcOrderDetailEntity pgQcDetail : pgQcDetails) { RelatePoDto relatePoDto = new RelatePoDto() .setPoDetailId(pgQcDetail.getPoDetailId()) .setQcedQty(pgQcDetail.getActualQcQty()) .setQualifiedQty(pgQcDetail.getQualifiedQty()) .setUnqualifiedQty(pgQcDetail.getUnqualifiedQty()) .setBillNo(pgQcOrder.getBillNo()); relatePoDtos.add(relatePoDto); // 更新质检指令单明细 qcCmdOrderService.refreshQcCmdDetailRelate(...); } poApplyService.refreshPoDetailRelate(relatePoDtos, "PGQC"); // 写入良品记录 for (PgQcOrderDetailEntity pgQcDetail : pgQcDetails) { if(pgQcDetail.getQualifiedQty() > 0) { QualifiedRecordAddDto recordDto = new QualifiedRecordAddDto(); recordDto.setSkuId(pgQcDetail.getSkuId()); recordDto.setPrdBatchNo(pgQcDetail.getPrdBatchNo()); recordDto.setQualifiedQty(pgQcDetail.getQualifiedQty()); qualifiedRecordService.addQualifiedRecord(recordDto); } } pgQcOrder.setStatus(PgQcStatusEnum.QCED.name()); pgQcMapper.updateById(pgQcOrder);}
6.4 状态流转
DRAFT(草稿) → COMFIRE(已确认) → QCED(已质检)
7. 采购入库单 (cg_cgrk_order)
7.1 模块位置
erp-services-purchase/biz/src/main/java/com/idelamu/erp/purchase/rk/
7.2 数据库表
cg_cgrk_order -- 入库单主表cg_cgrk_order_detail -- 入库单明细表
7.3 核心接口
Service: RkOrderService.java
// 新增入库单(从签收单下推)public Long save(RkSaveDto rkSaveDto) { // 从签收单获取入库数据 RkOrderVo rkOrderVo = qsOrderService.getDeposit( rkSaveDto.getQsOrderId(), rkSaveDto.getStoreId(), Boolean.TRUE); // 构建入库单实体 RkOrderEntity rkOrder = new RkOrderEntity(); rkOrder.setBillNo("RK" + dateString + iSequenceApiService.nextSeq(...)); rkOrder.setQsOrderId(rkOrderVo.getQsOrderId()); rkOrder.setQsOrderBillNo(rkOrderVo.getQsOrderBillNo()); // 构建明细 List<RkOrderDetailEntity> rkOrderDetails = initRkOrderDetailsByQs( rkSaveDto.getAddRkDetailsByQs(), rkOrderId, billSource, rkOrderVo, basicDataVo); rkOrderMapper.insert(rkOrder); rkOrderDetailMapper.insertBatch(rkOrderDetails); return rkOrder.getId();}// 提交入库单public void submit(RkCommitDto rkCommitDto) { RkOrderEntity rkOrder = getRkOrderById(rkCommitDto.getId()); // 调用WMS入库 InventoryInboundBuilder builder = new InventoryInboundBuilder(); builder.setStoreId(rkOrder.getStoreId()); builder.setBillNo(rkOrder.getBillNo()); // ... 设置入库明细 iInventoryProxyV2Api.inbound(builder); // 回写采购订单入库数量 // 回写提货计划入库数量 // 回写良品记录}
7.4 状态流转
DRAFT(草稿) → AUDIT_REVIEW(待审核) → PROCESSED(已处理)
三、表关联关系
采购订单(po_apply) ├── po_bill_no ──────────────→ 供应商交货计划(supp_delivery_plan) │ └── po_detail_id ────────────→ 提货计划(pick_goods_plan) │ └── pick_goods_plan_bill_no ──→ 送货单明细分配(sh_order_detail_distribute) │ └── 送货单(sh_order) │ └── 签收单(qs_order) │ ├── 质检指令单(qc_cmd_order) │ │ │ └── 质检单(pg_qc_order) │ │ │ └── 良品记录(qualified_record) │ └── 入库单(rk_order)
四、关键业务逻辑说明
4.1 供应商交货计划 vs 提货计划
供应商交货计划:
- • 用于供应商协同平台
- • 供应商查看采购订单后手动创建
- • 主要用于展示供应商承诺的交货数量和日期
- • 不直接参与后续业务流转
提货计划:
- • 用于内部业务流转
- • 通过Excel导入生成
- • 送货单必须关联提货计划
- • 参与后续送货、签收、质检、入库全流程
4.2 送货单关联提货计划
送货单创建时必须关联提货计划,通过cg_sh_order_detail_distribute表记录关联关系:
// 送货单明细分配表SendGoodsOrderDetailDistributeEntity { Long detailId; // 送货单明细ID String pickGoodsPlanBillNo; // 提货计划编号 Integer distributeQty; // 分配数量}
4.3 质检流程
- 1. 生成质检指令单: 从采购订单明细生成,按供应商分组
- 2. 确认质检指令单: 确认后可下推生成质检单
- 3. 生成质检单: 从质检指令单下推
- 4. 填写质检详情: 录入良品数量、不良品数量、质检结果
- 5. 确认质检单: 校验质检详情是否完整
- 6. 完成质检:
- • 回写采购订单明细的已质检数量、良品数量、不良品数量
- • 写入良品记录表
- • 更新质检指令单状态
4.4 入库流程
入库单从签收单下推生成:
RkOrderEntity { Long qsOrderId; // 签收单ID String qsOrderBillNo; // 签收单编号}RkOrderDetailEntity { Long qsOrderDetailId; // 签收单明细ID Integer joinStoreQty; // 入库数量}
五、接口调用链路
1. 供应商创建交货计划: POST /supp/deliveryplan/createDeliveryPlanDetail (根据采购订单生成详情) POST /supp/deliveryplan/createAndSave (保存草稿) POST /supp/deliveryplan/effect (生效)2. 内部导入提货计划: POST /pgPlan/importFile (导入) POST /pgPlan/submit (提交)3. 创建送货单: POST /sh/add (新增,关联提货计划) POST /sh/sendGoods (送货)4. 签收: POST /qs/save (保存签收单) POST /qs/confirm (确认签收)5. 质检: POST /qccmd/genQcCmdAdd (生成质检指令单) POST /qccmd/confirmByIds (确认) POST /qccmd/initQcCmd (生成质检单) POST /pgqc/saveQcDesc (保存质检详情) POST /pgqc/comfire (确认质检单) POST /pgqc/finishQc (完成质检)6. 入库: POST /rk/save (新增入库单) POST /rk/submit (提交入库)