From 1ab2b329a40decba8ff7eefbeb158ff259aceb6d Mon Sep 17 00:00:00 2001
From: liyong <18434998025@163.com>
Date: 星期二, 14 七月 2026 15:23:34 +0800
Subject: [PATCH] 追溯+分类接口
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeServiceImpl.java | 5
docs/process_backflush_flag.md | 140 +++
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java | 129 +++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java | 17
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessSaveReqVO.java | 3
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java | 33
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java | 4
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemTypeApi.java | 44 +
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java | 32
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java | 18
yudao-module-mes-api/pom.xml | 33
pom.xml | 1
docs/crm_followup_record_fix.md | 302 +++++++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java | 5
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeService.java | 8
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackTraceRespVO.java | 36
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java | 5
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemSyncReqDTO.java | 45 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessRespVO.java | 3
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java | 16
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/ipqc/MesQcIpqcMapper.java | 9
.claude/plan.md | 129 +++
docs/feedback_traceability_plan.md | 135 +++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/unitmeasure/MesMdUnitMeasureServiceImpl.java | 58 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/item/MesMdItemTypeMapper.java | 4
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java | 18
docs/sql/batch_traceability_fix.sql | 19
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java | 59 +
docs/mdm_item_sync_mes.md | 90 ++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java | 82 ++
.claude/settings.local.json | 3
docs/item_type_integration.md | 111 ++
docs/task_pending_feedback_ledger.md | 161 ++++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java | 8
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java | 39
yudao-module-mdm/pom.xml | 7
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java | 2
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/route/MesProRouteProcessDO.java | 6
docs/batch_traceability_frontend.md | 262 ++++++
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemTypeRespDTO.java | 28
yudao-module-mes/pom.xml | 7
yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml | 34
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemTypeApiImpl.java | 49 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java | 12
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemService.java | 8
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/feedback/MesProFeedbackMapper.java | 18
46 files changed, 2,204 insertions(+), 33 deletions(-)
diff --git a/.claude/plan.md b/.claude/plan.md
new file mode 100644
index 0000000..0c3f965
--- /dev/null
+++ b/.claude/plan.md
@@ -0,0 +1,129 @@
+# 寰呮姤宸ュ彴璐� 鎺ュ彛鏂规
+
+## 闇�姹�
+
+鎺掍骇鍚庨渶瑕佷竴涓�"寰呮姤宸ュ彴璐�"锛氬睍绀哄凡鎺掍骇浣嗗皻鏈姤宸ュ畬鐨勪换鍔★紝鏍稿績鏄� **寰呮姤宸ユ暟閲� = 鎺掍骇鏁伴噺 鈭� 宸叉姤宸ユ暟閲�**銆�
+
+鏁版嵁婧愮敤 `mes_pro_task`锛堜换鍔$骇姹囨�伙紝姝g‘--寰呮姤宸ュぉ鐒舵槸浠诲姟绾ф蹇碉紱宸茬敓浜ф暟閲� producedQuantity 鐢辨姤宸ュ鎵圭疮鍔犲洖鍐欙紝鏃犻渶鐜扮畻 feedback锛夈��
+
+## 鏍稿績杩囨护涓庡瓧娈�
+
+- **杩囨护**锛歚status = PREPARE(0)`锛堣繘琛屼腑锛屾帓闄ゅ凡瀹屾垚/宸插彇娑堬級AND `(quantity - IFNULL(produced_quantity,0)) > 0`锛堜粛鏈夊緟鎶ュ伐閲忥級
+- **鏂板灞曠ず瀛楁**锛�
+ - `pendingQuantity` 寰呮姤宸ユ暟閲� = `quantity - producedQuantity`
+ - `feedbackRate` 鎶ュ伐瀹屾垚鐜� = `producedQuantity / quantity 脳 100`锛堟帓浜ф暟閲忎负 0 鏃剁粰 0锛�
+- 鍏朵綑灞曠ず瀛楁澶嶇敤鐜版湁 `MesProTaskRespVO`锛堝伐鍗曞彿銆佷骇鍝併�佸伐搴忋�佸伐浣嶃�佸鎴枫�佹帓浜�/宸茬敓浜�/鍚堟牸/涓嶈壇鏁伴噺銆侀渶姹傛棩鏈熺瓑锛屽凡鐢� `buildTaskRespVOList` 鎵归噺鍏宠仈鎷煎ソ锛�
+
+## 鏀瑰姩娓呭崟
+
+### 1. `MesProTaskRespVO` 鍔犱袱涓瓧娈�
+
+`controller/admin/pro/task/vo/MesProTaskRespVO.java`锛屽湪 `unqualifyQuantity` 涔嬪悗锛�
+
+```java
+@Schema(description = "寰呮姤宸ユ暟閲�", example = "50.00")
+private BigDecimal pendingQuantity;
+
+@Schema(description = "鎶ュ伐瀹屾垚鐜�(%)", example = "50.00")
+private BigDecimal feedbackRate;
+```
+
+### 2. `MesProTaskController.buildTaskRespVOList` 璁$畻杩欎袱涓瓧娈�
+
+鍦� `convertList` 鐨� lambda 閲岋紝`BeanUtils.toBean` 涔嬪悗杩藉姞锛�
+
+```java
+BigDecimal quantity = task.getQuantity() == null ? BigDecimal.ZERO : task.getQuantity();
+BigDecimal produced = task.getProducedQuantity() == null ? BigDecimal.ZERO : task.getProducedQuantity();
+vo.setPendingQuantity(quantity.subtract(produced));
+vo.setFeedbackRate(quantity.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO
+ : produced.multiply(new BigDecimal("100")).divide(quantity, 2, RoundingMode.HALF_UP));
+```
+
+> 鐜版湁 page / get / gantt 閮借蛋 `buildTaskRespVOList`锛屽姞杩欎袱鍒楀悗瀹冧滑涔熶細甯﹀嚭锛屾棤瀹充笖鏇村畬鏁淬��
+
+### 3. `MesProTaskMapper` 鍔� `selectPendingFeedbackPage`
+
+`dal/mysql/pro/task/MesProTaskMapper.java`锛屼豢 `selectPage`锛屽己鍒� `status=PREPARE` + 寰呮姤宸ラ噺>0锛屽鐢ㄥ叾浣欒繃婊ゆ潯浠讹紝涓嶉渶瑕� checkFlag join锛�
+
+```java
+default PageResult<MesProTaskDO> selectPendingFeedbackPage(MesProTaskPageReqVO reqVO) {
+ MPJLambdaWrapperX<MesProTaskDO> query = new MPJLambdaWrapperX<>();
+ query.selectAll(MesProTaskDO.class);
+ query.eq(MesProTaskDO::getStatus, MesProTaskStatusEnum.PREPARE.getStatus()); // 寰呮姤宸�=杩涜涓�
+ query.apply("(quantity - IFNULL(produced_quantity, 0)) > 0"); // 浠嶆湁寰呮姤宸ラ噺
+ query.likeIfPresent(MesProTaskDO::getCode, reqVO.getCode())
+ .likeIfPresent(MesProTaskDO::getName, reqVO.getName())
+ .eqIfPresent(MesProTaskDO::getWorkOrderId, reqVO.getWorkOrderId())
+ .eqIfPresent(MesProTaskDO::getRouteId, reqVO.getRouteId())
+ .eqIfPresent(MesProTaskDO::getProcessId, reqVO.getProcessId())
+ .eqIfPresent(MesProTaskDO::getWorkstationId, reqVO.getWorkstationId())
+ .betweenIfPresent(MesProTaskDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(MesProTaskDO::getId);
+ return selectPage(reqVO, query);
+}
+```
+
+> `apply` 鐢ㄨ8鍒楀悕锛氭湰鏌ヨ鏃� join锛宍quantity`/`produced_quantity` 鏃犳涔夈�俙reqVO.getStatus()`/`getStatuses()` 鍦ㄦ鎺ュ彛琚拷鐣ワ紙寮哄埗 PREPARE锛夛紝绗﹀悎"寰呮姤宸�"璇箟銆�
+
+### 4. `MesProTaskService` + Impl 鍔犳柟娉�
+
+鎺ュ彛鍔狅細
+
+```java
+PageResult<MesProTaskDO> getPendingFeedbackPage(MesProTaskPageReqVO pageReqVO);
+```
+
+Impl锛�
+
+```java
+@Override
+public PageResult<MesProTaskDO> getPendingFeedbackPage(MesProTaskPageReqVO pageReqVO) {
+ return taskMapper.selectPendingFeedbackPage(pageReqVO);
+}
+```
+
+### 5. `MesProTaskController` 鍔犳帴鍙�
+
+澶嶇敤 `buildTaskRespVOList` 鎷煎睍绀哄瓧娈碉紝澶嶇敤 `MesProTaskPageReqVO` 鍋氳繃婊わ細
+
+```java
+@GetMapping("/pending-feedback-page")
+@Operation(summary = "鑾峰緱寰呮姤宸ュ彴璐﹀垎椤�", description = "宸叉帓浜т笖浠嶆湁寰呮姤宸ユ暟閲忕殑浠诲姟锛堢姸鎬�=杩涜涓級")
+@PreAuthorize("@ss.hasPermission('mes:pro-task:query')")
+public CommonResult<PageResult<MesProTaskRespVO>> getPendingFeedbackPage(@Valid MesProTaskPageReqVO pageReqVO) {
+ PageResult<MesProTaskDO> pageResult = taskService.getPendingFeedbackPage(pageReqVO);
+ return success(new PageResult<>(buildTaskRespVOList(pageResult.getList()), pageResult.getTotal()));
+}
+```
+
+### 6. 鍓嶇鏂囨。
+
+鎸� frontend-docs 瑙勫垯杈撳嚭 `docs/task_pending_feedback_ledger.md`锛氭帴鍙h矾寰勩�佽繃婊ゅ弬鏁般�佸搷搴斿瓧娈碉紙鍚� pendingQuantity/feedbackRate锛夈�佸墠绔〃鏍煎垪寤鸿銆�
+
+### 7. 缂栬瘧鏍¢獙
+
+`mvn compile -pl yudao-module-mes -am -Dmaven.compiler.source=25 -Dmaven.compiler.target=25 -q`锛堟寜 compile-check 瑙勫垯锛�
+
+## 琛屼负璇存槑
+
+| 浠诲姟鎯呭喌 | 鏄惁鍑虹幇鍦ㄥ緟鎶ュ伐鍙拌处 |
+|---------|--------------------|
+| 鐘舵��=杩涜涓紝宸叉姤宸� < 鎺掍骇 | 鉁� 鍑虹幇锛宲endingQuantity>0 |
+| 鐘舵��=杩涜涓紝宸叉姤宸� = 鎺掍骇锛堟姤婊′簡浣嗗伐鍗曟病瀹屽伐锛墊 鉂� 涓嶅嚭鐜帮紙pendingQuantity=0锛墊
+| 鐘舵��=宸插畬鎴�/宸插彇娑� | 鉂� 涓嶅嚭鐜� |
+| 鐘舵��=杩涜涓紝浠庢湭鎶ュ伐 | 鉁� 鍑虹幇锛宲endingQuantity=鎺掍骇鏁伴噺 |
+
+> 宸叉姤宸�=鎺掍骇浣嗕换鍔′粛 PREPARE 鐨勪换鍔′笉鍦ㄥ彴璐﹂噷锛堟病寰呮姤宸ラ噺浜嗭級銆傝嫢鎯崇湅"鎵�鏈夎繘琛屼腑浠诲姟"锛岀敤鐜版湁 `/mes/pro/task/page?status=0`銆�
+
+## 涓嶆敼鍔ㄩ」
+
+- 鎶ュ伐鐩稿叧閫昏緫涓嶅姩
+- `MesProTaskPageReqVO` 涓嶅姩锛堝鐢級
+- `selectPage` 鍘熼�昏緫涓嶅姩
+- 涓嶆柊寤虹嫭绔� ReqVO/RespVO锛堝鐢ㄧ幇鏈夛紝鏈�灏忔敼鍔級
+
+## 寰呯‘璁�
+
+- `feedbackRate`锛堝畬鎴愮巼锛夎涓嶈锛熼粯璁ゅ姞涓婏紝灞曠ず鏇寸洿瑙傦紱涓嶈鍙幓鎺夈��
+- 鏄惁闇�瑕�"鎸夊緟鎶ュ伐鏁伴噺鍊掑簭"鎺掑垪锛熷綋鍓嶆寜 id 鍊掑簭銆傚彲鏀� `orderByDesc(pendingQuantity)`锛屼絾 MPJ 瀵硅绠楀垪鎺掑簭闇� raw SQL锛岀暐绻併�傞粯璁� id 鍊掑簭锛岄渶瑕佸啀璇淬��
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index b89dd57..ef2c24a 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -30,7 +30,8 @@
"Bash(chmod +x /tmp/fix_controller.sh)",
"Bash(/tmp/fix_controller.sh)",
"Bash(chmod +x /tmp/fix2.sh)",
- "Bash(/tmp/fix2.sh)"
+ "Bash(/tmp/fix2.sh)",
+ "Bash(\"E:/soft/IntelliJ IDEA 2025.3.4/plugins/maven/lib/maven3/bin/mvn.cmd\" -DskipTests=true -q compile -pl yudao-module-mdm-api,yudao-module-mdm,yudao-module-mes -am)"
],
"deny": [
"Bash(rm -rf *)",
diff --git a/docs/batch_traceability_frontend.md b/docs/batch_traceability_frontend.md
new file mode 100644
index 0000000..79ecbc0
--- /dev/null
+++ b/docs/batch_traceability_frontend.md
@@ -0,0 +1,262 @@
+# 鎵规杩芥函鍔熻兘 - 鍓嶇鑱旇皟鏂规
+
+## 姒傝堪
+
+鏈鏀归�犱慨澶嶅苟瀹屽杽浜嗘壒娆¤拷婧姛鑳斤紝鏀寔浠庢壒娆″嚭鍙戠殑姝e悜锛堝師鏉愭枡鈫掓垚鍝侊級鍜屽弽鍚戯紙鎴愬搧鈫掑師鏉愭枡锛夎拷婧煡璇€��
+
+---
+
+## 涓�銆佹秹鍙婇〉闈�
+
+| 椤甸潰 | 璺緞 | 鏀瑰姩璇存槑 |
+|------|------|----------|
+| 鎵规绠$悊鍒楄〃 | `/mes/wm/batch/list` | 娣诲姞"鍚戝墠杩芥函"銆�"鍚戝悗杩芥函"鎸夐挳 |
+| 鎵规璇︽儏 | `/mes/wm/batch/detail` | 灞曠ず杩芥函缁撴灉鏃堕棿绾� |
+
+---
+
+## 浜屻�丄PI 鎺ュ彛
+
+### 2.1 鎵规鍚戝墠杩芥函
+
+**璇锋眰鏂瑰紡**锛欸ET
+
+**璇锋眰璺緞**锛歚/mes/wm/batch/forward-list`
+
+**璇锋眰鍙傛暟**锛�
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| code | String | 鏄� | 鎵规缂栫爜 |
+
+**鍝嶅簲绀轰緥**锛�
+
+```json
+{
+ "code": 0,
+ "msg": "",
+ "data": [
+ {
+ "id": 1,
+ "code": "BATCH20250713001",
+ "itemId": 100,
+ "itemCode": "ITEM001",
+ "itemName": "鎴愬搧A",
+ "workOrderId": 50,
+ "workOrderCode": "WO20250713001",
+ "taskId": 80,
+ "taskCode": "TASK001",
+ "vendorId": 20,
+ "vendorCode": "VENDOR001",
+ "vendorName": "渚涘簲鍟咥",
+ "clientId": 30,
+ "clientCode": "CLIENT001",
+ "clientName": "瀹㈡埛A",
+ "workstationId": 40,
+ "workstationCode": "WS001",
+ "toolId": 50,
+ "toolCode": "TOOL001"
+ }
+ ]
+}
+```
+
+### 2.2 鎵规鍚戝悗杩芥函
+
+**璇锋眰鏂瑰紡**锛欸ET
+
+**璇锋眰璺緞**锛歚/mes/wm/batch/backward-list`
+
+**璇锋眰鍙傛暟**锛�
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| code | String | 鏄� | 鎵规缂栫爜 |
+
+**鍝嶅簲绀轰緥**锛氬悓鍚戝墠杩芥函
+
+---
+
+## 涓夈�佸墠绔疄鐜拌鐐�
+
+### 3.1 鏁版嵁鑾峰彇鏃舵満
+
+鐢ㄦ埛鐐瑰嚮"鍚戝墠杩芥函"鎴�"鍚戝悗杩芥函"鎸夐挳鏃讹紝璋冪敤瀵瑰簲鎺ュ彛鑾峰彇杩芥函缁撴灉銆�
+
+### 3.2 杩芥函缁撴灉灞曠ず
+
+寤鸿浣跨敤鏃堕棿绾跨粍浠跺睍绀鸿拷婧摼璺細
+
+```html
+<!-- 鍚戝墠杩芥函锛氬師鏉愭枡 鈫� 鎴愬搧 -->
+<el-timeline>
+ <el-timeline-item
+ v-for="(batch, index) in forwardList"
+ :key="batch.id"
+ :timestamp="formatDate(batch.createTime)"
+ placement="top"
+ >
+ <el-card shadow="hover">
+ <h4>鎵规锛歿{ batch.code }}</h4>
+ <p>鐗╂枡锛歿{ batch.itemName }} ({{ batch.itemCode }})</p>
+ <p v-if="batch.workOrderCode">宸ュ崟锛歿{ batch.workOrderCode }}</p>
+ <p v-if="batch.taskCode">浠诲姟锛歿{ batch.taskCode }}</p>
+ <p v-if="batch.vendorName">渚涘簲鍟嗭細{{ batch.vendorName }}</p>
+ <p v-if="batch.clientName">瀹㈡埛锛歿{ batch.clientName }}</p>
+ </el-card>
+ </el-timeline-item>
+</el-timeline>
+
+<!-- 鍚戝悗杩芥函锛氭垚鍝� 鈫� 鍘熸潗鏂� -->
+<el-timeline>
+ <el-timeline-item
+ v-for="(batch, index) in backwardList"
+ :key="batch.id"
+ :timestamp="formatDate(batch.createTime)"
+ placement="top"
+ >
+ <el-card shadow="hover">
+ <h4>鎵规锛歿{ batch.code }}</h4>
+ <p>鐗╂枡锛歿{ batch.itemName }} ({{ batch.itemCode }})</p>
+ <p v-if="batch.workOrderCode">宸ュ崟锛歿{ batch.workOrderCode }}</p>
+ <p v-if="batch.taskCode">浠诲姟锛歿{ batch.taskCode }}</p>
+ <p v-if="batch.vendorName">渚涘簲鍟嗭細{{ batch.vendorName }}</p>
+ </el-card>
+ </el-timeline-item>
+</el-timeline>
+```
+
+### 3.3 鏃犳暟鎹彁绀�
+
+褰撹拷婧粨鏋滀负绌烘椂锛屾樉绀哄弸濂芥彁绀猴細
+
+```html
+<el-empty v-if="!loading && traceList.length === 0" description="鏆傛棤杩芥函鏁版嵁">
+ <template #image>
+ <svg viewBox="0 0 1024 1024" width="128">
+ <path d="M512 128c-199.8 0-360 160.2-360 360s160.2 360 360 360 360-160.2 360-360S711.8 128 512 128zm0 128c119.8 0 216 96.2 216 216s-96.2 216-216 216-216-96.2-216-216 96.2-216 216-216zm0 128c-52.9 0-96 43.1-96 96s43.1 96 96 96 96-43.1 96-96-43.1-96-96-96z" fill="#d9d9d9"/>
+ </svg>
+ </template>
+</el-empty>
+```
+
+---
+
+## 鍥涖�佷笟鍔℃祦绋嬪浘
+
+### 4.1 鎵规鍚戝墠杩芥函娴佺▼
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鎵规鍒楄〃椤� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鐐瑰嚮"鍚戝墠杩芥函"
+ 鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 璋冪敤 GET /mes/wm/batch/forward-list 鈹�
+鈹� 鍙傛暟: code = 褰撳墠鎵规缂栫爜 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹�
+ 鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 杩斿洖璇ユ壒娆′骇鍑虹殑鎵�鏈変笅娓告壒娆″垪琛� 鈹�
+鈹� 鈹�
+鈹� 杩芥函璺緞锛� 鈹�
+鈹� 搴撳瓨浜嬪姟(娑堣��) 鈫� 娑堣�楀崟 鈫� 鎶ュ伐璁板綍 鈹�
+鈹� 鈫� 搴撳瓨浜嬪姟(浜у嚭) 鈫� 涓嬫父鎵规 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹�
+ 鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 浠ユ椂闂寸嚎褰㈠紡灞曠ず杩芥函缁撴灉 鈹�
+鈹� 鍙偣鍑讳换涓�鏉$洰璺宠浆鍒板搴旀壒娆¤鎯� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+### 4.2 鎵规鍚戝悗杩芥函娴佺▼
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鎵规鍒楄〃椤� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹� 鐐瑰嚮"鍚戝悗杩芥函"
+ 鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 璋冪敤 GET /mes/wm/batch/backward-list鈹�
+鈹� 鍙傛暟: code = 褰撳墠鎵规缂栫爜 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹�
+ 鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 杩斿洖鐢熶骇璇ユ壒娆℃墍娑堣�楃殑鎵�鏈変笂娓告壒娆� 鈹�
+鈹� 鈹�
+鈹� 杩芥函璺緞锛� 鈹�
+鈹� 搴撳瓨浜嬪姟(浜у嚭) 鈫� 浜у嚭鍗� 鈫� 鎶ュ伐璁板綍 鈹�
+鈹� 鈫� 搴撳瓨浜嬪姟(娑堣��) 鈫� 涓婃父鎵规 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈹�
+ 鈻�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 浠ユ椂闂寸嚎褰㈠紡灞曠ず杩芥函缁撴灉 鈹�
+鈹� 鍙偣鍑讳换涓�鏉$洰璺宠浆鍒板搴旀壒娆¤鎯� 鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+---
+
+## 浜斻�佹敞鎰忎簨椤�
+
+### 5.1 鏁版嵁瀹屾暣鎬�
+
+- 杩芥函鍔熻兘渚濊禆 `mes_wm_transaction` 搴撳瓨浜嬪姟琛ㄧ殑鏁版嵁瀹屾暣鎬�
+- 纭繚鎵�鏈夋秷鑰楀拰浜у嚭鎿嶄綔閮芥纭敓鎴愪簡搴撳瓨浜嬪姟璁板綍
+- `biz_type` 瀛楁鍊硷細`123` = 鐗╂枡娑堣�楋紝`124` = 浜у搧浜у嚭
+
+### 5.2 鎬ц兘鑰冭檻
+
+- 鎵规杩芥函閲囩敤閫掑綊鏌ヨ锛屾渶澶ф繁搴﹂檺鍒朵负 20 灞�
+- 瀵逛簬澶嶆潅鐢熶骇鍦烘櫙锛屽彲鑳借繑鍥炲ぇ閲忔暟鎹紝寤鸿鍒嗛〉灞曠ず鎴栭檺鍒跺睍绀烘暟閲�
+
+### 5.3 鏉冮檺鎺у埗
+
+- 杩芥函鏌ヨ闇�瑕� `mes:wm-batch:query` 鏉冮檺
+- 濡傞渶缂栬緫杩芥函鐩稿叧鏁版嵁锛岄渶鐩稿簲妯″潡鐨勭紪杈戞潈闄�
+
+---
+
+## 鍏�佺浉鍏虫帴鍙f眹鎬�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /mes/wm/batch/get | 鑾峰彇鎵规璇︽儏 |
+| GET | /mes/wm/batch/page | 鑾峰彇鎵规鍒嗛〉鍒楄〃 |
+| GET | /mes/wm/batch/forward-list | 鎵规鍚戝墠杩芥函 |
+| GET | /mes/wm/batch/backward-list | 鎵规鍚戝悗杩芥函 |
+
+---
+
+## 涓冦�佹暟鎹簱鍙樻洿璇存槑
+
+### 7.1 鏂板瀛楁
+
+浠ヤ笅琛ㄥ凡娣诲姞 `feedback_id` 瀛楁鐢ㄤ簬绮剧‘鍏宠仈锛�
+
+| 琛ㄥ悕 | 鏂板瀛楁 | 璇存槑 |
+|------|----------|------|
+| mes_wm_item_consume | feedback_id | 鍏宠仈鎶ュ伐璁板綍缂栧彿 |
+| mes_wm_product_produce | feedback_id | 鍏宠仈鎶ュ伐璁板綍 ID |
+
+### 7.2 绱㈠紩浼樺寲
+
+```sql
+CREATE INDEX idx_item_consume_feedback_id ON mes_wm_item_consume(feedback_id);
+CREATE INDEX idx_product_produce_feedback_id ON mes_wm_product_produce(feedback_id);
+```
+
+---
+
+## 鍏�佹洿鏂版棩蹇�
+
+| 鏃ユ湡 | 鐗堟湰 | 璇存槑 |
+|------|------|------|
+| 2026-07-13 | v1.0 | 鍒濆鐗堟湰锛屽畬鎴愭壒娆¤拷婧姛鑳戒慨澶� |
\ No newline at end of file
diff --git a/docs/crm_followup_record_fix.md b/docs/crm_followup_record_fix.md
new file mode 100644
index 0000000..915e5bf
--- /dev/null
+++ b/docs/crm_followup_record_fix.md
@@ -0,0 +1,302 @@
+# CRM 璺熻繘璁板綍鎺ュ彛 - 鍓嶇淇敼璇存槑
+
+## 闂鎻忚堪
+
+璋冪敤 `/admin-api/crm/follow-up-record/create` 鎺ュ彛鏃跺嚭鐜� JSON 瑙f瀽閿欒锛�
+
+```
+Cannot deserialize value of type `java.util.ArrayList<java.lang.String>` from String value
+```
+
+---
+
+## 鍘熷洜鍒嗘瀽
+
+鍚庣瀛楁 `picUrls` 鍜� `fileUrls` 瀹氫箟涓� `List<String>` 绫诲瀷锛屾湡鏈涙帴鏀舵暟缁勬牸寮忕殑鏁版嵁锛屼絾鍓嶇浼犻�掔殑鏄瓧绗︿覆鏍煎紡銆�
+
+### 褰撳墠鍓嶇鍙戦�佺殑鏁版嵁锛堥敊璇級
+
+```json
+{
+ "bizId": 2,
+ "bizType": 1,
+ "type": 2,
+ "nextTime": "1783924981689",
+ "content": "4123123",
+ "picUrls": "http://127.0.0.1:48080/admin-api/infra/file/29/get/xxx.png",
+ "fileUrls": "http://127.0.0.1:48080/admin-api/infra/file/29/get/xxx.png"
+}
+```
+
+### 鍚庣鏈熸湜鐨勬暟鎹紙姝g‘锛�
+
+```json
+{
+ "bizId": 2,
+ "bizType": 1,
+ "type": 2,
+ "nextTime": "1783924981689",
+ "content": "4123123",
+ "picUrls": ["http://127.0.0.1:48080/admin-api/infra/file/29/get/xxx.png"],
+ "fileUrls": ["http://127.0.0.1:48080/admin-api/infra/file/29/get/xxx.png"]
+}
+```
+
+---
+
+## 淇敼鏂规
+
+### 娑夊強椤甸潰
+
+| 椤甸潰 | 璺緞 | 璇存槑 |
+|------|------|------|
+| 鍟嗘満璇︽儏 | `/crm/business/detail` | 娣诲姞璺熻繘璁板綍 |
+| 瀹㈡埛璇︽儏 | `/crm/customer/detail` | 娣诲姞璺熻繘璁板綍 |
+| 鍚堝悓璇︽儏 | `/crm/contract/detail` | 娣诲姞璺熻繘璁板綍 |
+
+### 淇敼鐐�
+
+#### 1. API 璋冪敤鍙傛暟澶勭悊
+
+鍦ㄨ皟鐢ㄥ垱寤鸿窡杩涜褰曟帴鍙e墠锛屽皢 URL 瀛楃涓茶浆涓烘暟缁勶細
+
+```javascript
+// 鏂囦欢璺緞锛歴rc/api/crm/followUpRecord.js
+
+import request from '@/utils/request'
+
+/**
+ * 鍒涘缓璺熻繘璁板綍
+ */
+export function createFollowUpRecord(data) {
+ // 澶勭悊 picUrls 鍜� fileUrls锛氱‘淇濇槸鏁扮粍鏍煎紡
+ const processedData = {
+ ...data,
+ picUrls: Array.isArray(data.picUrls) ? data.picUrls : (data.picUrls ? [data.picUrls] : []),
+ fileUrls: Array.isArray(data.fileUrls) ? data.fileUrls : (data.fileUrls ? [data.fileUrls] : [])
+ }
+
+ return request({
+ url: '/crm/follow-up-record/create',
+ method: 'post',
+ data: processedData
+ })
+}
+```
+
+#### 2. 琛ㄥ崟鎻愪氦鏁版嵁澶勭悊
+
+鍦ㄦ彁浜よ〃鍗曟椂锛岀‘淇濇暟鎹牸寮忔纭細
+
+```javascript
+// 绀轰緥锛氬晢鏈鸿鎯呴〉娣诲姞璺熻繘璁板綍
+methods: {
+ async submitFollowUp() {
+ const formData = {
+ bizId: this.businessId,
+ bizType: 1,
+ type: this.form.type,
+ nextTime: this.form.nextTime,
+ content: this.form.content,
+ // 纭繚 picUrls 鍜� fileUrls 鏄暟缁�
+ picUrls: this.form.picUrls || [],
+ fileUrls: this.form.fileUrls || []
+ }
+
+ await createFollowUpRecord(formData)
+ }
+}
+```
+
+#### 3. 鍥剧墖/闄勪欢涓婁紶鍚庣殑鏁版嵁澶勭悊
+
+涓婁紶鎴愬姛鍚庯紝鐩存帴瀛樺偍涓烘暟缁勬牸寮忥細
+
+```javascript
+// 涓婁紶鍥剧墖鎴愬姛鍚�
+handleUploadSuccess(response) {
+ if (!this.form.picUrls) {
+ this.form.picUrls = []
+ }
+ this.form.picUrls.push(response.url)
+}
+
+// 鍒犻櫎鍥剧墖鏃�
+handleRemoveImage(index) {
+ this.form.picUrls.splice(index, 1)
+}
+```
+
+---
+
+## 瀹屾暣绀轰緥
+
+### 鏂板璺熻繘璁板綍寮圭獥缁勪欢
+
+```vue
+<template>
+ <el-dialog v-model="visible" title="娣诲姞璺熻繘璁板綍" width="600px">
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+ <el-form-item label="璺熻繘绫诲瀷" prop="type">
+ <el-select v-model="form.type" placeholder="璇烽�夋嫨璺熻繘绫诲瀷">
+ <el-option label="鐢佃瘽璺熻繘" :value="1" />
+ <el-option label="鎷滆璺熻繘" :value="2" />
+ <el-option label="寰俊璺熻繘" :value="3" />
+ </el-select>
+ </el-form-item>
+
+ <el-form-item label="璺熻繘鍐呭" prop="content">
+ <el-input v-model="form.content" type="textarea" :rows="3" placeholder="璇疯緭鍏ヨ窡杩涘唴瀹�" />
+ </el-form-item>
+
+ <el-form-item label="鍥剧墖">
+ <el-upload
+ :action="uploadUrl"
+ :on-success="handlePicSuccess"
+ :on-remove="handlePicRemove"
+ :file-list="picFileList"
+ list-type="picture-card"
+ >
+ <el-icon><Plus /></el-icon>
+ </el-upload>
+ </el-form-item>
+
+ <el-form-item label="闄勪欢">
+ <el-upload
+ :action="uploadUrl"
+ :on-success="handleFileSuccess"
+ :on-remove="handleFileRemove"
+ :file-list="fileFileList"
+ >
+ <el-button>涓婁紶闄勪欢</el-button>
+ </el-upload>
+ </el-form-item>
+
+ <el-form-item label="涓嬫鑱旂郴" prop="nextTime">
+ <el-date-picker v-model="form.nextTime" type="datetime" placeholder="閫夋嫨涓嬫鑱旂郴鏃堕棿" />
+ </el-form-item>
+ </el-form>
+
+ <template #footer>
+ <el-button @click="visible = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="submitForm">纭畾</el-button>
+ </template>
+ </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue'
+import { createFollowUpRecord } from '@/api/crm/followUpRecord'
+
+const visible = ref(false)
+const formRef = ref()
+
+const form = reactive({
+ type: null,
+ content: '',
+ nextTime: null,
+ picUrls: [], // 纭繚鍒濆鍖栦负鏁扮粍
+ fileUrls: [] // 纭繚鍒濆鍖栦负鏁扮粍
+})
+
+const rules = {
+ type: [{ required: true, message: '璇烽�夋嫨璺熻繘绫诲瀷', trigger: 'change' }],
+ content: [{ required: true, message: '璇疯緭鍏ヨ窡杩涘唴瀹�', trigger: 'blur' }],
+ nextTime: [{ required: true, message: '璇烽�夋嫨涓嬫鑱旂郴鏃堕棿', trigger: 'change' }]
+}
+
+const picFileList = ref([])
+const fileFileList = ref([])
+
+// 鍥剧墖涓婁紶鎴愬姛
+const handlePicSuccess = (response) => {
+ if (!form.picUrls) form.picUrls = []
+ form.picUrls.push(response.url)
+ picFileList.value.push({ name: response.name, url: response.url })
+}
+
+// 鍥剧墖绉婚櫎
+const handlePicRemove = (file) => {
+ const index = picFileList.value.indexOf(file)
+ if (index > -1) {
+ picFileList.value.splice(index, 1)
+ form.picUrls.splice(index, 1)
+ }
+}
+
+// 闄勪欢涓婁紶鎴愬姛
+const handleFileSuccess = (response) => {
+ if (!form.fileUrls) form.fileUrls = []
+ form.fileUrls.push(response.url)
+ fileFileList.value.push({ name: response.name, url: response.url })
+}
+
+// 闄勪欢绉婚櫎
+const handleFileRemove = (file) => {
+ const index = fileFileList.value.indexOf(file)
+ if (index > -1) {
+ fileFileList.value.splice(index, 1)
+ form.fileUrls.splice(index, 1)
+ }
+}
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = async () => {
+ await formRef.value.validate()
+ await createFollowUpRecord({
+ bizId: props.bizId,
+ bizType: props.bizType,
+ ...form
+ })
+ ElMessage.success('娣诲姞鎴愬姛')
+ visible.value = false
+}
+
+// 鎵撳紑寮圭獥
+const open = (bizId, bizType) => {
+ visible.value = true
+ form.bizId = bizId
+ form.bizType = bizType
+ form.picUrls = []
+ form.fileUrls = []
+ picFileList.value = []
+ fileFileList.value = []
+}
+
+defineExpose({ open })
+</script>
+```
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+1. **鍒濆鍖栦负绌烘暟缁�**锛歚picUrls` 鍜� `fileUrls` 瀛楁鍦� `data` 涓垵濮嬪寲涓虹┖鏁扮粍 `[]`锛岃�屼笉鏄┖瀛楃涓� `''`
+
+2. **涓婁紶鍚庤拷鍔犲埌鏁扮粍**锛氬浘鐗�/闄勪欢涓婁紶鎴愬姛鍚庯紝浣跨敤 `push()` 鏂规硶杩藉姞鍒版暟缁勪腑
+
+3. **鍒犻櫎鏃朵粠鏁扮粍绉婚櫎**锛氬垹闄ゅ浘鐗�/闄勪欢鏃讹紝浣跨敤 `splice()` 鏂规硶浠庢暟缁勪腑绉婚櫎瀵瑰簲椤�
+
+4. **鎻愪氦鍓嶆鏌ユ牸寮�**锛氱‘淇濅紶閫掔粰鎺ュ彛鐨勬暟鎹腑锛宍picUrls` 鍜� `fileUrls` 濮嬬粓鏄暟缁勬牸寮�
+
+---
+
+## 娴嬭瘯楠岃瘉
+
+淇敼瀹屾垚鍚庯紝璇疯繘琛屼互涓嬫祴璇曪細
+
+1. 涓嶄笂浼犲浘鐗囧拰闄勪欢锛岀洿鎺ユ彁浜� 鈫� 搴旀垚鍔�
+2. 涓婁紶涓�寮犲浘鐗囷紝鎻愪氦 鈫� 搴旀垚鍔�
+3. 涓婁紶澶氬紶鍥剧墖锛屾彁浜� 鈫� 搴旀垚鍔�
+4. 涓婁紶鍥剧墖鍚庡垹闄わ紝鍐嶆彁浜� 鈫� 搴旀垚鍔�
+
+---
+
+## 鐩稿叧鎺ュ彛
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /admin-api/crm/follow-up-record/create | 鍒涘缓璺熻繘璁板綍 |
+| PUT | /admin-api/crm/follow-up-record/update | 鏇存柊璺熻繘璁板綍 |
+| DELETE | /admin-api/crm/follow-up-record/delete | 鍒犻櫎璺熻繘璁板綍 |
+| GET | /admin-api/crm/follow-up-record/page | 鍒嗛〉鏌ヨ璺熻繘璁板綍 |
\ No newline at end of file
diff --git a/docs/feedback_traceability_plan.md b/docs/feedback_traceability_plan.md
new file mode 100644
index 0000000..3492fdc
--- /dev/null
+++ b/docs/feedback_traceability_plan.md
@@ -0,0 +1,135 @@
+# 鐢熶骇鎶ュ伐杩芥函鏂规
+
+## 涓�銆佽拷婧洰鏍�
+
+| 鏂瑰悜 | 閾捐矾 |
+|------|------|
+| **姝e悜**锛堣鍗� -> 浜у嚭锛墊 閿�鍞潵婧� -> 宸ュ崟 -> 浠诲姟 -> 鎶ュ伐 -> 浜у嚭/娑堣��/璐ㄦ -> 搴撳瓨浜嬪姟 -> 鎵规搴撳瓨 |
+| **鍙嶅悜**锛堜骇鍑� -> 璁㈠崟锛墊 搴撳瓨浜嬪姟/鎵规 -> 浜у嚭/娑堣�楀崟 -> 鎶ュ伐 -> 浠诲姟 -> 宸ュ崟 -> 瀹㈡埛/鏉ユ簮 |
+| **妯悜**锛堟寜鐗╂枡/鎵规锛墊 鐗╂枡/鎵规 -> 鎵�鏈夎繘鍑轰簨鍔� -> 鐩稿叧鎶ュ伐/宸ュ崟 |
+
+## 浜屻�佺幇鏈夎拷婧祫浜э紙閾捐矾宸查�氱殑閮ㄥ垎锛�
+
+鐜版湁鐨� ID 閾惧凡缁忔瀯鎴愪竴寮犺拷婧綉锛�**姝e父娴佺▼涓嬭拷婧槸瀹屾暣鐨�**锛�
+
+```
+mes_pro_work_order (宸ュ崟)
+ 鈹斺攢> mes_pro_task (浠诲姟, work_order_id) [寰呮姤宸ュ彴璐﹀叆鍙
+ 鈹斺攢> mes_pro_feedback (鎶ュ伐, task_id + work_order_id) 鈫� 鍙岀粦瀹氾紝宸ュ崟渚у厹搴�
+ 鈹溾攢> mes_wm_product_produce / _line (浜у嚭, feedback_id)
+ 鈹� 鈹溾攢> mes_wm_transaction (IN, biz_type=WM_PRODUCT_PRODUCE, biz_id=produce.id)
+ 鈹� 鈹斺攢> mes_qc_ipqc (source_doc_id=feedback_id, source_line_id=produce_line_id)
+ 鈹� 鈹斺攢> mes_qc_defect_record (qc_id)
+ 鈹斺攢> mes_wm_item_consume / _line / _detail (娑堣��, feedback_id)
+ 鈹斺攢> mes_wm_transaction (OUT, biz_type=WM_ITEM_CONSUME, biz_id=consume.id)
+```
+
+鍏抽敭鐐癸細
+
+- `mes_wm_transaction` 璁板綍**姣忎竴绗�**搴撳瓨杩涘嚭锛堟墸鏂� OUT / 鍏ュ簱 IN锛夛紝甯� `biz_type` + `biz_id` + `biz_line_id` + `batch_id` + `transaction_time` + `related_transaction_id`銆傝繖鏄渶缁嗙矑搴︾殑娴佹按锛屾壒娆�/鐗╂枡绾ц拷婧潬瀹冦��
+- feedback **鍚屾椂瀛� `task_id` 鍜� `work_order_id`**锛屼换涓�閾捐矾鍑洪棶棰橀兘鑳戒粠鍙︿竴渚х粫鍥炪��
+- 宸插鎵� feedback 涓嶅彲鍒狅紙浠呰崏绋垮彲鍒狅級锛屽凡鍥炲啓鏁伴噺鐨勫璁$棔杩逛繚鐣欍��
+- `BaseDO` 浣跨敤 `@TableLogic` 杞垹闄わ紝`deleteById` 瀹炰负杞垹锛坄deleted=1`锛夛紝鏁版嵁琛屼繚鐣欙紝鍙栬瘉鏃跺彲缁曡繃杩囨护鏌ヨ銆�
+
+## 涓夈�侀闄╃偣涓庡搴旀柟妗�
+
+### 椋庨櫓1锛氬垹浠诲姟瀵艰嚧杞垹寮曠敤锛圲X 杩芥函鏂摼锛夆瓙 鏈�瀹為檯
+
+- **鐜扮姸**锛歚MesProTaskServiceImpl.deleteTask` 鍙牎楠屼换鍔¢潪缁堟�侊紝涓嶆煡鎶ュ伐銆傛姤宸ュ洖鍐欎笉鏀逛换鍔$姸鎬侊紝鏁呭凡琚姤宸ョ殑浠诲姟浠嶆槸 PREPARE銆佸彲鍒犮�傝蒋鍒犲悗 `feedback.task_id` 鎸囧悜 `deleted=1` 鐨勪换鍔★紝姝e父鏌ヨ鐪嬩笉鍒帮紝鐐瑰嚮杩芥函鏂��
+- **鏂规**锛歚deleteTask` 鍔犳牎楠屸�斺�旀湁鎶ュ伐璁板綍鐨勪换鍔$姝㈠垹闄ゃ��
+ ```java
+ // feedbackMapper 鎸� task_id 鏌ユ暟閲� > 0 鍒欐姏 PRO_TASK_HAS_FEEDBACK
+ ```
+- **鎴愭湰**锛氭瀬灏忥紙涓�涓� count + 涓�涓敊璇爜锛夈��**寤鸿绔嬪嵆鍋氥��**
+
+### 椋庨櫓2锛歵askId 寮辩粦瀹氾紙鏃� DB 澶栭敭锛孌O 娉ㄩ噴 `TODO 寰呭叧鑱擿锛�
+
+- **鐜扮姸**锛氶潬 `validateFeedbackData` 涓氬姟鏍¢獙淇濊瘉 task 涓庡伐鍗�/宸ヤ綅/璺嚎/宸ュ簭/鐗╂枡涓�鑷达紱DB 灞傛棤 FK銆傚綋鍓嶆暟鎹� 0 鏉� null task_id銆�
+- **鏂规**锛�
+ - **涓嶅姞纭� FK**锛堣蒋鍒犻櫎 + 璺ㄦā鍧椾細鍐茬獊锛寉udao 浣撶郴鎯緥涓嶇敤 FK锛夈��
+ - 缁� `mes_pro_feedback.task_id`銆乣work_order_id` **鍔犳櫘閫氱储寮�**锛屾彁鍗囪拷婧煡璇㈡�ц兘銆�
+ - feedback 璇︽儏鏌ヨ鏃剁‘淇濊兘甯﹀嚭 task 鍩烘湰淇℃伅锛堝嵆浣夸换鍔¤杞垹涔熸彁绀�"浠诲姟宸插垹闄�"鑰岄潪鎶ラ敊锛夈��
+- **鎴愭湰**锛氬皬銆�**寤鸿绔嬪嵆鍋氾紙鍔犵储寮曪級銆�**
+
+### 椋庨櫓3锛氬彴璐︾湅涓嶅埌鍦ㄩ�旀姤宸ワ紙鐩插尯锛岄潪鏂摼锛�
+
+- **鐜扮姸**锛氬彴璐︾敤 `task.produced_quantity`锛堝彧鍚凡瀹℃壒 status=4锛夈�傝崏绋�/瀹℃壒涓�/寰呮楠屾姤宸ユ湭鍥炲啓锛屽彴璐�"寰呮姤宸ユ暟閲�"鍋忓ぇ锛屼笌瀹為檯鍦ㄩ�斾笉绗︺��
+- **鏂规**锛氫换鍔�/鍙拌处澧炲姞鍦ㄩ�旀姤宸ラ噺瀛楁锛屽彛寰勬槑纭細
+ - `approvedQuantity` = 璇ヤ换鍔′笅 status=4 鐨� feedbackQuantity 涔嬪拰锛�= producedQuantity锛屽啑浣欏睍绀猴級
+ - `inTransitQuantity` = 璇ヤ换鍔′笅 status IN (0,2,3) 鐨� feedbackQuantity 涔嬪拰
+ - `pendingQuantity` = 鎺掍骇鏁伴噺 鈭� approvedQuantity锛堝彴璐﹀彛寰勪笉鍙橈紝"寰呮姤宸�"鎸囧皻鏈姤锛�
+ - `reportableQuantity` = 鎺掍骇 鈭� approved 鈭� inTransit锛堣繕鍙姤鐨勯噺锛�
+- **鎴愭湰**锛氫腑锛堜竴涓仛鍚堟煡璇級銆�**寤鸿涓�鏈熷仛銆�**
+
+### 椋庨櫓4锛氭棤鎶ュ伐鍐查攢/鎾ら攢锛堢籂閿欑己鍙o級猸� 鏈�澶х己鍙�
+
+- **鐜扮姸**锛氭姤宸ュ鎵瑰悗涓嶅彲鎾ら攢銆佹棤鍙嶅悜鍐查攢鍗曘�乣feedbackQuantity` 鏍¢獙 >0 涓嶅厑璁歌礋鏁般�傛暟閲忓~閿欏彧鑳芥敼 DB銆�
+- **鏂规**锛氭柊澧�**鎶ュ伐鍐查攢娴佺▼**锛�
+ - feedback 琛ㄥ姞 `source_feedback_id`锛堝啿閿�鍏宠仈鍘熸姤宸ワ級+ 鐘舵�佸姞 `REVERSED(宸插啿閿�)`
+ - 鍐查攢鏃跺垱寤轰竴鏉″啿閿� feedback锛堟暟閲忎负姝o紝浣嗘爣璁颁负鍐查攢绫诲瀷锛夛紝鐢熸垚**鍙嶅悜搴撳瓨浜嬪姟**锛坄related_transaction_id` 閰嶅鍘熶簨鍔★級锛屼换鍔�/宸ュ崟鏁伴噺**鍙嶅悜鍥炲啓**锛堝噺锛�
+ - 鍘� feedback 淇濈暀锛堝璁$棔杩癸級锛岀姸鎬佺疆 `REVERSED`
+ - 鏉冮檺鐙珛锛坄mes:pro-feedback:reverse`锛�
+- **鎴愭湰**锛氬ぇ锛堟柊娴佺▼銆佸弽鍚戜簨鍔°�佺姸鎬佹満锛夈��**寤鸿浜屾湡銆�**
+
+### 椋庨櫓5锛歱roducedQuantity 鏃犲彉鏇村巻鍙�
+
+- **鐜扮姸**锛歚setSql increment` 绱姞锛屾棤鐙珛娴佹按銆傝杩樺師鏌愭椂鍒诲�煎緱鎸� feedback 瀹℃壒鏃堕棿绱姞銆�
+- **鏂规**锛�
+ - **A锛堟帹鑽愶紝杞婚噺锛�**锛氫笉寤鸿〃锛岄潬 feedback 璁板綍閲嶅缓锛坒eedback 鏈韩灏辨槸娴佹按锛屾寜 `update_time` 鎺掑簭绱姞鍗冲彲杩樺師锛夈�傚湪浠诲姟璇︽儏鍔�"鎶ュ伐鍘嗗彶"tab 灞曠ず銆�
+ - B锛堥噸锛夛細鏂板缓 `mes_pro_task_progress_log` 姣忔鍥炲啓璁颁竴鏉°�傛洿涓ヨ皑浣嗙淮鎶ゆ垚鏈珮銆�
+- **鎴愭湰**锛欰 灏忋��**寤鸿涓�鏈熷仛锛堝姞鎶ュ伐鍘嗗彶 tab锛夈��**
+
+## 鍥涖�佸垎鏈熷疄鏂藉缓璁�
+
+| 鏈� | 鍐呭 | 鎴愭湰 | 鏀剁泭 |
+|----|------|------|------|
+| **涓�鏈熉峰牭婕�** | 椋庨櫓1 鍒犱换鍔℃牎楠� + 椋庨櫓2 鍔犵储寮� + 椋庨櫓3 鍦ㄩ�旀姤宸ラ噺 + 椋庨櫓5 鎶ュ伐鍘嗗彶 tab | 灏忥綖涓� | 鍫典綇鏂摼銆佽ˉ榻愮洸鍖猴紝杩芥函鍩烘湰瀹屾暣 |
+| **浜屾湡路绾犻敊** | 椋庨櫓4 鎶ュ伐鍐查攢娴佺▼ | 澶� | 闂幆绾犻敊锛屽璁″畬鏁� |
+| **涓夋湡路澧炲己**锛堝彲閫夛級 | 鎵规绾ф鍙嶅悜杩芥函鎺ュ彛锛涗换鍔¤繘搴︽祦姘磋〃锛圔 鏂规锛� | 涓� | 绮剧粏杩芥函 |
+
+## 浜斻�佹帹鑽愮殑杩芥函鏌ヨ鎺ュ彛锛堜竴鏈熷彲椤烘墜鍔狅級
+
+| 鎺ュ彛 | 鍏ュ弬 | 杩斿洖 | 鐢ㄩ�� |
+|------|------|------|------|
+| `GET /mes/pro/feedback/trace/{id}` | 鎶ュ伐 ID | 鎶ュ伐 + 浜у嚭/娑堣��/璐ㄦ 鍏ㄩ摼 | 鎸夋姤宸ュ崟杩芥函 |
+| `GET /mes/pro/task/feedback-list?taskId=` | 浠诲姟 ID | 璇ヤ换鍔℃墍鏈夋姤宸ワ紙鍚湪閫旓級 | 浠诲姟 -> 鎶ュ伐鏄庣粏 |
+| `GET /mes/wm/transaction/trace-by-item?itemId=&batchId=` | 鐗╂枡/鎵规 | 鐩稿叧浜嬪姟 + 鏉ユ簮鍗曟嵁 | 鐗╂枡/鎵规鍙嶅悜杩芥函 |
+
+鍓嶄袱涓槸涓�鏈熷氨璇ユ湁鐨勫熀鏈拷婧兘鍔涳紙鍙拌处鐐硅繘鍘昏兘灞曞紑鏄庣粏锛夛紱绗笁涓彲涓夋湡銆�
+
+## 鍏�佷竴鏈熻惤鍦板悗鐨勮拷婧兘鍔涳紙棰勬湡锛�
+
+鍋氬畬涓�鏈熷悗锛屼粠"寰呮姤宸ュ彴璐�"鍑哄彂鐨勫畬鏁磋拷婧矾寰勶細
+
+```
+寰呮姤宸ュ彴璐︼紙浠诲姟琛岋細鎺掍骇/宸插鎵�/鍦ㄩ��/寰呮姤宸�/鍙姤閲忥級
+ 鈹� 鐐瑰嚮浠诲姟
+ 鈻�
+浠诲姟璇︽儏 + 鎶ュ伐鍘嗗彶 tab锛堟寜鏃堕棿鎺掑垪鎵�鏈夋姤宸ワ紝鍚湪閫旂姸鎬侊級
+ 鈹� 鐐瑰嚮鏌愭潯鎶ュ伐
+ 鈻�
+鎶ュ伐杩芥函椤碉紙鎶ュ伐鍗� + 鍏宠仈浜у嚭/娑堣��/璐ㄦ + 搴撳瓨浜嬪姟娴佹按锛�
+ 鈹� 鐐瑰嚮搴撳瓨浜嬪姟
+ 鈻�
+鎵规搴撳瓨鍙樺姩鏄庣粏锛堣鎵规鎵�鏈夎繘鍑猴級
+```
+
+涓斿洜椋庨櫓1宸插牭锛屼换鍔′笉浼氳璇垹瀵艰嚧鏂摼锛涘洜椋庨櫓2鍔犵储寮曪紝澶ц〃涓嬭拷婧煡璇笉鍗°��
+
+## 涓冦�佺粨璁�
+
+- 姝e父娴佺▼涓嬶紝鐜版湁 ID 閾惧凡鏋勬垚瀹屾暣杩芥函缃戯紝**杩芥函鏈韩鏄�氱殑**銆�
+- 涓昏闅愭偅鏄�"鍒犱换鍔′笉鏍¢獙鎶ュ伐"閫犳垚鐨勮蒋鍒犳柇閾撅紝**涓�鏈熺珛鍗充慨**銆�
+- 鍙拌处鐨�"鍦ㄩ�旀姤宸�"鐩插尯鍜�"鎶ュ伐鍘嗗彶"鏄竴鏈熻ˉ榻愮殑閲嶇偣銆�
+- 鎶ュ伐鍐查攢鏄渶澶х殑缂哄彛锛屼絾宸ョ▼閲忓ぇ锛屽缓璁竴鏈熺ǔ瀹氬悗鍐嶄笂浜屾湡銆�
+
+## 闄勶細娑夊強鏂囦欢娓呭崟锛堜竴鏈熼浼帮級
+
+| 鏀瑰姩 | 鏂囦欢 |
+|------|------|
+| 鍒犱换鍔℃牎楠� | `MesProTaskServiceImpl.deleteTask`銆乣MesProFeedbackMapper`锛堝姞 countByTaskId锛夈�乣ErrorCodeConstants`锛堝姞 PRO_TASK_HAS_FEEDBACK锛� |
+| 鍔犵储寮� | DDL锛歚mes_pro_feedback` 鍔� `idx_task_id`銆乣idx_work_order_id` |
+| 鍦ㄩ�旀姤宸ラ噺 | `MesProTaskRespVO` 鍔犲瓧娈点�乣MesProTaskMapper`/Service 鍔犺仛鍚堟煡璇€�乣buildTaskRespVOList` 濉厖 |
+| 鎶ュ伐鍘嗗彶 tab | `MesProFeedbackController` 鍔� `list-by-task` 鎺ュ彛銆乣MesProFeedbackMapper` 鍔� selectListByTaskId |
+| 杩芥函鎺ュ彛 | `MesProFeedbackController` 鍔� `/trace/{id}`锛岀粍瑁呬骇鍑�/娑堣��/璐ㄦ |
+| 鍓嶇鏂囨。 | `docs/feedback_traceability_*` 绯诲垪鏂囨。 |
diff --git a/docs/item_type_integration.md b/docs/item_type_integration.md
new file mode 100644
index 0000000..2132109
--- /dev/null
+++ b/docs/item_type_integration.md
@@ -0,0 +1,111 @@
+# 鐗╂枡鍒嗙被缁熶竴浣跨敤 mes_md_item_type - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- MDM 鐗╂枡绠$悊椤甸潰锛堟柊澧�/缂栬緫鐗╂枡锛�
+- MES 鐗╂枡绠$悊椤甸潰锛堟柊澧�/缂栬緫鐗╂枡锛�
+
+## 涓氬姟娴佺▼涓庢暟鎹甫鍏�
+
+1. 鐗╂枡鍒嗙被缁熶竴浣跨敤 `mes_md_item_type` 琛紝MDM 鍜� MES 妯″潡鍏辩敤鍚屼竴濂楀垎绫绘暟鎹�
+2. 鐗╂枡绫诲瀷瀛楁 `itemType` 鐢ㄤ簬蹇�熼�夋嫨锛�1鍘熸枡銆�2鍗婃垚鍝併��3鎴愬搧銆�4杈呮枡
+3. 褰撻�夋嫨 `categoryId` 鏃讹紝鐩存帴鍏宠仈鍒嗙被 ID锛涘綋鍙�夋嫨 `itemType` 鏃讹紝绯荤粺鑷姩鍖归厤瀵瑰簲鍚嶇О鐨勫垎绫�
+
+## API
+
+### 鑾峰彇鐗╂枡鍒嗙被鍒楄〃
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /admin-api/mes/md/item-type/list | 鑾峰彇鐗╂枡鍒嗙被鍒楄〃 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| status | Integer | 鍚� | 鐘舵�佺瓫閫� |
+
+**鍝嶅簲瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 鍒嗙被缂栧彿 |
+| code | String | 鍒嗙被缂栫爜 |
+| name | String | 鍒嗙被鍚嶇О |
+| parentId | Long | 鐖跺垎绫荤紪鍙� |
+| itemOrProduct | String | 鐗╂枡/浜у搧鏍囪瘑 |
+| sort | Integer | 鎺掑簭 |
+| status | Integer | 鐘舵�� |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+ "code": 0,
+ "data": [
+ {
+ "id": 1,
+ "code": "RAW001",
+ "name": "鍘熸枡",
+ "parentId": 0,
+ "itemOrProduct": "1",
+ "sort": 1,
+ "status": 0
+ }
+ ]
+}
+```
+
+### 鑾峰彇鐗╂枡鍒嗙被璇︽儏
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /admin-api/mes/md/item-type/get | 鑾峰彇鐗╂枡鍒嗙被璇︽儏 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 鍒嗙被缂栧彿 |
+
+### 淇濆瓨鐗╂枡鍒嗙被
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /admin-api/mes/md/item-type/create | 鍒涘缓鐗╂枡鍒嗙被 |
+| PUT | /admin-api/mes/md/item-type/update | 鏇存柊鐗╂枡鍒嗙被 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鍚� | 鍒嗙被缂栧彿锛堟洿鏂版椂蹇呭~锛� |
+| code | String | 鏄� | 鍒嗙被缂栫爜 |
+| name | String | 鏄� | 鍒嗙被鍚嶇О |
+| parentId | Long | 鍚� | 鐖跺垎绫荤紪鍙� |
+| itemOrProduct | String | 鍚� | 鐗╂枡/浜у搧鏍囪瘑 |
+| sort | Integer | 鍚� | 鎺掑簭 |
+| status | Integer | 鍚� | 鐘舵�� |
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| categoryId | 鏂板/缂栬緫琛ㄥ崟 | 鐗╂枡鍒嗙被涓嬫媺妗嗭紝鏁版嵁鏉ユ簮锛歮es_md_item_type |
+| itemType | 鏂板/缂栬緫琛ㄥ崟 | 鐗╂枡绫诲瀷蹇嵎閫夋嫨锛�1鍘熸枡銆�2鍗婃垚鍝併��3鎴愬搧銆�4杈呮枡 |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 鐗╂枡鍒嗙被閫夋嫨 | 浣跨敤 `mes_md_item_type` 琛紝MDM 鍜� MES 鍏辩敤鍚屼竴濂楀垎绫绘暟鎹� |
+| itemType 涓� categoryId 鍏崇郴 | 褰� categoryId 涓虹┖鏃讹紝鍙牴鎹� itemType 鑷姩鍖归厤瀵瑰簲鍚嶇О鐨勫垎绫� |
+| itemType 鏄犲皠 | 1鈫掑師鏂欙紝2鈫掑崐鎴愬搧锛�3鈫掓垚鍝侊紝4鈫掕緟鏂� |
+| 鍒嗙被鏍¢獙 | 淇濆瓨鐗╂枡鏃讹紝浼氶�氳繃 RPC API 鏍¢獙鍒嗙被鏄惁瀛樺湪 |
+
+## 娉ㄦ剰浜嬮」
+
+- 鐗╂枡鍒嗙被鏁版嵁鍦� MES 妯″潡绠$悊锛孧DM 妯″潡閫氳繃 RPC API 璋冪敤鑾峰彇
+- `itemType` 鏄揩鎹烽�夋嫨瀛楁锛屾渶缁堜細杞崲涓哄搴旂殑鍒嗙被缂栫爜
+- 濡傛灉鍚屾椂璁剧疆浜� `categoryId` 鍜� `itemType`锛屼紭鍏堜娇鐢� `categoryId`
+- 鍒嗙被鏀寔鏍戝舰缁撴瀯锛屽彲閫氳繃 `parentId` 鏋勫缓灞傜骇鍏崇郴
diff --git a/docs/mdm_item_sync_mes.md b/docs/mdm_item_sync_mes.md
new file mode 100644
index 0000000..1e8a5a1
--- /dev/null
+++ b/docs/mdm_item_sync_mes.md
@@ -0,0 +1,90 @@
+# MDM 鐗╂枡鍚屾鍒� MES
+
+## 娑夊強椤甸潰
+
+- MDM 鐗╂枡绠$悊 - 鏂板鐗╂枡寮圭獥
+- MDM 鐗╂枡绠$悊 - 缂栬緫鐗╂枡寮圭獥
+
+## 鍔熻兘璇存槑
+
+鍦� MDM 鐗╂枡銆屾柊澧� / 缂栬緫銆嶆椂锛屾柊澧炪�屽悓姝ュ埌 MES銆嶅紑鍏炽�傚嬀閫夊悗锛孧DM 鐗╂枡淇濆瓨鎴愬姛鐨勫悓鏃讹紝浼氳嚜鍔ㄥ湪 MES 渚у垱寤�/鏇存柊涓�鏉″搴旂殑鐗╂枡浜у搧锛坄mes_md_item`锛夛紝瀹炵幇涓绘暟鎹悜 MES 鐨勫悓姝ャ��
+
+- 鏂板锛坈reate锛夛細鍕鹃�夊悓姝� 鈫� MDM 鏂板缓 + MES 鏂板缓銆�
+- 缂栬緫锛坲pdate锛夛細鍕鹃�夊悓姝� 鈫� MDM 鏇存柊 + MES 鏇存柊锛圡ES 渚ф寜鐗╂枡缂栫爜 code 鍖归厤锛氬凡瀛樺湪鍒欐洿鏂帮紝涓嶅瓨鍦ㄥ垯鏂板缓锛屽嵆 upsert锛夈��
+- 涓嶅嬀閫夋垨 `syncMes=false`锛氫粎鎿嶄綔 MDM锛屼笉褰卞搷 MES銆�
+
+## API
+
+鎺ュ彛璺緞涓嶅彉锛屼粎鍦ㄨ姹備綋鏂板 `syncMes` 瀛楁銆�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /admin-api/mdm/item/create | 鍒涘缓鐗╂枡锛堝彲鍚屾 MES锛� |
+| PUT | /admin-api/mdm/item/update | 鏇存柊鐗╂枡锛堝彲鍚屾 MES锛� |
+
+**璇锋眰鍙傛暟锛堜粎鍒楀嚭鏂板/鐩稿叧瀛楁锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| syncMes | Boolean | 鍚� | 鏄惁鍚屾鍒� MES锛岄粯璁� false銆傚嬀閫夋椂瑙﹀彂 MES 鍚屾 |
+| code | String | 鏄� | 鐗╂枡缂栫爜锛圡DM/MES 鍏宠仈閿紝涓よ竟椤讳竴鑷达級 |
+| name | String | 鏄� | 鐗╂枡鍚嶇О |
+| unitMeasureId | Long | 鏄� | MDM 璁¢噺鍗曚綅 id锛堝悓姝ユ椂鎸� code 鏄犲皠鍒� MES 鍗曚綅锛� |
+| categoryId | Long | 鏄� | MDM 鐗╂枡鍒嗙被 id锛堝悓姝ユ椂鎸� code 鏄犲皠鍒� MES 鐗╂枡鍒嗙被锛� |
+
+**鍝嶅簲锛�** 鍒涘缓杩斿洖 `{ "code": 200, "data": <鐗╂枡id> }`锛涙洿鏂拌繑鍥� `{ "code": 200, "data": true }`锛堜笌鍘熸帴鍙d竴鑷达級銆�
+
+> 娉ㄦ剰锛氬悓姝ュけ璐ユ椂锛堝 MES 渚ф壘涓嶅埌瀵瑰簲 code 鐨勫崟浣�/鍒嗙被銆佹垨 MES 鍚嶇О閲嶅绛夛級锛屾帴鍙d細杩斿洖閿欒锛屼笖 **MDM 鐨勫垱寤�/鏇存柊浼氫竴骞跺洖婊�**锛堝悓涓�浜嬪姟锛夈��
+
+## 鍓嶇淇敼鐐�
+
+### 1. 琛ㄥ崟澧炲姞銆屽悓姝ュ埌 MES銆嶅紑鍏�
+
+鍦ㄧ墿鏂欐柊澧�/缂栬緫琛ㄥ崟锛坄mes/pd/...` 鎴� `mdm/item` 瀵瑰簲琛ㄥ崟缁勪欢锛変腑鏂板涓�涓紑鍏筹細
+
+```html
+<el-form-item label="鍚屾鍒� MES">
+ <el-switch v-model="formData.syncMes" />
+</el-form-item>
+```
+
+### 2. data 鏁版嵁
+
+```js
+data() {
+ return {
+ formData: {
+ // ... 鍘熸湁瀛楁
+ syncMes: false, // 鏄惁鍚屾鍒� MES锛岄粯璁や笉鍕鹃��
+ },
+ }
+}
+```
+
+> 缂栬緫鍥炴樉鏃讹紝`syncMes` 榛樿缃负 `false`锛堜笉鍚屾锛夊嵆鍙紝鐢辩敤鎴锋寜闇�鍕鹃�夈��
+
+### 3. 鎻愪氦鏂规硶
+
+鏃犻渶鏀瑰姩鎻愪氦閫昏緫锛宍syncMes` 闅忚〃鍗曚竴骞舵彁浜ゅ嵆鍙細
+
+```js
+submitForm() {
+ this.$refs['form'].validate(valid => {
+ if (!valid) return
+ // formData 涓凡鍖呭惈 syncMes锛屾甯歌皟鐢� create / update 鎺ュ彛
+ if (this.formData.id) {
+ updateItem(this.formData).then(...)
+ } else {
+ createItem(this.formData).then(...)
+ }
+ })
+}
+```
+
+## 娉ㄦ剰浜嬮」
+
+- **浠� create / update 鐢熸晥**锛氬垹闄ゃ�佺姸鎬佸彉鏇翠笉鍚屾 MES銆�
+- **鍚屾澶辫触浼氬洖婊�**锛氬嬀閫夊悓姝ュ悗鑻� MES 渚у嚭閿欙紝MDM 鐨勪繚瀛樹篃浼氬洖婊氾紝鍓嶇浼氭敹鍒伴敊璇彁绀猴紝闇�鏍规嵁閿欒淇℃伅澶勭悊锛堥�氬父鏄� MES 渚х己瀵瑰簲鍗曚綅/鍒嗙被锛夈��
+- **MES 鐗╂枡榛樿绂佺敤**锛氬悓姝ュ垱寤虹殑 MES 鐗╂枡鐘舵�佷负銆岀鐢ㄣ�嶏紝闇�鍦� MES 鐗╂枡浜у搧椤甸潰鍙﹁鍚敤銆�
+- **鍓嶆彁锛氬崟浣�/鍒嗙被缂栫爜椤讳竴鑷�**锛氬悓姝ユ寜 `code` 鏄犲皠鈥斺�擬DM 鐨勮閲忓崟浣� `code` 蹇呴』鍦� MES `mes_md_unit_measure` 涓瓨鍦紝MDM 鐨勭墿鏂欏垎绫� `code` 蹇呴』鍦� MES `mes_md_item_type` 涓瓨鍦紙涓斾负鍙跺瓙鍒嗙被锛夛紝鍚﹀垯鍚屾鎶ラ敊鍥炴粴銆傝纭繚涓よ竟涓绘暟鎹紪鐮佸凡瀵归綈銆�
+- **瀛楁鏄犲皠**锛歁DM `isHighValue`鈫扢ES `highValue`锛孧DM `isBatchManaged`鈫扢ES `batchFlag`锛宍minStock/maxStock/specification/remark` 鐩翠紶锛汳ES `safeStockFlag` 鍥哄畾涓� true 浠ヤ繚鐣欏簱瀛樹笂涓嬮檺銆�
diff --git a/docs/process_backflush_flag.md b/docs/process_backflush_flag.md
new file mode 100644
index 0000000..5cc019d
--- /dev/null
+++ b/docs/process_backflush_flag.md
@@ -0,0 +1,140 @@
+# 宸ュ簭銆屾槸鍚﹀�掑啿銆嶅瓧娈�
+
+## 鑳屾櫙
+
+鎶ュ伐瀹℃壒鏃讹紝鍊掑啿宸ュ簭鎸� BOM 鑷姩鎵f枡锛圔OM 鐢ㄩ噺 脳 鎶ュ伐鏁伴噺锛屼粠绾胯竟搴� FIFO 鎵e噺锛夛紱闈炲�掑啿宸ュ簭涓嶈嚜鍔ㄦ墸鏂欙紝鐗╂枡璧版墜宸ラ鏂�/鎶曟枡銆�
+
+鏂板瀛楁鎸傚湪**宸ヨ壓璺嚎宸ュ簭**锛坄mes_pro_route_process`锛屼笌銆屾槸鍚﹀叧閿伐搴忋�嶃�屾槸鍚﹁川妫�宸ュ簭銆嶅悓绾э級锛屼笉鏄伐搴忎富琛ㄣ��
+
+## 娑夊強椤甸潰
+
+- 宸ヨ壓璺嚎 - 宸ュ簭鍒楄〃/琛ㄥ崟锛堢淮鎶よ矾绾夸笅鐨勫伐搴忛厤缃紝鏂板銆屾槸鍚﹀�掑啿銆嶅紑鍏筹級
+- 鐢熶骇鎶ュ伐 - 鎶ュ伐瀹℃壒锛堝�掑啿宸ュ簭鏈厤 BOM 鏃剁粰鍑洪敊璇彁绀猴級
+
+## 鏁版嵁搴�
+
+```sql
+ALTER TABLE mes_pro_route_process
+ ADD COLUMN backflush_flag bit(1) DEFAULT b'1' COMMENT '鏄惁鍊掑啿' AFTER check_flag;
+```
+
+- 榛樿 `b'1'`锛堝�掑啿锛夛紝淇濈暀鐜版湁銆岄厤浜� BOM 灏辨墸鏂欍�嶇殑琛屼负銆�
+- 鍘嗗彶鏁版嵁鍥炲~涓哄�掑啿=true銆�
+
+## API
+
+宸ヨ壓璺嚎宸ュ簭鎺ュ彛锛坄/mes/pro/route-process`锛夌殑 **create / update / get / list** 璇锋眰涓庡搷搴斿潎鏂板 `backflushFlag` 瀛楁锛屽叾浣欎笉鍙樸��
+
+**鏂板瀛楁锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| backflushFlag | Boolean | 鍚︼紙榛樿 true锛墊 鏄惁鍊掑啿銆倀rue=鎶ュ伐鏃舵寜 BOM 鑷姩鎵f枡锛沠alse=涓嶈嚜鍔ㄦ墸鏂� |
+
+**璇锋眰绀轰緥锛坈reate/update锛夛細**
+
+```json
+{
+ "routeId": 1,
+ "processId": 10,
+ "sort": 1,
+ "keyFlag": false,
+ "checkFlag": false,
+ "backflushFlag": true
+}
+```
+
+**鍝嶅簲绀轰緥锛坓et/list锛夛細**
+
+```json
+{
+ "id": 1,
+ "routeId": 1,
+ "processId": 10,
+ "keyFlag": false,
+ "checkFlag": false,
+ "backflushFlag": true
+}
+```
+
+鎶ュ伐瀹℃壒鎺ュ彛 `PUT /mes/pro/feedback/approve` 琛屼负鍙樻洿锛堟棤鍏ュ弬鍙樺寲锛夛細
+
+| 宸ュ簭 backflushFlag | BOM 閰嶇疆 | 瀹℃壒缁撴灉 |
+|--------------------|----------|----------|
+| true锛堥粯璁わ級| 鏈� | 姝e父鎵f枡鍏ュ簱 |
+| true | 鏃� | 鎶ラ敊锛歚褰撳墠宸ュ簭涓哄�掑啿宸ュ簭锛屼絾鏈厤缃瓸OM娑堣�楋紝鏃犳硶鎶ュ伐`锛堥敊璇爜 1040506013锛墊
+| false | 鏈� | 涓嶆墸鏂欙紙BOM 閰嶇疆瀛樺湪浣嗕笉浣跨敤锛墊
+| false | 鏃� | 涓嶆墸鏂� |
+
+## 鍓嶇淇敼鐐�
+
+### 1. 宸ヨ壓璺嚎 - 宸ュ簭琛ㄥ崟鍔犮�屾槸鍚﹀�掑啿銆嶅紑鍏�
+
+鍦ㄣ�屾槸鍚﹀叧閿伐搴忋�嶃�屾槸鍚﹁川妫�宸ュ簭銆嶄箣鍚庡姞涓�涓� `el-switch`锛�
+
+```html
+<el-form-item label="鏄惁鍏抽敭宸ュ簭">
+ <el-switch v-model="form.keyFlag" />
+</el-form-item>
+<el-form-item label="鏄惁璐ㄦ宸ュ簭">
+ <el-switch v-model="form.checkFlag" />
+</el-form-item>
+<!-- 鏂板 -->
+<el-form-item label="鏄惁鍊掑啿">
+ <el-switch v-model="form.backflushFlag" />
+ <span style="margin-left: 8px; color: #909399; font-size: 12px;">
+ 寮�鍚悗鎶ュ伐鏃舵寜BOM鑷姩鎵f枡锛涘叧闂悗涓嶈嚜鍔ㄦ墸鏂欙紝璧版墜宸ラ鏂�
+ </span>
+</el-form-item>
+```
+
+### 2. data 鏁版嵁
+
+```js
+data() {
+ return {
+ form: {
+ // ...鍘熸湁瀛楁
+ keyFlag: false,
+ checkFlag: false,
+ backflushFlag: true, // 榛樿鍊掑啿
+ },
+ }
+}
+```
+
+### 3. 宸ュ簭鍒楄〃琛ㄦ牸灞曠ず锛堝彲閫夛級
+
+```html
+<el-table-column label="鏄惁鍊掑啿" align="center" width="100">
+ <template #default="scope">
+ <el-tag :type="scope.row.backflushFlag ? 'success' : 'info'">
+ {{ scope.row.backflushFlag ? '鍊掑啿' : '涓嶅�掑啿' }}
+ </el-tag>
+ </template>
+</el-table-column>
+```
+
+### 4. 琛ㄥ崟閲嶇疆/鍥炴樉
+
+```js
+resetForm() {
+ this.form = {
+ // ...鍘熸湁瀛楁
+ keyFlag: false,
+ checkFlag: false,
+ backflushFlag: true,
+ }
+}
+handleEdit(row) {
+ this.form = { ...row }
+ // backflushFlag 闅� row 鍥炴樉
+}
+```
+
+## 娉ㄦ剰浜嬮」
+
+- 瀛楁鎸傚湪**宸ヨ壓璺嚎宸ュ簭**锛堟瘡鏉¤矾绾夸笅鐨勫伐搴忛�愭潯閰嶇疆锛夛紝涓嶆槸宸ュ簭涓昏〃锛涗笌 `keyFlag`/`checkFlag` 鍚岀骇锛屽墠绔湪璺嚎-宸ュ簭閰嶇疆椤电淮鎶ゃ��
+- 榛樿鍊间负銆屽�掑啿=true銆嶏紝涓庡巻鍙茶涓轰竴鑷淬�傚彧鏈夋樉寮忓叧闂殑宸ュ簭鎵嶄細璺宠繃鑷姩鎵f枡銆�
+- 鍊掑啿宸ュ簭蹇呴』鍦ㄣ�屽伐鑹鸿矾绾夸骇鍝� BOM銆嶏紙`/mes/pro/route-product-bom`锛夐噷閰嶅ソ璇ヨ矾绾�+宸ュ簭+浜у搧鐨� BOM 娑堣�楋紝鍚﹀垯鎶ュ伐瀹℃壒浼氭姤閿欍��
+- 闈炲�掑啿宸ュ簭鐨勭墿鏂欒閫氳繃浠诲姟鎶曟枡锛坄/mes/pro/task-issue`锛夋垨浠撳簱棰嗘枡鍗曟墜宸ュ鐞嗐��
diff --git a/docs/sql/batch_traceability_fix.sql b/docs/sql/batch_traceability_fix.sql
new file mode 100644
index 0000000..02c39f7
--- /dev/null
+++ b/docs/sql/batch_traceability_fix.sql
@@ -0,0 +1,19 @@
+-- =============================================
+-- 鎵规杩芥函鍔熻兘淇 - DDL 鑴氭湰
+-- 鏃ユ湡: 2026-07-13
+-- 璇存槑: 娣诲姞 feedback_id 瀛楁鐢ㄤ簬鎵规杩芥函鐨勭簿纭叧鑱�
+-- =============================================
+
+-- 1. mes_wm_item_consume 琛ㄦ坊鍔犵己澶卞瓧娈�
+ALTER TABLE mes_wm_item_consume ADD COLUMN task_id bigint COMMENT '鐢熶骇浠诲姟缂栧彿' AFTER work_order_id;
+ALTER TABLE mes_wm_item_consume ADD COLUMN workstation_id bigint COMMENT '宸ヤ綔绔欑紪鍙�' AFTER task_id;
+ALTER TABLE mes_wm_item_consume ADD COLUMN process_id bigint COMMENT '宸ュ簭缂栧彿' AFTER workstation_id;
+ALTER TABLE mes_wm_item_consume ADD COLUMN feedback_id bigint COMMENT '鎶ュ伐璁板綍缂栧彿' AFTER process_id;
+ALTER TABLE mes_wm_item_consume ADD COLUMN consume_date datetime COMMENT '娑堣�楁棩鏈�' AFTER feedback_id;
+
+-- 2. mes_wm_product_produce 琛ㄦ坊鍔� feedback_id 瀛楁
+ALTER TABLE mes_wm_product_produce ADD COLUMN feedback_id bigint COMMENT '鎶ュ伐璁板綍 ID' AFTER work_order_id;
+
+-- 3. 涓哄弽棣� ID 娣诲姞绱㈠紩锛堟彁鍗囪拷婧煡璇㈡�ц兘锛�
+CREATE INDEX idx_item_consume_feedback_id ON mes_wm_item_consume(feedback_id);
+CREATE INDEX idx_product_produce_feedback_id ON mes_wm_product_produce(feedback_id);
diff --git a/docs/task_pending_feedback_ledger.md b/docs/task_pending_feedback_ledger.md
new file mode 100644
index 0000000..383e9ec
--- /dev/null
+++ b/docs/task_pending_feedback_ledger.md
@@ -0,0 +1,161 @@
+# 寰呮姤宸ュ彴璐�
+
+## 鑳屾櫙
+
+鎺掍骇鍚庨渶瑕佷竴涓�"寰呮姤宸ュ彴璐�"锛氬睍绀哄凡鎺掍骇浣嗗皻鏈姤宸ュ畬鐨勪换鍔★紝鏍稿績鎸囨爣 **寰呮姤宸ユ暟閲� = 鎺掍骇鏁伴噺 鈭� 宸叉姤宸ユ暟閲�**銆傛暟鎹簮涓虹敓浜т换鍔★紙`mes_pro_task`锛夛紝浠诲姟绾ф眹鎬昏鍥俱��
+
+## 娑夊強椤甸潰
+
+- 鏂板鑿滃崟/椤甸潰锛氬緟鎶ュ伐鍙拌处锛堢敓浜х鐞嗕笅锛�
+- 澶嶇敤鐢熶骇浠诲姟鍒楄〃鐨勫叧鑱斿睍绀猴紙宸ュ崟/浜у搧/宸ュ簭/宸ヤ綅/瀹㈡埛锛�
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /mes/pro/task/pending-feedback-page | 寰呮姤宸ュ彴璐﹀垎椤碉紙杩涜涓笖浠嶆湁寰呮姤宸ラ噺鐨勪换鍔★級|
+
+### 璇锋眰鍙傛暟锛圦uery锛屽鐢� MesProTaskPageReqVO锛�
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| pageNo | Integer | 鏄� | 椤电爜 |
+| pageSize | Integer | 鏄� | 姣忛〉鏉℃暟 |
+| code | String | 鍚� | 浠诲姟缂栫爜锛堟ā绯婏級|
+| name | String | 鍚� | 浠诲姟鍚嶇О锛堟ā绯婏級|
+| workOrderId | Long | 鍚� | 鐢熶骇宸ュ崟缂栧彿 |
+| routeId | Long | 鍚� | 宸ヨ壓璺嚎缂栧彿 |
+| processId | Long | 鍚� | 宸ュ簭缂栧彿 |
+| workstationId | Long | 鍚� | 宸ヤ綔绔欑紪鍙� |
+| createTime | LocalDateTime[] | 鍚� | 鍒涘缓鏃堕棿鍖洪棿 |
+
+> `status`/`statuses` 鍙傛暟鍦ㄦ鎺ュ彛琚拷鐣ワ細鍚庣寮哄埗 `status=杩涜涓�(0)`锛屼笖杩囨护 `寰呮姤宸ユ暟閲� > 0`銆�
+
+### 鍝嶅簲锛圡esProTaskRespVO锛屽叧閿瓧娈碉級
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 浠诲姟缂栧彿 |
+| code | String | 浠诲姟缂栫爜 |
+| name | String | 浠诲姟鍚嶇О |
+| workOrderCode / workOrderName | String | 宸ュ崟缂栫爜/鍚嶇О |
+| itemCode / itemName / itemSpecification | String | 浜у搧缂栫爜/鍚嶇О/瑙勬牸 |
+| unitMeasureName | String | 璁¢噺鍗曚綅 |
+| processName | String | 宸ュ簭鍚嶇О |
+| workstationCode / workstationName | String | 宸ヤ綅缂栫爜/鍚嶇О |
+| clientName | String | 瀹㈡埛鍚嶇О |
+| quantity | BigDecimal | 鎺掍骇鏁伴噺 |
+| producedQuantity | BigDecimal | 宸叉姤宸ユ暟閲忥紙宸插鎵圭疮鍔狅級|
+| qualifyQuantity | BigDecimal | 鍚堟牸鍝佹暟閲� |
+| unqualifyQuantity | BigDecimal | 涓嶈壇鍝佹暟閲� |
+| **pendingQuantity** | BigDecimal | **寰呮姤宸ユ暟閲� = 鎺掍骇 鈭� 宸叉姤宸�** |
+| **feedbackRate** | BigDecimal | **鎶ュ伐瀹屾垚鐜�(%) = 宸叉姤宸� / 鎺掍骇 脳 100** |
+| startTime / endTime | LocalDateTime | 鎺掍骇寮�濮�/缁撴潫鏃堕棿 |
+| requestDate | LocalDateTime | 闇�姹傛棩鏈� |
+| status | Integer | 浠诲姟鐘舵�侊紙姝ゅ鎭掍负 0=杩涜涓級|
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "list": [
+ {
+ "id": 101,
+ "code": "PT202507130001",
+ "name": "鐢佃矾鏉裤��100銆戜釜",
+ "workOrderCode": "WO-001",
+ "itemName": "鐢佃矾鏉�",
+ "processName": "娉ㄥ",
+ "workstationName": "娉ㄥ宸ヤ綔绔�",
+ "quantity": 100.00,
+ "producedQuantity": 30.00,
+ "qualifyQuantity": 29.00,
+ "unqualifyQuantity": 1.00,
+ "pendingQuantity": 70.00,
+ "feedbackRate": 30.00,
+ "status": 0
+ }
+ ],
+ "total": 1
+ }
+}
+```
+
+## 鍓嶇淇敼鐐�
+
+### 1. 琛ㄦ牸鍒楀缓璁�
+
+```html
+<el-table-column label="浠诲姟缂栫爜" prop="code" />
+<el-table-column label="宸ュ崟缂栫爜" prop="workOrderCode" />
+<el-table-column label="浜у搧" prop="itemName" />
+<el-table-column label="宸ュ簭" prop="processName" />
+<el-table-column label="宸ヤ綅" prop="workstationName" />
+<el-table-column label="鎺掍骇鏁伴噺" prop="quantity" />
+<el-table-column label="宸叉姤宸�" prop="producedQuantity" />
+<el-table-column label="鍚堟牸" prop="qualifyQuantity" />
+<el-table-column label="涓嶈壇" prop="unqualifyQuantity" />
+<el-table-column label="寰呮姤宸ユ暟閲�" prop="pendingQuantity">
+ <template #default="scope">
+ <el-tag type="warning">{{ scope.row.pendingQuantity }}</el-tag>
+ </template>
+</el-table-column>
+<el-table-column label="瀹屾垚鐜�" prop="feedbackRate">
+ <template #default="scope">
+ <el-progress :percentage="Number(scope.row.feedbackRate)" />
+ </template>
+</el-table-column>
+<el-table-column label="闇�姹傛棩鏈�" prop="requestDate" />
+```
+
+### 2. 鏌ヨ琛ㄥ崟
+
+```html
+<el-form :inline="true" :model="queryParams">
+ <el-form-item label="浠诲姟缂栫爜">
+ <el-input v-model="queryParams.code" clearable />
+ </el-form-item>
+ <el-form-item label="宸ュ崟">
+ <el-select v-model="queryParams.workOrderId" clearable><!-- 宸ュ崟涓嬫媺 --></el-select>
+ </el-form-item>
+ <el-form-item label="宸ュ簭">
+ <el-select v-model="queryParams.processId" clearable><!-- 宸ュ簭涓嬫媺 --></el-select>
+ </el-form-item>
+ <el-form-item label="宸ヤ綅">
+ <el-select v-model="queryParams.workstationId" clearable><!-- 宸ヤ綅涓嬫媺 --></el-select>
+ </el-form-item>
+</el-form>
+```
+
+### 3. API 璋冪敤
+
+```js
+// api/mes/pro/task.js
+export function getPendingFeedbackPage(params) {
+ return request({
+ url: '/mes/pro/task/pending-feedback-page',
+ method: 'get',
+ params
+ })
+}
+```
+
+```js
+// 椤甸潰 list 鏂规硶
+getList() {
+ this.loading = true
+ getPendingFeedbackPage(this.queryParams).then(res => {
+ this.list = res.data.list
+ this.total = res.data.total
+ }).finally(() => this.loading = false)
+}
+```
+
+## 娉ㄦ剰浜嬮」
+
+- 鍙拌处鍙樉绀� **杩涜涓� + 浠嶆湁寰呮姤宸ラ噺** 鐨勪换鍔★細宸叉姤婊★紙寰呮姤宸�=0锛夋垨宸插畬宸�/宸插彇娑堢殑浠诲姟涓嶅嚭鐜般��
+- 鎯崇湅"鎵�鏈夎繘琛屼腑浠诲姟锛堝惈宸叉姤婊℃湭鍏冲崟锛�"鐢� `/mes/pro/task/page?status=0`銆�
+- `producedQuantity` 鍙惈**宸插鎵归�氳繃**鐨勬姤宸ワ紱鑽夌/瀹℃壒涓�/寰呮楠岀殑鎶ュ伐鏈洖鍐欙紝鍙拌处鐪嬩笉鍒拌繖閮ㄥ垎鍦ㄩ�旀姤宸ラ噺銆�
+- 璇ユ帴鍙e鐢ㄤ换鍔″垎椤垫潈闄� `mes:pro-task:query`锛屽闇�鐙珛鏉冮檺鍙柊澧炴寜閽潈闄愮爜銆�
diff --git a/pom.xml b/pom.xml
index dc9f57d..e5cb565 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,6 +27,7 @@
<module>yudao-module-erp-api</module>
<!-- <module>yudao-module-iot</module>-->
<module>yudao-module-mes</module>
+ <module>yudao-module-mes-api</module>
<module>yudao-module-im</module>
<!-- MDM 涓绘暟鎹鐞嗘ā鍧� -->
<module>yudao-module-mdm-api</module>
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java
index c4d5385..ce14897 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java
@@ -40,9 +40,9 @@
@Schema(description = "鍏宠仈鐨勮仈绯讳汉缂栧彿鏁扮粍")
private List<Long> contactIds;
- @Schema(description = "鍥剧墖")
+ @Schema(description = "鍥剧墖 URL 鍒楄〃")
private List<String> picUrls;
- @Schema(description = "闄勪欢")
+ @Schema(description = "闄勪欢 URL 鍒楄〃")
private List<String> fileUrls;
}
\ No newline at end of file
diff --git a/yudao-module-mdm/pom.xml b/yudao-module-mdm/pom.xml
index f8bf40f..c611213 100644
--- a/yudao-module-mdm/pom.xml
+++ b/yudao-module-mdm/pom.xml
@@ -24,6 +24,13 @@
<version>${revision}</version>
</dependency>
+ <!-- MES 鍒堕�犳墽琛� API锛堢敤浜庡悓姝ョ墿鏂欎富鏁版嵁鍒� MES锛� -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-mes-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-system</artifactId>
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java
index 54e0da8..5f1be14 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemSaveReqVO.java
@@ -24,7 +24,7 @@
@Schema(description = "瑙勬牸鍨嬪彿", example = "M8*20")
private String specification;
- @Schema(description = "鐗╂枡鍒嗙被缂栧彿", example = "1")
+ @Schema(description = "鐗╂枡鍒嗙被缂栧彿锛堝叧鑱� MES 鐗╂枡鍒嗙被 mes_md_item_type锛�", example = "1")
private Long categoryId;
@Schema(description = "璁¢噺鍗曚綅缂栧彿", example = "1")
@@ -87,4 +87,7 @@
@Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
private String remark;
+ @Schema(description = "鏄惁鍚屾鍒� MES", example = "false")
+ private Boolean syncMes;
+
}
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java
index 0ab2d68..04edd6b 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/dal/mysql/item/MdmItemMapper.java
@@ -45,6 +45,22 @@
return selectOne(MdmItemDO::getCode, code);
}
+ /**
+ * 鏍规嵁缂栫爜鍜屽垹闄ょ姸鎬佹煡璇㈢墿鏂欙紙鐢ㄤ簬瑙e喅杞垹闄ゅ敮涓�閿啿绐侊級
+ */
+ default MdmItemDO selectByCodeAndDeleted(String code, Boolean deleted) {
+ return selectOne(new LambdaQueryWrapperX<MdmItemDO>()
+ .eq(MdmItemDO::getCode, code)
+ .eq(MdmItemDO::getDeleted, deleted));
+ }
+
+ /**
+ * 鐗╃悊鍒犻櫎锛堢敤浜庢竻鐞嗗凡杞垹闄ょ殑閲嶅璁板綍锛�
+ * 娉ㄦ剰锛氱敱浜� BaseDO 鏈� @TableLogic锛宒eleteById 鏄蒋鍒犻櫎锛岄渶瑕佷娇鐢ㄥ師鐢� SQL
+ */
+ @org.apache.ibatis.annotations.Delete("DELETE FROM mdm_item WHERE id = #{id}")
+ void physicalDeleteById(Long id);
+
default MdmItemDO selectByBarCode(String barCode) {
return selectOne(MdmItemDO::getBarCode, barCode);
}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
index 13e8dec..dc40a64 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
@@ -2,17 +2,25 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemPageReqVO;
import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSaveReqVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemMapper;
+import cn.iocoder.yudao.module.mdm.service.brand.MdmBrandService;
import cn.iocoder.yudao.module.mdm.service.category.MdmItemCategoryService;
import cn.iocoder.yudao.module.mdm.service.unit.MdmUnitMeasureService;
-import cn.iocoder.yudao.module.mdm.service.brand.MdmBrandService;
+import cn.iocoder.yudao.module.mes.api.item.MesMdItemApi;
+import cn.iocoder.yudao.module.mes.api.item.MesMdItemTypeApi;
+import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemSyncReqDTO;
+import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemTypeRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.*;
@@ -40,32 +48,107 @@
@Resource
private MdmBrandService brandService;
+ @Resource
+ private MesMdItemApi mesMdItemApi;
+ @Resource
+ private MesMdItemTypeApi mesMdItemTypeApi;
+
@Override
+ @Transactional(rollbackFor = Exception.class)
public Long createItem(MdmItemSaveReqVO createReqVO) {
// 鏍¢獙鏁版嵁
validateItemSaveData(createReqVO);
- // 鎻掑叆
+ // 鎻掑叆 MDM 鐗╂枡
MdmItemDO item = BeanUtils.toBean(createReqVO, MdmItemDO.class);
itemMapper.insert(item);
+
+ // 鍚屾鍒� MES锛堝嬀閫� syncMes 鏃讹級
+ if (Boolean.TRUE.equals(createReqVO.getSyncMes())) {
+ syncItemToMes(createReqVO);
+ }
return item.getId();
}
@Override
+ @Transactional(rollbackFor = Exception.class)
public void updateItem(MdmItemSaveReqVO updateReqVO) {
// 鏍¢獙瀛樺湪
validateItemExists(updateReqVO.getId());
// 鏍¢獙鏁版嵁
validateItemSaveData(updateReqVO);
- // 鏇存柊
+ // 鏇存柊 MDM 鐗╂枡
MdmItemDO updateObj = BeanUtils.toBean(updateReqVO, MdmItemDO.class);
itemMapper.updateById(updateObj);
+
+ // 鍚屾鍒� MES锛堝嬀閫� syncMes 鏃讹級
+ if (Boolean.TRUE.equals(updateReqVO.getSyncMes())) {
+ syncItemToMes(updateReqVO);
+ }
}
private void validateItemSaveData(MdmItemSaveReqVO reqVO) {
// 鏍¢獙鐗╂枡缂栫爜鐨勫敮涓�鎬�
validateItemCodeUnique(reqVO.getId(), reqVO.getCode());
+ }
+
+ /**
+ * 鍚屾鐗╂枡鍒� MES锛堝崟浣�/鍒嗙被鎸� code 鏄犲皠锛�
+ *
+ * MDM 鐨� unitMeasureId/categoryId 涓� MES 鐨� unitMeasureId/itemTypeId 鍒嗗睘涓ゅ涓绘暟鎹紝
+ * 杩欓噷鍏堝湪 MDM 渚цВ鏋愬嚭 code锛屽啀浜ょ敱 MesMdItemApi 鍦� MES 渚цВ鏋愪负瀵瑰簲 id銆�
+ * 鍚屾澶辫触灏嗘姏鍑哄紓甯革紝瑙﹀彂澶栧眰 @Transactional 鏁翠綋鍥炴粴锛圡DM 鎿嶄綔涓�骞跺洖婊氾級銆�
+ */
+ private void syncItemToMes(MdmItemSaveReqVO reqVO) {
+ // 瑙f瀽璁¢噺鍗曚綅 id -> code
+ String unitCode = null;
+ if (reqVO.getUnitMeasureId() != null) {
+ MdmUnitMeasureDO unitMeasure = unitMeasureService.getUnitMeasure(reqVO.getUnitMeasureId());
+ if (unitMeasure == null) {
+ throw exception(MDM_UNIT_MEASURE_NOT_EXISTS);
+ }
+ unitCode = unitMeasure.getCode();
+ }
+ // 瑙f瀽鍒嗙被 id -> code锛堜娇鐢� MES 鐗╂枡鍒嗙被 API锛�
+ String itemTypeCode = null;
+ if (reqVO.getCategoryId() != null) {
+ CommonResult<MesMdItemTypeRespDTO> result = mesMdItemTypeApi.getItemType(reqVO.getCategoryId());
+ if (result != null && result.isSuccess() && result.getData() != null) {
+ itemTypeCode = result.getData().getCode();
+ }
+ } else if (reqVO.getItemType() != null) {
+ // 鏍规嵁 itemType 鏌ユ壘瀵瑰簲鍒嗙被缂栫爜
+ // itemType: 1鍘熸枡, 2鍗婃垚鍝�, 3鎴愬搧, 4杈呮枡
+ String itemTypeName = convertItemTypeToName(reqVO.getItemType());
+ CommonResult<List<MesMdItemTypeRespDTO>> listResult = mesMdItemTypeApi.getItemTypeList(null);
+ if (listResult != null && listResult.isSuccess() && listResult.getData() != null) {
+ itemTypeCode = listResult.getData().stream()
+ .filter(c -> c.getName() != null && c.getName().equals(itemTypeName))
+ .findFirst()
+ .map(MesMdItemTypeRespDTO::getCode)
+ .orElse(null);
+ }
+ }
+ // 缁勮鍚屾 DTO
+ MesMdItemSyncReqDTO syncReqDTO = new MesMdItemSyncReqDTO();
+ syncReqDTO.setCode(reqVO.getCode());
+ syncReqDTO.setName(reqVO.getName());
+ syncReqDTO.setSpecification(reqVO.getSpecification());
+ syncReqDTO.setUnitCode(unitCode);
+ syncReqDTO.setItemTypeCode(itemTypeCode);
+ syncReqDTO.setSafeStockFlag(Boolean.TRUE); // 淇濈暀 MDM 鐨� minStock/maxStock锛岄伩鍏嶈 MES 娓呴浂
+ syncReqDTO.setMinStock(reqVO.getMinStock());
+ syncReqDTO.setMaxStock(reqVO.getMaxStock());
+ syncReqDTO.setHighValue(reqVO.getIsHighValue());
+ syncReqDTO.setBatchFlag(reqVO.getIsBatchManaged());
+ syncReqDTO.setRemark(reqVO.getRemark());
+ // create 璧� createItem锛寀pdate 璧� updateItem锛圡ES 渚ф寜 code upsert锛�
+ if (reqVO.getId() == null) {
+ mesMdItemApi.createItem(syncReqDTO);
+ } else {
+ mesMdItemApi.updateItem(syncReqDTO);
+ }
}
@Override
@@ -77,10 +160,16 @@
}
@Override
+ @Transactional(rollbackFor = Exception.class)
public void deleteItem(Long id) {
// 鏍¢獙瀛樺湪
- validateItemExists(id);
- // 鍒犻櫎
+ MdmItemDO item = validateItemExists(id);
+ // 瑙e喅杞垹闄ゅ敮涓�閿啿绐侊細濡傛灉宸插瓨鍦ㄧ浉鍚� code 涓斿凡杞垹闄ょ殑璁板綍锛屽厛鐗╃悊鍒犻櫎瀹�
+ MdmItemDO existingDeleted = itemMapper.selectByCodeAndDeleted(item.getCode(), true);
+ if (existingDeleted != null && !existingDeleted.getId().equals(id)) {
+ itemMapper.physicalDeleteById(existingDeleted.getId());
+ }
+ // 杞垹闄ゅ綋鍓嶈褰�
itemMapper.deleteById(id);
}
@@ -113,7 +202,11 @@
}
private void validateCategoryExists(Long categoryId) {
- if (itemCategoryService.getItemCategory(categoryId) == null) {
+ if (categoryId == null) {
+ return;
+ }
+ CommonResult<MesMdItemTypeRespDTO> result = mesMdItemTypeApi.getItemType(categoryId);
+ if (result == null || !result.isSuccess() || result.getData() == null) {
throw exception(MDM_ITEM_CATEGORY_NOT_EXISTS);
}
}
@@ -130,6 +223,30 @@
}
}
+ /**
+ * 灏� itemType 杞崲涓轰腑鏂囧悕绉�
+ *
+ * @param itemType 鐗╂枡绫诲瀷锛�1鍘熸枡, 2鍗婃垚鍝�, 3鎴愬搧, 4杈呮枡
+ * @return 涓枃鍚嶇О
+ */
+ private String convertItemTypeToName(Integer itemType) {
+ if (itemType == null) {
+ return null;
+ }
+ switch (itemType) {
+ case 1:
+ return "鍘熸枡";
+ case 2:
+ return "鍗婃垚鍝�";
+ case 3:
+ return "鎴愬搧";
+ case 4:
+ return "杈呮枡";
+ default:
+ return null;
+ }
+ }
+
@Override
public MdmItemDO getItem(Long id) {
return itemMapper.selectById(id);
diff --git a/yudao-module-mes-api/pom.xml b/yudao-module-mes-api/pom.xml
new file mode 100644
index 0000000..1194535
--- /dev/null
+++ b/yudao-module-mes-api/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>yudao-module-mes-api</artifactId>
+ <packaging>jar</packaging>
+
+ <name>${project.artifactId}</name>
+ <description>
+ mes 妯″潡涓嬶紝鍒堕�犳墽琛岀郴缁熺殑 API 鎺ュ彛瀹氫箟锛屼緵鍏朵粬妯″潡寮曠敤
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-common</artifactId>
+ </dependency>
+
+ <!-- Swagger 娉ㄨВ -->
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java
new file mode 100644
index 0000000..a683eaf
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.mes.api.item;
+
+import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemSyncReqDTO;
+
+/**
+ * MES 鐗╂枡浜у搧 API 鎺ュ彛
+ *
+ * 渚涘叾浠栨ā鍧楋紙濡� MDM锛夊悓姝ョ墿鏂欎富鏁版嵁鍒� MES 浣跨敤銆�
+ * 鍗曚綅銆佸垎绫诲潎鎸� code 浼犻�掞紝鐢� MES 渚цВ鏋愪负瀵瑰簲 id銆�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MesMdItemApi {
+
+ /**
+ * 鍒涘缓 MES 鐗╂枡浜у搧锛堝悓姝ョ敤锛�
+ *
+ * @param syncReqDTO 鍚屾淇℃伅锛堝崟浣�/鍒嗙被浼� code锛�
+ * @return MES 鐗╂枡缂栧彿
+ */
+ Long createItem(MesMdItemSyncReqDTO syncReqDTO);
+
+ /**
+ * 鏇存柊 MES 鐗╂枡浜у搧锛堝悓姝ョ敤锛�
+ *
+ * 鎸� code 鍖归厤锛氬瓨鍦ㄥ垯鏇存柊锛屼笉瀛樺湪鍒欏垱寤猴紙upsert锛夈��
+ *
+ * @param syncReqDTO 鍚屾淇℃伅
+ */
+ void updateItem(MesMdItemSyncReqDTO syncReqDTO);
+
+}
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemTypeApi.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemTypeApi.java
new file mode 100644
index 0000000..8ee6460
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemTypeApi.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.mes.api.item;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemTypeRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * MES 鐗╂枡鍒嗙被 API 鎺ュ彛
+ *
+ * 渚涘叾浠栨ā鍧楋紙濡� MDM锛夋煡璇㈢墿鏂欏垎绫绘暟鎹娇鐢ㄣ��
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MesMdItemTypeApi {
+
+ String PREFIX = "/rpc-api/mes/item-type";
+
+ /**
+ * 鑾峰彇鐗╂枡鍒嗙被
+ *
+ * @param id 鍒嗙被缂栧彿
+ * @return 鐗╂枡鍒嗙被
+ */
+ CommonResult<MesMdItemTypeRespDTO> getItemType(Long id);
+
+ /**
+ * 鑾峰彇鐗╂枡鍒嗙被鍒楄〃
+ *
+ * @param ids 鍒嗙被缂栧彿鍒楄〃
+ * @return 鐗╂枡鍒嗙被鍒楄〃
+ */
+ CommonResult<List<MesMdItemTypeRespDTO>> getItemTypeList(Collection<Long> ids);
+
+ /**
+ * 鏍规嵁缂栫爜鑾峰彇鐗╂枡鍒嗙被
+ *
+ * @param code 鍒嗙被缂栫爜
+ * @return 鐗╂枡鍒嗙被
+ */
+ CommonResult<MesMdItemTypeRespDTO> getItemTypeByCode(String code);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemSyncReqDTO.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemSyncReqDTO.java
new file mode 100644
index 0000000..44cd667
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemSyncReqDTO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.mes.api.item.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "MES 鐗╂枡浜у搧鍚屾 Request DTO")
+@Data
+public class MesMdItemSyncReqDTO {
+
+ @Schema(description = "鐗╂枡缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "ITEM001")
+ private String code;
+
+ @Schema(description = "鐗╂枡鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "铻轰笣")
+ private String name;
+
+ @Schema(description = "瑙勬牸鍨嬪彿", example = "M6*20")
+ private String specification;
+
+ @Schema(description = "璁¢噺鍗曚綅缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "PCS")
+ private String unitCode;
+
+ @Schema(description = "鐗╂枡鍒嗙被缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "TYPE001")
+ private String itemTypeCode;
+
+ @Schema(description = "鏄惁鍚敤瀹夊叏搴撳瓨", example = "false")
+ private Boolean safeStockFlag;
+
+ @Schema(description = "鏈�浣庡簱瀛橀噺", example = "100")
+ private BigDecimal minStock;
+
+ @Schema(description = "鏈�楂樺簱瀛橀噺", example = "10000")
+ private BigDecimal maxStock;
+
+ @Schema(description = "鏄惁楂樺�肩墿鏂�", example = "false")
+ private Boolean highValue;
+
+ @Schema(description = "鏄惁鍚敤鎵规绠$悊", example = "true")
+ private Boolean batchFlag;
+
+ @Schema(description = "澶囨敞", example = "澶囨敞")
+ private String remark;
+
+}
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemTypeRespDTO.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemTypeRespDTO.java
new file mode 100644
index 0000000..6d07b86
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemTypeRespDTO.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.mes.api.item.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "RPC 鏈嶅姟 - MES 鐗╂枡鍒嗙被 Response DTO")
+@Data
+public class MesMdItemTypeRespDTO {
+
+ @Schema(description = "鍒嗙被缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "鍒嗙被缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "RAW001")
+ private String code;
+
+ @Schema(description = "鍒嗙被鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鍘熸枡")
+ private String name;
+
+ @Schema(description = "鐖跺垎绫荤紪鍙�", example = "0")
+ private Long parentId;
+
+ @Schema(description = "鐗╂枡/浜у搧鏍囪瘑", example = "1")
+ private String itemOrProduct;
+
+ @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Integer status;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/pom.xml b/yudao-module-mes/pom.xml
index 4f6e41f..dc0e8fe 100644
--- a/yudao-module-mes/pom.xml
+++ b/yudao-module-mes/pom.xml
@@ -24,6 +24,13 @@
<version>${revision}</version>
</dependency>
+ <!-- MES 鍒堕�犳墽琛� API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-mes-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<!-- MDM 涓绘暟鎹鐞� API -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java
new file mode 100644
index 0000000..230bd63
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java
@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.mes.api.item;
+
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemSyncReqDTO;
+import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.MesMdItemSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
+import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
+import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
+import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+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.mes.enums.ErrorCodeConstants.*;
+
+/**
+ * MES 鐗╂枡浜у搧 API 瀹炵幇绫�
+ *
+ * 渚� MDM 绛夋ā鍧楀悓姝ョ墿鏂欎富鏁版嵁鍒� MES 浣跨敤銆�
+ * 鍗曚綅銆佸垎绫绘寜 code 鍦� MES 渚цВ鏋愪负瀵瑰簲 id銆�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+@Slf4j
+public class MesMdItemApiImpl implements MesMdItemApi {
+
+ @Resource
+ private MesMdItemService itemService;
+ @Resource
+ private MesMdItemTypeService itemTypeService;
+ @Resource
+ private MesMdUnitMeasureService unitMeasureService;
+
+ @Override
+ public Long createItem(MesMdItemSyncReqDTO syncReqDTO) {
+ log.info("[createItem] 鍚屾鍒涘缓 MES 鐗╂枡浜у搧锛宑ode={}", syncReqDTO.getCode());
+ MesMdItemSaveReqVO saveReqVO = buildSaveReqVO(syncReqDTO);
+ return itemService.createItem(saveReqVO);
+ }
+
+ @Override
+ public void updateItem(MesMdItemSyncReqDTO syncReqDTO) {
+ log.info("[updateItem] 鍚屾鏇存柊 MES 鐗╂枡浜у搧锛宑ode={}", syncReqDTO.getCode());
+ MesMdItemSaveReqVO saveReqVO = buildSaveReqVO(syncReqDTO);
+ // 鎸� code 鍖归厤锛氬瓨鍦ㄥ垯鏇存柊锛屼笉瀛樺湪鍒欏垱寤猴紙upsert锛�
+ MesMdItemDO existItem = itemService.getItemByCode(syncReqDTO.getCode());
+ if (existItem == null) {
+ itemService.createItem(saveReqVO);
+ } else {
+ saveReqVO.setId(existItem.getId());
+ itemService.updateItem(saveReqVO);
+ }
+ }
+
+ /**
+ * 灏嗗悓姝� DTO 杞崲涓� MES 鐗╂枡浜у搧 SaveReqVO锛屽苟鎸� code 瑙f瀽鍗曚綅/鍒嗙被 id
+ */
+ private MesMdItemSaveReqVO buildSaveReqVO(MesMdItemSyncReqDTO syncReqDTO) {
+ // 瑙f瀽璁¢噺鍗曚綅 code -> id
+ MesMdUnitMeasureDO unitMeasure = unitMeasureService.getUnitMeasureByCode(syncReqDTO.getUnitCode());
+ if (unitMeasure == null) {
+ throw exception(MD_UNIT_MEASURE_NOT_EXISTS);
+ }
+ // 瑙f瀽鐗╂枡鍒嗙被 code -> id
+ MesMdItemTypeDO itemType = itemTypeService.getItemTypeByCode(syncReqDTO.getItemTypeCode());
+ if (itemType == null) {
+ throw exception(MD_ITEM_TYPE_NOT_EXISTS);
+ }
+ // 缁勮锛坈ode/name/specification/safeStockFlag/minStock/maxStock/highValue/batchFlag/remark 鍚屽悕鎷疯礉锛�
+ MesMdItemSaveReqVO saveReqVO = BeanUtils.toBean(syncReqDTO, MesMdItemSaveReqVO.class);
+ saveReqVO.setUnitMeasureId(unitMeasure.getId());
+ saveReqVO.setItemTypeId(itemType.getId());
+ return saveReqVO;
+ }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemTypeApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemTypeApiImpl.java
new file mode 100644
index 0000000..5a22204
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemTypeApiImpl.java
@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.mes.api.item;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemTypeRespDTO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
+import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
+import jakarta.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * MES 鐗╂枡鍒嗙被 API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RestController
+@RequestMapping(MesMdItemTypeApi.PREFIX)
+@Validated
+public class MesMdItemTypeApiImpl implements MesMdItemTypeApi {
+
+ @Resource
+ private MesMdItemTypeService itemTypeService;
+
+ @Override
+ public CommonResult<MesMdItemTypeRespDTO> getItemType(Long id) {
+ MesMdItemTypeDO itemType = itemTypeService.getItemType(id);
+ return success(BeanUtils.toBean(itemType, MesMdItemTypeRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<List<MesMdItemTypeRespDTO>> getItemTypeList(Collection<Long> ids) {
+ List<MesMdItemTypeDO> list = itemTypeService.getItemTypeList(ids);
+ return success(BeanUtils.toBean(list, MesMdItemTypeRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<MesMdItemTypeRespDTO> getItemTypeByCode(String code) {
+ MesMdItemTypeDO itemType = itemTypeService.getItemTypeByCode(code);
+ return success(BeanUtils.toBean(itemType, MesMdItemTypeRespDTO.class));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java
index 787c3bd..f97f9e4 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java
@@ -41,6 +41,19 @@
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackTraceRespVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeLineDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemconsume.MesWmItemConsumeLineMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemconsume.MesWmItemConsumeMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceLineMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceMapper;
+import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
+
import java.io.IOException;
import java.util.*;
import java.util.stream.Stream;
@@ -78,6 +91,17 @@
@Resource
private AdminUserApi adminUserApi;
+
+ @Resource
+ private MesWmProductProduceMapper productProduceMapper;
+ @Resource
+ private MesWmProductProduceLineMapper productProduceLineMapper;
+ @Resource
+ private MesWmItemConsumeMapper itemConsumeMapper;
+ @Resource
+ private MesWmItemConsumeLineMapper itemConsumeLineMapper;
+ @Resource
+ private MesQcIpqcMapper ipqcMapper;
@PostMapping("/create")
@Operation(summary = "鍒涘缓鐢熶骇鎶ュ伐")
@@ -123,6 +147,15 @@
return success(new PageResult<>(buildFeedbackRespVOList(pageResult.getList()), pageResult.getTotal()));
}
+ @GetMapping("/list-by-task")
+ @Operation(summary = "鎸変换鍔¤幏寰楁姤宸ュ巻鍙插垪琛�")
+ @Parameter(name = "taskId", description = "浠诲姟缂栧彿", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('mes:pro-feedback:query')")
+ public CommonResult<List<MesProFeedbackRespVO>> getFeedbackListByTask(@RequestParam("taskId") Long taskId) {
+ List<MesProFeedbackDO> list = feedbackService.getFeedbackListByTaskId(taskId);
+ return success(buildFeedbackRespVOList(list));
+ }
+
@GetMapping("/export-excel")
@Operation(summary = "瀵煎嚭鐢熶骇鎶ュ伐 Excel")
@PreAuthorize("@ss.hasPermission('mes:pro-feedback:export')")
@@ -163,6 +196,32 @@
// ==================== 鎷兼帴 VO ====================
+ @GetMapping("/trace/{id}")
+ @Operation(summary = "鑾峰緱鎶ュ伐杩芥函淇℃伅", description = "鎶ュ伐 + 浜у嚭/浜у嚭鏄庣粏 + 娑堣��/娑堣�楁槑缁� + IPQC妫�楠�")
+ @Parameter(name = "id", description = "鎶ュ伐缂栧彿", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('mes:pro-feedback:query')")
+ public CommonResult<MesProFeedbackTraceRespVO> getFeedbackTrace(@PathVariable("id") Long id) {
+ MesProFeedbackDO feedback = feedbackService.getFeedback(id);
+ if (feedback == null) {
+ return success(null);
+ }
+ MesProFeedbackTraceRespVO trace = new MesProFeedbackTraceRespVO();
+ // 1. 鎶ュ伐鏄庣粏锛堝鐢ㄥ睍绀烘嫾鎺ワ紝甯﹀伐鍗�/浠诲姟/宸ュ簭/鐗╂枡/鎶ュ伐浜虹瓑鏄剧ず鍚嶏級
+ trace.setFeedback(buildFeedbackRespVOList(ListUtil.of(feedback)).get(0));
+ // 2. 鐢熶骇浜у嚭 + 浜у嚭鏄庣粏琛岋紙鍏抽敭宸ュ簭鎵嶆湁锛�
+ trace.setProductProduce(productProduceMapper.selectByFeedbackId(id));
+ trace.setProductProduceLines(productProduceLineMapper.selectListByFeedbackId(id));
+ // 3. 鐗╂枡娑堣�� + 娑堣�楁槑缁嗚锛堝�掑啿宸ュ簭鎵嶆湁锛�
+ MesWmItemConsumeDO itemConsume = itemConsumeMapper.selectByFeedbackId(id);
+ trace.setItemConsume(itemConsume);
+ if (itemConsume != null) {
+ trace.setItemConsumeLines(itemConsumeLineMapper.selectListByConsumeId(itemConsume.getId()));
+ }
+ // 4. IPQC 杩囩▼妫�楠屽崟锛堝叧閿�+闇�妫�楠屽伐搴忔墠鏈夛級
+ trace.setIpqcList(ipqcMapper.selectListBySourceDoc(MesBizTypeConstants.PRO_FEEDBACK, id));
+ return success(trace);
+ }
+
private List<MesProFeedbackRespVO> buildFeedbackRespVOList(List<MesProFeedbackDO> list) {
if (CollUtil.isEmpty(list)) {
return Collections.emptyList();
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackTraceRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackTraceRespVO.java
new file mode 100644
index 0000000..807e326
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackTraceRespVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo;
+
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeLineDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "绠$悊鍚庡彴 - 鎶ュ伐杩芥函 Response VO")
+@Data
+public class MesProFeedbackTraceRespVO {
+
+ @Schema(description = "鎶ュ伐淇℃伅")
+ private MesProFeedbackRespVO feedback;
+
+ @Schema(description = "鐢熶骇浜у嚭鍗曪紙鍏抽敭宸ュ簭鎵嶆湁锛�")
+ private MesWmProductProduceDO productProduce;
+
+ @Schema(description = "鐢熶骇浜у嚭鏄庣粏琛岋紙鎸夊悎鏍�/涓嶅悎鏍�/寰呮楠屾媶琛岋級")
+ private List<MesWmProductProduceLineDO> productProduceLines;
+
+ @Schema(description = "鐗╂枡娑堣�楀崟锛堝�掑啿宸ュ簭鎵嶆湁锛�")
+ private MesWmItemConsumeDO itemConsume;
+
+ @Schema(description = "鐗╂枡娑堣�楁槑缁嗚锛堟寜 BOM 鐗╂枡锛�")
+ private List<MesWmItemConsumeLineDO> itemConsumeLines;
+
+ @Schema(description = "IPQC 杩囩▼妫�楠屽崟鍒楄〃锛堝叧閿�+闇�妫�楠屽伐搴忔墠鏈夛級")
+ private List<MesQcIpqcDO> ipqcList;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessRespVO.java
index 78608e6..44d6790 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessRespVO.java
@@ -51,6 +51,9 @@
@Schema(description = "鏄惁璐ㄦ宸ュ簭", example = "false")
private Boolean checkFlag;
+ @Schema(description = "鏄惁鍊掑啿锛坱rue=鎶ュ伐鏃舵寜BOM鑷姩鎵f枡锛沠alse=涓嶈嚜鍔ㄦ墸鏂欙級", example = "true")
+ private Boolean backflushFlag;
+
@Schema(description = "澶囨敞")
private String remark;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessSaveReqVO.java
index 9cde37c..5589e8c 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessSaveReqVO.java
@@ -41,6 +41,9 @@
@Schema(description = "鏄惁璐ㄦ宸ュ簭", example = "false")
private Boolean checkFlag;
+ @Schema(description = "鏄惁鍊掑啿锛坱rue=鎶ュ伐鏃舵寜BOM鑷姩鎵f枡锛沠alse=涓嶈嚜鍔ㄦ墸鏂欙級", example = "true")
+ private Boolean backflushFlag;
+
@Schema(description = "澶囨敞")
private String remark;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java
index 20fbfb9..6f394e9 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java
@@ -41,6 +41,9 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
+import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService;
import java.io.IOException;
import java.math.BigDecimal;
@@ -74,6 +77,8 @@
private MesMdUnitMeasureService unitMeasureService;
@Resource
private MesProRouteProcessService routeProcessService;
+ @Resource
+ private MesProFeedbackService feedbackService;
@PostMapping("/create")
@Operation(summary = "鍒涘缓鐢熶骇浠诲姟")
@@ -116,6 +121,14 @@
@PreAuthorize("@ss.hasPermission('mes:pro-task:query')")
public CommonResult<PageResult<MesProTaskRespVO>> getTaskPage(@Valid MesProTaskPageReqVO pageReqVO) {
PageResult<MesProTaskDO> pageResult = taskService.getTaskPage(pageReqVO);
+ return success(new PageResult<>(buildTaskRespVOList(pageResult.getList()), pageResult.getTotal()));
+ }
+
+ @GetMapping("/pending-feedback-page")
+ @Operation(summary = "鑾峰緱寰呮姤宸ュ彴璐﹀垎椤�", description = "宸叉帓浜т笖浠嶆湁寰呮姤宸ユ暟閲忕殑浠诲姟锛堢姸鎬�=杩涜涓級")
+ @PreAuthorize("@ss.hasPermission('mes:pro-task:query')")
+ public CommonResult<PageResult<MesProTaskRespVO>> getPendingFeedbackPage(@Valid MesProTaskPageReqVO pageReqVO) {
+ PageResult<MesProTaskDO> pageResult = taskService.getPendingFeedbackPage(pageReqVO);
return success(new PageResult<>(buildTaskRespVOList(pageResult.getList()), pageResult.getTotal()));
}
@@ -232,9 +245,29 @@
.put(rp.getProcessId(), Boolean.TRUE.equals(rp.getCheckFlag()));
}
}
+ // 鎶ュ伐鍦ㄩ�旈噺锛氭寜浠诲姟鑱氬悎 feedbackQuantity锛堣崏绋�/瀹℃壒涓�/寰呮楠� = 鍦ㄩ�旓紱宸插畬鎴� = 宸插鎵癸級
+ List<MesProFeedbackDO> taskFeedbacks = feedbackService.getFeedbackListByTaskIds(
+ convertSet(list, MesProTaskDO::getId));
+ Map<Long, BigDecimal> inTransitMap = new HashMap<>();
+ for (MesProFeedbackDO fb : taskFeedbacks) {
+ BigDecimal fbQty = fb.getFeedbackQuantity() == null ? BigDecimal.ZERO : fb.getFeedbackQuantity();
+ if (!ObjUtil.equal(fb.getStatus(), MesProFeedbackStatusEnum.FINISHED.getStatus())) {
+ inTransitMap.merge(fb.getTaskId(), fbQty, BigDecimal::add);
+ }
+ }
// 鎷兼帴 VO
return convertList(list, task -> {
MesProTaskRespVO vo = BeanUtils.toBean(task, MesProTaskRespVO.class);
+ // 寰呮姤宸ユ暟閲� = 鎺掍骇鏁伴噺 - 宸茬敓浜ф暟閲忥紱鎶ュ伐瀹屾垚鐜� = 宸茬敓浜� / 鎺掍骇 脳 100
+ BigDecimal quantity = task.getQuantity() == null ? BigDecimal.ZERO : task.getQuantity();
+ BigDecimal produced = task.getProducedQuantity() == null ? BigDecimal.ZERO : task.getProducedQuantity();
+ vo.setPendingQuantity(quantity.subtract(produced));
+ vo.setFeedbackRate(quantity.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO
+ : produced.multiply(new BigDecimal("100")).divide(quantity, 2, RoundingMode.HALF_UP));
+ // 鍦ㄩ�旀姤宸ラ噺锛堣崏绋�/瀹℃壒涓�/寰呮楠岋級+ 鍙姤宸ユ暟閲� = 鎺掍骇 - 宸茬敓浜� - 鍦ㄩ��
+ BigDecimal inTransit = inTransitMap.getOrDefault(task.getId(), BigDecimal.ZERO);
+ vo.setInTransitQuantity(inTransit);
+ vo.setReportableQuantity(quantity.subtract(produced).subtract(inTransit).max(BigDecimal.ZERO));
findAndThen(workOrderMap, task.getWorkOrderId(), wo ->
vo.setWorkOrderCode(wo.getCode()).setWorkOrderName(wo.getName()).setRequestDate(wo.getRequestDate()));
findAndThen(workstationMap, task.getWorkstationId(), ws ->
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java
index 468636f..b6f3266 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java
@@ -73,6 +73,18 @@
@Schema(description = "涓嶈壇鍝佹暟閲�", example = "2.00")
private BigDecimal unqualifyQuantity;
+ @Schema(description = "寰呮姤宸ユ暟閲�", example = "50.00")
+ private BigDecimal pendingQuantity;
+
+ @Schema(description = "鍦ㄩ�旀姤宸ユ暟閲�", example = "20.00")
+ private BigDecimal inTransitQuantity;
+
+ @Schema(description = "鍙姤宸ユ暟閲�", example = "30.00")
+ private BigDecimal reportableQuantity;
+
+ @Schema(description = "鎶ュ伐瀹屾垚鐜�(%)", example = "50.00")
+ private BigDecimal feedbackRate;
+
@Schema(description = "璋冩暣鏁伴噺", example = "0")
private BigDecimal changedQuantity;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/route/MesProRouteProcessDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/route/MesProRouteProcessDO.java
index beebe42..14160b4 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/route/MesProRouteProcessDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/route/MesProRouteProcessDO.java
@@ -78,6 +78,12 @@
*/
private Boolean checkFlag;
/**
+ * 鏄惁鍊掑啿
+ *
+ * true=鎶ュ伐鏃舵寜 BOM 鑷姩鎵f枡锛沠alse=涓嶈嚜鍔ㄦ墸鏂欙紝鐗╂枡璧版墜宸ラ鏂�/鎶曟枡
+ */
+ private Boolean backflushFlag;
+ /**
* 澶囨敞
*/
private String remark;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/item/MesMdItemTypeMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/item/MesMdItemTypeMapper.java
index 6dbddff..2ff6a59 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/item/MesMdItemTypeMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/item/MesMdItemTypeMapper.java
@@ -31,6 +31,10 @@
return selectOne(MesMdItemTypeDO::getParentId, parentId, MesMdItemTypeDO::getCode, code);
}
+ default MesMdItemTypeDO selectByCode(String code) {
+ return selectOne(MesMdItemTypeDO::getName, code);
+ }
+
default Long selectCountByParentId(Long parentId) {
return selectCount(MesMdItemTypeDO::getParentId, parentId);
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/feedback/MesProFeedbackMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/feedback/MesProFeedbackMapper.java
index 6308ae9..b40c765 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/feedback/MesProFeedbackMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/feedback/MesProFeedbackMapper.java
@@ -7,6 +7,9 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
import org.apache.ibatis.annotations.Mapper;
+import java.util.Collection;
+import java.util.List;
+
/**
* MES 鐢熶骇鎶ュ伐 Mapper
*
@@ -28,4 +31,19 @@
.orderByDesc(MesProFeedbackDO::getId));
}
+ default Long selectCountByTaskId(Long taskId) {
+ return selectCount(MesProFeedbackDO::getTaskId, taskId);
+ }
+
+ default List<MesProFeedbackDO> selectListByTaskId(Long taskId) {
+ return selectList(new LambdaQueryWrapperX<MesProFeedbackDO>()
+ .eq(MesProFeedbackDO::getTaskId, taskId)
+ .orderByDesc(MesProFeedbackDO::getId));
+ }
+
+ default List<MesProFeedbackDO> selectListByTaskIds(Collection<Long> taskIds) {
+ return selectList(new LambdaQueryWrapperX<MesProFeedbackDO>()
+ .in(MesProFeedbackDO::getTaskId, taskIds));
+ }
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java
index 21b9f44..02bcef1 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProTaskStatusEnum;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
@@ -44,6 +45,23 @@
return selectPage(reqVO, query);
}
+ default PageResult<MesProTaskDO> selectPendingFeedbackPage(MesProTaskPageReqVO reqVO) {
+ MPJLambdaWrapperX<MesProTaskDO> query = new MPJLambdaWrapperX<>();
+ query.selectAll(MesProTaskDO.class);
+ // 寰呮姤宸ュ彴璐︼細杩涜涓殑浠诲姟锛堟帓闄ゅ凡瀹屾垚/宸插彇娑堬級锛屼笖浠嶆湁寰呮姤宸ラ噺
+ query.eq(MesProTaskDO::getStatus, MesProTaskStatusEnum.PREPARE.getStatus());
+ query.apply("(quantity - IFNULL(produced_quantity, 0)) > 0");
+ query.likeIfPresent(MesProTaskDO::getCode, reqVO.getCode())
+ .likeIfPresent(MesProTaskDO::getName, reqVO.getName())
+ .eqIfPresent(MesProTaskDO::getWorkOrderId, reqVO.getWorkOrderId())
+ .eqIfPresent(MesProTaskDO::getRouteId, reqVO.getRouteId())
+ .eqIfPresent(MesProTaskDO::getProcessId, reqVO.getProcessId())
+ .eqIfPresent(MesProTaskDO::getWorkstationId, reqVO.getWorkstationId())
+ .betweenIfPresent(MesProTaskDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(MesProTaskDO::getId);
+ return selectPage(reqVO, query);
+ }
+
default List<MesProTaskDO> selectListByWorkOrderId(Long workOrderId) {
return selectList(new LambdaQueryWrapperX<MesProTaskDO>()
.eqIfPresent(MesProTaskDO::getWorkOrderId, workOrderId)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/ipqc/MesQcIpqcMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/ipqc/MesQcIpqcMapper.java
index 2a7bc21..43882b6 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/ipqc/MesQcIpqcMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/ipqc/MesQcIpqcMapper.java
@@ -7,6 +7,8 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO;
import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+
/**
* MES 杩囩▼妫�楠屽崟锛圛PQC锛� Mapper
*
@@ -31,4 +33,11 @@
return selectOne(MesQcIpqcDO::getCode, code);
}
+ default List<MesQcIpqcDO> selectListBySourceDoc(Integer sourceDocType, Long sourceDocId) {
+ return selectList(new LambdaQueryWrapperX<MesQcIpqcDO>()
+ .eq(MesQcIpqcDO::getSourceDocType, sourceDocType)
+ .eq(MesQcIpqcDO::getSourceDocId, sourceDocId)
+ .orderByDesc(MesQcIpqcDO::getId));
+ }
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
index b5f3ad9..f89cf62 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -272,6 +272,7 @@
ErrorCode PRO_TASK_WORKSTATION_MISMATCH = new ErrorCode(1_040_503_003, "鐢熶骇浠诲姟涓嶅睘浜庡綋鍓嶅伐浣滅珯");
ErrorCode PRO_TASK_ROUTE_PROCESS_MISMATCH = new ErrorCode(1_040_503_004, "鐢熶骇浠诲姟涓庡綋鍓嶅伐鑹鸿矾绾挎垨宸ュ簭涓嶄竴鑷�");
ErrorCode PRO_TASK_ITEM_MISMATCH = new ErrorCode(1_040_503_005, "鐢熶骇浠诲姟浜у搧涓庡綋鍓嶄骇鍝佺墿鏂欎笉涓�鑷�");
+ ErrorCode PRO_TASK_HAS_FEEDBACK = new ErrorCode(1_040_503_006, "璇ヤ换鍔′笅瀛樺湪鎶ュ伐璁板綍锛屾棤娉曞垹闄わ紙閬垮厤鎶ュ伐杩芥函鏂摼锛�");
ErrorCode PRO_WORKSTATION_PROCESS_MISMATCH = new ErrorCode(1_040_503_006, "宸ヤ綔绔欐墍灞炲伐搴忎笌褰撳墠宸ュ簭涓嶄竴鑷�");
// ========== MES 鐢熶骇绠$悊-鐢熶骇浠诲姟鎶曟枡锛�1-040-503-100锛� ==========
ErrorCode PRO_TASK_ISSUE_NOT_EXISTS = new ErrorCode(1_040_503_100, "鐢熶骇浠诲姟鎶曟枡璁板綍涓嶅瓨鍦�");
@@ -299,6 +300,7 @@
ErrorCode PRO_FEEDBACK_QUANTITY_MUST_POSITIVE = new ErrorCode(1_040_506_010, "鎶ュ伐鏁伴噺蹇呴』澶т簬 0");
ErrorCode PRO_FEEDBACK_QUALIFIED_UNQUALIFIED_REQUIRED = new ErrorCode(1_040_506_011, "璇疯緭鍏ュ悎鏍煎搧鍜屼笉鑹搧鏁伴噺锛屼笖鍚堣椤诲ぇ浜� 0");
ErrorCode PRO_FEEDBACK_UNCHECK_QUANTITY_EXISTS = new ErrorCode(1_040_506_012, "褰撳墠鎶ュ伐鍗曟湭瀹屾垚妫�楠岋紙寰呮鏁伴噺锛歿}锛夛紝鏃犳硶鎵ц鎶ュ伐");
+ ErrorCode PRO_FEEDBACK_BACKFLUSH_BOM_REQUIRED = new ErrorCode(1_040_506_013, "褰撳墠宸ュ簭涓哄�掑啿宸ュ簭锛屼絾鏈厤缃瓸OM娑堣�楋紝鏃犳硶鎶ュ伐");
// ========== MES 鐢熶骇绠$悊-鐢熶骇娴佽浆鍗★紙1-040-507-000锛� ==========
ErrorCode PRO_CARD_NOT_EXISTS = new ErrorCode(1_040_507_000, "鐢熶骇娴佽浆鍗′笉瀛樺湪");
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemService.java
index 8383470..c86b91e 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemService.java
@@ -60,6 +60,14 @@
MesMdItemDO getItem(Long id);
/**
+ * 鏍规嵁缂栫爜鑾峰緱鐗╂枡浜у搧
+ *
+ * @param code 鐗╂枡缂栫爜
+ * @return 鐗╂枡浜у搧
+ */
+ MesMdItemDO getItemByCode(String code);
+
+ /**
* 鏍¢獙鐗╂枡浜у搧瀛樺湪
*
* @param id 缂栧彿
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
index 23f6e22..860e5b2 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
@@ -245,6 +245,11 @@
}
@Override
+ public MesMdItemDO getItemByCode(String code) {
+ return itemMapper.selectByCode(code);
+ }
+
+ @Override
public PageResult<MesMdItemDO> getItemPage(MesMdItemPageReqVO pageReqVO) {
// 鏌ユ壘鏃讹紝濡傛灉鏌ユ壘鏌愪釜鍒嗙被缂栧彿锛屽垯鍖呭惈瀹冪殑瀛愬垎绫�
Set<Long> itemTypeIds = null;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeService.java
index 56ca66e..8041d9a 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeService.java
@@ -49,6 +49,14 @@
MesMdItemTypeDO getItemType(Long id);
/**
+ * 鏍规嵁缂栫爜鑾峰緱鐗╂枡浜у搧鍒嗙被
+ *
+ * @param code 鍒嗙被缂栫爜
+ * @return 鐗╂枡浜у搧鍒嗙被
+ */
+ MesMdItemTypeDO getItemTypeByCode(String code);
+
+ /**
* 鑾峰緱鐗╂枡浜у搧鍒嗙被鍒楄〃
*
* @param listReqVO 鏌ヨ鏉′欢
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeServiceImpl.java
index b260230..dd1a2e6 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeServiceImpl.java
@@ -148,6 +148,11 @@
}
@Override
+ public MesMdItemTypeDO getItemTypeByCode(String code) {
+ return itemTypeMapper.selectByCode(code);
+ }
+
+ @Override
public List<MesMdItemTypeDO> getItemTypeList(MesMdItemTypeListReqVO listReqVO) {
return itemTypeMapper.selectList(listReqVO);
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/unitmeasure/MesMdUnitMeasureServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/unitmeasure/MesMdUnitMeasureServiceImpl.java
index 139fb12..05be23e 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/unitmeasure/MesMdUnitMeasureServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/unitmeasure/MesMdUnitMeasureServiceImpl.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.mes.service.md.unitmeasure;
import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.md.unitmeasure.vo.MesMdUnitMeasurePageReqVO;
@@ -14,6 +15,8 @@
import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcLineService;
import cn.iocoder.yudao.module.mes.service.qc.rqc.MesQcRqcLineService;
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateIndicatorService;
+import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
+import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
import org.springframework.context.annotation.Lazy;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@@ -23,6 +26,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -30,11 +34,16 @@
/**
* MES 璁¢噺鍗曚綅 Service 瀹炵幇绫�
*
+ * 娉ㄦ剰锛氭煡璇㈡搷浣滅粺涓�浣跨敤 MDM API 鑾峰彇鏁版嵁锛屼繚鎸佷笌 MDM 涓绘暟鎹竴鑷�
+ *
* @author 鑺嬮亾婧愮爜
*/
@Service
@Validated
public class MesMdUnitMeasureServiceImpl implements MesMdUnitMeasureService {
+
+ @Resource
+ private MdmUnitMeasureApi unitMeasureApi;
@Resource
private MesMdUnitMeasureMapper unitMeasureMapper;
@@ -138,16 +147,28 @@
@Override
public MesMdUnitMeasureDO getUnitMeasure(Long id) {
- return unitMeasureMapper.selectById(id);
+ // 浼樺厛浠� MDM API 鑾峰彇
+ CommonResult<MdmUnitMeasureRespDTO> result = unitMeasureApi.getUnitMeasure(id);
+ if (result != null && result.isSuccess() && result.getData() != null) {
+ return convertToDo(result.getData());
+ }
+ // 濡傛灉 MDM API 澶辫触锛屽洖閫�鍒版湰鍦版暟鎹簱
+ MesMdUnitMeasureDO local = unitMeasureMapper.selectById(id);
+ if (local != null) {
+ return local;
+ }
+ return null;
}
@Override
public PageResult<MesMdUnitMeasureDO> getUnitMeasurePage(MesMdUnitMeasurePageReqVO pageReqVO) {
+ // 浣跨敤鏈湴鏁版嵁搴撳垎椤垫煡璇紙MDM API 涓嶆敮鎸佸垎椤靛弬鏁帮級
return unitMeasureMapper.selectPage(pageReqVO);
}
@Override
public List<MesMdUnitMeasureDO> getUnitMeasureListByStatus(Integer status) {
+ // 浣跨敤鏈湴鏁版嵁搴撴煡璇紙MDM API 涓嶆敮鎸佹寜鐘舵�佽繃婊わ級
return unitMeasureMapper.selectListByStatus(status);
}
@@ -156,12 +177,45 @@
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
+ // 浼樺厛浠� MDM API 鑾峰彇
+ CommonResult<List<MdmUnitMeasureRespDTO>> result = unitMeasureApi.getUnitMeasureList(ids);
+ if (result != null && result.isSuccess() && result.getData() != null) {
+ return result.getData().stream()
+ .map(this::convertToDo)
+ .collect(Collectors.toList());
+ }
+ // 濡傛灉 MDM API 澶辫触锛屽洖閫�鍒版湰鍦版暟鎹簱
return unitMeasureMapper.selectByIds(ids);
}
@Override
public MesMdUnitMeasureDO getUnitMeasureByCode(String code) {
- return unitMeasureMapper.selectByCode(code);
+ // 浼樺厛浠� MDM API 鑾峰彇
+ CommonResult<MdmUnitMeasureRespDTO> result = unitMeasureApi.getUnitMeasureByCode(code);
+ if (result != null && result.isSuccess() && result.getData() != null) {
+ return convertToDo(result.getData());
+ }
+ // 濡傛灉 MDM API 澶辫触锛屽洖閫�鍒版湰鍦版暟鎹簱
+ MesMdUnitMeasureDO local = unitMeasureMapper.selectByCode(code);
+ if (local != null) {
+ return local;
+ }
+ return null;
+ }
+
+ /**
+ * 灏� MDM DTO 杞崲涓� MES DO
+ */
+ private MesMdUnitMeasureDO convertToDo(MdmUnitMeasureRespDTO dto) {
+ MesMdUnitMeasureDO doObj = new MesMdUnitMeasureDO();
+ doObj.setId(dto.getId());
+ doObj.setCode(dto.getCode());
+ doObj.setName(dto.getName());
+ doObj.setPrimaryFlag(dto.getPrimaryFlag());
+ doObj.setPrimaryId(dto.getPrimaryId());
+ doObj.setChangeRate(dto.getChangeRate());
+ doObj.setStatus(dto.getStatus());
+ return doObj;
}
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java
index b528c35..c5ab540 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java
@@ -7,6 +7,8 @@
import jakarta.validation.Valid;
import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
/**
* MES 鐢熶骇鎶ュ伐 Service 鎺ュ彛
@@ -62,6 +64,22 @@
PageResult<MesProFeedbackDO> getFeedbackPage(MesProFeedbackPageReqVO pageReqVO);
/**
+ * 鎸変换鍔$紪鍙峰垪琛ㄦ壒閲忚幏寰楁姤宸ヨ褰曪紙鐢ㄤ簬浠诲姟鍙拌处鑱氬悎鍦ㄩ��/宸插鎵规姤宸ラ噺锛�
+ *
+ * @param taskIds 浠诲姟缂栧彿鍒楄〃
+ * @return 鎶ュ伐璁板綍鍒楄〃
+ */
+ List<MesProFeedbackDO> getFeedbackListByTaskIds(Collection<Long> taskIds);
+
+ /**
+ * 鎸変换鍔$紪鍙疯幏寰楁姤宸ヨ褰曞垪琛紙鎶ュ伐鍘嗗彶锛屽惈鑽夌/瀹℃壒涓�/寰呮楠�/宸插畬鎴愶級
+ *
+ * @param taskId 浠诲姟缂栧彿
+ * @return 鎶ュ伐璁板綍鍒楄〃
+ */
+ List<MesProFeedbackDO> getFeedbackListByTaskId(Long taskId);
+
+ /**
* 鎻愪氦鎶ュ伐锛堣崏绋� 鈫� 瀹℃壒涓級
*
* @param id 缂栧彿
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
index e7148ee..71f0852 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
@@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.mes.service.pro.feedback;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -9,6 +11,7 @@
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDO;
@@ -19,6 +22,7 @@
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
+import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductBomService;
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeService;
@@ -31,6 +35,8 @@
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -52,6 +58,8 @@
private MesProWorkOrderService workOrderService;
@Resource
private MesProRouteProcessService routeProcessService;
+ @Resource
+ private MesProRouteProductBomService routeProductBomService;
@Resource
private MesMdWorkstationService workstationService;
@Resource
@@ -105,6 +113,19 @@
}
@Override
+ public List<MesProFeedbackDO> getFeedbackListByTaskIds(Collection<Long> taskIds) {
+ if (CollUtil.isEmpty(taskIds)) {
+ return Collections.emptyList();
+ }
+ return feedbackMapper.selectListByTaskIds(taskIds);
+ }
+
+ @Override
+ public List<MesProFeedbackDO> getFeedbackListByTaskId(Long taskId) {
+ return feedbackMapper.selectListByTaskId(taskId);
+ }
+
+ @Override
public PageResult<MesProFeedbackDO> getFeedbackPage(MesProFeedbackPageReqVO pageReqVO) {
return feedbackMapper.selectPage(pageReqVO);
}
@@ -154,10 +175,20 @@
throw exception(PRO_FEEDBACK_UNCHECK_QUANTITY_EXISTS, feedback.getUncheckQuantity());
}
- // 3. 鐗╂枡娑堣�楋細鏍规嵁宸ュ簭 BOM 鐢熸垚娑堣�楄褰曞苟鎵ц鎵e噺
- MesWmItemConsumeDO itemConsume = itemConsumeService.generateItemConsume(feedback);
- if (itemConsume != null) {
- itemConsumeService.finishItemConsume(itemConsume.getId());
+ // 3. 鐗╂枡娑堣�楋細鍊掑啿宸ュ簭鎸� BOM 鑷姩鎵f枡锛涢潪鍊掑啿宸ュ簭璺宠繃锛堢墿鏂欒蛋鎵嬪伐棰嗘枡/鎶曟枡锛�
+ boolean backflushFlag = routeProcess != null && Boolean.TRUE.equals(routeProcess.getBackflushFlag());
+ if (backflushFlag) {
+ // 3.1 鏍¢獙鍊掑啿宸ュ簭蹇呴』閰嶇疆 BOM 娑堣�楋紝閬垮厤闈欓粯涓嶆墸鏂�
+ List<MesProRouteProductBomDO> boms = routeProductBomService.getRouteProductBomList(
+ feedback.getRouteId(), feedback.getProcessId(), feedback.getItemId());
+ if (CollUtil.isEmpty(boms)) {
+ throw exception(PRO_FEEDBACK_BACKFLUSH_BOM_REQUIRED);
+ }
+ // 3.2 鐢熸垚娑堣�楄褰曞苟鎵ц鎵e噺锛圔OM 脳 鎶ュ伐鏁伴噺锛孎IFO 鎵g嚎杈瑰簱锛�
+ MesWmItemConsumeDO itemConsume = itemConsumeService.generateItemConsume(feedback);
+ if (itemConsume != null) {
+ itemConsumeService.finishItemConsume(itemConsume.getId());
+ }
}
// 4. 鍏抽敭宸ュ簭锛氱敓鎴愪骇鍑哄崟锛屽苟鏍规嵁鏄惁闇�瑕佹楠屽喅瀹氬叆搴撴柟寮�
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java
index 058ec83..2be45b8 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java
@@ -59,6 +59,14 @@
PageResult<MesProTaskDO> getTaskPage(MesProTaskPageReqVO pageReqVO);
/**
+ * 鑾峰緱寰呮姤宸ュ彴璐﹀垎椤碉紙杩涜涓笖浠嶆湁寰呮姤宸ラ噺鐨勪换鍔★級
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @return 寰呮姤宸ヤ换鍔″垎椤�
+ */
+ PageResult<MesProTaskDO> getPendingFeedbackPage(MesProTaskPageReqVO pageReqVO);
+
+ /**
* 鏍规嵁宸ュ崟缂栧彿鍒楄〃锛屾壒閲忚幏寰楃敓浜т换鍔″垪琛�
*
* @param workOrderIds 宸ュ崟缂栧彿鍒楄〃
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
index 4052b07..f45fb1b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
@@ -10,6 +10,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProTaskStatusEnum;
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
@@ -33,6 +34,7 @@
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_ALREADY_FINISHED;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_HAS_FEEDBACK;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_NOT_EXISTS;
/**
@@ -46,6 +48,9 @@
@Resource
private MesProTaskMapper taskMapper;
+
+ @Resource
+ private MesProFeedbackMapper feedbackMapper;
@Resource
@Lazy
@@ -134,8 +139,11 @@
public void deleteTask(Long id) {
// 1. 鏍¢獙瀛樺湪涓旈潪缁堟��
validateTaskNotFinished(id);
-
- // 2. 鍒犻櫎
+ // 2. 鏍¢獙浠诲姟涓嬫棤鎶ュ伐璁板綍锛岄伩鍏嶅垹闄ゅ悗鎶ュ伐杩芥函鏂摼
+ if (feedbackMapper.selectCountByTaskId(id) > 0) {
+ throw exception(PRO_TASK_HAS_FEEDBACK);
+ }
+ // 3. 鍒犻櫎
taskMapper.deleteById(id);
}
@@ -150,6 +158,11 @@
}
@Override
+ public PageResult<MesProTaskDO> getPendingFeedbackPage(MesProTaskPageReqVO pageReqVO) {
+ return taskMapper.selectPendingFeedbackPage(pageReqVO);
+ }
+
+ @Override
public List<MesProTaskDO> getTaskListByWorkOrderIds(Collection<Long> workOrderIds) {
if (CollUtil.isEmpty(workOrderIds)) {
return Collections.emptyList();
diff --git a/yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml b/yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml
index 4d71f7f..78ba8f4 100644
--- a/yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml
+++ b/yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml
@@ -11,32 +11,36 @@
<!--
鍚戝墠杩芥函锛氫粠鏌愪釜鍘熸潗鏂欐壒娆″嚭鍙戯紝鏌ユ壘瀹冭鍝簺宸ュ崟娑堣�楀悗锛屼骇鍑轰簡鍝簺鎴愬搧鎵规
- SQL 璺緞锛氭秷鑰楁槑缁�(icd) 鈫� 娑堣�楀崟(ic) 鈫� 鎶ュ伐璁板綍(pf) 鈫� 鐢熶骇鍏ュ簱鍗�(pp) 鈫� 鍏ュ簱琛�(ppl)
+ SQL 璺緞锛氬簱瀛樹簨鍔�(娑堣�� OUT) 鈫� 娑堣�楀崟 鈫� 鎶ュ伐璁板綍 鈫� 搴撳瓨浜嬪姟(浜у嚭 IN) 鈫� 鎵规
-->
<select id="selectListByForward" parameterType="String" resultMap="BatchTraceResult">
SELECT DISTINCT pf.work_order_id, pf.item_id, ppl.batch_id, ppl.batch_code
- FROM mes_wm_item_consume_detail icd
- LEFT JOIN mes_wm_item_consume ic ON icd.consume_id = ic.id AND ic.deleted = 0
+ FROM mes_wm_transaction tx_consume
+ LEFT JOIN mes_wm_item_consume ic ON tx_consume.biz_id = ic.id AND tx_consume.biz_type = 123 AND ic.deleted = 0
LEFT JOIN mes_pro_feedback pf ON pf.id = ic.feedback_id AND pf.deleted = 0
- LEFT JOIN mes_wm_product_produce pp ON pp.work_order_id = ic.work_order_id AND pp.deleted = 0
- LEFT JOIN mes_wm_product_produce_line ppl ON pp.id = ppl.produce_id AND ppl.deleted = 0
- WHERE icd.batch_code = #{batchCode}
- AND icd.deleted = 0
+ LEFT JOIN mes_wm_transaction tx_produce ON tx_produce.biz_id = pf.id AND tx_produce.biz_type = 124 AND tx_produce.deleted = 0
+ LEFT JOIN mes_wm_product_produce pp ON tx_produce.biz_id = pp.id AND tx_produce.biz_type = 124 AND pp.deleted = 0
+ WHERE tx_consume.batch_code = #{batchCode}
+ AND tx_consume.biz_type = 123
+ AND tx_consume.deleted = 0
+ AND tx_produce.batch_id IS NOT NULL
</select>
<!--
鍚戝悗杩芥函锛氫粠鏌愪釜鎴愬搧鎵规鍑哄彂锛屾煡鎵剧敓浜у畠鏃舵秷鑰椾簡鍝簺鍘熸潗鏂欐壒娆�
- SQL 璺緞锛氬叆搴撴槑缁�(ppd) 鈫� 鍏ュ簱鍗�(pp) 鈫� 娑堣�楀崟(ic) 鈫� 娑堣�楁槑缁�(icd)
+ SQL 璺緞锛氬簱瀛樹簨鍔�(浜у嚭 IN) 鈫� 浜у嚭鍗� 鈫� 鎶ュ伐璁板綍 鈫� 搴撳瓨浜嬪姟(娑堣�� OUT) 鈫� 鎵规
-->
<select id="selectListByBackward" parameterType="String" resultMap="BatchTraceResult">
SELECT DISTINCT ic.work_order_id, icd.item_id, icd.batch_id, icd.batch_code
- FROM mes_wm_product_produce_detail ppd
- LEFT JOIN mes_wm_product_produce pp ON ppd.produce_id = pp.id AND pp.deleted = 0
- LEFT JOIN mes_wm_item_consume ic ON pp.work_order_id = ic.work_order_id AND ic.deleted = 0
- LEFT JOIN mes_wm_item_consume_detail icd ON ic.id = icd.consume_id AND icd.deleted = 0
- WHERE ppd.batch_code = #{batchCode}
- AND ppd.deleted = 0
- AND icd.batch_code IS NOT NULL
+ FROM mes_wm_transaction tx_produce
+ LEFT JOIN mes_wm_product_produce pp ON tx_produce.biz_id = pp.id AND tx_produce.biz_type = 124 AND pp.deleted = 0
+ LEFT JOIN mes_pro_feedback pf ON pf.id = pp.feedback_id AND pf.deleted = 0
+ LEFT JOIN mes_wm_transaction tx_consume ON tx_consume.biz_id = pf.id AND tx_consume.biz_type = 123 AND tx_consume.deleted = 0
+ LEFT JOIN mes_wm_item_consume ic ON tx_consume.biz_id = ic.id AND tx_consume.biz_type = 123 AND ic.deleted = 0
+ WHERE tx_produce.batch_code = #{batchCode}
+ AND tx_produce.biz_type = 124
+ AND tx_produce.deleted = 0
+ AND tx_consume.batch_id IS NOT NULL
</select>
</mapper>
--
Gitblit v1.9.3