编辑 | blame | 历史 | 原始文档

生产订单、排产、报工优化方案

一、现状分析

1.1 数据模型关系

生产工单 (mes_pro_work_order)
    ├── 工单工序 (mes_pro_work_order_process)     -- 工单级别的工序安排
    ├── 工单BOM (mes_pro_work_order_bom)          -- 工单级别的物料清单
    └── 生产任务 (mes_pro_task)                   -- 排产后的生产任务
            ├── 任务投料 (mes_pro_task_issue)     -- 任务级别的投料记录
            └── 报工单 (mes_pro_feedback)         -- 任务级别的报工记录
                    ├── 物料消耗 (mes_wm_item_consume)  -- 报工消耗的物料
                    └── 产出单 (mes_wm_product_produce) -- 报工产出的产品

1.2 当前状态流转

实体 状态流转 触发条件
生产工单 草稿 → 已确认 → 已完成/已取消 手动确认、手动完成/取消
生产任务 草稿 → 已完成/已取消 工单完成/取消时级联更新
报工单 草稿 → 审批中 → 待检验/已完成 → 已完成 手动提交、审批通过

1.3 存在的问题

问题1:工单工序与任务关系断裂

  • MesProWorkOrderProcessDO 记录工单的工序安排,但创建任务时没有校验工序是否在工单工序中
  • 任务可以随意指定任意工序,导致工单工序配置无效
  • 排产时需要手动逐个工序创建任务,效率低

问题2:排产数量无约束

  • quantityScheduled(已排产数量)字段存在但没有累加逻辑
  • 可以超量排产,无校验
  • 无法直观看到工单的排产进度

问题3:任务缺少中间状态

  • 任务只有三种状态:草稿、已完成、已取消
  • 缺少「生产中」「部分完成」等中间状态
  • 无法区分「未开始生产」和「正在生产」

问题4:报工流程繁琐

  • 报工需要从独立菜单进入,手动选择任务
  • 无法从任务列表直接发起报工
  • 每次报工需要经过:新增 → 保存 → 提交 → 审批 4步

问题5:工单完成条件不明确

  • 工单完成是手动操作,不判断实际进度
  • 可能工单数量未达标就被手动完成
  • 缺少「自动完成」机制

问题6:前端操作割裂

  • 工单、排产、报工是三个独立菜单
  • 工单确认后需手动跳到「排产管理」创建任务
  • 任务创建后需手动跳到「报工列表」创建报工
  • 用户需要在多个页面来回切换

二、优化方案

2.1 数据模型优化

2.1.1 任务状态扩展

新增任务状态枚举值

状态 说明 进入条件
PREPARE 0 草稿 任务创建时
PRODUCING 1 生产中 首次报工审批通过后
PARTIAL_FINISHED 2 部分完成 已生产数量 > 0 且 < 排产数量
FINISHED 3 已完成 已生产数量 >= 排产数量 或 工单完成
CANCELED 4 已取消 工单取消时

修改文件MesProTaskStatusEnum.java

public enum MesProTaskStatusEnum implements ArrayValuable<Integer> {
    PREPARE(0, "草稿"),
    PRODUCING(1, "生产中"),
    PARTIAL_FINISHED(2, "部分完成"),
    FINISHED(3, "已完成"),
    CANCELED(4, "已取消");

    /**
     * 判断是否为终态
     */
    public static boolean isEndStatus(Integer status) {
        return ObjectUtils.equalsAny(status, FINISHED.status, CANCELED.status);
    }

    /**
     * 判断是否可以报工(草稿或非终态)
     */
    public static boolean canFeedback(Integer status) {
        return !isEndStatus(status);
    }
}

2.1.2 工单增加排产相关字段

新增字段(已存在,补充逻辑):

字段 类型 说明
quantityScheduled decimal(24,6) 已排产数量(关键工序任务数量之和)
quantityProduced decimal(24,6) 已生产数量(关键工序报工数量之和)

新增字段

字段 类型 说明
route_id bigint 工艺路线编号(确认时从产品默认路线获取或手动选择)
auto_finish_flag tinyint(1) 是否自动完成(默认 true,报工达标后自动完成工单)

2.1.3 任务增加进度追踪字段

新增字段

字段 类型 说明
first_feedback_time datetime 首次报工时间(状态变为生产中时记录)
last_feedback_time datetime 最近报工时间

数据库脚本

-- 任务表新增字段
ALTER TABLE mes_pro_task ADD COLUMN first_feedback_time datetime COMMENT '首次报工时间' AFTER cancel_date;
ALTER TABLE mes_pro_task ADD COLUMN last_feedback_time datetime COMMENT '最近报工时间' AFTER first_feedback_time;

-- 工单表新增字段
ALTER TABLE mes_pro_work_order ADD COLUMN route_id bigint COMMENT '工艺路线编号' AFTER batch_code;
ALTER TABLE mes_pro_work_order ADD COLUMN auto_finish_flag tinyint(1) DEFAULT 1 COMMENT '是否自动完成' AFTER route_id;

2.2 业务逻辑优化

2.2.1 排产数量校验

规则
- 创建任务时校验:工单.已排产数量 + 本次排产数量 <= 工单.生产数量
- 任务创建成功后累加工单的 quantityScheduled
- 任务删除时扣减工单的 quantityScheduled

接口变更MesProWorkOrderService

/**
 * 累加工单的已排产数量
 *
 * @param id 工单编号
 * @param incrQuantityScheduled 本次排产数量增量
 */
void updateScheduledQuantity(Long id, BigDecimal incrQuantityScheduled);

Mapper 新增方法MesProWorkOrderMapper

@Update("UPDATE mes_pro_work_order SET quantity_scheduled = quantity_scheduled + #{incrQuantity} " +
        "WHERE id = #{id} AND deleted = 0")
void updateScheduledQuantity(@Param("id") Long id, @Param("incrQuantity") BigDecimal incrQuantity);

2.2.2 一键排产接口

接口设计

/**
 * 一键排产:根据工艺路线自动生成所有工序任务
 *
 * @param workOrderId 工单编号
 * @param routeId 工艺路线编号(可选,不传则使用工单关联的工艺路线)
 * @return 生成的任务ID列表
 */
List<Long> autoGenerateTasks(Long workOrderId, Long routeId);

业务逻辑

  1. 校验工单存在且状态为「已确认」
  2. 获取工艺路线工序列表(按 sort 排序)
  3. 计算每个工序的排产数量:
  • 首道工序:工单生产数量
  • 后续工序:前道工序数量 × 产出比(默认 1:1)
  1. 自动分配工作站:
  • 优先分配工艺路线工序配置的工作站
  • 若未配置,则分配产品默认工作站
  • 若仍无,则留空由用户手动调整
  1. 批量创建任务,累加工单已排产数量
  2. 自动创建工单工序记录(mes_pro_work_order_process

Controller 接口

@PostMapping("/auto-generate")
@Operation(summary = "一键排产")
@PreAuthorize("@ss.hasPermission('mes:pro-task:create')")
public CommonResult<List<Long>> autoGenerateTasks(
        @RequestParam("workOrderId") Long workOrderId,
        @RequestParam(value = "routeId", required = false) Long routeId) {
    return success(taskService.autoGenerateTasks(workOrderId, routeId));
}

2.2.3 任务状态自动流转

触发时机:报工审批通过后

状态流转规则

当前状态         报工后状态判断
─────────────────────────────────────────────────
草稿(0)    →    生产中(1) 或 部分完成(2) 或 已完成(3)
生产中(1)  →    生产中(1) 或 部分完成(2) 或 已完成(3)
部分完成(2)→    部分完成(2) 或 已完成(3)

判断逻辑

if (producedQuantity.compareTo(quantity) >= 0) {
    // 已生产 >= 计划 → 已完成
    newStatus = MesProTaskStatusEnum.FINISHED;
} else if (producedQuantity.compareTo(BigDecimal.ZERO) > 0) {
    // 0 < 已生产 < 计划 → 部分完成(或保持生产中)
    newStatus = MesProTaskStatusEnum.PARTIAL_FINISHED;
} else {
    // 已生产 = 0 且有报工 → 生产中(首次报工)
    newStatus = MesProTaskStatusEnum.PRODUCING;
}

2.2.4 工单进度自动更新

规则
- 所有关键工序的报工审批通过后,累加工单的 quantityProduced
- 非关键工序的报工不更新工单进度(只更新任务进度)

触发时机:报工审批通过后

// MesProFeedbackServiceImpl#approveFeedback
if (keyFlag) {
    // 关键工序:更新工单已生产数量
    workOrderService.updateProducedQuantity(feedback.getWorkOrderId(), feedback.getFeedbackQuantity());

    // 检查是否自动完成
    checkAndAutoFinishWorkOrder(feedback.getWorkOrderId());
}

2.2.5 工单自动完成检查

规则
- 当工单的 autoFinishFlag = true 时,检查是否满足自动完成条件
- 条件:所有关键工序任务的已生产数量 >= 工单生产数量
- 或者:工单下所有任务状态都为「已完成」

接口设计

/**
 * 检查并自动完成工单
 *
 * @param workOrderId 工单编号
 * @return true=已自动完成, false=未满足条件
 */
boolean checkAndAutoFinishWorkOrder(Long workOrderId);

业务逻辑

public boolean checkAndAutoFinishWorkOrder(Long workOrderId) {
    MesProWorkOrderDO workOrder = getWorkOrder(workOrderId);
    if (workOrder == null || !Boolean.TRUE.equals(workOrder.getAutoFinishFlag())) {
        return false;
    }
    if (workOrder.getStatus() != MesProWorkOrderStatusEnum.CONFIRMED.getStatus()) {
        return false;
    }

    // 检查:已生产数量 >= 生产数量
    if (workOrder.getQuantityProduced().compareTo(workOrder.getQuantity()) >= 0) {
        finishWorkOrder(workOrderId);
        return true;
    }

    return false;
}

2.2.6 工序校验增强

规则
- 创建任务时,校验工序必须在工单工序列表中
- 如果工单没有工序记录,允许创建(兼容老数据)

修改MesProTaskServiceImpl#createTask

// 校验工序在工单工序中(如果工单有工序配置)
List<MesProWorkOrderProcessDO> workOrderProcesses = workOrderProcessService.getListByWorkOrderId(createReqVO.getWorkOrderId());
if (CollUtil.isNotEmpty(workOrderProcesses)) {
    boolean processExists = workOrderProcesses.stream()
            .anyMatch(p -> ObjUtil.equal(p.getProcessId(), createReqVO.getProcessId()));
    if (!processExists) {
        throw exception(PRO_TASK_PROCESS_NOT_IN_WORK_ORDER);
    }
}

2.3 前端交互优化

2.3.1 整合为「工单中心」

设计思路:将工单、排产、报工整合到一个页面,通过 Tab 切换不同视图

页面结构

┌─────────────────────────────────────────────────────────────────────┐
│ 工单中心                                                              │
├─────────────────────────────────────────────────────────────────────┤
│ 【搜索条件区】                                                        │
│  工单编码: [____] 产品: [____] 状态: [全部 ▼] 客户: [____]           │
│  [查询] [重置]                                                       │
├─────────────────────────────────────────────────────────────────────┤
│ 【工单列表区】                                                        │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 工单编码 │ 产品 │ 计划数量 │ 已排产 │ 已生产 │ 进度 │ 状态 │ 操作│ │
│ │ WO001   │ A   │ 100     │ 100   │ 80    │ ████████░░ │ 生产中│ 详情│
│ │ WO002   │ B   │ 50      │ 0     │ 0     │ ░░░░░░░░░░ │ 已确认│ 排产│
│ └─────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ 【工单详情区】(点击工单编码展开)                                      │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 【工序进度条】                                                    │ │
│ │  [下料] → [机加工] → [热处理] → [质检] → [包装]                    │ │
│ │   ✓       生产中     待生产     待生产    待生产                   │ │
│ │  100%      60%        0%        0%       0%                      │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ 【当前工序任务列表】(点击工序tab切换)                             │ │
│ │ [下料] [机加工] [热处理] [质检] [包装]                             │ │
│ │ ┌───────────────────────────────────────────────────────────────┐│ │
│ │ │ 任务编码 │ 工作站 │ 计划 │ 已报工 │ 进度 │ 状态 │ 操作         ││ │
│ │ │ T001    │ A-01  │ 60   │ 36    │ 60%  │ 生产中│ [报工]       ││ │
│ │ │ T002    │ A-02  │ 40   │ 0     │ 0%   │ 草稿  │ [编辑] [删除]││ │
│ │ └───────────────────────────────────────────────────────────────┘│ │
│ │ [新增任务] [一键排产]                                              │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘

2.3.2 快速报工入口

场景1:从任务列表报工

任务行操作列 → 点击「报工」按钮 → 弹出报工表单

┌────────────────────────────────────┐
│ 快速报工                            │
├────────────────────────────────────┤
│ 任务编码: T001 (自动带入,不可改)    │
│ 工作站: A-01 (自动带入)             │
│ 计划数量: 60 (自动带入)             │
│ 已报工: 36 (自动带入)               │
│ ─────────────────────────────      │
│ 本次报工数量: [____] *              │
│ 合格品数量: [____]                  │
│ 不良品数量: [____]                  │
│ 备注: [________________]            │
├────────────────────────────────────┤
│           [取消] [保存并提交]        │
└────────────────────────────────────┘

场景2:批量报工

选中多个任务 → 点击「批量报工」→ 填写相同数量批量提交

┌────────────────────────────────────┐
│ 批量报工                            │
├────────────────────────────────────┤
│ 已选择 3 个任务:                    │
│  - T001 (机加工, 计划60, 已报36)    │
│  - T003 (机加工, 计划50, 已报20)    │
│  - T005 (机加工, 计划40, 已报0)     │
│ ─────────────────────────────      │
│ 本次报工数量: [____] (应用到所有)   │
│ 备注: [________________]            │
├────────────────────────────────────┤
│           [取消] [保存并提交]        │
└────────────────────────────────────┘

2.3.3 一键排产交互

┌────────────────────────────────────────┐
│ 一键排产                                │
├────────────────────────────────────────┤
│ 工单编号: WO002                        │
│ 产品: 产品A                            │
│ 生产数量: 100                          │
│ ─────────────────────────────          │
│ 工艺路线: [产品A默认路线 ▼]             │
│                                         │
│ 预生成任务预览:                         │
│ ┌────────────────────────────────────┐ │
│ │ 工序     │ 工作站 │ 数量 │ 时间    │ │
│ │ 下料     │ 自动   │ 100  │ 8:00   │ │
│ │ 机加工   │ 自动   │ 100  │ 10:00  │ │
│ │ 热处理   │ 自动   │ 100  │ 14:00  │ │
│ │ 质检     │ 自动   │ 100  │ 16:00  │ │
│ │ 包装     │ 自动   │ 100  │ 17:00  │ │
│ └────────────────────────────────────┘ │
│ ☑ 自动分配工作站                       │
│ ☑ 自动计算生产时间                     │
├────────────────────────────────────────┤
│           [取消] [确认排产]             │
└────────────────────────────────────────┘

2.3.4 进度可视化

工单列表进度条

<template #progress="{ row }">
  <div class="flex items-center gap-2">
    <Progress
      :percent="calculateProgress(row)"
      :stroke-color="getProgressColor(row.status)"
      size="small"
    />
    <span class="text-xs text-gray-500">
      {{ row.quantityProduced }}/{{ row.quantity }}
    </span>
  </div>
</template>

工序进度条

<div class="flex items-center gap-1">
  <div
    v-for="process in processList"
    :key="process.processId"
    class="flex-1 rounded px-2 py-1 text-center text-xs"
    :class="getProcessStatusClass(process)"
  >
    <div class="font-medium">{{ process.processName }}</div>
    <div class="text-gray-500">{{ process.progress }}%</div>
  </div>
  <RightOutlined class="text-gray-400" />
</div>

三、接口变更清单

3.1 新增接口

接口 方法 路径 说明
一键排产 POST /mes/pro-task/auto-generate 根据工艺路线自动生成所有工序任务
批量报工 POST /mes/pro-feedback/batch-create 批量创建报工单
工单进度 GET /mes/pro-work-order/progress/{id} 获取工单进度详情(含各工序进度)
工单自动完成检查 POST /mes/pro-work-order/check-auto-finish/{id} 检查并自动完成工单

3.2 修改接口

接口 方法 路径 变更说明
创建任务 POST /mes/pro-task/create 增加工序校验、排产数量校验、累加工单已排产数量
删除任务 DELETE /mes/pro-task/delete 扣减工单已排产数量
审批报工 POST /mes/pro-feedback/approve 增加任务状态流转逻辑、工单自动完成检查
确认工单 POST /mes/pro-work-order/confirm 可选择关联工艺路线
工单详情 GET /mes/pro-work-order/get 增加返回 routeIdquantityScheduledquantityProduced

3.3 新增 VO

工单进度 VO

@Data
public class MesProWorkOrderProgressRespVO {
    /** 工单ID */
    private Long workOrderId;
    /** 工单编码 */
    private String workOrderCode;
    /** 生产数量 */
    private BigDecimal quantity;
    /** 已排产数量 */
    private BigDecimal quantityScheduled;
    /** 已生产数量 */
    private BigDecimal quantityProduced;
    /** 总进度百分比 */
    private BigDecimal progressPercent;
    /** 工序进度列表 */
    private List<ProcessProgressVO> processList;

    @Data
    public static class ProcessProgressVO {
        /** 工序ID */
        private Long processId;
        /** 工序编码 */
        private String processCode;
        /** 工序名称 */
        private String processName;
        /** 是否关键工序 */
        private Boolean keyFlag;
        /** 计划数量(关键工序) */
        private BigDecimal plannedQuantity;
        /** 已生产数量 */
        private BigDecimal producedQuantity;
        /** 进度百分比 */
        private BigDecimal progressPercent;
        /** 任务列表 */
        private List<TaskProgressVO> taskList;
    }

    @Data
    public static class TaskProgressVO {
        /** 任务ID */
        private Long taskId;
        /** 任务编码 */
        private String taskCode;
        /** 工作站名称 */
        private String workstationName;
        /** 排产数量 */
        private BigDecimal quantity;
        /** 已生产数量 */
        private BigDecimal producedQuantity;
        /** 进度百分比 */
        private BigDecimal progressPercent;
        /** 任务状态 */
        private Integer status;
    }
}

一键排产请求 VO

@Data
public class MesProTaskAutoGenerateReqVO {
    /** 工单ID */
    @NotNull(message = "工单ID不能为空")
    private Long workOrderId;
    /** 工艺路线ID(可选,不传则使用工单关联的工艺路线) */
    private Long routeId;
    /** 是否自动分配工作站 */
    private Boolean autoAssignWorkstation = true;
    /** 是否自动计算生产时间 */
    private Boolean autoCalculateTime = true;
}

一键排产预览 VO

@Data
public class MesProTaskAutoGeneratePreviewRespVO {
    /** 工单ID */
    private Long workOrderId;
    /** 工艺路线ID */
    private Long routeId;
    /** 工艺路线名称 */
    private String routeName;
    /** 预生成任务列表 */
    private List<TaskPreviewVO> taskList;

    @Data
    public static class TaskPreviewVO {
        /** 工序ID */
        private Long processId;
        /** 工序名称 */
        private String processName;
        /** 工序顺序 */
        private Integer sort;
        /** 建议工作站ID */
        private Long suggestWorkstationId;
        /** 建议工作站名称 */
        private String suggestWorkstationName;
        /** 排产数量 */
        private BigDecimal quantity;
        /** 预计开始时间 */
        private LocalDateTime suggestStartTime;
        /** 预计结束时间 */
        private LocalDateTime suggestEndTime;
        /** 是否关键工序 */
        private Boolean keyFlag;
    }
}

批量报工请求 VO

@Data
public class MesProFeedbackBatchCreateReqVO {
    /** 任务ID列表 */
    @NotEmpty(message = "任务ID列表不能为空")
    private List<Long> taskIds;
    /** 本次报工数量(应用到所有任务) */
    @NotNull(message = "报工数量不能为空")
    @DecimalMin(value = "0.01", message = "报工数量必须大于0")
    private BigDecimal feedbackQuantity;
    /** 合格品数量(应用到所有任务,非质检工序必填) */
    private BigDecimal qualifiedQuantity;
    /** 不良品数量 */
    private BigDecimal unqualifiedQuantity;
    /** 备注 */
    private String remark;
}

四、数据库变更

4.1 表结构变更

-- =============================================
-- 生产任务表新增字段
-- =============================================
ALTER TABLE mes_pro_task
    ADD COLUMN first_feedback_time datetime COMMENT '首次报工时间' AFTER cancel_date,
    ADD COLUMN last_feedback_time datetime COMMENT '最近报工时间' AFTER first_feedback_time;

-- =============================================
-- 生产工单表新增字段
-- =============================================
ALTER TABLE mes_pro_work_order
    ADD COLUMN route_id bigint COMMENT '工艺路线编号' AFTER batch_code,
    ADD COLUMN auto_finish_flag tinyint(1) DEFAULT 1 COMMENT '是否自动完成(报工达标后自动完成工单)' AFTER route_id;

-- =============================================
-- 补充索引
-- =============================================
ALTER TABLE mes_pro_task ADD INDEX idx_work_order_process (work_order_id, process_id);

4.2 枚举值变更

-- 任务状态枚举扩展
-- 0=草稿, 1=生产中, 2=部分完成, 3=已完成, 4=已取消
-- 注意:需要更新字典数据 mes_pro_task_status
INSERT INTO system_dict_data (sort, label, value, dict_type, status, color_type, css_class, remark)
VALUES
    (1, '生产中', '1', 'mes_pro_task_status', 0, 'processing', '', '任务正在生产中'),
    (2, '部分完成', '2', 'mes_pro_task_status', 0, 'warning', '', '任务部分完成');

-- 更新原有枚举的 sort
UPDATE system_dict_data SET sort = 0 WHERE dict_type = 'mes_pro_task_status' AND value = '0';
UPDATE system_dict_data SET sort = 3 WHERE dict_type = 'mes_pro_task_status' AND value = '3';
UPDATE system_dict_data SET sort = 4 WHERE dict_type = 'mes_pro_task_status' AND value = '4';

五、实施计划

5.1 阶段一:后端核心逻辑(预计 3 天)

任务 说明 优先级
任务状态枚举扩展 新增 PRODUCING、PARTIAL_FINISHED 状态 P0
数据库变更执行 执行 SQL 脚本 P0
排产数量校验 创建/删除任务时校验和更新工单已排产数量 P0
任务状态自动流转 报工审批后自动更新任务状态 P0
工单进度自动更新 报工审批后更新工单已生产数量 P0
工单自动完成检查 报工达标后自动完成工单 P1

5.2 阶段二:新接口开发(预计 2 天)

任务 说明 优先级
一键排产接口 根据工艺路线自动生成任务 P0
工单进度接口 返回工单各工序进度详情 P0
批量报工接口 批量创建报工单 P1

5.3 阶段三:前端改造(预计 4 天)

任务 说明 优先级
工单中心页面 整合工单、排产、报工到一个页面 P0
工序进度条组件 可视化展示各工序进度 P0
快速报工入口 任务行直接报工 P0
一键排产弹窗 选择工艺路线后预览并确认排产 P1
批量报工功能 选中多任务批量报工 P2

5.4 阶段四:测试与优化(预计 1 天)

  • 单元测试覆盖新增逻辑
  • 集成测试验证完整流程
  • 性能测试(一键排产、批量报工)

六、风险与注意事项

6.1 兼容性

  • 老数据处理:历史任务的 status 需要按新枚举值迁移
  • API 兼容:新接口不影响现有接口,前端可逐步迁移

6.2 性能考虑

  • 一键排产可能生成大量任务,建议限制单次排产数量(如最多 10 个工序)
  • 批量报工建议限制批量数量(如最多 50 个任务)

6.3 业务规则确认

  • 工单自动完成是否默认开启?(建议默认开启)
  • 非关键工序报工是否更新工单进度?(建议不更新,工单进度只看关键工序)
  • 任务部分完成状态是否需要明确区分?(建议简化为「生产中」包含部分完成)

七、前端联调方案

详见:docs/work_order_center_integration.md(待创建)