Pengxy 2 сар өмнө
parent
commit
7086b67535
25 өөрчлөгдсөн 1797 нэмэгдсэн , 441 устгасан
  1. 0 208
      mapper/makeplan/WorkOrderScheduleMapper.xml
  2. 175 0
      yudao-module-makeplan/WORK_ORDER_MATERIAL_IMPLEMENTATION.md
  3. 82 0
      yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/WorkOrderMaterialController.java
  4. 26 0
      yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/vo/WorkOrderMaterialPageReqVO.java
  5. 156 0
      yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/vo/WorkOrderMaterialRespVO.java
  6. 122 0
      yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/vo/WorkOrderMaterialSaveReqVO.java
  7. 3 0
      yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/vo/WorkOrderScheduleRespVO.java
  8. 29 0
      yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/convert/WorkOrderMaterialConvert.java
  9. 314 0
      yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/dal/dataobject/workorder/WorkOrderMaterialDO.java
  10. 2 1
      yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/dal/mysql/WorkOrderScheduleMapper.java
  11. 51 0
      yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/dal/mysql/workorder/WorkOrderMaterialMapper.java
  12. 17 0
      yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/enums/ErrorCodeConstants.java
  13. 60 0
      yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/service/WorkOrderMaterialService.java
  14. 85 0
      yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/service/WorkOrderMaterialServiceImpl.java
  15. 21 9
      yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/service/impl/WorkOrderScheduleServiceImpl.java
  16. 2 2
      yudao-module-makeplan/src/main/resources/mapper/makeplan/WorkOrderScheduleMapper.xml
  17. 285 0
      yudao-module-makeplan/src/main/resources/mapper/workorder/WorkOrderMaterialMapper.xml
  18. 125 0
      yudao-ui/yudao-ui-admin-vue3/src/api/makeplan/workorderMaterial.ts
  19. 10 4
      yudao-ui/yudao-ui-admin-vue3/src/api/makeplan/workorderSchedule.ts
  20. 38 17
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/ProductionSchedule.vue
  21. 51 39
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/WorkOrderMaterial.vue
  22. 15 7
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/WorkOrderProcess.vue
  23. 7 7
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/components/PriorityAdjustForm.vue
  24. 107 144
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/components/WorkOrderMaterialForm.vue
  25. 14 3
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/components/WorkOrderViewForm.vue

+ 0 - 208
mapper/makeplan/WorkOrderScheduleMapper.xml

@@ -1,208 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="cn.iocoder.yudao.module.makeplan.dal.mysql.WorkOrderScheduleMapper">
-
-    <!-- 工单排产列表查询 -->
-    <select id="selectSchedulePage" resultType="cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo.WorkOrderScheduleRespVO">
-        SELECT 
-            a.RecID AS id,
-            exm.checktime,
-            a.Batch AS batch,
-            a.Drawing AS drawing,
-            a.Typed AS typed,
-            a.WorkOrd AS workOrd,
-            a.OrdDate AS ordDate,
-            a.DueDate AS dueDate,
-            a.ItemNum AS itemNum,
-            a.Project AS project,
-            a.QtyOrded AS qtyOrded,
-            a.QtyCompleted AS qtyCompleted,
-            a.Remark AS remark,
-            LOWER(a.Status) AS status,
-            a.WoTyped AS woTyped,
-            b.Descr AS descr,
-            b.Descr1 AS descr1,
-            a.lbrvar,
-            r.Area AS area,
-            se.custom_no AS customNo,
-            cm.Terms AS terms,
-            a.IsInitial AS isInitial,
-            CASE 
-                WHEN cm.Terms = '' OR cm.Terms IS NULL THEN
-                    CASE r.Area 
-                        WHEN '中国' THEN DATE_ADD(a.DueDate, INTERVAL 15 DAY)
-                        WHEN '海外' THEN DATE_ADD(a.DueDate, INTERVAL 8 DAY)
-                        ELSE NULL 
-                    END
-                ELSE
-                    CASE 
-                        WHEN cm.Terms = '海外' THEN DATE_ADD(a.DueDate, INTERVAL 8 DAY)
-                        ELSE DATE_ADD(a.DueDate, INTERVAL 15 DAY)
-                    END
-            END AS mjtime,
-            a.LotSerial AS lotSerial,
-            CASE WHEN a.CreateGLforLaborVar = 1 THEN '是' ELSE '否' END AS createGLforLaborVar,
-            a.QtyOrded - a.QtyCompleted AS zz,
-            s.PlanDate AS planDate,
-            s.ProdDate AS prodDate,
-            CASE 
-                WHEN s.PlanDate IS NULL THEN ''
-                WHEN s.PlanDate IS NOT NULL AND s.ProdDate IS NULL THEN
-                    CASE 
-                        WHEN DATEDIFF(CURDATE(), s.PlanDate) <= 2 AND DATEDIFF(CURDATE(), s.PlanDate) > 0 THEN '低'
-                        WHEN DATEDIFF(CURDATE(), s.PlanDate) <= 5 AND DATEDIFF(CURDATE(), s.PlanDate) > 2 THEN '中'
-                        WHEN DATEDIFF(CURDATE(), s.PlanDate) > 5 THEN '高'
-                    END
-                WHEN s.PlanDate IS NOT NULL AND s.ProdDate IS NOT NULL THEN
-                    CASE 
-                        WHEN DATEDIFF(s.ProdDate, s.PlanDate) <= 2 AND DATEDIFF(s.ProdDate, s.PlanDate) > 0 THEN '低'
-                        WHEN DATEDIFF(s.ProdDate, s.PlanDate) <= 5 AND DATEDIFF(s.ProdDate, s.PlanDate) > 2 THEN '中'
-                        WHEN DATEDIFF(s.ProdDate, s.PlanDate) > 5 THEN '高'
-                    END
-            END AS level,
-            a.IssueSite AS issueSite,
-            IFNULL(ins.WorkOrd, '') AS insWorkOrd,
-            CASE 
-                WHEN a.DispatchWeek = '' OR a.DispatchWeek IS NULL THEN
-                    CASE 
-                        WHEN a.LotSerial != '' AND a.LotSerial IS NOT NULL THEN
-                            CONCAT('WK', WEEK(STR_TO_DATE(SUBSTRING(a.LotSerial, 1, 6), '%y%m%d')))
-                        ELSE ''
-                    END
-                ELSE a.DispatchWeek
-            END AS checkweek,
-            a.Priority AS priority,
-            IFNULL(a.LocationStock, 0) + IFNULL(a.OpQtyCompleted, 0) AS locationStock
-        FROM WorkOrdMaster a
-        LEFT JOIN ItemMaster b ON a.ItemNum = b.ItemNum AND b.Domain = a.Domain
-        LEFT JOIN ReplenishmentWeekPlan r ON a.WorkOrd = r.ProductionOrder AND a.Domain = r.factory_id
-        LEFT JOIN crm_seorder se ON se.bill_no = a.SalesJob
-        LEFT JOIN CustMaster cm ON cm.Cust = se.custom_no
-        LEFT JOIN (
-            SELECT Domain, WorkOrds, MIN(PlanDate) AS PlanDate, MIN(ProdDate) AS ProdDate 
-            FROM PeriodSequenceDet 
-            GROUP BY Domain, WorkOrds
-        ) s ON a.Domain = s.Domain AND a.WorkOrd = s.WorkOrds
-        LEFT JOIN (
-            SELECT morder_no, create_time AS checktime
-            FROM (
-                SELECT morder_no, create_time,
-                       ROW_NUMBER() OVER (PARTITION BY morder_no ORDER BY create_time DESC) AS rn
-                FROM b_examine_result
-            ) ranked
-            WHERE rn = 1
-        ) exm ON exm.morder_no = a.WorkOrd
-        LEFT JOIN WorkOrdInStorage ins ON a.WorkOrd = ins.WorkOrd AND ins.Remark = '工单预留'
-        <where>
-            a.Status != ''
-            <if test="workOrd != null and workOrd != ''">
-                AND a.WorkOrd LIKE CONCAT('%', #{workOrd}, '%')
-            </if>
-            <if test="itemNum != null and itemNum != ''">
-                AND a.ItemNum LIKE CONCAT('%', #{itemNum}, '%')
-            </if>
-            <if test="status != null and status != ''">
-                AND LOWER(a.Status) = #{status}
-            </if>
-            <if test="woTyped != null and woTyped != ''">
-                AND a.WoTyped = #{woTyped}
-            </if>
-            <if test="batch != null and batch != ''">
-                AND a.Batch = #{batch}
-            </if>
-            <if test="drawing != null and drawing != ''">
-                AND a.Drawing = #{drawing}
-            </if>
-            <if test="customNo != null and customNo != ''">
-                AND se.custom_no = #{customNo}
-            </if>
-            <if test="ordDateStart != null">
-                AND a.OrdDate &gt;= #{ordDateStart}
-            </if>
-            <if test="ordDateEnd != null">
-                AND a.OrdDate &lt;= #{ordDateEnd}
-            </if>
-            <if test="dueDateStart != null">
-                AND a.DueDate &gt;= #{dueDateStart}
-            </if>
-            <if test="dueDateEnd != null">
-                AND a.DueDate &lt;= #{dueDateEnd}
-            </if>
-            <if test="priority != null">
-                AND a.Priority = #{priority}
-            </if>
-            <if test="urgent != null">
-                AND a.Urgent = #{urgent}
-            </if>
-            <if test="isInitial != null">
-                AND a.IsInitial = #{isInitial}
-            </if>
-        </where>
-        ORDER BY a.UpdateTime DESC, a.Priority DESC
-    </select>
-
-    <!-- 根据ID查询工单详情 -->
-    <select id="selectScheduleById" resultType="cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo.WorkOrderScheduleRespVO">
-        SELECT 
-            a.RecID AS id,
-            a.Batch AS batch,
-            a.Drawing AS drawing,
-            a.Typed AS typed,
-            a.WorkOrd AS workOrd,
-            a.OrdDate AS ordDate,
-            a.DueDate AS dueDate,
-            a.ItemNum AS itemNum,
-            a.Project AS project,
-            a.QtyOrded AS qtyOrded,
-            a.QtyCompleted AS qtyCompleted,
-            a.Remark AS remark,
-            LOWER(a.Status) AS status,
-            a.WoTyped AS woTyped,
-            b.Descr AS descr,
-            b.Descr1 AS descr1,
-            a.lbrvar,
-            a.IsInitial AS isInitial,
-            a.LotSerial AS lotSerial,
-            CASE WHEN a.CreateGLforLaborVar = 1 THEN '是' ELSE '否' END AS createGLforLaborVar,
-            a.QtyOrded - a.QtyCompleted AS zz,
-            a.IssueSite AS issueSite,
-            a.Priority AS priority,
-            IFNULL(a.LocationStock, 0) + IFNULL(a.OpQtyCompleted, 0) AS locationStock
-        FROM WorkOrdMaster a
-        LEFT JOIN ItemMaster b ON a.ItemNum = b.ItemNum AND b.Domain = a.Domain
-        WHERE a.RecID = #{id}
-    </select>
-
-    <!-- 调用存储过程关闭工单 -->
-    <select id="callCloseWorkOrders" statementType="CALLABLE">
-        {CALL pr_MES_CloseWorkOrders(#{ids})}
-    </select>
-
-    <!-- 同步工艺路线 -->
-    <update id="syncRoutingByWorkOrd">
-        DELETE FROM WorkOrdRouting WHERE WorkOrd = #{workOrd};
-        
-        INSERT INTO WorkOrdRouting(
-            Domain, Descr, MilestoneOp, WorkOrd, OP, ParentOp, RunTime, ItemNum, 
-            QtyOrded, OverlapUnits, Status, IsActive, CommentIndex, CreateTime, 
-            StdOp, PackingQty, WorkOrdMasterRecID
-        )
-        SELECT 
-            w.Domain, r.Descr, r.MilestoneOp, w.WorkOrd, r.OP, r.ParentOp, r.RunTime, 
-            w.ItemNum, w.QtyOrded, r.OverlapUnits, w.Status, 1, r.CommentIndex, NOW(), 
-            r.StdOp, r.PackingQty, w.RecID 
-        FROM WorkOrdMaster w 
-        LEFT JOIN RoutingOpDetail r ON w.ItemNum = r.RoutingCode 
-        WHERE w.WorkOrd = #{workOrd} AND r.MilestoneOp IS NOT NULL
-    </update>
-
-    <!-- 批量查询工单信息 -->
-    <select id="selectBatchByIds" resultType="cn.iocoder.yudao.module.makeplan.dal.dataobject.WorkOrderScheduleDO">
-        SELECT * FROM WorkOrdMaster
-        WHERE RecID IN
-        <foreach collection="ids" item="id" open="(" separator="," close=")">
-            #{id}
-        </foreach>
-    </select>
-
-</mapper>

+ 175 - 0
yudao-module-makeplan/WORK_ORDER_MATERIAL_IMPLEMENTATION.md

@@ -0,0 +1,175 @@
+# 工单物料明细功能实现说明
+
+## 功能概述
+工单物料明细模块提供了对工单物料的完整增删改查功能,支持多表关联查询,展示物料的详细信息。
+
+## 已实现的功能
+
+### 后端接口
+
+#### 1. 数据模型 (DO)
+- **文件**: `WorkOrderMaterialDO.java`
+- **表名**: `WorkOrdDetail`
+- **特点**: 
+  - 不使用多租户功能(添加了 `@InterceptorIgnore` 注解)
+  - 不使用逻辑删除(使用 `IsActive` 字段标记状态)
+  - 所有字段使用 `@TableField` 注解指定实际数据库字段名(大写开头)
+  - 包含关联查询的虚拟字段(descr, opDescr, batch等)
+
+#### 2. Mapper接口
+- **文件**: `WorkOrderMaterialMapper.java`, `WorkOrderMaterialMapper.xml`
+- **功能**:
+  - `selectPageWithJoin`: 分页查询(多表关联)
+  - `selectListWithJoin`: 列表查询(多表关联)
+- **关联表**:
+  - `WorkOrdMaster`: 工单主表(获取Batch, QtyOrded)
+  - `ItemMaster`: 物料主表(获取Descr物料名称)
+  - `RoutingOpDetail`: 工序明细表(获取工序描述)
+  - `NbrDetail`: 数量明细表(获取QtyRec收货数量)
+
+#### 3. Service服务
+- **文件**: `WorkOrderMaterialService.java`, `WorkOrderMaterialServiceImpl.java`
+- **方法**:
+  - `createWorkOrderMaterial`: 创建物料明细
+  - `updateWorkOrderMaterial`: 更新物料明细
+  - `deleteWorkOrderMaterial`: 删除物料明细(逻辑删除,设置IsActive=0)
+  - `getWorkOrderMaterial`: 获取单条记录
+  - `getWorkOrderMaterialPage`: 分页查询
+
+#### 4. Controller控制器
+- **文件**: `WorkOrderMaterialController.java`
+- **接口**:
+  - `POST /makeplan/work-order-material/create`: 创建
+  - `PUT /makeplan/work-order-material/update`: 更新
+  - `DELETE /makeplan/work-order-material/delete`: 删除
+  - `GET /makeplan/work-order-material/get`: 获取详情
+  - `GET /makeplan/work-order-material/page`: 分页查询
+
+#### 5. VO类
+- **文件**: 
+  - `WorkOrderMaterialPageReqVO.java`: 分页查询请求
+  - `WorkOrderMaterialRespVO.java`: 响应VO
+  - `WorkOrderMaterialSaveReqVO.java`: 保存请求VO
+
+#### 6. Convert转换类
+- **文件**: `WorkOrderMaterialConvert.java`
+- **功能**: DO与VO之间的对象转换
+
+### 前端实现
+
+#### 1. API接口
+- **文件**: `src/api/makeplan/workorderMaterial.ts`
+- **方法**:
+  - `getWorkOrderMaterialPage`: 分页查询
+  - `getWorkOrderMaterial`: 获取详情
+  - `createWorkOrderMaterial`: 创建
+  - `updateWorkOrderMaterial`: 更新
+  - `deleteWorkOrderMaterial`: 删除
+
+#### 2. 列表页面
+- **文件**: `src/views/jiaohuo/WorkOrderMaterial.vue`
+- **功能**:
+  - 搜索表单(工单编号、物料编号、库位)
+  - 数据列表展示
+  - 分页功能
+  - 添加、编辑、删除、查看操作
+  - 根据IsActive状态显示不同背景色
+
+#### 3. 表单组件
+- **文件**: `src/views/jiaohuo/components/WorkOrderMaterialForm.vue`
+- **功能**:
+  - 支持添加、编辑、查看三种模式
+  - 表单验证
+  - 字段大小写兼容处理
+
+## SQL语法转换说明
+
+原始SQL Server语法已转换为MySQL语法:
+
+| SQL Server | MySQL |
+|-----------|-------|
+| `ISNULL(field, default)` | `IFNULL(field, default)` |
+| `cast(field as nvarchar(10)) + ' ' + field2` | `CONCAT(CAST(field AS CHAR), ' ', field2)` |
+| `wd.[Domain]` | `wd.Domain` |
+| `upper(Type)='SM'` | `UPPER(nd.Type)='SM'` |
+| `CAST(field as int)` | `CAST(field AS SIGNED)` |
+
+## 查询逻辑说明
+
+### 多表关联查询
+```sql
+FROM WorkOrdDetail wd
+INNER JOIN WorkOrdMaster wm ON wd.Domain = wm.Domain AND wd.WorkOrd = wm.WorkOrd
+INNER JOIN ItemMaster im ON wd.Domain = im.Domain AND wd.ItemNum = im.ItemNum
+LEFT JOIN RoutingOpDetail rd ON wd.Domain = rd.Domain AND wd.ItemNum = rd.RoutingCode AND wd.Op = rd.Op
+LEFT JOIN NbrDetail nd ON wd.WorkOrd = nd.WorkOrd 
+    AND nd.ItemNum = wd.ItemNum 
+    AND nd.IsActive = wd.IsActive 
+    AND wd.Domain = nd.Domain 
+    AND UPPER(nd.Type) = 'SM'
+```
+
+### 过滤条件
+- 工单状态: `UPPER(wm.Status) IN ('', 'P', 'R', 'W', 'S')`
+- 工单类型: `UPPER(IFNULL(wm.Typed, '')) IN ('', 'RW', 'TEST', 'TCN')`
+
+### 计算字段
+- `OpDescr`: 工序号 + 工序描述
+- `background`: 根据IsActive状态返回背景色
+- `QtyRec`: 收货数量(默认0)
+
+## 测试步骤
+
+### 1. 启动后端服务
+```bash
+cd yudao-server
+mvn spring-boot:run -Dspring-boot.run.profiles=local
+```
+
+### 2. 启动前端服务
+```bash
+cd yudao-ui/yudao-ui-admin-vue3
+pnpm dev
+```
+
+### 3. 访问页面
+- 登录系统
+- 导航到工单物料明细页面
+- 测试查询、添加、编辑、删除功能
+
+### 4. API测试
+访问 Swagger 文档: `http://localhost:48080/doc.html`
+- 找到 "工单物料明细" 接口组
+- 测试各个接口
+
+## 注意事项
+
+1. **数据库字段命名**: 表中字段使用大写开头(如 `WorkOrd`, `ItemNum`),不是下划线命名
+2. **多租户**: 该表不使用多租户功能,已添加 `@InterceptorIgnore` 注解
+3. **逻辑删除**: 不使用框架的逻辑删除,使用 `IsActive` 字段(1-有效,0-已删除)
+4. **字段映射**: DO类中所有字段都添加了 `@TableField` 注解指定实际字段名
+5. **虚拟字段**: 关联查询的字段(descr, opDescr等)标记为 `exist = false`
+
+## 错误码
+
+- `1_003_002_001`: 工单物料明细不存在
+
+## 相关文件清单
+
+### 后端文件
+- `yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/dal/dataobject/workorder/WorkOrderMaterialDO.java`
+- `yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/dal/mysql/workorder/WorkOrderMaterialMapper.java`
+- `yudao-module-makeplan/src/main/resources/mapper/workorder/WorkOrderMaterialMapper.xml`
+- `yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/service/WorkOrderMaterialService.java`
+- `yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/service/WorkOrderMaterialServiceImpl.java`
+- `yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/WorkOrderMaterialController.java`
+- `yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/vo/WorkOrderMaterialPageReqVO.java`
+- `yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/vo/WorkOrderMaterialRespVO.java`
+- `yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/vo/WorkOrderMaterialSaveReqVO.java`
+- `yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/convert/WorkOrderMaterialConvert.java`
+- `yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/enums/ErrorCodeConstants.java`
+
+### 前端文件
+- `yudao-ui/yudao-ui-admin-vue3/src/api/makeplan/workorderMaterial.ts`
+- `yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/WorkOrderMaterial.vue`
+- `yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/components/WorkOrderMaterialForm.vue`

+ 82 - 0
yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/WorkOrderMaterialController.java

@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.makeplan.controller.admin.workorder;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo.WorkOrderMaterialPageReqVO;
+import cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo.WorkOrderMaterialRespVO;
+import cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo.WorkOrderMaterialSaveReqVO;
+import cn.iocoder.yudao.module.makeplan.convert.WorkOrderMaterialConvert;
+import cn.iocoder.yudao.module.makeplan.dal.dataobject.workorder.WorkOrderMaterialDO;
+import cn.iocoder.yudao.module.makeplan.service.WorkOrderMaterialService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * 工单物料明细 Controller
+ *
+ * @author 芋道源码
+ */
+@Tag(name = "管理后台 - 工单物料明细")
+@RestController
+@RequestMapping("/makeplan/workorder-material")
+@Validated
+public class WorkOrderMaterialController {
+
+    @Resource
+    private WorkOrderMaterialService workOrderMaterialService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建工单物料明细")
+    @PreAuthorize("@ss.hasPermission('makeplan:workorder-material:create')")
+    public CommonResult<Long> createWorkOrderMaterial(@Valid @RequestBody WorkOrderMaterialSaveReqVO createReqVO) {
+        return success(workOrderMaterialService.createWorkOrderMaterial(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新工单物料明细")
+    @PreAuthorize("@ss.hasPermission('makeplan:workorder-material:update')")
+    public CommonResult<Boolean> updateWorkOrderMaterial(@Valid @RequestBody WorkOrderMaterialSaveReqVO updateReqVO) {
+        workOrderMaterialService.updateWorkOrderMaterial(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除工单物料明细")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('makeplan:workorder-material:delete')")
+    public CommonResult<Boolean> deleteWorkOrderMaterial(@RequestParam("id") Long id) {
+        workOrderMaterialService.deleteWorkOrderMaterial(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得工单物料明细")
+    @Parameter(name = "id", description = "编号", required = true, example = "1")
+    @PreAuthorize("@ss.hasPermission('makeplan:workorder-material:query')")
+    public CommonResult<WorkOrderMaterialRespVO> getWorkOrderMaterial(@RequestParam("id") Long id) {
+        WorkOrderMaterialDO material = workOrderMaterialService.getWorkOrderMaterial(id);
+        return success(WorkOrderMaterialConvert.INSTANCE.convert(material));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得工单物料明细分页")
+    @PreAuthorize("@ss.hasPermission('makeplan:workorder-material:query')")
+    public CommonResult<PageResult<WorkOrderMaterialRespVO>> getWorkOrderMaterialPage(@Valid WorkOrderMaterialPageReqVO pageReqVO) {
+        PageResult<WorkOrderMaterialDO> pageResult = workOrderMaterialService.getWorkOrderMaterialPage(pageReqVO);
+        return success(WorkOrderMaterialConvert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/work-order-list")
+    @Operation(summary = "获取工单列表(用于下拉选择)")
+    public CommonResult<java.util.List<java.util.Map<String, String>>> getWorkOrderList() {
+        return success(workOrderMaterialService.getWorkOrderList());
+    }
+}

+ 26 - 0
yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/vo/WorkOrderMaterialPageReqVO.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 工单物料明细分页请求 VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class WorkOrderMaterialPageReqVO extends PageParam {
+
+    @Schema(description = "工单编号", example = "WO202401001")
+    private String workOrd;
+
+    @Schema(description = "物料编码", example = "MAT001")
+    private String itemNum;
+
+    @Schema(description = "库位", example = "A01")
+    private String location;
+
+    @Schema(description = "工序", example = "OP10")
+    private String op;
+}

+ 156 - 0
yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/vo/WorkOrderMaterialRespVO.java

@@ -0,0 +1,156 @@
+package cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 工单物料明细响应 VO")
+@Data
+public class WorkOrderMaterialRespVO {
+
+    @Schema(description = "主键ID", example = "1")
+    private Long id;
+
+    @Schema(description = "工单编号", example = "WO202401001")
+    private String workOrd;
+
+    @Schema(description = "行号", example = "1")
+    private Integer line;
+
+    @Schema(description = "物料编码", example = "MAT001")
+    private String itemNum;
+
+    @Schema(description = "物料名称", example = "钢板")
+    private String descr;
+
+    @Schema(description = "工序", example = "OP10")
+    private String op;
+
+    @Schema(description = "工序描述", example = "切割")
+    private String opDescr;
+
+    @Schema(description = "生产批号", example = "BATCH001")
+    private String batch;
+
+    @Schema(description = "冻结BOM数量", example = "100.00")
+    private BigDecimal frozenBOMQty;
+
+    @Schema(description = "订单数量", example = "100.00")
+    private BigDecimal qtyOrded;
+
+    @Schema(description = "收货数量", example = "50.00")
+    private BigDecimal qtyRec;
+
+    @Schema(description = "背景色", example = "WorkOrd:#FFFFFF;ItemNum:#FFFFFF")
+    private String background;
+
+    @Schema(description = "库位", example = "A01")
+    private String location;
+
+    @Schema(description = "批序号", example = "LOT001")
+    private String lotSerial;
+
+    @Schema(description = "需求数量", example = "100.00")
+    private BigDecimal qtyRequired;
+
+    @Schema(description = "已分配数量", example = "50.00")
+    private BigDecimal qtyAllocated;
+
+    @Schema(description = "发放数量", example = "50.00")
+    private BigDecimal qtytoIssue;
+
+    @Schema(description = "过账数量", example = "50.00")
+    private BigDecimal qtyIssued;
+
+    @Schema(description = "备料量", example = "50.00")
+    private BigDecimal qtyPicked;
+
+    @Schema(description = "发布数量", example = "50.00")
+    private BigDecimal qtyPosted;
+
+    @Schema(description = "退回数量", example = "0.00")
+    private BigDecimal qtyReturned;
+
+    @Schema(description = "拒绝数量", example = "0.00")
+    private BigDecimal qtyReject;
+
+    @Schema(description = "状态", example = "1")
+    private String status;
+
+    @Schema(description = "单位成本", example = "10.50")
+    private BigDecimal unitCost;
+
+    @Schema(description = "标准成本", example = "10.00")
+    private BigDecimal stdCost;
+
+    @Schema(description = "价格", example = "12.00")
+    private BigDecimal price;
+
+    @Schema(description = "计量单位", example = "KG")
+    private String um;
+
+    @Schema(description = "是否主材", example = "true")
+    private Boolean isMainMas;
+
+    @Schema(description = "域名", example = "8010")
+    private String domain;
+
+    @Schema(description = "地点", example = "SITE01")
+    private String site;
+
+    @Schema(description = "从库位", example = "B01")
+    private String fromLocation;
+
+    @Schema(description = "从地点", example = "SITE02")
+    private String fromSite;
+
+    @Schema(description = "项目", example = "PRJ001")
+    private String project;
+
+    @Schema(description = "产品类", example = "PROD01")
+    private String productLine;
+
+    @Schema(description = "维度1", example = "DIM1")
+    private String dimension1;
+
+    @Schema(description = "维度2", example = "DIM2")
+    private String dimension2;
+
+    @Schema(description = "备注", example = "备注信息")
+    private String remark;
+
+    @Schema(description = "有效状态:1-有效,0-已删除", example = "1")
+    private Integer isActive;
+
+    @Schema(description = "是否确认", example = "true")
+    private Boolean isConfirm;
+
+    @Schema(description = "是否改变", example = "false")
+    private Boolean isChanged;
+
+    @Schema(description = "自增列", example = "1001")
+    private Long recID;
+
+    @Schema(description = "工单流水号", example = "100")
+    private Long workOrdMasterRecID;
+
+    @Schema(description = "业务流程ID", example = "BIZ001")
+    private String businessID;
+
+    @Schema(description = "失效时间")
+    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime effTime;
+
+    @Schema(description = "创建时间")
+    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime createTime;
+
+    @Schema(description = "更新时间")
+    @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime updateTime;
+}

+ 122 - 0
yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/vo/WorkOrderMaterialSaveReqVO.java

@@ -0,0 +1,122 @@
+package cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 工单物料明细创建/更新请求 VO")
+@Data
+public class WorkOrderMaterialSaveReqVO {
+
+    @Schema(description = "主键ID(更新时必填)", example = "1")
+    private Long id;
+
+    @Schema(description = "工单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "WO202401001")
+    @NotBlank(message = "工单编号不能为空")
+    private String workOrd;
+
+    @Schema(description = "行号", example = "1")
+    private Integer line;
+
+    @Schema(description = "物料编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "MAT001")
+    @NotBlank(message = "物料编码不能为空")
+    private String itemNum;
+
+    @Schema(description = "工序", example = "OP10")
+    private String op;
+
+    @Schema(description = "库位", example = "A01")
+    private String location;
+
+    @Schema(description = "批序号", example = "LOT001")
+    private String lotSerial;
+
+    @Schema(description = "需求数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+    @NotNull(message = "需求数量不能为空")
+    private BigDecimal qtyRequired;
+
+    @Schema(description = "已分配数量", example = "50.00")
+    private BigDecimal qtyAllocated;
+
+    @Schema(description = "发放数量", example = "50.00")
+    private BigDecimal qtytoIssue;
+
+    @Schema(description = "过账数量", example = "50.00")
+    private BigDecimal qtyIssued;
+
+    @Schema(description = "备料量", example = "50.00")
+    private BigDecimal qtyPicked;
+
+    @Schema(description = "发布数量", example = "50.00")
+    private BigDecimal qtyPosted;
+
+    @Schema(description = "退回数量", example = "0.00")
+    private BigDecimal qtyReturned;
+
+    @Schema(description = "拒绝数量", example = "0.00")
+    private BigDecimal qtyReject;
+
+    @Schema(description = "状态", example = "1")
+    private String status;
+
+    @Schema(description = "单位成本", example = "10.50")
+    private BigDecimal unitCost;
+
+    @Schema(description = "标准成本", example = "10.00")
+    private BigDecimal stdCost;
+
+    @Schema(description = "价格", example = "12.00")
+    private BigDecimal price;
+
+    @Schema(description = "计量单位", example = "KG")
+    private String um;
+
+    @Schema(description = "是否主材", example = "true")
+    private Boolean isMainMas;
+
+    @Schema(description = "域名", example = "8010")
+    private String domain;
+
+    @Schema(description = "地点", example = "SITE01")
+    private String site;
+
+    @Schema(description = "从库位", example = "B01")
+    private String fromLocation;
+
+    @Schema(description = "从地点", example = "SITE02")
+    private String fromSite;
+
+    @Schema(description = "项目", example = "PRJ001")
+    private String project;
+
+    @Schema(description = "产品类", example = "PROD01")
+    private String productLine;
+
+    @Schema(description = "维度1", example = "DIM1")
+    private String dimension1;
+
+    @Schema(description = "维度2", example = "DIM2")
+    private String dimension2;
+
+    @Schema(description = "备注", example = "备注信息")
+    private String remark;
+
+    @Schema(description = "是否确认", example = "true")
+    private Boolean isConfirm;
+
+    @Schema(description = "是否改变", example = "false")
+    private Boolean isChanged;
+
+    @Schema(description = "工单流水号", example = "100")
+    private Long workOrdMasterRecID;
+
+    @Schema(description = "业务流程ID", example = "BIZ001")
+    private String businessID;
+
+    @Schema(description = "失效时间")
+    private LocalDateTime effTime;
+}

+ 3 - 0
yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/controller/admin/workorder/vo/WorkOrderScheduleRespVO.java

@@ -120,4 +120,7 @@ public class WorkOrderScheduleRespVO {
 
     @Schema(description = "在库齐套数量")
     private BigDecimal locationStock;
+
+    @Schema(description = "加急状态:0-普通, 1-加急, 2-特急")
+    private Integer urgent;
 }

+ 29 - 0
yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/convert/WorkOrderMaterialConvert.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.makeplan.convert;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo.WorkOrderMaterialRespVO;
+import cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo.WorkOrderMaterialSaveReqVO;
+import cn.iocoder.yudao.module.makeplan.dal.dataobject.workorder.WorkOrderMaterialDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 工单物料明细 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface WorkOrderMaterialConvert {
+
+    WorkOrderMaterialConvert INSTANCE = Mappers.getMapper(WorkOrderMaterialConvert.class);
+
+    WorkOrderMaterialDO convert(WorkOrderMaterialSaveReqVO bean);
+
+    WorkOrderMaterialRespVO convert(WorkOrderMaterialDO bean);
+
+    List<WorkOrderMaterialRespVO> convertList(List<WorkOrderMaterialDO> list);
+
+    PageResult<WorkOrderMaterialRespVO> convertPage(PageResult<WorkOrderMaterialDO> page);
+}

+ 314 - 0
yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/dal/dataobject/workorder/WorkOrderMaterialDO.java

@@ -0,0 +1,314 @@
+package cn.iocoder.yudao.module.makeplan.dal.dataobject.workorder;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 工单物料明细 DO
+ * 
+ * 注意:该表不使用多租户和逻辑删除功能
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "WorkOrdDetail", autoResultMap = true)
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WorkOrderMaterialDO {
+
+    /**
+     * 主键ID - 对应数据库RecID字段
+     */
+    @TableId(type = IdType.AUTO)
+    @TableField("RecID")
+    private Long id;
+
+    /**
+     * 工单编号
+     */
+    @TableField("WorkOrd")
+    private String workOrd;
+
+    /**
+     * 行号
+     */
+    @TableField("Line")
+    private Integer line;
+
+    /**
+     * 物料编码
+     */
+    @TableField("ItemNum")
+    private String itemNum;
+
+    /**
+     * 工序
+     */
+    @TableField("Op")
+    private String op;
+
+    /**
+     * 库位
+     */
+    @TableField("Location")
+    private String location;
+
+    /**
+     * 批序号
+     */
+    @TableField("LotSerial")
+    private String lotSerial;
+
+    /**
+     * 需求数量
+     */
+    @TableField("QtyRequired")
+    private BigDecimal qtyRequired;
+
+    /**
+     * 已分配数量
+     */
+    @TableField("QtyAllocated")
+    private BigDecimal qtyAllocated;
+
+    /**
+     * 发放数量
+     */
+    @TableField("QtytoIssue")
+    private BigDecimal qtytoIssue;
+
+    /**
+     * 过账数量
+     */
+    @TableField("QtyIssued")
+    private BigDecimal qtyIssued;
+
+    /**
+     * 备料量
+     */
+    @TableField("QtyPicked")
+    private BigDecimal qtyPicked;
+
+    /**
+     * 发布数量
+     */
+    @TableField("QtyPosted")
+    private BigDecimal qtyPosted;
+
+    /**
+     * 退回数量
+     */
+    @TableField("QtyReturned")
+    private BigDecimal qtyReturned;
+
+    /**
+     * 拒绝数量
+     */
+    @TableField("QtyReject")
+    private BigDecimal qtyReject;
+
+    /**
+     * 状态
+     */
+    @TableField("Status")
+    private String status;
+
+    /**
+     * 单位成本
+     */
+    @TableField("UnitCost")
+    private BigDecimal unitCost;
+
+    /**
+     * 标准成本
+     */
+    @TableField("StdCost")
+    private BigDecimal stdCost;
+
+    /**
+     * 价格
+     */
+    @TableField("Price")
+    private BigDecimal price;
+
+    /**
+     * 计量单位
+     */
+    @TableField("UM")
+    private String um;
+
+    /**
+     * 是否主材
+     */
+    @TableField("IsMainMas")
+    private Boolean isMainMas;
+
+    /**
+     * 域名
+     */
+    @TableField("Domain")
+    private String domain;
+
+    /**
+     * 地点
+     */
+    @TableField("Site")
+    private String site;
+
+    /**
+     * 从库位
+     */
+    @TableField("FromLocation")
+    private String fromLocation;
+
+    /**
+     * 从地点
+     */
+    @TableField("FromSite")
+    private String fromSite;
+
+    /**
+     * 项目
+     */
+    @TableField("Project")
+    private String project;
+
+    /**
+     * 产品类
+     */
+    @TableField("ProductLine")
+    private String productLine;
+
+    /**
+     * 维度1
+     */
+    @TableField("Dimension1")
+    private String dimension1;
+
+    /**
+     * 维度2
+     */
+    @TableField("Dimension2")
+    private String dimension2;
+
+    /**
+     * 备注 - 表中不存在此字段,标记为不存在
+     */
+    @TableField(exist = false)
+    private String remark;
+
+    /**
+     * 物料名称 - 来自ItemMaster表的关联查询
+     */
+    @TableField(exist = false)
+    private String descr;
+
+    /**
+     * 工序描述 - 来自RoutingOpDetail表的关联查询
+     */
+    @TableField(exist = false)
+    private String opDescr;
+
+    /**
+     * 生产批号 - 来自WorkOrdMaster表的关联查询
+     */
+    @TableField(exist = false)
+    private String batch;
+
+    /**
+     * 冻结BOM数量
+     */
+    @TableField("FrozenBOMQty")
+    private BigDecimal frozenBOMQty;
+
+    /**
+     * 订单数量 - 来自WorkOrdMaster表的关联查询
+     */
+    @TableField(exist = false)
+    private BigDecimal qtyOrded;
+
+    /**
+     * 收货数量 - 来自NbrDetail表的关联查询
+     */
+    @TableField(exist = false)
+    private BigDecimal qtyRec;
+
+    /**
+     * 背景色 - 根据IsActive计算
+     */
+    @TableField(exist = false)
+    private String background;
+
+    /**
+     * 有效状态:1-有效,0-已删除
+     */
+    @TableField("IsActive")
+    private Integer isActive;
+
+    /**
+     * 是否确认
+     */
+    @TableField("IsConfirm")
+    private Boolean isConfirm;
+
+    /**
+     * 是否改变
+     */
+    @TableField("IsChanged")
+    private Boolean isChanged;
+
+    /**
+     * 自增列
+     */
+    @TableField("RecID")
+    private Long recID;
+
+    /**
+     * 工单流水号
+     */
+    @TableField("WorkOrdMasterRecID")
+    private Long workOrdMasterRecID;
+
+    /**
+     * 业务流程ID
+     */
+    @TableField("BusinessID")
+    private String businessID;
+
+    /**
+     * 失效时间
+     */
+    @TableField("EffTime")
+    private LocalDateTime effTime;
+
+    /**
+     * 创建时间
+     */
+    @TableField("CreateTime")
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    @TableField("UpdateTime")
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建用户
+     */
+    @TableField("CreateUser")
+    private String createUser;
+
+    /**
+     * 更新用户
+     */
+    @TableField("UpdateUser")
+    private String updateUser;
+}

+ 2 - 1
yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/dal/mysql/WorkOrderScheduleMapper.java

@@ -41,10 +41,11 @@ public interface WorkOrderScheduleMapper extends BaseMapperX<WorkOrderScheduleDO
     /**
      * 调用存储过程关闭工单
      *
+     * @param domain 域
      * @param ids 工单ID列表(逗号分隔)
      */
     @InterceptorIgnore(tenantLine = "true")
-    void callCloseWorkOrders(@Param("ids") String ids);
+    void callCloseWorkOrders(@Param("domain") String domain, @Param("ids") String ids);
 
     /**
      * 同步工艺路线

+ 51 - 0
yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/dal/mysql/workorder/WorkOrderMaterialMapper.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.makeplan.dal.mysql.workorder;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo.WorkOrderMaterialPageReqVO;
+import cn.iocoder.yudao.module.makeplan.dal.dataobject.workorder.WorkOrderMaterialDO;
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 工单物料明细 Mapper
+ * 
+ * 注意:该表不使用多租户功能,需要忽略多租户拦截器
+ *
+ * @author 芋道源码
+ */
+@Mapper
+@InterceptorIgnore(tenantLine = "true")
+public interface WorkOrderMaterialMapper extends BaseMapperX<WorkOrderMaterialDO> {
+
+    /**
+     * 分页查询工单物料明细(使用自定义SQL)
+     */
+    IPage<WorkOrderMaterialDO> selectPageWithJoin(Page<WorkOrderMaterialDO> page, @Param("reqVO") WorkOrderMaterialPageReqVO reqVO);
+
+    /**
+     * 查询工单物料明细列表(使用自定义SQL)
+     */
+    List<WorkOrderMaterialDO> selectListWithJoin(@Param("reqVO") WorkOrderMaterialPageReqVO reqVO);
+
+    /**
+     * 获取工单列表(用于下拉选择)
+     */
+    List<java.util.Map<String, String>> selectWorkOrderList();
+
+    /**
+     * 根据ID查询(忽略多租户)
+     */
+    WorkOrderMaterialDO selectByIdIgnoreTenant(@Param("id") Long id);
+
+    /**
+     * 根据ID更新(忽略多租户)
+     */
+    int updateByIdIgnoreTenant(@Param("entity") WorkOrderMaterialDO entity);
+}
+

+ 17 - 0
yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/enums/ErrorCodeConstants.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.makeplan.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * makeplan 错误码枚举类
+ * 
+ * makeplan 系统,使用 1-003-000-000 段
+ */
+public interface ErrorCodeConstants {
+
+    // ========== 工单排产 1-003-001-000 ==========
+    ErrorCode WORK_ORDER_SCHEDULE_NOT_EXISTS = new ErrorCode(1_003_001_001, "工单不存在");
+
+    // ========== 工单物料明细 1-003-002-000 ==========
+    ErrorCode WORK_ORDER_MATERIAL_NOT_EXISTS = new ErrorCode(1_003_002_001, "工单物料明细不存在");
+}

+ 60 - 0
yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/service/WorkOrderMaterialService.java

@@ -0,0 +1,60 @@
+package cn.iocoder.yudao.module.makeplan.service;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo.WorkOrderMaterialPageReqVO;
+import cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo.WorkOrderMaterialSaveReqVO;
+import cn.iocoder.yudao.module.makeplan.dal.dataobject.workorder.WorkOrderMaterialDO;
+import jakarta.validation.Valid;
+
+/**
+ * 工单物料明细 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface WorkOrderMaterialService {
+
+    /**
+     * 创建工单物料明细
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createWorkOrderMaterial(@Valid WorkOrderMaterialSaveReqVO createReqVO);
+
+    /**
+     * 更新工单物料明细
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateWorkOrderMaterial(@Valid WorkOrderMaterialSaveReqVO updateReqVO);
+
+    /**
+     * 删除工单物料明细
+     *
+     * @param id 编号
+     */
+    void deleteWorkOrderMaterial(Long id);
+
+    /**
+     * 获得工单物料明细
+     *
+     * @param id 编号
+     * @return 工单物料明细
+     */
+    WorkOrderMaterialDO getWorkOrderMaterial(Long id);
+
+    /**
+     * 获得工单物料明细分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 工单物料明细分页
+     */
+    PageResult<WorkOrderMaterialDO> getWorkOrderMaterialPage(WorkOrderMaterialPageReqVO pageReqVO);
+
+    /**
+     * 获取工单列表(用于下拉选择)
+     *
+     * @return 工单列表
+     */
+    java.util.List<java.util.Map<String, String>> getWorkOrderList();
+}

+ 85 - 0
yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/service/WorkOrderMaterialServiceImpl.java

@@ -0,0 +1,85 @@
+package cn.iocoder.yudao.module.makeplan.service;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo.WorkOrderMaterialPageReqVO;
+import cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo.WorkOrderMaterialSaveReqVO;
+import cn.iocoder.yudao.module.makeplan.convert.WorkOrderMaterialConvert;
+import cn.iocoder.yudao.module.makeplan.dal.dataobject.workorder.WorkOrderMaterialDO;
+import cn.iocoder.yudao.module.makeplan.dal.mysql.workorder.WorkOrderMaterialMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.makeplan.enums.ErrorCodeConstants.WORK_ORDER_MATERIAL_NOT_EXISTS;
+
+/**
+ * 工单物料明细 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class WorkOrderMaterialServiceImpl implements WorkOrderMaterialService {
+
+    @Resource
+    private WorkOrderMaterialMapper workOrderMaterialMapper;
+
+    @Override
+    public Long createWorkOrderMaterial(WorkOrderMaterialSaveReqVO createReqVO) {
+        // 插入
+        WorkOrderMaterialDO material = WorkOrderMaterialConvert.INSTANCE.convert(createReqVO);
+        material.setIsActive(1); // 默认激活状态
+        workOrderMaterialMapper.insert(material);
+        // 返回
+        return material.getId();
+    }
+
+    @Override
+    public void updateWorkOrderMaterial(WorkOrderMaterialSaveReqVO updateReqVO) {
+        // 校验存在 - 使用自定义查询
+        validateWorkOrderMaterialExists(updateReqVO.getId());
+        // 更新 - 使用自定义更新方法
+        WorkOrderMaterialDO updateObj = WorkOrderMaterialConvert.INSTANCE.convert(updateReqVO);
+        workOrderMaterialMapper.updateByIdIgnoreTenant(updateObj);
+    }
+
+    @Override
+    public void deleteWorkOrderMaterial(Long id) {
+        // 校验存在
+        validateWorkOrderMaterialExists(id);
+        // 逻辑删除 - 设置IsActive为0,使用自定义更新方法
+        WorkOrderMaterialDO updateObj = new WorkOrderMaterialDO();
+        updateObj.setId(id);
+        updateObj.setIsActive(0);
+        workOrderMaterialMapper.updateByIdIgnoreTenant(updateObj);
+    }
+
+    private void validateWorkOrderMaterialExists(Long id) {
+        // 使用自定义查询方法,避免多租户拦截器影响
+        WorkOrderMaterialDO material = workOrderMaterialMapper.selectByIdIgnoreTenant(id);
+        if (material == null) {
+            throw exception(WORK_ORDER_MATERIAL_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public WorkOrderMaterialDO getWorkOrderMaterial(Long id) {
+        return workOrderMaterialMapper.selectByIdIgnoreTenant(id);
+    }
+
+    @Override
+    public PageResult<WorkOrderMaterialDO> getWorkOrderMaterialPage(WorkOrderMaterialPageReqVO pageReqVO) {
+        // 使用自定义SQL进行多表关联查询
+        com.baomidou.mybatisplus.extension.plugins.pagination.Page<WorkOrderMaterialDO> page = 
+            new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
+        com.baomidou.mybatisplus.core.metadata.IPage<WorkOrderMaterialDO> result = 
+            workOrderMaterialMapper.selectPageWithJoin(page, pageReqVO);
+        return new PageResult<>(result.getRecords(), result.getTotal());
+    }
+
+    @Override
+    public java.util.List<java.util.Map<String, String>> getWorkOrderList() {
+        return workOrderMaterialMapper.selectWorkOrderList();
+    }
+}

+ 21 - 9
yudao-module-makeplan/src/main/java/cn/iocoder/yudao/module/makeplan/service/impl/WorkOrderScheduleServiceImpl.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.makeplan.service.impl;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
 import cn.iocoder.yudao.module.makeplan.controller.admin.workorder.vo.*;
 import cn.iocoder.yudao.module.makeplan.dal.dataobject.WorkOrderScheduleDO;
 import cn.iocoder.yudao.module.makeplan.dal.mysql.WorkOrderScheduleMapper;
@@ -37,9 +38,13 @@ public class WorkOrderScheduleServiceImpl implements WorkOrderScheduleService {
 
     @Resource
     private RestTemplate restTemplate;
+    
+    @Resource
+    private ConfigApi configApi;
 
-    private static final String PRODUCTION_SCHEDULE_URL = "http://123.60.180.165:9898/api/business/resource-examine/productionschedule?domain=";
-    private static final String MATERIAL_REQUIREMENT_URL = "http://123.60.180.165:9898/api/business/resource-examine/AutomaticPrAdjustDate?domain=";
+    private static final String PRODUCTION_SCHEDULE_URL = "http://123.60.180.165:8087/api/business/resource-examine/productionschedule?domain=";
+    private static final String MATERIAL_REQUIREMENT_URL = "http://123.60.180.165:8087/api/business/resource-examine/AutomaticPrAdjustDate?domain=";
+    private static final String DEFAULT_DOMAIN = "8010";
 
     @Override
     public PageResult<WorkOrderScheduleRespVO> getWorkOrderSchedulePage(WorkOrderSchedulePageReqVO pageReqVO) {
@@ -107,17 +112,24 @@ public class WorkOrderScheduleServiceImpl implements WorkOrderScheduleService {
             }
         }
 
-        // 拼接ID字符串
-        String ids = closeReqVO.getIds().stream()
-                .map(String::valueOf)
-                .collect(Collectors.joining(","));
+        // 从配置管理中获取 domain 值
+        String domain = configApi.getConfigValueByKey("order.default.domain");
+        if (domain == null || domain.isEmpty()) {
+            domain = DEFAULT_DOMAIN;
+            log.warn("未找到配置 order.default.domain,使用默认值: {}", DEFAULT_DOMAIN);
+        }
+
+        // 拼接工单编号字符串 (使用 | 分隔符)
+        String workOrds = workOrders.stream()
+                .map(WorkOrderScheduleDO::getWorkOrd)
+                .collect(Collectors.joining("|"));
 
         // 调用存储过程关闭工单
         try {
-            workOrderScheduleMapper.callCloseWorkOrders(ids);
-            log.info("成功关闭工单,IDs: {}", ids);
+            workOrderScheduleMapper.callCloseWorkOrders(domain, workOrds);
+            log.info("成功关闭工单,Domain: {}, WorkOrds: {}", domain, workOrds);
         } catch (Exception e) {
-            log.error("关闭工单失败,IDs: {}", ids, e);
+            log.error("关闭工单失败,Domain: {}, WorkOrds: {}", domain, workOrds, e);
             throw exception(BAD_REQUEST, "关闭工单失败: " + e.getMessage());
         }
     }

+ 2 - 2
yudao-module-makeplan/src/main/resources/mapper/makeplan/WorkOrderScheduleMapper.xml

@@ -176,8 +176,8 @@
     </select>
 
     <!-- 调用存储过程关闭工单 -->
-    <select id="callCloseWorkOrders" statementType="CALLABLE">
-        {CALL pr_MES_CloseWorkOrders(#{ids})}
+    <select id="callCloseWorkOrders" statementType="CALLABLE" resultType="java.util.HashMap">
+        {CALL pr_MES_CloseWorkOrders(#{domain, mode=IN, jdbcType=VARCHAR}, #{ids, mode=IN, jdbcType=VARCHAR})}
     </select>
 
     <!-- 同步工艺路线 -->

+ 285 - 0
yudao-module-makeplan/src/main/resources/mapper/workorder/WorkOrderMaterialMapper.xml

@@ -0,0 +1,285 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.makeplan.dal.mysql.workorder.WorkOrderMaterialMapper">
+
+    <resultMap id="BaseResultMap" type="cn.iocoder.yudao.module.makeplan.dal.dataobject.workorder.WorkOrderMaterialDO">
+        <id column="id" property="id"/>
+        <result column="WorkOrd" property="workOrd"/>
+        <result column="Line" property="line"/>
+        <result column="ItemNum" property="itemNum"/>
+        <result column="Descr" property="descr"/>
+        <result column="Op" property="op"/>
+        <result column="OpDescr" property="opDescr"/>
+        <result column="Batch" property="batch"/>
+        <result column="Location" property="location"/>
+        <result column="LotSerial" property="lotSerial"/>
+        <result column="QtyRequired" property="qtyRequired"/>
+        <result column="FrozenBOMQty" property="frozenBOMQty"/>
+        <result column="QtyOrded" property="qtyOrded"/>
+        <result column="QtyRec" property="qtyRec"/>
+        <result column="background" property="background"/>
+        <result column="QtyAllocated" property="qtyAllocated"/>
+        <result column="QtytoIssue" property="qtytoIssue"/>
+        <result column="QtyIssued" property="qtyIssued"/>
+        <result column="QtyPicked" property="qtyPicked"/>
+        <result column="QtyPosted" property="qtyPosted"/>
+        <result column="QtyReturned" property="qtyReturned"/>
+        <result column="QtyReject" property="qtyReject"/>
+        <result column="Status" property="status"/>
+        <result column="UnitCost" property="unitCost"/>
+        <result column="StdCost" property="stdCost"/>
+        <result column="Price" property="price"/>
+        <result column="UM" property="um"/>
+        <result column="IsMainMas" property="isMainMas"/>
+        <result column="Domain" property="domain"/>
+        <result column="Site" property="site"/>
+        <result column="FromLocation" property="fromLocation"/>
+        <result column="FromSite" property="fromSite"/>
+        <result column="Project" property="project"/>
+        <result column="ProductLine" property="productLine"/>
+        <result column="Dimension1" property="dimension1"/>
+        <result column="Dimension2" property="dimension2"/>
+        <result column="IsActive" property="isActive"/>
+        <result column="IsConfirm" property="isConfirm"/>
+        <result column="IsChanged" property="isChanged"/>
+        <result column="RecID" property="recID"/>
+        <result column="WorkOrdMasterRecID" property="workOrdMasterRecID"/>
+        <result column="BusinessID" property="businessID"/>
+        <result column="EffTime" property="effTime"/>
+        <result column="CreateTime" property="createTime"/>
+        <result column="UpdateTime" property="updateTime"/>
+        <result column="CreateUser" property="createUser"/>
+        <result column="UpdateUser" property="updateUser"/>
+    </resultMap>
+
+    <!-- 分页查询工单物料明细 -->
+    <select id="selectPageWithJoin" resultMap="BaseResultMap">
+        SELECT
+            wd.RecID as id,
+            wd.Domain,
+            wm.Batch,
+            wd.WorkOrd,
+            wd.Line,
+            wd.ItemNum,
+            im.Descr,
+            wd.Op,
+            CONCAT(CAST(wd.Op AS CHAR), ' ', IFNULL(rd.Descr, '')) as OpDescr,
+            wd.QtyRequired,
+            wd.Location,
+            wd.LotSerial,
+            wd.FrozenBOMQty,
+            wm.QtyOrded,
+            wd.WorkOrdMasterRecID,
+            IFNULL(nd.QtyRec, 0) as QtyRec,
+            CASE 
+                WHEN wd.IsActive = 0 THEN 'WorkOrd:#FF6C6C;ItemNum:#FF6C6C' 
+                ELSE 'WorkOrd:#FFFFFF;ItemNum:#FFFFFF' 
+            END as background,
+            CAST(wd.IsActive AS SIGNED) as IsActive,
+            wd.QtyAllocated,
+            wd.QtytoIssue,
+            wd.QtyIssued,
+            wd.QtyPicked,
+            wd.QtyPosted,
+            wd.QtyReturned,
+            wd.QtyReject,
+            wd.Status,
+            wd.UnitCost,
+            wd.StdCost,
+            wd.Price,
+            wd.UM,
+            wd.IsMainMas,
+            wd.Site,
+            wd.FromLocation,
+            wd.FromSite,
+            wd.Project,
+            wd.ProductLine,
+            wd.Dimension1,
+            wd.Dimension2,
+            wd.IsConfirm,
+            wd.IsChanged,
+            wd.BusinessID,
+            wd.EffTime,
+            wd.CreateTime,
+            wd.UpdateTime,
+            wd.CreateUser,
+            wd.UpdateUser
+        FROM WorkOrdDetail wd
+        INNER JOIN WorkOrdMaster wm ON wd.Domain = wm.Domain AND wd.WorkOrd = wm.WorkOrd
+        INNER JOIN ItemMaster im ON wd.Domain = im.Domain AND wd.ItemNum = im.ItemNum
+        LEFT JOIN RoutingOpDetail rd ON wd.Domain = rd.Domain AND wd.ItemNum = rd.RoutingCode AND wd.Op = rd.Op
+        LEFT JOIN NbrDetail nd ON wd.WorkOrd = nd.WorkOrd 
+            AND nd.ItemNum = wd.ItemNum 
+            AND nd.IsActive = wd.IsActive 
+            AND wd.Domain = nd.Domain 
+            AND UPPER(nd.Type) = 'SM'
+        WHERE UPPER(wm.Status) IN ('', 'P', 'R', 'W', 'S')
+            AND UPPER(IFNULL(wm.Typed, '')) IN ('', 'RW', 'TEST', 'TCN')
+            <if test="reqVO.workOrd != null and reqVO.workOrd != ''">
+                AND wd.WorkOrd LIKE CONCAT('%', #{reqVO.workOrd}, '%')
+            </if>
+            <if test="reqVO.itemNum != null and reqVO.itemNum != ''">
+                AND wd.ItemNum LIKE CONCAT('%', #{reqVO.itemNum}, '%')
+            </if>
+            <if test="reqVO.location != null and reqVO.location != ''">
+                AND wd.Location = #{reqVO.location}
+            </if>
+            <if test="reqVO.op != null and reqVO.op != ''">
+                AND wd.Op = #{reqVO.op}
+            </if>
+        ORDER BY wd.RecID DESC
+    </select>
+
+    <!-- 查询工单物料明细列表 -->
+    <select id="selectListWithJoin" resultMap="BaseResultMap">
+        SELECT
+            wd.RecID as id,
+            wd.Domain,
+            wm.Batch,
+            wd.WorkOrd,
+            wd.Line,
+            wd.ItemNum,
+            im.Descr,
+            wd.Op,
+            CONCAT(CAST(wd.Op AS CHAR), ' ', IFNULL(rd.Descr, '')) as OpDescr,
+            wd.QtyRequired,
+            wd.Location,
+            wd.LotSerial,
+            wd.FrozenBOMQty,
+            wm.QtyOrded,
+            wd.WorkOrdMasterRecID,
+            IFNULL(nd.QtyRec, 0) as QtyRec,
+            CASE 
+                WHEN wd.IsActive = 0 THEN 'WorkOrd:#FF6C6C;ItemNum:#FF6C6C' 
+                ELSE 'WorkOrd:#FFFFFF;ItemNum:#FFFFFF' 
+            END as background,
+            CAST(wd.IsActive AS SIGNED) as IsActive
+        FROM WorkOrdDetail wd
+        INNER JOIN WorkOrdMaster wm ON wd.Domain = wm.Domain AND wd.WorkOrd = wm.WorkOrd
+        INNER JOIN ItemMaster im ON wd.Domain = im.Domain AND wd.ItemNum = im.ItemNum
+        LEFT JOIN RoutingOpDetail rd ON wd.Domain = rd.Domain AND wd.ItemNum = rd.RoutingCode AND wd.Op = rd.Op
+        LEFT JOIN NbrDetail nd ON wd.WorkOrd = nd.WorkOrd 
+            AND nd.ItemNum = wd.ItemNum 
+            AND nd.IsActive = wd.IsActive 
+            AND wd.Domain = nd.Domain 
+            AND UPPER(nd.Type) = 'SM'
+        WHERE UPPER(wm.Status) IN ('', 'P', 'R', 'W', 'S')
+            AND UPPER(IFNULL(wm.Typed, '')) IN ('', 'RW', 'TEST', 'TCN')
+            <if test="reqVO.workOrd != null and reqVO.workOrd != ''">
+                AND wd.WorkOrd LIKE CONCAT('%', #{reqVO.workOrd}, '%')
+            </if>
+            <if test="reqVO.itemNum != null and reqVO.itemNum != ''">
+                AND wd.ItemNum LIKE CONCAT('%', #{reqVO.itemNum}, '%')
+            </if>
+            <if test="reqVO.location != null and reqVO.location != ''">
+                AND wd.Location = #{reqVO.location}
+            </if>
+            <if test="reqVO.op != null and reqVO.op != ''">
+                AND wd.Op = #{reqVO.op}
+            </if>
+        ORDER BY wd.RecID DESC
+    </select>
+
+    <!-- 获取工单列表(用于下拉选择) -->
+    <select id="selectWorkOrderList" resultType="java.util.HashMap">
+        SELECT 
+            WorkOrd as value,
+            WorkOrd as label
+        FROM WorkOrdMaster
+        WHERE UPPER(IFNULL(Status, '')) IN ('', 'P', 'R', 'W', 'S')
+            AND UPPER(IFNULL(Typed, '')) IN ('', 'RW', 'TEST', 'TCN')
+        ORDER BY WorkOrd DESC
+    </select>
+
+    <!-- 根据ID查询(忽略多租户) -->
+    <select id="selectByIdIgnoreTenant" resultType="cn.iocoder.yudao.module.makeplan.dal.dataobject.workorder.WorkOrderMaterialDO">
+        SELECT 
+            RecID as id,
+            WorkOrd,
+            Line,
+            ItemNum,
+            Op,
+            Location,
+            LotSerial,
+            QtyRequired,
+            QtyAllocated,
+            QtytoIssue,
+            QtyIssued,
+            QtyPicked,
+            QtyPosted,
+            QtyReturned,
+            QtyReject,
+            Status,
+            UnitCost,
+            StdCost,
+            Price,
+            UM,
+            IsMainMas,
+            Domain,
+            Site,
+            FromLocation,
+            FromSite,
+            Project,
+            ProductLine,
+            Dimension1,
+            Dimension2,
+            IsActive,
+            IsConfirm,
+            IsChanged,
+            RecID,
+            WorkOrdMasterRecID,
+            BusinessID,
+            EffTime,
+            CreateTime,
+            UpdateTime,
+            CreateUser,
+            UpdateUser
+        FROM WorkOrdDetail
+        WHERE RecID = #{id}
+    </select>
+
+    <!-- 根据ID更新(忽略多租户) -->
+    <update id="updateByIdIgnoreTenant">
+        UPDATE WorkOrdDetail
+        <set>
+            <if test="entity.workOrd != null">WorkOrd = #{entity.workOrd},</if>
+            <if test="entity.line != null">Line = #{entity.line},</if>
+            <if test="entity.itemNum != null">ItemNum = #{entity.itemNum},</if>
+            <if test="entity.op != null">Op = #{entity.op},</if>
+            <if test="entity.location != null">Location = #{entity.location},</if>
+            <if test="entity.lotSerial != null">LotSerial = #{entity.lotSerial},</if>
+            <if test="entity.qtyRequired != null">QtyRequired = #{entity.qtyRequired},</if>
+            <if test="entity.qtyAllocated != null">QtyAllocated = #{entity.qtyAllocated},</if>
+            <if test="entity.qtytoIssue != null">QtytoIssue = #{entity.qtytoIssue},</if>
+            <if test="entity.qtyIssued != null">QtyIssued = #{entity.qtyIssued},</if>
+            <if test="entity.qtyPicked != null">QtyPicked = #{entity.qtyPicked},</if>
+            <if test="entity.qtyPosted != null">QtyPosted = #{entity.qtyPosted},</if>
+            <if test="entity.qtyReturned != null">QtyReturned = #{entity.qtyReturned},</if>
+            <if test="entity.qtyReject != null">QtyReject = #{entity.qtyReject},</if>
+            <if test="entity.status != null">Status = #{entity.status},</if>
+            <if test="entity.unitCost != null">UnitCost = #{entity.unitCost},</if>
+            <if test="entity.stdCost != null">StdCost = #{entity.stdCost},</if>
+            <if test="entity.price != null">Price = #{entity.price},</if>
+            <if test="entity.um != null">UM = #{entity.um},</if>
+            <if test="entity.isMainMas != null">IsMainMas = #{entity.isMainMas},</if>
+            <if test="entity.domain != null">Domain = #{entity.domain},</if>
+            <if test="entity.site != null">Site = #{entity.site},</if>
+            <if test="entity.fromLocation != null">FromLocation = #{entity.fromLocation},</if>
+            <if test="entity.fromSite != null">FromSite = #{entity.fromSite},</if>
+            <if test="entity.project != null">Project = #{entity.project},</if>
+            <if test="entity.productLine != null">ProductLine = #{entity.productLine},</if>
+            <if test="entity.dimension1 != null">Dimension1 = #{entity.dimension1},</if>
+            <if test="entity.dimension2 != null">Dimension2 = #{entity.dimension2},</if>
+            <if test="entity.isActive != null">IsActive = #{entity.isActive},</if>
+            <if test="entity.isConfirm != null">IsConfirm = #{entity.isConfirm},</if>
+            <if test="entity.isChanged != null">IsChanged = #{entity.isChanged},</if>
+            <if test="entity.workOrdMasterRecID != null">WorkOrdMasterRecID = #{entity.workOrdMasterRecID},</if>
+            <if test="entity.businessID != null">BusinessID = #{entity.businessID},</if>
+            <if test="entity.effTime != null">EffTime = #{entity.effTime},</if>
+            UpdateTime = NOW(),
+            <if test="entity.updateUser != null">UpdateUser = #{entity.updateUser},</if>
+        </set>
+        WHERE RecID = #{entity.id}
+    </update>
+
+</mapper>

+ 125 - 0
yudao-ui/yudao-ui-admin-vue3/src/api/makeplan/workorderMaterial.ts

@@ -0,0 +1,125 @@
+import request from '@/config/axios'
+
+// 工单物料明细分页请求参数
+export interface WorkOrderMaterialPageReqVO {
+  workOrd?: string
+  itemNum?: string
+  location?: string
+  op?: string
+  pageNo?: number
+  pageSize?: number
+}
+
+// 工单物料明细响应数据
+export interface WorkOrderMaterialRespVO {
+  id: number
+  workOrd: string
+  line?: number
+  itemNum: string
+  descr?: string
+  op?: string
+  opDescr?: string
+  location?: string
+  lotSerial?: string
+  qtyRequired: number
+  qtyAllocated?: number
+  qtytoIssue?: number
+  qtyIssued?: number
+  qtyPicked?: number
+  qtyPosted?: number
+  qtyReturned?: number
+  qtyReject?: number
+  status?: string
+  unitCost?: number
+  stdCost?: number
+  price?: number
+  um?: string
+  isMainMas?: boolean
+  domain?: string
+  site?: string
+  fromLocation?: string
+  fromSite?: string
+  project?: string
+  productLine?: string
+  dimension1?: string
+  dimension2?: string
+  remark?: string
+  isActive?: number
+  isConfirm?: boolean
+  isChanged?: boolean
+  recID?: number
+  workOrdMasterRecID?: number
+  businessID?: string
+  effTime?: string
+  createTime?: string
+  updateTime?: string
+}
+
+// 工单物料明细保存请求参数
+export interface WorkOrderMaterialSaveReqVO {
+  id?: number
+  workOrd: string
+  line?: number
+  itemNum: string
+  op?: string
+  location?: string
+  lotSerial?: string
+  qtyRequired: number
+  qtyAllocated?: number
+  qtytoIssue?: number
+  qtyIssued?: number
+  qtyPicked?: number
+  qtyPosted?: number
+  qtyReturned?: number
+  qtyReject?: number
+  status?: string
+  unitCost?: number
+  stdCost?: number
+  price?: number
+  um?: string
+  isMainMas?: boolean
+  domain?: string
+  site?: string
+  fromLocation?: string
+  fromSite?: string
+  project?: string
+  productLine?: string
+  dimension1?: string
+  dimension2?: string
+  remark?: string
+  isConfirm?: boolean
+  isChanged?: boolean
+  workOrdMasterRecID?: number
+  businessID?: string
+  effTime?: string
+}
+
+// 获取工单物料明细分页列表
+export const getWorkOrderMaterialPage = (params: WorkOrderMaterialPageReqVO) => {
+  return request.get({ url: '/makeplan/workorder-material/page', params })
+}
+
+// 获取工单物料明细详情
+export const getWorkOrderMaterial = (id: number) => {
+  return request.get({ url: '/makeplan/workorder-material/get', params: { id } })
+}
+
+// 创建工单物料明细
+export const createWorkOrderMaterial = (data: WorkOrderMaterialSaveReqVO) => {
+  return request.post({ url: '/makeplan/workorder-material/create', data })
+}
+
+// 更新工单物料明细
+export const updateWorkOrderMaterial = (data: WorkOrderMaterialSaveReqVO) => {
+  return request.put({ url: '/makeplan/workorder-material/update', data })
+}
+
+// 删除工单物料明细
+export const deleteWorkOrderMaterial = (id: number) => {
+  return request.delete({ url: '/makeplan/workorder-material/delete', params: { id } })
+}
+
+// 获取工单列表(用于下拉选择)
+export const getWorkOrderList = () => {
+  return request.get({ url: '/makeplan/workorder-material/work-order-list' })
+}

+ 10 - 4
yudao-ui/yudao-ui-admin-vue3/src/api/makeplan/workorderSchedule.ts

@@ -1,4 +1,5 @@
 import request from '@/config/axios'
+import axios from 'axios'
 
 // 工单排产分页请求参数
 export interface WorkOrderSchedulePageReqVO {
@@ -56,6 +57,7 @@ export interface WorkOrderScheduleRespVO {
   checkweek?: string
   priority?: number
   locationStock?: number
+  urgent?: number  // 加急状态: 0-普通, 1-加急, 2-特急
 }
 
 // 工单更新请求参数
@@ -98,14 +100,18 @@ export const closeWorkOrders = (data: WorkOrderCloseReqVO) => {
   return request.post({ url: '/makeplan/workorder-schedule/close', data })
 }
 
-// 生产排程
+// 生产排程 - 调用外部接口
 export const productionSchedule = (domain: string) => {
-  return request.post({ url: `/makeplan/workorder-schedule/production-schedule?domain=${domain}` })
+  return axios.post(
+    `http://123.60.180.165:8087/api/business/resource-examine/productionschedule?domain=${domain}`
+  )
 }
 
-// 同步物料需求
+// 同步物料需求 - 调用外部接口 (GET请求)
 export const syncMaterialRequirement = (domain: string) => {
-  return request.post({ url: `/makeplan/workorder-schedule/sync-material-requirement?domain=${domain}` })
+  return axios.get(
+    `http://123.60.180.165:8087/api/business/resource-examine/AutomaticPrAdjustDate?domain=${domain}`
+  )
 }
 
 // 同步工艺路线

+ 38 - 17
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/ProductionSchedule.vue

@@ -21,9 +21,11 @@
         </el-form-item>
         <el-form-item label="状态">
           <el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
-            <el-option label="开立" value="open" />
-            <el-option label="下达" value="released" />
-            <el-option label="关闭" value="closed" />
+            <el-option label="投产" value="w" />
+            <el-option label="下达" value="r" />
+            <el-option label="关闭" value="c" />
+            <el-option label="初始" value="p" />
+            <el-option label="暂停" value="s" />
           </el-select>
         </el-form-item>
         <el-form-item>
@@ -164,6 +166,7 @@ import {
   type WorkOrderSchedulePageReqVO,
   type WorkOrderScheduleRespVO
 } from '@/api/makeplan/workorderSchedule'
+import { getConfigKey } from '@/api/infra/config'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { useRouter } from 'vue-router'
 import PriorityAdjustForm from './components/PriorityAdjustForm.vue'
@@ -284,10 +287,17 @@ const handleGenerateSchedule = async () => {
     })
 
     loading.value = true
-    const domain = '' // 需要根据实际情况获取domain
-    await productionSchedule(domain)
-    ElMessage.success('生产排程生成成功')
-    handleSearch()
+    // 从配置管理中获取domain值
+    const domain = await getConfigKey('order.default.domain')
+    
+    const res = await productionSchedule(domain || '')
+    // 外部接口返回文本格式处理
+    if (res.data === 'OK') {
+      ElMessage.success('生产排程生成成功')
+      handleSearch()
+    } else {
+      ElMessage.error('生产排程失败:' + res.data)
+    }
   } catch (error) {
     if (error !== 'cancel') {
       ElMessage.error('生产排程失败:' + (error.message || '未知错误'))
@@ -305,10 +315,17 @@ const handleSyncMaterial = async () => {
     })
 
     loading.value = true
-    const domain = '' // 需要根据实际情况获取domain
-    await syncMaterialRequirement(domain)
-    ElMessage.success('物料需求同步成功')
-    handleSearch()
+    // 从配置管理中获取domain值
+    const domain = await getConfigKey('order.default.domain')
+    
+    const res = await syncMaterialRequirement(domain || '')
+    // 外部接口返回文本格式处理 (小写ok)
+    if (res.data === 'ok') {
+      ElMessage.success('物料需求同步成功')
+      handleSearch()
+    } else {
+      ElMessage.error('物料需求同步失败:' + res.data)
+    }
   } catch (error) {
     if (error !== 'cancel') {
       ElMessage.error('同步物料需求失败:' + (error.message || '未知错误'))
@@ -397,9 +414,11 @@ const formatNumber = (value) => {
 // 获取状态类型
 const getStatusType = (status) => {
   const typeMap = {
-    'open': 'info',
-    'released': 'success',
-    'closed': 'danger'
+    'w': 'success',    // 投产 - 绿色
+    'r': 'primary',    // 下达 - 蓝色
+    'c': 'info',       // 关闭 - 灰色
+    'p': 'warning',    // 初始 - 橙色
+    's': 'danger'      // 暂停 - 红色
   }
   return typeMap[status?.toLowerCase()] || 'info'
 }
@@ -407,9 +426,11 @@ const getStatusType = (status) => {
 // 获取状态文本
 const getStatusText = (status) => {
   const textMap = {
-    'open': '开立',
-    'released': '下达',
-    'closed': '关闭'
+    'w': '投产',
+    'r': '下达',
+    'c': '关闭',
+    'p': '初始',
+    's': '暂停'
   }
   return textMap[status?.toLowerCase()] || status
 }

+ 51 - 39
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/WorkOrderMaterial.vue

@@ -5,26 +5,29 @@
       <el-form :model="searchForm" inline>
         <el-form-item label="工单编号">
           <el-input
-            v-model="searchForm.WorkOrd"
+            v-model="searchForm.workOrd"
             placeholder="请输入工单编号"
             clearable
             style="width: 200px"
+            @keyup.enter="handleSearch"
           />
         </el-form-item>
         <el-form-item label="物料编号">
           <el-input
-            v-model="searchForm.ItemNum"
+            v-model="searchForm.itemNum"
             placeholder="请输入物料编号"
             clearable
             style="width: 200px"
+            @keyup.enter="handleSearch"
           />
         </el-form-item>
         <el-form-item label="库位">
           <el-input
-            v-model="searchForm.Location"
+            v-model="searchForm.location"
             placeholder="请输入库位"
             clearable
             style="width: 200px"
+            @keyup.enter="handleSearch"
           />
         </el-form-item>
         <el-form-item>
@@ -34,13 +37,14 @@
       </el-form>
     </el-card>
 
-    <!-- 操作按钮 -->
-    <el-card class="toolbar-card" shadow="never">
-      <el-button type="primary" :icon="Plus" @click="handleAdd">添加</el-button>
-    </el-card>
-
-    <!-- 列表表格 -->
+    <!-- 表格区域 -->
     <el-card class="table-card" shadow="never">
+      <!-- 操作按钮 -->
+      <div class="toolbar">
+        <el-button type="primary" :icon="Plus" @click="handleAdd">添加</el-button>
+      </div>
+
+      <!-- 列表表格 -->
       <el-table
         v-loading="loading"
         :data="tableData"
@@ -50,31 +54,31 @@
         :row-style="getRowStyle"
       >
         <el-table-column
-          prop="WorkOrd"
+          prop="workOrd"
           label="工单编号"
           min-width="150"
           fixed="left"
         />
-        <el-table-column prop="Line" label="行号" width="80" />
+        <el-table-column prop="line" label="行号" width="80" />
         <el-table-column
-          prop="ItemNum"
+          prop="itemNum"
           label="物料编码"
           min-width="150"
           fixed="left"
         />
-        <el-table-column prop="Descr" label="物料名称" min-width="200" />
-        <el-table-column prop="OpDescr" label="工序" min-width="150" />
-        <el-table-column prop="Location" label="库位" width="120" />
-        <el-table-column prop="LotSerial" label="生产批号" width="150" />
-        <el-table-column prop="QtyRequired" label="需求数量" width="120" align="right">
+        <el-table-column prop="descr" label="物料名称" min-width="200" />
+        <el-table-column prop="opDescr" label="工序" min-width="150" />
+        <el-table-column prop="location" label="库位" width="120" />
+        <el-table-column prop="lotSerial" label="生产批号" width="150" />
+        <el-table-column prop="qtyRequired" label="需求数量" width="120" align="right">
           <template #default="{ row }">
-            {{ formatNumber(row.QtyRequired) }}
+            {{ formatNumber(row.qtyRequired) }}
           </template>
         </el-table-column>
-        <el-table-column prop="IsActive" label="状态" width="100" align="center">
+        <el-table-column prop="isActive" label="状态" width="100" align="center">
           <template #default="{ row }">
-            <el-tag :type="row.IsActive === 1 ? 'success' : 'danger'">
-              {{ row.IsActive === 1 ? '激活' : '已删除' }}
+            <el-tag :type="row.isActive === 1 ? 'success' : 'danger'">
+              {{ row.isActive === 1 ? '激活' : '已删除' }}
             </el-tag>
           </template>
         </el-table-column>
@@ -84,7 +88,7 @@
               编辑
             </el-button>
             <el-button
-              v-if="row.IsActive === 1"
+              v-if="row.isActive === 1"
               link
               type="danger"
               size="small"
@@ -126,7 +130,10 @@
 import { ref, reactive, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Search, Refresh, Plus } from '@element-plus/icons-vue'
-import { getWorkOrderMaterialList, deleteWorkOrderMaterial } from '@/api/jiaohuo/production'
+import { 
+  getWorkOrderMaterialPage, 
+  deleteWorkOrderMaterial 
+} from '@/api/makeplan/workorderMaterial'
 import { formatNumber } from '@/utils/format'
 import WorkOrderMaterialForm from './components/WorkOrderMaterialForm.vue'
 import { useRoute } from 'vue-router'
@@ -135,9 +142,9 @@ const route = useRoute()
 
 // 搜索表单
 const searchForm = reactive({
-  WorkOrd: '',
-  ItemNum: '',
-  Location: ''
+  workOrd: '',
+  itemNum: '',
+  location: ''
 })
 
 // 分页
@@ -161,13 +168,15 @@ const handleSearch = async () => {
   try {
     loading.value = true
     const params = {
-      ...searchForm,
-      page: pagination.page,
+      workOrd: searchForm.workOrd || undefined,
+      itemNum: searchForm.itemNum || undefined,
+      location: searchForm.location || undefined,
+      pageNo: pagination.page,
       pageSize: pagination.pageSize
     }
-    const { data } = await getWorkOrderMaterialList(params)
-    tableData.value = data.list || []
-    pagination.total = data.total || 0
+    const res = await getWorkOrderMaterialPage(params)
+    tableData.value = res?.list || []
+    pagination.total = res?.total || 0
   } catch (error) {
     ElMessage.error(error.message || '查询失败')
   } finally {
@@ -177,9 +186,9 @@ const handleSearch = async () => {
 
 // 重置搜索
 const handleReset = () => {
-  searchForm.WorkOrd = ''
-  searchForm.ItemNum = ''
-  searchForm.Location = ''
+  searchForm.workOrd = ''
+  searchForm.itemNum = ''
+  searchForm.location = ''
   pagination.page = 1
   handleSearch()
 }
@@ -225,7 +234,7 @@ const handleView = (row) => {
 
 // 获取行样式
 const getRowStyle = ({ row }) => {
-  if (row.IsActive === 0) {
+  if (row.isActive === 0) {
     return {
       'background-color': '#ffe6e6'
     }
@@ -237,10 +246,10 @@ const getRowStyle = ({ row }) => {
 onMounted(() => {
   // 如果从工单排产页面跳转过来,会带上工单编号
   if (route.query.workOrd) {
-    searchForm.WorkOrd = route.query.workOrd
+    searchForm.workOrd = route.query.workOrd
   }
-  // 注释掉自动查询,等后端API准备好后再启用
-  // handleSearch()
+  // 启用自动查询
+  handleSearch()
 })
 </script>
 
@@ -249,11 +258,14 @@ onMounted(() => {
   padding: 20px;
 
   .search-card,
-  .toolbar-card,
   .table-card {
     margin-bottom: 20px;
   }
 
+  .toolbar {
+    margin-bottom: 16px;
+  }
+
   .el-table {
     :deep(.el-table__cell) {
       padding: 8px 0;

+ 15 - 7
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/WorkOrderProcess.vue

@@ -9,6 +9,7 @@
             placeholder="请输入生产指令"
             clearable
             style="width: 200px"
+            @keyup.enter="handleSearch"
           />
         </el-form-item>
         <el-form-item label="生产批次">
@@ -17,6 +18,7 @@
             placeholder="请输入生产批次"
             clearable
             style="width: 200px"
+            @keyup.enter="handleSearch"
           />
         </el-form-item>
         <el-form-item label="物料编号">
@@ -25,6 +27,7 @@
             placeholder="请输入物料编号"
             clearable
             style="width: 200px"
+            @keyup.enter="handleSearch"
           />
         </el-form-item>
         <el-form-item label="班组">
@@ -33,6 +36,7 @@
             placeholder="请输入班组"
             clearable
             style="width: 200px"
+            @keyup.enter="handleSearch"
           />
         </el-form-item>
         <el-form-item label="工单创建时间">
@@ -62,13 +66,14 @@
       </el-form>
     </el-card>
 
-    <!-- 操作按钮 -->
-    <el-card class="toolbar-card" shadow="never">
-      <el-button type="primary" :icon="Plus" @click="handleAdd">添加</el-button>
-    </el-card>
-
-    <!-- 列表表格 -->
+    <!-- 表格区域 -->
     <el-card class="table-card" shadow="never">
+      <!-- 操作按钮 -->
+      <div class="toolbar">
+        <el-button type="primary" :icon="Plus" @click="handleAdd">添加</el-button>
+      </div>
+
+      <!-- 列表表格 -->
       <el-table
         v-loading="loading"
         :data="tableData"
@@ -279,11 +284,14 @@ onMounted(() => {
   padding: 20px;
 
   .search-card,
-  .toolbar-card,
   .table-card {
     margin-bottom: 20px;
   }
 
+  .toolbar {
+    margin-bottom: 16px;
+  }
+
   .el-table {
     :deep(.el-table__cell) {
       padding: 8px 0;

+ 7 - 7
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/components/PriorityAdjustForm.vue

@@ -84,13 +84,13 @@ const open = (row) => {
   currentRow.value = row
   dialogVisible.value = true
   
-  // 填充表单数据
-  formData.WorkOrd = row.WorkOrd || ''
-  formData.ItemNum = row.ItemNum || ''
-  formData.QtyOrded = row.QtyOrded || 0
-  formData.Priority = row.Priority || 0
-  formData.LotSerial = row.LotSerial || ''
-  formData.Urgent = row.Urgent || 0
+  // 填充表单数据 - 兼容大小写字段名
+  formData.WorkOrd = row.workOrd || row.WorkOrd || ''
+  formData.ItemNum = row.itemNum || row.ItemNum || ''
+  formData.QtyOrded = row.qtyOrded || row.QtyOrded || 0
+  formData.Priority = row.priority || row.Priority || 0
+  formData.LotSerial = row.lotSerial || row.LotSerial || ''
+  formData.Urgent = row.urgent || row.Urgent || 0
 }
 
 // 提交

+ 107 - 144
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/components/WorkOrderMaterialForm.vue

@@ -10,84 +10,70 @@
       ref="formRef"
       :model="formData"
       :rules="formRules"
-      label-width="120px"
-      :disabled="props.mode === 'view'"
+      label-width="100px"
+      :disabled="mode === 'view'"
     >
-      <el-form-item label="工单编号" prop="WorkOrd">
+      <el-form-item label="工单编号" prop="workOrd">
         <el-select
-          v-model="formData.WorkOrd"
+          v-model="formData.workOrd"
           placeholder="请选择工单编号"
           filterable
           style="width: 100%"
-          :disabled="props.mode === 'edit'"
-          @change="handleWorkOrdChange"
+          :disabled="mode === 'edit'"
         >
           <el-option
             v-for="item in workOrderList"
-            :key="item.workord"
-            :label="item.workord"
-            :value="item.workord"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
           />
         </el-select>
       </el-form-item>
 
-      <el-form-item label="物料号" prop="ItemNum">
-        <el-input
-          v-model="formData.ItemNum"
-          placeholder="请输入物料号"
-          :disabled="props.mode === 'edit'"
-        />
+      <el-form-item label="物料号" prop="itemNum">
+        <el-input v-model="formData.itemNum" placeholder="请输入物料号" />
       </el-form-item>
 
-      <el-form-item label="工序" prop="Op">
-        <el-input v-model="formData.Op" placeholder="请输入工序" />
+      <el-form-item label="工序" prop="op">
+        <el-input v-model="formData.op" placeholder="请输入工序" />
       </el-form-item>
 
-      <el-form-item label="需求数量" prop="QtyRequired">
+      <el-form-item label="需求数量" prop="qtyRequired">
         <el-input-number
-          v-model="formData.QtyRequired"
-          :min="0"
+          v-model="formData.qtyRequired"
           :precision="2"
-          :controls="false"
+          :min="0"
+          placeholder="请输入需求数量"
           style="width: 100%"
         />
       </el-form-item>
 
-      <el-form-item label="库位" prop="Location">
-        <el-input v-model="formData.Location" placeholder="请输入库位" />
+      <el-form-item label="库位" prop="location">
+        <el-input v-model="formData.location" placeholder="请输入库位" />
       </el-form-item>
 
-      <el-form-item label="生产批次" prop="LotSerial">
-        <el-input v-model="formData.LotSerial" placeholder="请输入生产批次" />
+      <el-form-item label="生产批次" prop="lotSerial">
+        <el-input v-model="formData.lotSerial" placeholder="请输入生产批次" />
       </el-form-item>
     </el-form>
 
     <template #footer>
-      <span class="dialog-footer">
-        <el-button @click="handleClose">取消</el-button>
-        <el-button
-          v-if="props.mode !== 'view'"
-          type="primary"
-          :loading="submitLoading"
-          @click="handleSubmit"
-        >
-          保存
-        </el-button>
-      </span>
+      <el-button @click="handleClose">取消</el-button>
+      <el-button v-if="mode !== 'view'" type="primary" @click="handleSubmit" :loading="loading">
+        保存
+      </el-button>
     </template>
   </el-dialog>
 </template>
 
 <script setup>
-import { ref, reactive, computed, watch } from 'vue'
+import { ref, computed, watch, onMounted } from 'vue'
 import { ElMessage } from 'element-plus'
 import {
-  getWorkOrderList,
-  addWorkOrderMaterial,
+  createWorkOrderMaterial,
   updateWorkOrderMaterial,
-  checkMaterialExists,
-  checkWorkOrderMaterialExists
-} from '@/api/jiaohuo/production'
+  getWorkOrderList
+} from '@/api/makeplan/workorderMaterial'
 
 const props = defineProps({
   visible: {
@@ -106,6 +92,10 @@ const props = defineProps({
 
 const emit = defineEmits(['update:visible', 'success'])
 
+const formRef = ref()
+const loading = ref(false)
+const workOrderList = ref([])
+
 const dialogVisible = computed({
   get: () => props.visible,
   set: (val) => emit('update:visible', val)
@@ -120,55 +110,63 @@ const dialogTitle = computed(() => {
   return titles[props.mode] || '工单物料明细'
 })
 
-const formRef = ref(null)
-const submitLoading = ref(false)
-const workOrderList = ref([])
-
-// 表单数据
-const formData = reactive({
-  RecID: '',
-  WorkOrd: '',
-  ItemNum: '',
-  Op: '',
-  QtyRequired: 0,
-  Location: '',
-  LotSerial: ''
+const formData = ref({
+  id: undefined,
+  recID: undefined,
+  workOrd: '',
+  itemNum: '',
+  op: '',
+  qtyRequired: undefined,
+  location: '',
+  lotSerial: ''
 })
 
-// 表单验证规则
 const formRules = {
-  WorkOrd: [{ required: true, message: '请选择工单编号', trigger: 'change' }],
-  ItemNum: [{ required: true, message: '请输入物料号', trigger: 'blur' }],
-  Op: [{ required: true, message: '请输入工序', trigger: 'blur' }],
-  QtyRequired: [{ required: true, message: '请输入需求数量', trigger: 'blur' }]
+  workOrd: [{ required: true, message: '请选择工单编号', trigger: 'change' }],
+  itemNum: [{ required: true, message: '请输入物料号', trigger: 'blur' }],
+  qtyRequired: [{ required: true, message: '请输入需求数量', trigger: 'blur' }]
 }
 
 // 重置表单
 const resetForm = () => {
-  formData.RecID = ''
-  formData.WorkOrd = ''
-  formData.ItemNum = ''
-  formData.Op = ''
-  formData.QtyRequired = 0
-  formData.Location = ''
-  formData.LotSerial = ''
+  formData.value = {
+    id: undefined,
+    recID: undefined,
+    workOrd: '',
+    itemNum: '',
+    op: '',
+    qtyRequired: undefined,
+    location: '',
+    lotSerial: ''
+  }
   formRef.value?.clearValidate()
 }
 
+// 加载工单列表
+const loadWorkOrderList = async () => {
+  try {
+    const res = await getWorkOrderList()
+    workOrderList.value = res || []
+  } catch (error) {
+    console.error('加载工单列表失败:', error)
+  }
+}
+
 // 监听数据变化
 watch(
   () => props.data,
-  (newData) => {
-    if (newData) {
-      Object.assign(formData, {
-        RecID: newData.id || newData.RecID || '',
-        WorkOrd: newData.WorkOrd || '',
-        ItemNum: newData.ItemNum || '',
-        Op: newData.Op || '',
-        QtyRequired: newData.QtyRequired || 0,
-        Location: newData.Location || '',
-        LotSerial: newData.LotSerial || ''
-      })
+  (newVal) => {
+    if (newVal) {
+      formData.value = {
+        id: newVal.id || newVal.ID,
+        recID: newVal.recID || newVal.RecID,
+        workOrd: newVal.workOrd || newVal.WorkOrd || '',
+        itemNum: newVal.itemNum || newVal.ItemNum || '',
+        op: newVal.op || newVal.Op || '',
+        qtyRequired: newVal.qtyRequired || newVal.QtyRequired,
+        location: newVal.location || newVal.Location || '',
+        lotSerial: newVal.lotSerial || newVal.LotSerial || ''
+      }
     } else {
       resetForm()
     }
@@ -176,67 +174,41 @@ watch(
   { immediate: true }
 )
 
-// 监听对话框显示
-watch(dialogVisible, (val) => {
-  if (val) {
-    loadWorkOrderList()
-  }
-})
-
-// 加载工单列表
-const loadWorkOrderList = async () => {
-  try {
-    const { data } = await getWorkOrderList()
-    workOrderList.value = data || []
-  } catch (error) {
-    console.error('加载工单列表失败:', error)
+// 监听对话框打开
+watch(
+  () => props.visible,
+  (newVal) => {
+    if (newVal && props.mode === 'add') {
+      loadWorkOrderList()
+    }
   }
-}
+)
 
-// 工单变更
-const handleWorkOrdChange = () => {
-  // 可以根据工单编号加载更多信息
+const handleClose = () => {
+  dialogVisible.value = false
+  resetForm()
 }
 
-// 提交表单
 const handleSubmit = async () => {
   try {
-    await formRef.value?.validate()
-
-    submitLoading.value = true
-
-    // 新增时的验证
-    if (props.mode === 'add') {
-      // 1. 检查物料是否存在于基础数据中
-      const materialExistsRes = await checkMaterialExists({
-        ItemNum: formData.ItemNum
-      })
-      if (!materialExistsRes.data.exists) {
-        ElMessage.error('该物料在基础数据中不存在,无法添加')
-        submitLoading.value = false
-        return
-      }
-
-      // 2. 检查工单中是否已有此物料
-      const workOrderMaterialExistsRes = await checkWorkOrderMaterialExists({
-        WorkOrd: formData.WorkOrd,
-        ItemNum: formData.ItemNum,
-        Op: formData.Op
-      })
-      if (workOrderMaterialExistsRes.data.exists) {
-        ElMessage.error('该工单中已存在相同物料和工序,无法添加')
-        submitLoading.value = false
-        return
-      }
+    await formRef.value.validate()
+    loading.value = true
+
+    const submitData = {
+      id: formData.value.id,
+      workOrd: formData.value.workOrd,
+      itemNum: formData.value.itemNum,
+      op: formData.value.op,
+      qtyRequired: formData.value.qtyRequired,
+      location: formData.value.location,
+      lotSerial: formData.value.lotSerial
     }
 
-    // 保存数据
-    const saveData = { ...formData }
     if (props.mode === 'add') {
-      await addWorkOrderMaterial(saveData)
+      await createWorkOrderMaterial(submitData)
       ElMessage.success('添加成功')
-    } else {
-      await updateWorkOrderMaterial(formData.RecID, saveData)
+    } else if (props.mode === 'edit') {
+      await updateWorkOrderMaterial(submitData)
       ElMessage.success('更新成功')
     }
 
@@ -247,22 +219,13 @@ const handleSubmit = async () => {
       ElMessage.error(error.message || '操作失败')
     }
   } finally {
-    submitLoading.value = false
+    loading.value = false
   }
 }
 
-// 关闭对话框
-const handleClose = () => {
-  resetForm()
-  dialogVisible.value = false
-}
+onMounted(() => {
+  if (props.mode === 'add') {
+    loadWorkOrderList()
+  }
+})
 </script>
-
-<style lang="scss" scoped>
-.dialog-footer {
-  display: flex;
-  justify-content: flex-end;
-  gap: 10px;
-}
-</style>
-

+ 14 - 3
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/components/WorkOrderViewForm.vue

@@ -72,7 +72,7 @@
 
 <script setup>
 import { ref, reactive } from 'vue'
-import { getWorkOrderDetail } from '@/api/jiaohuo/production'
+import { getWorkOrderSchedule } from '@/api/makeplan/workorderSchedule'
 import { ElMessage } from 'element-plus'
 
 const dialogVisible = ref(false)
@@ -105,8 +105,19 @@ const open = async (id) => {
 const loadData = async (id) => {
   loading.value = true
   try {
-    const { data } = await getWorkOrderDetail(id)
-    Object.assign(formData, data)
+    const data = await getWorkOrderSchedule(id)
+    // 兼容大小写字段名
+    formData.WorkOrd = data.workOrd || data.WorkOrd || ''
+    formData.QtyOrded = data.qtyOrded || data.QtyOrded || 0
+    formData.QtyCompleted = data.qtyCompleted || data.QtyCompleted || 0
+    formData.ItemNum = data.itemNum || data.ItemNum || ''
+    formData.ItemName = data.descr || data.ItemName || ''
+    formData.ItemModel = data.descr1 || data.ItemModel || ''
+    formData.OrdDate = data.ordDate || data.OrdDate || ''
+    formData.DueDate = data.dueDate || data.DueDate || ''
+    formData.Status = data.status || data.Status || ''
+    formData.Remark = data.remark || data.Remark || ''
+    
     processList.value = data.processList || []
     materialList.value = data.materialList || []
   } catch (error) {