From f350465db70d6f9db9494483ee0cec7793a9007b Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 19 八月 2026 17:47:57 +0800
Subject: [PATCH] feat(approve): 实现审批流程或签会签功能并优化数据库配置
---
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java | 22
src/main/resources/mapper/production/ProductWorkOrderMapper.xml | 9
docs/stock_inventory_model_query.md | 68 +
.gitignore | 1
src/main/resources/mapper/production/ProductProcessMapper.xml | 2
src/main/resources/mapper/production/SalesLedgerProductionAccountingMapper.xml | 10
src/main/java/com/ruoyi/approve/service/IApproveProcessService.java | 3
docs/work_order_batch_report_fix.md | 55
sql/seal_application_add_approve_process.sql | 16
src/main/java/com/ruoyi/production/service/ProductionProductMainService.java | 10
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java | 10
src/main/resources/mapper/production/ProductionProductOutputMapper.xml | 2
src/main/java/com/ruoyi/approve/controller/ApproveProcessController.java | 10
src/main/resources/application-dev.yml | 6
src/main/java/com/ruoyi/production/dto/ProductWorkOrderDto.java | 14
sql/quality_inspect_add_work_order_id.sql | 5
src/main/java/com/ruoyi/approve/pojo/ApproveNode.java | 5
src/main/java/com/ruoyi/approve/pojo/ApproveProcessConfigNode.java | 6
src/main/java/com/ruoyi/production/mapper/ProductionReportPersonMapper.java | 9
src/main/java/com/ruoyi/project/common/CommonController.java | 36
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java | 348 ++++-
src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java | 96 +
src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java | 15
docs/auto_quality_inspect_on_dispatch.md | 76 +
docs/production_work_order_add_qty.md | 94 +
src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java | 9
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java | 271 ++++
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessConfigNodeServiceImpl.java | 9
src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java | 2
src/main/java/com/ruoyi/quality/pojo/QualityInspect.java | 5
src/main/java/com/ruoyi/production/service/impl/ProductProcessRouteItemServiceImpl.java | 6
src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java | 14
src/main/resources/mapper/quality/QualityInspectMapper.xml | 4
docs/seal_approval_unified_review.md | 177 +++
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java | 482 ++++++--
src/main/java/com/ruoyi/collaborativeApproval/pojo/SealApplicationManagement.java | 20
src/main/java/com/ruoyi/production/service/impl/ProductWorkOrderServiceImpl.java | 48
doc/不合格管理模块逻辑分析.md | 486 ++++++++
doc/inspectiontask_module_logic.md | 246 ++++
src/main/resources/mapper/production/ProductionProductMainMapper.xml | 5
docs/approve_or_and_mode.md | 223 +++
docs/production_report_person_detail.md | 181 +++
docs/sales_ledger_auto_invoice_receipt.md | 109 +
sql/production_report_person_create.sql | 16
src/main/java/com/ruoyi/production/service/impl/ProcessRouteServiceImpl.java | 4
src/main/java/com/ruoyi/approve/service/IApproveNodeService.java | 6
src/main/java/com/ruoyi/production/pojo/ProductionReportPerson.java | 58 +
47 files changed, 2,982 insertions(+), 327 deletions(-)
diff --git a/.gitignore b/.gitignore
index b60b03f..12df0c1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@
### STS ###
.apt_generated
+.claude
.classpath
.factorypath
.project
diff --git a/doc/inspectiontask_module_logic.md b/doc/inspectiontask_module_logic.md
new file mode 100644
index 0000000..2d2eed0
--- /dev/null
+++ b/doc/inspectiontask_module_logic.md
@@ -0,0 +1,246 @@
+# inspectiontask 妯″潡閫昏緫璇存槑
+
+## 1. 妯″潡瀹氫綅
+
+`inspectiontask` 鏄竴涓洿缁曗�滃畾鏃剁敓鎴愬贰妫�浠诲姟銆佷汉宸ュ綍鍏ュ贰妫�缁撴灉銆佹壂鐮佽褰曠暀鐥曗�濈殑涓氬姟妯″潡銆傚畠鎶婂贰妫�璁″垝銆佸贰妫�鍗曘�佷簩缁寸爜銆佹壂鐮佽褰曞洓绫昏兘鍔涗覆鎴愪竴鏉¢棴鐜��
+
+鏍稿績鐩爣鏈変笁涓細
+
+1. 缁存姢宸℃璁″垝锛屽苟閫氳繃 Quartz 瀹氭椂瑙﹀彂銆�
+2. 鐢熸垚宸℃鍗曪紝鏀寔浜哄伐琛ュ綍銆佺紪杈戙�佸鍑恒�佸垹闄ゃ��
+3. 璁板綍浜岀淮鐮佷笌鎵爜琛屼负锛屼綔涓哄贰妫�鐜板満鐨勮緟鍔╁彴璐︺��
+
+## 2. 妯″潡鎷嗗垎
+
+### 2.1 瀹氭椂宸℃浠诲姟
+
+瀵瑰簲绫伙細
+
+- `TimingTaskController`
+- `TimingTaskService`
+- `TimingTaskServiceImpl`
+- `TimingTaskScheduler`
+- `TimingTaskJob`
+- `QuartzConfig`
+
+鑱岃矗锛�
+
+- 缁存姢宸℃璁″垝銆�
+- 璁$畻棣栨鎵ц鏃堕棿鍜屼笅娆℃墽琛屾椂闂淬��
+- 鎶婂贰妫�璁″垝娉ㄥ唽鍒� Quartz銆�
+- 鍒扮偣鍚庤嚜鍔ㄧ敓鎴愬贰妫�浠诲姟銆�
+
+### 2.2 宸℃浠诲姟璁板綍
+
+瀵瑰簲绫伙細
+
+- `InspectionTaskController`
+- `InspectionTaskService`
+- `InspectionTaskServiceImpl`
+
+鑱岃矗锛�
+
+- 鏌ヨ宸℃浠诲姟鍒楄〃銆�
+- 鏌ョ湅鏌愪釜瀹氭椂浠诲姟涓嬬殑宸℃璁板綍銆�
+- 鎵归噺鏂板鎴栫紪杈戝贰妫�浠诲姟銆�
+- 鍒犻櫎宸℃浠诲姟鍙婂叾鍏宠仈闄勪欢銆�
+
+### 2.3 浜岀淮鐮佺鐞�
+
+瀵瑰簲绫伙細
+
+- `QrCodeController`
+- `QrCodeService`
+- `QrCodeServiceImpl`
+
+鑱岃矗锛�
+
+- 缁存姢浜岀淮鐮佸熀纭�淇℃伅銆�
+- 鎻愪緵鍒楄〃銆佸鏀广�佸垹闄よ兘鍔涖��
+
+### 2.4 鎵爜璁板綍绠$悊
+
+瀵瑰簲绫伙細
+
+- `QrCodeScanRecordController`
+- `QrCodeScanRecordService`
+- `QrCodeScanRecordServiceImpl`
+
+鑱岃矗锛�
+
+- 璁板綍浜岀淮鐮佹壂鐮佽涓恒��
+- 鍏宠仈浜岀淮鐮佷俊鎭笌闄勪欢淇℃伅銆�
+- 鏀寔鍒楄〃銆佸鏀广�佸垹闄ゃ��
+
+## 3. 涓讳笟鍔℃祦绋�
+
+### 3.1 瀹氭椂浠诲姟鍒涘缓涓庡惎鍋�
+
+鍓嶇閫氳繃 `POST /timingTask/addOrEditTimingTask` 鍒涘缓鎴栦慨鏀瑰贰妫�璁″垝銆�
+
+鍚庣澶勭悊椤哄簭鏄細
+
+1. 澶嶅埗璇锋眰鏁版嵁鍒� `TimingTask`銆�
+2. 鏍规嵁閫夋嫨鐨勮澶囧垪琛ㄨˉ榻愪换鍔″悕绉般�佸尯鍩熴�佽澶囦覆绛変俊鎭��
+3. 璁$畻棣栨鎵ц鏃堕棿銆�
+4. 淇濆瓨鍒版暟鎹簱銆�
+5. 鍚屾 Quartz 璋冨害鐘舵�併��
+
+濡傛灉鍚庣画璋冪敤 `POST /timingTask/changeEnable`锛�
+
+1. 鍏堟洿鏂版暟鎹簱閲岀殑鍚敤鐘舵�併��
+2. 鍐嶉噸鏂版敞鍐屾垨鍒锋柊 Quartz 瑙﹀彂鍣ㄣ��
+3. 鍚敤鏃舵仮澶嶆墽琛岋紝绂佺敤鏃舵殏鍋滄墽琛屻��
+
+鍒犻櫎鏃朵細鍏堝垹闄ゆ暟鎹簱璁板綍锛屽啀浠� Quartz 涓彇娑堝搴斾换鍔°��
+
+### 3.2 瀹氭椂鐢熸垚宸℃鍗�
+
+Quartz 鍒扮偣鍚庤Е鍙� `TimingTaskJob`銆�
+
+鎵ц閫昏緫鏄細
+
+1. 閫氳繃浠诲姟 ID 璇诲彇褰撳墠宸℃璁″垝銆�
+2. 濡傛灉浠诲姟宸茬鐢紝鐩存帴閫�鍑恒��
+3. 瑙f瀽璇ヤ换鍔$粦瀹氱殑璁惧鍒楄〃銆�
+4. 鎸夎澶囬�愪釜鐢熸垚宸℃鍗曘��
+5. 鎸夎澶囨暟閲忓喅瀹氱敓鎴愭鏁般��
+6. 鍐欏叆 `inspection_task`銆�
+7. 鏇存柊璇ュ畾鏃朵换鍔$殑鏈�鍚庢墽琛屾椂闂村拰涓嬫鎵ц鏃堕棿銆�
+
+杩欎竴姝ユ槸妯″潡鐨勬牳蹇冭嚜鍔ㄥ寲鍏ュ彛銆�
+
+### 3.3 宸℃鍗曚汉宸ュ綍鍏ヤ笌寮傚父鑱斿姩
+
+鍓嶇閫氳繃 `POST /inspectionTask/addOrEditInspectionTask` 鎵归噺鎻愪氦宸℃鍗曘��
+
+鍚庣閫昏緫鏄細
+
+1. 閫愭潯淇濆瓨鎴栨洿鏂板贰妫�鍗曘��
+2. 鑻ュ贰妫�缁撴灉涓哄紓甯革紝鍒欐寜鍖哄煙鏀堕泦寮傚父浠诲姟銆�
+3. 鍚屼竴鎵规彁浜ら噷锛屽悓涓�鍖哄煙鍙繚鐣欎竴鏉″紓甯歌褰曠敤浜庡悗缁敓鎴愮淮淇崟銆�
+4. 鍦ㄥ綋澶╄寖鍥村唴锛屽鏋滆鍖哄煙宸茬粡瀛樺湪寰呯淮淇崟锛屽垯璺宠繃锛岄伩鍏嶉噸澶嶇敓鎴愩��
+5. 濡傛灉涓嶅瓨鍦紝鍒欒嚜鍔ㄥ垱寤轰竴鏉$淮淇崟銆�
+
+鍥犳锛岃繖涓ā鍧椾笉浠呬繚瀛樺贰妫�缁撴灉锛岃繕鎵挎媴鈥滃贰妫�寮傚父 -> 鑷姩鐢熸垚缁翠慨浠诲姟鈥濈殑鑱斿姩鑱岃矗銆�
+
+### 3.4 宸℃鍗曞垪琛ㄥ睍绀�
+
+宸℃鍗曞垪琛ㄦ帴鍙d細鍋氫竴娆♀�滄寜瀹氭椂浠诲姟鑱氬悎鈥濈殑鏁寸悊锛岃�屼笉鏄畝鍗曡繑鍥炴暟鎹簱鍘熷琛屻��
+
+鑱氬悎閫昏緫澶ц嚧鏄細
+
+1. 鎸夊畾鏃朵换鍔� ID 褰掔粍銆�
+2. 鍚屼竴缁勯噷鍙繑鍥炰竴鏉′唬琛ㄨ褰曘��
+3. 鍚堝苟浠诲姟鍚嶇О銆佸贰妫�浜恒�佸娉ㄣ�佸湴鐐广�侀鐜囩瓑灞曠ず淇℃伅銆�
+4. 鍚堝苟璇ョ粍涓嬬殑闄勪欢鍒楄〃銆�
+5. 鎸夊畾鏃朵换鍔″拰鍒涘缓鏃堕棿鎺掑簭鍚庡垎椤佃繑鍥炪��
+
+鎵�浠ュ垪琛ㄩ〉鐪嬪埌鐨勬槸鈥滀笟鍔¤瑙掔殑宸℃浠诲姟鍗$墖鈥濓紝涓嶆槸閫愭潯搴曞眰璁板綍銆�
+
+### 3.5 宸℃璁板綍鎸夊畾鏃朵换鍔℃煡璇�
+
+`GET /timingTask/recordList/{timingId}` 浼氭煡璇㈡煇涓畾鏃朵换鍔″湪褰撳ぉ鐢熸垚鐨勫贰妫�璁板綍銆�
+
+杩欓噷鐨勮寖鍥撮檺瀹氫负褰撳ぉ锛屾槸涓轰簡璁╅〉闈㈡煡鐪嬧�滀粖鏃ユ墽琛屾儏鍐碘�濇洿鐩存帴銆�
+
+### 3.6 浜岀淮鐮佷笌鎵爜璁板綍
+
+浜岀淮鐮佹ā鍧楀彧璐熻矗浜岀淮鐮佹湰韬殑澧炲垹鏀规煡銆�
+
+鎵爜璁板綍妯″潡璐熻矗鎶婃壂鐮佽涓恒�佹壂鐮佷汉銆佷簩缁寸爜淇℃伅銆侀檮浠朵俊鎭覆璧锋潵锛�
+
+1. 鍏堝垎椤垫煡鎵爜璁板綍銆�
+2. 鍐嶆壒閲忔煡鍏宠仈浜岀淮鐮併��
+3. 鍐嶆壒閲忔煡鍏宠仈闄勪欢銆�
+4. 鍐嶆煡闄勪欢瀵瑰簲鐨勬枃浠跺厓鏁版嵁銆�
+5. 鏈�鍚庣粍瑁呮垚鍓嶇鍙洿鎺ュ睍绀虹殑 DTO銆�
+
+瀹冪殑瑙掕壊鏇村儚鈥滃贰妫�鐜板満鐨勮緟鍔╃暀鐥曟ā鍧椻�濄��
+
+## 4. 璋冨害涓庢椂闂磋绠�
+
+### 4.1 Quartz 鎺ュ叆鏂瑰紡
+
+`QuartzConfig` 鎻愪緵浜� `SchedulerFactoryBean` 鍜� `Scheduler`锛屽苟閫氳繃鑷畾涔� JobFactory 鏀寔 Job 鍐呴儴鑷姩娉ㄥ叆 Spring 渚濊禆銆�
+
+`TimingTaskScheduler` 璐熻矗锛�
+
+- 鍒涘缓 JobDetail銆�
+- 鍒涘缓 Trigger銆�
+- 閲嶆柊璋冨害銆�
+- 鏆傚仠銆佹仮澶嶃�佸垹闄や换鍔°��
+
+### 4.2 鏀寔鐨勯鐜囩被鍨�
+
+浠g爜閲屾敮鎸佺殑棰戠巼绫诲瀷鍖呮嫭锛�
+
+- `DAILY`
+- `WEEKLY`
+- `MONTHLY`
+- `QUARTERLY`
+- `YEARLY`
+
+鏃堕棿琛ㄨ揪寮忕敱鏈嶅姟灞傚拰璋冨害灞傚叡鍚岃В鏋愶紝鏈�缁堣浆鎹负 Quartz cron銆�
+
+### 4.3 闇�瑕佹敞鎰忕殑瀹炵幇缁嗚妭
+
+1. 鏈嶅姟灞傚拰 Job 灞傞兘鍚勮嚜瀹炵幇浜嗕竴濂椻�滀笅娆℃墽琛屾椂闂粹�濊绠楅�昏緫銆�
+2. Job 灞傛墽琛屽畬鍚庯紝浼氱洿鎺ユ洿鏂� `timing_task` 鐨勬渶鍚庢墽琛屾椂闂村拰涓嬫鎵ц鏃堕棿銆�
+3. 鏈嶅姟灞傚湪鏂板浠诲姟鏃朵細鍏堣绠楅娆℃墽琛屾椂闂达紝浣嗗搴︾被鍨嬪湪褰撳墠瀹炵幇閲岄娆℃椂闂磋绠楁柟娉曚负绌猴紝灞炰簬涓�涓渶瑕佹敞鎰忕殑瀹炵幇缂哄彛銆�
+
+## 5. 鏁版嵁涓庡叧鑱斿叧绯�
+
+杩欎釜妯″潡鐨勪富瑕佸叧鑱斿叧绯诲彲浠ョ悊瑙d负锛�
+
+- `TimingTask` 鏄贰妫�璁″垝銆�
+- `InspectionTask` 鏄贰妫�鎵ц璁板綍銆�
+- `QrCode` 鏄幇鍦轰簩缁寸爜鍙拌处銆�
+- `QrCodeScanRecord` 鏄壂鐮佽涓鸿褰曘��
+- 宸℃寮傚父浼氳仈鍔ㄧ敓鎴愮淮淇崟銆�
+- 宸℃鍗曞拰鎵爜璁板綍閮戒細鍏宠仈闄勪欢璧勬簮銆�
+
+## 6. 瀵瑰墠绔殑鎰忎箟
+
+鍓嶇瀵硅繖涓ā鍧楃殑浣跨敤鏂瑰紡鍙互姒傛嫭涓猴細
+
+1. 鍏堢淮鎶ゅ贰妫�璁″垝锛屽啀鐢辩郴缁熻嚜鍔ㄧ敓鎴愬贰妫�鍗曘��
+2. 宸℃鍗曟敮鎸佷汉宸ヨˉ褰曘�佺紪杈戙�佸鍑恒�佸垹闄ゃ��
+3. 瀹氭椂浠诲姟鏀寔鍚敤銆佺鐢ㄥ拰閲嶆柊璋冨害銆�
+4. 宸℃寮傚父鍚庯紝绯荤粺浼氳嚜鍔ㄨˉ涓�鏉$淮淇崟锛屽墠绔渶瑕佽兘鍙婃椂鍒锋柊鐩稿叧椤甸潰銆�
+5. 浜岀淮鐮佸拰鎵爜璁板綍灞炰簬鐙珛鐨勮緟鍔╃鐞嗛〉闈€��
+
+## 7. 鎺ュ彛鎬昏
+
+### 宸℃浠诲姟
+
+- `GET /inspectionTask/list`
+- `POST /inspectionTask/export`
+- `POST /inspectionTask/addOrEditInspectionTask`
+- `DELETE /inspectionTask/delInspectionTask`
+
+### 瀹氭椂浠诲姟
+
+- `GET /timingTask/list`
+- `GET /timingTask/recordList/{timingId}`
+- `POST /timingTask/export`
+- `POST /timingTask/addOrEditTimingTask`
+- `POST /timingTask/changeEnable`
+- `DELETE /timingTask/delTimingTask`
+
+### 浜岀淮鐮�
+
+- `GET /qrCode/list`
+- `POST /qrCode/addOrEditQrCode`
+- `DELETE /qrCode/delQrCode`
+
+### 鎵爜璁板綍
+
+- `GET /qrCodeScanRecord/list`
+- `POST /qrCodeScanRecord/addOrEditQrCodeRecord`
+- `DELETE /qrCodeScanRecord/delSalesRecord`
+
+## 8. 缁撹
+
+`inspectiontask` 妯″潡鏈川涓婃槸涓�鏉♀�滆鍒� -> 璋冨害 -> 鐢熸垚宸℃鍗� -> 褰曞叆缁撴灉 -> 寮傚父鑱斿姩缁翠慨 -> 鐣欑棔鏌ヨ鈥濈殑涓氬姟閾捐矾銆�
+濡傛灉鍚庣画瑕佺户缁媶鍓嶅悗绔仈璋冿紝浼樺厛鍏虫敞涓夊潡锛氬畾鏃朵换鍔$殑鏃堕棿琛ㄨ揪寮忋�佸贰妫�缁撴灉鐨勫紓甯歌仈鍔ㄣ�佷互鍙婂贰妫�鍒楄〃鐨勮仛鍚堝睍绀洪�昏緫銆�
diff --git "a/doc/\344\270\215\345\220\210\346\240\274\347\256\241\347\220\206\346\250\241\345\235\227\351\200\273\350\276\221\345\210\206\346\236\220.md" "b/doc/\344\270\215\345\220\210\346\240\274\347\256\241\347\220\206\346\250\241\345\235\227\351\200\273\350\276\221\345\210\206\346\236\220.md"
new file mode 100644
index 0000000..bac79c9
--- /dev/null
+++ "b/doc/\344\270\215\345\220\210\346\240\274\347\256\241\347\220\206\346\250\241\345\235\227\351\200\273\350\276\221\345\210\206\346\236\220.md"
@@ -0,0 +1,486 @@
+# 涓嶅悎鏍肩鐞嗘ā鍧楅�昏緫鍒嗘瀽
+
+## 1. 妯″潡瀹氫綅
+
+`quality` 鍖呬笅鐨�"涓嶅悎鏍肩鐞�"鏄竴涓嫭绔嬬殑涓氬姟闂幆锛�**妫�楠�(璐ㄦ) 鈫� 涓嶅悎鏍煎搧鐧昏 鈫� 涓嶅悎鏍煎搧澶勭悊 鈫� 涓嶅悎鏍煎搧澶勭悊鍗�(瀹℃壒娴�)**銆傛澶栬繕鍖呭惈妫�娴嬫爣鍑嗙淮鎶ゃ�佹楠屾姤鍛婄敓鎴愩�佽川閲忔姤琛ㄧ粺璁′笁涓厤濂楄兘鍔涖��
+
+鏍稿績鐩爣锛�
+1. 瑕嗙洊鍘熸潗鏂欐楠屻�佽繃绋嬫楠屻�佸嚭鍘傛楠屼笁绉嶈川妫�鍦烘櫙銆�
+2. 妫�楠屼笉鍚堟牸鏃惰嚜鍔ㄧ櫥璁颁笉鍚堟牸鍝侊紝鏀寔杩斿伐/杩斾慨/鎶ュ簾/璁╂鏀捐鍥涚澶勭悊鏂瑰紡銆�
+3. 涓嶅悎鏍煎搧澶勭悊鍗曡蛋瀹℃壒娴佺▼锛堣崏绋库啋寰呭鎵光啋瀹℃壒涓啋宸插畬鎴�/宸查┏鍥烇級銆�
+4. 鎻愪緵鍚堟牸鐜囥�佹湀搴﹁秼鍔裤�佹娴嬫寚鏍囨帓鍚嶇瓑缁熻鎶ヨ〃銆�
+
+---
+
+## 2. 鏁版嵁搴撹〃缁撴瀯
+
+### 2.1 quality_inspect锛堟楠岃褰曚富琛級
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | BIGINT | 涓婚敭锛岃嚜澧� |
+| inspect_type | INT | **蹇呭~**銆�0=鍘熸潗鏂欐楠岋紝1=杩囩▼妫�楠岋紝2=鍑哄巶妫�楠� |
+| inspect_state | INT | 0=鏈彁浜�(鑽夌)锛�1=宸叉彁浜� |
+| check_time | DATE | 妫�娴嬫棩鏈� |
+| supplier | VARCHAR | 渚涘簲鍟嗗悕绉帮紙鍘熸潗鏂欐楠屼娇鐢級 |
+| customer | VARCHAR | 瀹㈡埛鍚嶇О锛堣繃绋�/鍑哄巶妫�楠屼娇鐢級 |
+| process | VARCHAR | 宸ュ簭/閮ㄤ欢绫诲瀷锛堣繃绋嬫楠屼娇鐢級 |
+| process_type | INT | 宸ュ簭绫诲瀷锛岄渶鍖归厤鏁版嵁瀛楀吀 `product_process_type` |
+| check_name | VARCHAR | 妫�楠屽憳 |
+| product_id | BIGINT | **蹇呭~**銆傚叧鑱斾骇鍝両D |
+| product_name | VARCHAR | 浜у搧鍚嶇О |
+| model | VARCHAR | 瑙勬牸鍨嬪彿 |
+| unit | VARCHAR | 鍗曚綅 |
+| quantity | DECIMAL | 鏁伴噺 |
+| check_company | VARCHAR | 妫�娴嬪崟浣� |
+| check_result | VARCHAR | 妫�娴嬬粨鏋滐紙"鍚堟牸"/"涓嶅悎鏍�"锛� |
+| defective_phenomena | VARCHAR | 涓嶅悎鏍肩幇璞℃弿杩� |
+| product_main_id | BIGINT | 鍏宠仈鎶ュ伐ID锛堣繃绋�/鍑哄巶妫�楠屼娇鐢級 |
+| product_model_id | BIGINT | 鍏宠仈浜у搧鍨嬪彿ID |
+| purchase_ledger_id | BIGINT | 鍏宠仈閲囪喘鍙拌处ID锛堝師鏉愭枡妫�楠屼娇鐢級 |
+| test_standard_id | BIGINT | 鍏宠仈妫�娴嬫爣鍑嗕富琛↖D |
+| create_time | DATETIME | 鍒涘缓鏃堕棿锛堣嚜鍔ㄥ~鍏咃級 |
+| create_user | INT | 鍒涘缓鐢ㄦ埛锛堣嚜鍔ㄥ~鍏咃級 |
+| update_time | DATETIME | 淇敼鏃堕棿锛堣嚜鍔ㄥ~鍏咃級 |
+| update_user | INT | 淇敼鐢ㄦ埛锛堣嚜鍔ㄥ~鍏咃級 |
+| tenant_id | BIGINT | 绉熸埛ID锛堣嚜鍔ㄥ~鍏咃級 |
+| dept_id | BIGINT | 閮ㄩ棬ID锛堣嚜鍔ㄥ~鍏咃級 |
+
+### 2.2 quality_inspect_param锛堟楠屽弬鏁版槑缁嗭級
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | BIGINT | 涓婚敭锛岃嚜澧� |
+| parameter_item | VARCHAR | 妫�娴嬫寚鏍囧悕绉� |
+| unit | VARCHAR | 鍗曚綅 |
+| standard_value | VARCHAR | 鏍囧噯鍊� |
+| control_value | VARCHAR | 鍐呮帶鍊� |
+| test_value | VARCHAR | 妫�楠屽�� |
+| inspect_id | BIGINT | **蹇呭~**銆傚叧鑱� quality_inspect.id |
+| create_time / update_time / create_user / update_user / tenant_id / dept_id | 鈥� | 閫氱敤瀹¤瀛楁 |
+
+> 涓�鏉℃楠岃褰� (quality_inspect) 瀵瑰鏉℃楠屽弬鏁� (quality_inspect_param)銆�
+
+### 2.3 quality_inspect_file锛堟楠岄檮浠讹級
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | BIGINT | 涓婚敭锛岃嚜澧� |
+| name | VARCHAR | 鏂囦欢鍚嶇О |
+| url | VARCHAR | 鏂囦欢璺緞 |
+| file_size | INT | 鏂囦欢澶у皬 |
+| inspect_id | BIGINT | **蹇呭~**銆傚叧鑱� quality_inspect.id |
+| create_time / update_time / create_user / update_user / tenant_id / dept_id | 鈥� | 閫氱敤瀹¤瀛楁 |
+
+### 2.4 quality_unqualified锛堜笉鍚堟牸鍝佽褰曪級
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | BIGINT | 涓婚敭锛岃嚜澧� |
+| inspect_type | INT | **蹇呭~**銆�0=鍘熸潗鏂欐楠岋紝1=杩囩▼妫�楠岋紝2=鍑哄巶妫�楠� |
+| inspect_state | INT | **蹇呭~**銆�0=寰呭鐞嗭紝1=宸插鐞� |
+| check_time | DATE | 妫�娴嬫棩鏈� |
+| check_name | VARCHAR | 妫�楠屽憳 |
+| product_id | BIGINT | 鍏宠仈浜у搧ID |
+| product_name | VARCHAR | 浜у搧鍚嶇О |
+| model | VARCHAR | 瑙勬牸鍨嬪彿锛堝瓨鍌ㄧ殑鍙兘鏄� product_model.id 鎴栫洿鎺ユ枃鏈級 |
+| unit | VARCHAR | 鍗曚綅 |
+| quantity | DECIMAL | 鏁伴噺 |
+| defective_phenomena | VARCHAR | 涓嶅悎鏍肩幇璞� |
+| deal_result | VARCHAR | 澶勭悊缁撴灉锛�**"杩斾慨"/"杩斿伐"/"鎶ュ簾"/"璁╂鏀捐"** |
+| deal_name | VARCHAR | 澶勭悊浜� |
+| deal_time | DATE | 澶勭悊鏃ユ湡 |
+| reason_analysis | VARCHAR | 鍘熷洜鍒嗘瀽 |
+| preventive_corrective | VARCHAR | 棰勯槻涓庣籂姝f帾鏂� |
+| loss_working | VARCHAR | 宸ユ椂鎹熷け |
+| loss_material | VARCHAR | 鏉愭枡璐规崯澶� |
+| inspect_id | BIGINT | 鍏宠仈 quality_inspect.id锛堝鏋滄槸妫�楠屼笉鍚堟牸鑷姩鐢熸垚锛� |
+| create_time / update_time / create_user / update_user / tenant_id / dept_id | 鈥� | 閫氱敤瀹¤瀛楁 |
+| temp_file_ids (铏氭嫙) | List\<String\> | 涓存椂鏂囦欢ID鍒楄〃锛堜粎鐢ㄤ簬鎺ユ敹鍓嶇鍙傛暟锛� |
+| common_file_list (铏氭嫙) | List\<CommonFile\> | 姝e紡闄勪欢鍒楄〃锛堟煡璇㈡椂鍥炲~锛� |
+| method (铏氭嫙) | Boolean | 鏍囪瘑 model 瀛楁鏄惁瀛樺偍鐨勬槸 product_model.id锛堢敤浜庡垪琛ㄥ睍绀烘椂瑙f瀽鍨嬪彿锛� |
+
+### 2.5 quality_unqualified_order锛堝啗姝屽寲宸ヤ笉鍚堟牸鍝佸鐞嗗崟锛�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | BIGINT | 涓婚敭锛岃嚜澧� |
+| order_no | VARCHAR | 澶勭悊鍗曠紪鍙凤紝鍓嶇紑 "BHG"+ 鏃ユ湡 + 搴忓彿锛岃嚜鍔ㄧ敓鎴� |
+| unqualified_id | BIGINT | 鍏宠仈 quality_unqualified.id |
+| project_name | VARCHAR | 椤圭洰鍚嶇О |
+| project_no | VARCHAR | 椤圭洰缂栧彿 |
+| equipment_id | BIGINT | 鍏宠仈璁惧ID |
+| equipment_name | VARCHAR | 璁惧鍚嶇О |
+| equipment_drawing_no | VARCHAR | 璁惧鍥惧彿 |
+| material_name | VARCHAR | 鐗╂枡/閮ㄤ欢鍚嶇О |
+| product_model_id | BIGINT | 鍏宠仈浜у搧鍨嬪彿ID |
+| material_drawing_no | VARCHAR | 鐗╂枡鍥惧彿 |
+| specification_model | VARCHAR | 鍨嬪彿瑙勬牸 |
+| material_quality | VARCHAR | 鏉愯川 |
+| quantity | DECIMAL | 鎬绘暟閲� |
+| unqualified_quantity | DECIMAL | 涓嶅悎鏍兼暟閲� |
+| unqualified_process | TINYINT | 涓嶅悎鏍煎伐搴忥細1=鏉ユ枡锛�2=鍒剁▼锛�3=鎴愬搧 |
+| supplier_name | VARCHAR | 渚涘簲鍟嗗悕绉� |
+| inspector_name | VARCHAR | 妫�楠屽憳 |
+| inspect_date | DATE | 妫�楠屾棩鏈� |
+| responsible_person | VARCHAR | 璐d换浜� |
+| responsible_dept | VARCHAR | 璐d换閮ㄩ棬 |
+| problem_description | VARCHAR | 闂鎻忚堪 |
+| reason_analysis | VARCHAR | 鍘熷洜鍒嗘瀽鍙婂缓璁� |
+| correction_action | VARCHAR | 绾犳鎺柦 |
+| disposal_method | TINYINT | 澶勭疆鏂瑰紡锛�1=璁╂鎺ユ敹锛�2=鍘傚唴缁翠慨锛�3=杩斿巶缁翠慨锛�4=鎹㈣揣锛�5=閫�璐э紝6=鎶ュ簾 |
+| repair_evaluation | VARCHAR | 鍘傚唴/杩斿巶缁翠慨璇勪及 |
+| preventive_action | VARCHAR | 棰勯槻鎺柦 |
+| status | TINYINT | 鐘舵�侊細0=鑽夌锛�1=寰呭鎵癸紝2=瀹℃壒涓紝3=宸插畬鎴愶紝4=宸查┏鍥� |
+| remark | VARCHAR | 澶囨敞 |
+| create_by / update_by / create_time / update_time / tenant_id / dept_id | 鈥� | 閫氱敤瀹¤瀛楁 |
+| deleted | TINYINT | 閫昏緫鍒犻櫎锛�0=鍚︼紝1=鏄� |
+
+### 2.6 quality_test_standard锛堟娴嬫爣鍑嗕富琛級
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | BIGINT | 涓婚敭锛岃嚜澧� |
+| standard_no | VARCHAR | 鏍囧噯缂栧彿 |
+| standard_name | VARCHAR | 鏍囧噯鍚嶇О |
+| state | VARCHAR | 鐘舵�� |
+| remark | VARCHAR | 澶囨敞 |
+| inspect_type | INT | 绫诲埆锛�0=鍘熸潗鏂欐楠岋紝1=杩囩▼妫�楠岋紝2=鍑哄巶妫�楠� |
+| process_id | INT | 宸ュ簭ID |
+| process_type | INT | 宸ュ簭绫诲瀷 |
+| create_time / update_time / create_user / update_user / tenant_id / dept_id | 鈥� | 閫氱敤瀹¤瀛楁 |
+
+### 2.7 quality_test_standard_param锛堟娴嬫爣鍑嗗弬鏁帮級
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | BIGINT | 涓婚敭锛岃嚜澧� |
+| parameter_item | VARCHAR | 鍙傛暟椤癸紙鎸囨爣鍚嶇О锛� |
+| unit | VARCHAR | 鍗曚綅 |
+| standard_value | VARCHAR | 鏍囧噯鍊� |
+| control_value | VARCHAR | 鍐呮帶鍊� |
+| default_value | VARCHAR | 榛樿鍊� |
+| test_standard_id | BIGINT | 鍏宠仈 quality_test_standard.id |
+| create_time / update_time / create_user / update_user / tenant_id / dept_id | 鈥� | 閫氱敤瀹¤瀛楁 |
+
+### 2.8 quality_test_standard_binding锛堟娴嬫爣鍑�-浜у搧鍏宠仈锛�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | INT | 涓婚敭锛岃嚜澧� |
+| product_id | BIGINT | 浜у搧ID |
+| test_standard_id | INT | 鍏宠仈 quality_test_standard.id |
+| create_time / update_time / create_user / update_user / tenant_id / dept_id | 鈥� | 閫氱敤瀹¤瀛楁 |
+
+> 澶氬澶氬叧绯伙細涓�涓骇鍝佸彲缁戝畾澶氫釜妫�娴嬫爣鍑嗭紝涓�涓娴嬫爣鍑嗗彲鐢ㄤ簬澶氫釜浜у搧銆�
+
+---
+
+## 3. 鏍稿績涓氬姟娴佺▼
+
+### 3.1 璐ㄦ瀹屾暣閾捐矾
+
+```
+妫�娴嬫爣鍑嗙淮鎶� 鈫� 鍒涘缓妫�楠屽崟 鈫� 濉啓妫�楠屽弬鏁� 鈫� 鎻愪氦妫�楠�
+ 鈫�
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹粹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈫� 鈫�
+ 缁撴灉=涓嶅悎鏍� 缁撴灉=鍚堟牸
+ 鈫� 鈫�
+ 鑷姩鐢熸垚涓嶅悎鏍煎搧璁板綍 鑷姩鍚堟牸鍏ュ簱
+ (quality_unqualified) (璋冪敤 StockUtils)
+ 鈫�
+ 涓嶅悎鏍煎搧澶勭悊(deal)
+ 鈫�
+ 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ 鈫� 鈫� 鈫� 鈫�
+ 杩斾慨 杩斿伐 鎶ュ簾 璁╂鏀捐
+(杩囩▼/鍑哄巶) (鍏ㄩ儴绫诲瀷)
+ 鈫� 鈫� 鈫� 鈫�
+ 鍏嬮殕鐢熶骇 鍏嬮殕鐢熶骇 鍏ヤ笉鍚堟牸搴� 鍏ュ悎鏍煎簱
+ 璁㈠崟+璺嚎 鍚屼笂 (unqualified (qualified
+ +宸ュ崟 stock) stock)
+ 鈫�
+ 涓嶅悎鏍煎搧澶勭悊鍗�(approval flow)
+ 鑽夌 鈫� 寰呭鎵� 鈫� 瀹℃壒涓� 鈫� 宸插畬鎴�/宸查┏鍥�
+```
+
+### 3.2 妫�楠屾彁浜ら�昏緫锛圦ualityInspectServiceImpl.submit锛�
+
+1. 鏍¢獙 `checkResult` 涓嶈兘涓虹┖銆�
+2. 濡傛灉 `checkResult = "涓嶅悎鏍�"`锛�
+ - 鑷姩鍒涘缓涓�鏉� `quality_unqualified` 璁板綍锛宍inspect_state=0`锛堝緟澶勭悊锛夈��
+ - 涓嶅悎鏍肩幇璞¤嚜鍔ㄧ敓鎴愪负锛氭墍鏈夋楠屽弬鏁板悕绉版嫾鎺� + "杩欎簺鎸囨爣涓瓨鍦ㄤ笉鍚堟牸"銆�
+3. 濡傛灉 `checkResult = "鍚堟牸"`锛�
+ - 璋冪敤 `StockUtils.addStock()` 鐩存帴鍏ュ簱锛堝叆搴撶被鍨� `QUALITYINSPECT_STOCK_IN`锛夈��
+4. 灏嗘楠屽崟 `inspect_state` 鏇存柊涓� 1锛堝凡鎻愪氦锛夈��
+
+### 3.3 涓嶅悎鏍煎搧澶勭悊閫昏緫锛圦ualityUnqualifiedServiceImpl.deal锛�
+
+鏍稿績鍙傛暟锛歚dealResult` 鍐冲畾澶勭悊璺緞锛屽垎涓轰袱澶у垎鏀��
+
+#### 鍒嗘敮A锛氳繃绋嬫楠�/鍑哄巶妫�楠岋紙inspectType != 0 涓旀湁 productMainId锛�
+
+| dealResult | 澶勭悊閫昏緫 |
+|------------|----------|
+| **"杩斾慨"鎴�"杩斿伐"** | 1. 鏍规嵁 `productMainId` 鏌ュ埌鍘熷鐢熶骇璁㈠崟(ProductOrder)銆佸伐鑹鸿矾绾夸富琛�(ProductProcessRoute)銆佸伐鑹鸿矾绾垮瓙琛�(ProductProcessRouteItem) |
+| | 2. 鍏嬮殕涓�浠芥柊鐨勭敓浜ц鍗曪紙quantity=涓嶅悎鏍兼暟閲忥紝completeQuantity=0锛屾椂闂存竻绌猴級 |
+| | 3. 鍏嬮殕涓�浠芥柊鐨勫伐鑹鸿矾绾夸富琛紙鍏宠仈鏂拌鍗曪級 |
+| | 4. 閫愭潯鍏嬮殕宸ヨ壓璺嚎瀛愯〃锛屾瘡鏉″厠闅嗗悗鑷姩鐢熸垚瀵瑰簲宸ュ崟(ProductWorkOrder)锛氬伐鍗曞彿瑙勫垯 `"FG" + yyyyMMdd + 3浣嶈嚜澧炲簭鍙穈 |
+| **"鎶ュ簾"** | 璋冪敤 `StockUtils.addUnStock()` 鍏ヤ笉鍚堟牸搴擄紙绫诲瀷 `DEFECTIVE_SCRAP`锛� |
+| **"璁╂鏀捐"** | 璋冪敤 `StockUtils.addStock()` 鍏ュ悎鏍煎簱锛堢被鍨� `DEFECTIVE_PASS`锛� |
+
+#### 鍒嗘敮B锛氬師鏉愭枡妫�楠岋紙inspectType == 0锛�
+
+| dealResult | 澶勭悊閫昏緫 |
+|------------|----------|
+| **"鎶ュ簾"** | 鍏堥�氳繃 productName + model 鏌� product_model.id锛屽啀璋冪敤 `StockUtils.addUnStock()` 鍏ヤ笉鍚堟牸搴� |
+| **"璁╂鏀捐"** | 鍚屼笂鏌ユ壘 modelId锛屽啀璋冪敤 `StockUtils.addStock()` 鍏ュ悎鏍煎簱 |
+| **"杩斾慨"/"杩斿伐"** | 鍘熸潗鏂欐楠屼笉鏀寔杩斾慨/杩斿伐锛堜唬鐮佷腑鏃犳鍒嗘敮锛屼笉鎵ц浠讳綍鎿嶄綔锛� |
+
+> 鍏抽敭鍒ゆ柇閫昏緫浣嶄簬 `QualityUnqualifiedServiceImpl.java:81`锛�
+> ```java
+> if (ObjectUtils.isNotNull(qualityInspect) && qualityInspect.getInspectType() != 0)
+> ```
+> 鍙湁鍏宠仈浜嗘楠屽崟涓旀楠岀被鍨嬩笉鏄師鏉愭枡(0)鏃讹紝鎵嶈蛋杩囩▼/鍑哄巶鐨勫鐞嗗垎鏀紙鍖呭惈杩斾慨/杩斿伐閫昏緫锛夈��
+
+澶勭悊瀹屾垚鍚庡皢 `inspect_state` 缃负 1锛堝凡澶勭悊锛夈��
+
+### 3.4 涓嶅悎鏍煎搧澶勭悊鍗曟祦绋嬶紙QualityUnqualifiedOrder锛�
+
+澶勭悊鍗曟槸瀵逛笉鍚堟牸鍝佺殑姝e紡澶勭疆璁板綍锛屽寘鍚洿涓板瘜鐨勫瓧娈靛拰瀹℃壒鐘舵�佹満锛�
+
+```
+鑽夌(0) 鈫� 寰呭鎵�(1) 鈫� 瀹℃壒涓�(2) 鈫� 宸插畬鎴�(3)
+ 鈫� 鈫�
+ 鍙紪杈�/鍒犻櫎 宸查┏鍥�(4)
+```
+
+- **缂栧彿鑷姩鐢熸垚**锛氬墠缂� `"BHG"` + 褰撴棩鑷搴忓彿锛堥�氳繃 `OrderUtils.countTodayByCreateTime` 瀹炵幇锛夈��
+- **澶勭疆鏂瑰紡鏋氫妇**锛�1=璁╂鎺ユ敹锛�2=鍘傚唴缁翠慨锛�3=杩斿巶缁翠慨锛�4=鎹㈣揣锛�5=閫�璐э紝6=鎶ュ簾銆�
+- **鍒嗛〉鏌ヨ**锛氭敮鎸佹寜 status銆乸rojectName銆乷rderNo 绛涢�夈��
+- **CRUD**锛氭敮鎸佹柊澧炪�佷慨鏀广�佸垹闄わ紙閫昏緫鍒犻櫎閫氳繃 deleted 瀛楁锛夈��
+
+### 3.5 妫�楠屽崟鍒犻櫎閫昏緫锛圦ualityInspectController.del锛�
+
+1. 鏍¢獙鏄惁宸叉彁浜わ紙`inspectState==1` 涓嶅厑璁稿垹闄わ級銆�
+2. 绾ц仈鍒犻櫎鍏宠仈鐨勬楠屽弬鏁� (quality_inspect_param)銆�
+3. 绾ц仈鍒犻櫎鍏宠仈鐨勬楠岄檮浠� (quality_inspect_file)銆�
+4. 鍒犻櫎妫�楠屽崟鏈韩銆�
+
+### 3.6 涓嶅悎鏍煎搧鍒犻櫎閫昏緫锛圦ualityUnqualifiedController.del锛�
+
+1. 鏍¢獙鏄惁宸插鐞嗭紙`inspectState==1` 涓嶅厑璁稿垹闄わ級銆�
+2. 绾ц仈鍒犻櫎鍏宠仈闄勪欢锛堥�氳繃 commonFileService 鎸� businessId + type 鍒犻櫎锛夈��
+3. 鎵归噺鍒犻櫎涓嶅悎鏍煎搧璁板綍銆�
+
+### 3.7 妫�娴嬫爣鍑嗙淮鎶�
+
+- 妫�娴嬫爣鍑嗕富琛� (quality_test_standard) 缁存姢鏍囧噯缂栧彿銆佸悕绉般�侀�傜敤妫�楠岀被鍨嬨�佸伐搴忕被鍨嬨��
+- 妫�娴嬫爣鍑嗗弬鏁� (quality_test_standard_param) 缁存姢鎸囨爣銆佸崟浣嶃�佹爣鍑嗗�笺�佸唴鎺у�笺�侀粯璁ゅ�笺��
+- 妫�娴嬫爣鍑�-浜у搧缁戝畾 (quality_test_standard_binding) 瀹炵幇澶氬澶氬叧鑱斻��
+- **鍒犻櫎淇濇姢**锛氬垹闄ゆ娴嬫爣鍑嗗墠浼氭鏌ユ槸鍚﹁妫�楠屽崟寮曠敤锛屽紩鐢ㄥ垯绂佹鍒犻櫎銆�
+- **鍙傛暟澶嶅埗**锛氭敮鎸� `copyParam` 灏嗘煇涓爣鍑嗙殑鍙傛暟澶嶅埗鍒板彟涓�涓爣鍑嗐��
+- **瀹℃牳**锛氭彁渚� `qualityTestStandardAudit` 鎺ュ彛銆�
+
+---
+
+## 4. API 鎺ュ彛鎬昏
+
+### 4.1 妫�楠岃褰�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/quality/qualityInspect/add` | 鏂板妫�楠屽崟锛堝惈妫�楠屽弬鏁帮級 |
+| DELETE | `/quality/qualityInspect/del` | 鎵归噺鍒犻櫎妫�楠屽崟锛堢骇鑱斿垹闄ゅ弬鏁板拰闄勪欢锛� |
+| GET | `/quality/qualityInspect/{id}` | 鏌ョ湅璇︽儏锛堝惈妫�楠屽弬鏁帮級 |
+| POST | `/quality/qualityInspect/update` | 淇敼妫�楠屽崟锛堝厛鍒犳棫鍙傛暟鍐嶆彃鏂板弬鏁帮級 |
+| GET | `/quality/qualityInspect/listPage` | 鍒嗛〉鏌ヨ锛堟寜绫诲瀷 JOIN 涓嶅悓涓氬姟琛級 |
+| POST | `/quality/qualityInspect/export` | 瀵煎嚭 Excel锛堜笉鍚岀被鍨嬮殣钘忎笉鍚屽垪锛� |
+| POST | `/quality/qualityInspect/submit` | 鎻愪氦妫�楠岋紙鍚堟牸鍏ュ簱/涓嶅悎鏍肩櫥璁帮級 |
+| POST | `/quality/qualityInspect/down` | 涓嬭浇 Word 妫�楠屾姤鍛婏紙poi-tl 妯℃澘娓叉煋锛� |
+
+### 4.2 涓嶅悎鏍煎搧绠$悊
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/quality/qualityUnqualified/add` | 鏂板涓嶅悎鏍煎搧锛堟墜宸ュ綍鍏ワ紝鐘舵��=寰呭鐞嗭級 |
+| DELETE | `/quality/qualityUnqualified/del` | 鎵归噺鍒犻櫎锛堝凡澶勭悊涓嶅彲鍒狅級 |
+| GET | `/quality/qualityUnqualified/{id}` | 璇︽儏锛堝惈闄勪欢鍒楄〃锛� |
+| POST | `/quality/qualityUnqualified/update` | 淇敼涓嶅悎鏍煎搧 |
+| GET | `/quality/qualityUnqualified/listPage` | 鍒嗛〉鏌ヨ锛堟寜绫诲瀷銆佺姸鎬併�佷骇鍝佸悕銆佹棩鏈熺瓫閫夛級 |
+| POST | `/quality/qualityUnqualified/export` | 瀵煎嚭 Excel |
+| POST | `/quality/qualityUnqualified/deal` | **澶勭悊涓嶅悎鏍煎搧**锛堣繑淇�/杩斿伐/鎶ュ簾/璁╂鏀捐锛� |
+
+### 4.3 涓嶅悎鏍煎搧澶勭悊鍗�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | `/qualityUnqualifiedOrder/listPage` | 鍒嗛〉鏌ヨ锛堟敮鎸� status/projectName/orderNo 绛涢�夛級 |
+| POST | `/qualityUnqualifiedOrder/save` | 鏂板锛堣嚜鍔ㄧ敓鎴愮紪鍙凤級 |
+| PUT | `/qualityUnqualifiedOrder/update` | 淇敼 |
+| DELETE | `/qualityUnqualifiedOrder/delete` | 鎵归噺鍒犻櫎 |
+
+### 4.4 妫�娴嬫爣鍑�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/qualityTestStandard/add` | 鏂板妫�娴嬫爣鍑� |
+| DELETE | `/qualityTestStandard/del` | 鍒犻櫎锛堟湁寮曠敤鏃剁姝級 |
+| POST | `/qualityTestStandard/update` | 淇敼 |
+| GET | `/qualityTestStandard/listPage` | 鍒嗛〉鏌ヨ |
+| POST | `/qualityTestStandard/copyParam` | 澶嶅埗鍙傛暟鍒板彟涓�涓爣鍑� |
+| POST | `/qualityTestStandard/qualityTestStandardAudit` | 瀹℃牳妫�娴嬫爣鍑� |
+| GET | `/qualityTestStandard/getQualityTestStandardByProductId` | 鎸変骇鍝両D鑾峰彇鏍囧噯锛堥�氳繃缁戝畾琛� JOIN锛� |
+| GET | `/qualityTestStandard/getQualityTestStandardParamByTestStandardId` | 鎸夋爣鍑咺D鑾峰彇鍙傛暟鍒楄〃 |
+
+### 4.5 妫�娴嬫爣鍑嗙粦瀹�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/qualityTestStandardBinding/add` | 鏂板缁戝畾 |
+| DELETE | `/qualityTestStandardBinding/del` | 鍒犻櫎缁戝畾 |
+| GET | `/qualityTestStandardBinding/list` | 鏌ヨ缁戝畾鍒楄〃 |
+
+### 4.6 妫�娴嬫爣鍑嗗弬鏁�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/qualityTestStandardParam/add` | 鏂板鍙傛暟 |
+| DELETE | `/qualityTestStandardParam/del` | 鍒犻櫎鍙傛暟 |
+| POST | `/qualityTestStandardParam/update` | 淇敼鍙傛暟 |
+| GET | `/qualityTestStandardParam/list` | 鏌ヨ鍙傛暟鍒楄〃 |
+
+### 4.7 妫�楠屽弬鏁�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | `/quality/qualityInspectParam/{inspectId}` | 鎸夋楠孖D鏌ュ弬鏁板垪琛� |
+| POST | `/quality/qualityInspectParam/update` | 淇敼鍙傛暟 |
+| DELETE | `/quality/qualityInspectParam/del` | 鍒犻櫎鍙傛暟 |
+
+### 4.8 妫�楠岄檮浠�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/quality/qualityInspectFile/add` | 鏂板闄勪欢 |
+| DELETE | `/quality/qualityInspectFile/del` | 鍒犻櫎闄勪欢 |
+| GET | `/quality/qualityInspectFile/listPage` | 鍒嗛〉鏌ヨ |
+
+### 4.9 璐ㄩ噺鎶ヨ〃
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | `/qualityReport/getInspectStatistics` | 妫�楠岀粺璁★紙鎸夊師鏉愭枡/鍗婃垚鍝�/鎴愬搧鍒嗙粍鐨勬�绘暟鍜屽畬鎴愭暟锛� |
+| GET | `/qualityReport/getPassRateStatistics` | 鍚堟牸鐜囩粺璁★紙鎸夋湡姹囨�伙細鎬绘暟銆佸畬鎴愭暟銆佸悎鏍兼暟銆佷笉鍚堟牸鏁般�佸畬鎴愮巼銆佸悎鏍肩巼锛� |
+| GET | `/qualityReport/getMonthlyPassRateStatistics` | 鏈堝害鍚堟牸鐜囷紙12涓湀 脳 3绫伙紝CTE閫掑綊锛� |
+| GET | `/qualityReport/getYearlyPassRateStatistics` | 骞村害鍚堟牸鐜� |
+| GET | `/qualityReport/getMonthlyCompletionDetails` | 鏈堝害瀹屾垚鏄庣粏锛堟瘡鏈堝師鏉愭枡/鍗婃垚鍝�/鎴愬搧瀹屾垚鏁帮級 |
+| GET | `/qualityReport/getTopParameters` | 妫�娴嬫寚鏍囨帓鍚嶏紙Top 4 + 鍏朵粬姹囨�伙級 |
+
+---
+
+## 5. 鍏抽敭鏁版嵁鍏宠仈
+
+```
+product (浜у搧鍒嗙被)
+ 鈹斺攢鈹� product (鍏蜂綋浜у搧)
+ 鈹斺攢鈹� product_model (浜у搧鍨嬪彿)
+ 鈹溾攢鈹� quality_inspect.product_model_id
+ 鈹溾攢鈹� quality_unqualified (閫氳繃 model 瀛楁鍏宠仈)
+ 鈹斺攢鈹� quality_test_standard_binding.product_id
+
+production_product_main (鐢熶骇鎶ュ伐)
+ 鈹斺攢鈹� quality_inspect.product_main_id (杩囩▼/鍑哄巶妫�楠�)
+ 鈹斺攢鈹� production_product_main.work_order_id 鈫� product_work_order
+ 鈹斺攢鈹� product_order 鈫� sales_ledger (閿�鍞彴璐�)
+
+purchase_ledger (閲囪喘鍙拌处)
+ 鈹斺攢鈹� quality_inspect.purchase_ledger_id (鍘熸潗鏂欐楠�)
+
+product_process_route (宸ヨ壓璺嚎涓昏〃)
+ 鈹斺攢鈹� product_process_route_item (宸ヨ壓璺嚎瀛愯〃)
+ 鈹斺攢鈹� product_work_order (宸ュ崟)
+```
+
+### 鍒嗛〉鏌ヨ鏃� JOIN 绛栫暐
+
+- **鍘熸潗鏂欐楠� (inspectType=0)**锛歀EFT JOIN `purchase_ledger` 鑾峰彇閲囪喘鍚堝悓鍙枫��
+- **杩囩▼/鍑哄巶妫�楠� (inspectType != 0)**锛歀EFT JOIN `production_product_main` 鈫� `product_work_order` 鈫� `product_order` 鈫� `sales_ledger` 鑾峰彇宸ュ崟鍙峰拰閿�鍞悎鍚屽彿銆�
+- **涓嶅悎鏍煎搧鍒楄〃**锛歀EFT JOIN `product_model` 瑙f瀽鍨嬪彿锛坢odel 瀛楁鍙兘鏄� ID 鎴栨枃鏈級銆�
+
+---
+
+## 6. 涓庡閮ㄦā鍧楃殑浜や簰
+
+| 浜や簰鐐� | 璋冪敤鏂瑰紡 | 璇存槑 |
+|--------|----------|------|
+| 搴撳瓨绠$悊 (stock) | `StockUtils.addStock()` / `StockUtils.addUnStock()` | 鍚堟牸鍏ュ簱 / 涓嶅悎鏍煎叆搴� |
+| 鐢熶骇绠$悊 (production) | 鐩存帴鎿嶄綔 Mapper | 杩斿伐/杩斾慨鏃跺厠闅嗙敓浜ц鍗�+宸ヨ壓璺嚎+宸ュ崟 |
+| 闄勪欢绠$悊 (commonFile) | `CommonFileService` / `TempFileService` | 涓存椂鏂囦欢杞寮忔枃浠躲�佹寜涓氬姟ID+绫诲瀷鏌ヨ鍒犻櫎 |
+| 鏁版嵁瀛楀吀 (dict) | `DictUtils` | processType 鏍¢獙锛屽鍑烘椂 translate |
+| 閲囪喘鍙拌处 (procurement) | `ProcurementRecordService` | 鍘熸潗鏂欐楠屽叧鑱旈噰璐褰� |
+
+---
+
+## 7. 鏋氫妇鍊兼眹鎬�
+
+### inspectType锛堟楠岀被鍒級
+| 鍊� | 鍚箟 |
+|----|------|
+| 0 | 鍘熸潗鏂欐楠� |
+| 1 | 杩囩▼妫�楠� |
+| 2 | 鍑哄巶妫�楠� |
+
+### inspectState锛堟楠屽崟鐘舵�� / 涓嶅悎鏍煎搧鐘舵�侊級
+| 鍊� | 鍚箟锛堟楠屽崟锛� | 鍚箟锛堜笉鍚堟牸鍝侊級 |
+|----|---------------|------------------|
+| 0 | 鏈彁浜�(鑽夌) | 寰呭鐞� |
+| 1 | 宸叉彁浜� | 宸插鐞� |
+
+### dealResult锛堜笉鍚堟牸鍝佸鐞嗙粨鏋滐級
+| 鍊� | 鍚箟 | 閫傜敤鍦烘櫙 |
+|----|------|----------|
+| 杩斾慨 | 杩斾慨澶勭悊 | 浠呰繃绋�/鍑哄巶妫�楠� |
+| 杩斿伐 | 杩斿伐澶勭悊锛堥噸寤虹敓浜ц鍗曪級 | 浠呰繃绋�/鍑哄巶妫�楠� |
+| 鎶ュ簾 | 鍏ヤ笉鍚堟牸搴� | 鍏ㄩ儴绫诲瀷 |
+| 璁╂鏀捐 | 鍏ュ悎鏍煎簱 | 鍏ㄩ儴绫诲瀷 |
+
+### disposalMethod锛堝鐞嗗崟澶勭疆鏂瑰紡锛�
+| 鍊� | 鍚箟 |
+|----|------|
+| 1 | 璁╂鎺ユ敹 |
+| 2 | 鍘傚唴缁翠慨 |
+| 3 | 杩斿巶缁翠慨 |
+| 4 | 鎹㈣揣 |
+| 5 | 閫�璐� |
+| 6 | 鎶ュ簾 |
+
+### unqualifiedProcess锛堜笉鍚堟牸宸ュ簭锛�
+| 鍊� | 鍚箟 |
+|----|------|
+| 1 | 鏉ユ枡 |
+| 2 | 鍒剁▼ |
+| 3 | 鎴愬搧 |
+
+### status锛堝鐞嗗崟鐘舵�侊級
+| 鍊� | 鍚箟 |
+|----|------|
+| 0 | 鑽夌 |
+| 1 | 寰呭鎵� |
+| 2 | 瀹℃壒涓� |
+| 3 | 宸插畬鎴� |
+| 4 | 宸查┏鍥� |
+
+---
+
+## 8. 鎶�鏈疄鐜拌鐐�
+
+1. **MyBatis-Plus 鑷姩濉厖**锛歝reate_time銆乧reate_user銆乽pdate_time銆乽pdate_user銆乼enant_id銆乨ept_id 鍧囬�氳繃 `@TableField(fill = ...)` 鑷姩濉厖銆�
+2. **浜嬪姟绠$悊**锛歚QualityInspectServiceImpl` 鏍囨敞 `@Transactional(rollbackFor = Exception.class)`锛岀‘淇濇柊澧�/淇敼妫�楠屽崟涓庡弬鏁版搷浣滅殑鍘熷瓙鎬с��
+3. **Word 鎶ュ憡鐢熸垚**锛氫娇鐢� poi-tl 妯℃澘寮曟搸锛屾ā鏉挎枃浠� `/static/report-template.docx`锛岄�氳繃 `HackLoopTableRenderPolicy` 娓叉煋鍙傛暟鍒楄〃琛ㄦ牸銆�
+4. **Excel 瀵煎嚭鍔ㄦ�佸垪**锛氭牴鎹� inspectType 鍔ㄦ�侀殣钘忎笉鐩稿叧鍒楋紙鍘熸潗鏂欐楠岄殣钘� customer/process锛屽嚭鍘傛楠岄殣钘� supplier/customer/process锛夈��
+5. **鏈堝害缁熻 CTE**锛歚getMonthlyPassRateStatistics` 浣跨敤 MySQL WITH RECURSIVE 鐢熸垚 1-12 鏈堝簭鍒楋紝CROSS JOIN 3 绉嶄骇鍝佺被鍨嬶紝LEFT JOIN 瀹為檯鏁版嵁锛岀‘淇濇瘡鏈堟瘡绫诲瀷閮芥湁鏁版嵁琛屻��
+6. **涓嶅悎鏍煎搧 model 瀛楁鍏煎**锛歮odel 瀛楁鍙兘瀛樺偍 product_model.id锛堟暟瀛楋級鎴栫洿鎺ユ枃鏈紝鏌ヨ鏃� LEFT JOIN product_model 灏濊瘯瑙f瀽锛屽苟鐢� method 瀛楁鏍囪銆�
+7. **杩斿伐宸ュ崟鍙风敓鎴�**锛氭棩鏈熷墠缂� `yyyyMMdd` + 3 浣嶈嚜澧炲簭鍙凤紝鍓嶇紑 `"FG"`锛岄�氳繃鏌ヨ褰撴棩鏈�澶у伐鍗曞彿瀹炵幇鑷銆�
+8. **鏂囦欢绠$悊**锛氬墠绔紶 tempFileIds锛屽悗绔�氳繃 `TempFileService.migrateTempFilesToFormal()` 灏嗕复鏃舵枃浠惰浆涓烘寮忔枃浠跺苟缁戝畾鍒颁笟鍔D銆�
\ No newline at end of file
diff --git a/docs/approve_or_and_mode.md b/docs/approve_or_and_mode.md
new file mode 100644
index 0000000..d603eb1
--- /dev/null
+++ b/docs/approve_or_and_mode.md
@@ -0,0 +1,223 @@
+# 瀹℃壒娴佺▼鏀寔鎴栫/浼氱妯″紡
+
+## 娑夊強椤甸潰
+
+- 瀹℃壒绠$悊閰嶇疆椤碉細`collaborativeApproval/approvalManagement/index.vue`
+- 瀹℃壒鍒楄〃椤碉紙鍗忓悓瀹℃壒锛夛細`collaborativeApproval/approvalProcess/index.vue`
+- 瀹℃壒鍒楄〃椤碉紙鍗遍櫓浣滀笟 type 8锛夛細`safeProduction/safeWorkApproval/index.vue`
+- 瀹℃壒寮规锛堝崗鍚屽鎵癸級锛歚collaborativeApproval/approvalProcess/components/approvalDia.vue`
+- 瀹℃壒寮规锛堝嵄闄╀綔涓� type 8锛夛細`safeProduction/safeWorkApproval/components/approvalDia.vue`
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /approveProcessConfigNode/listNode | 鏌ヨ瀹℃壒鑺傜偣閰嶇疆锛堝搷搴旀柊澧� `approveMode` 瀛楁锛� |
+| POST | /approveProcessConfigNode/addApproveProcessConfigNodes | 淇濆瓨鑺傜偣閰嶇疆锛堟瘡琛屼竴涓鎵逛汉锛屽悓涓� `nodeOrder` 澶氳鍗冲悓涓�鑺傜偣澶氫汉锛岄渶浼� `approveMode`锛� |
+| POST | /approveProcess/addApprove | 鍙戣捣瀹℃壒锛堟柊澧炲搷搴�/蹇収瀛楁 `approve_node_config`锛岃妭鐐瑰惈澶氫汉鏃跺啓鍏� `approve_user_current_ids/names`锛� |
+| GET | /approveProcess/details | 瀹℃壒璇︽儏锛堣妭鐐瑰垪琛ㄦ柊澧� `approveMode` 瀛楁锛沗isShen` 浠呭綋鍓嶅緟瀹¤妭鐐逛笖褰撳墠鐧诲綍浜哄彲瀹℃椂杩斿洖 true锛� |
+| POST | /approveNode/updateApproveNode | 鎻愪氦瀹℃壒锛�**涓嶅啀闇�瑕� `isLast` 鍙傛暟**锛屾祦杞垽鏂敱鍚庣鎸夎妭鐐规ā寮忓畬鎴愶級 |
+
+### 鑺傜偣閰嶇疆淇濆瓨璇锋眰鍙傛暟锛坅ddApproveProcessConfigNodes锛�
+
+鍚屼竴鑺傜偣澶氬鎵逛汉閫氳繃澶氭潯璁板綍琛ㄨ揪锛宍nodeOrder` 鐩稿悓銆乣approveMode` 鏍囪瘑妯″紡锛�
+
+```json
+[
+ { "approveType": 1, "nodeOrder": 1, "approverId": 11, "approverName": "寮犱笁", "approveMode": "or" },
+ { "approveType": 1, "nodeOrder": 1, "approverId": 12, "approverName": "鏉庡洓", "approveMode": "or" },
+ { "approveType": 1, "nodeOrder": 2, "approverId": 21, "approverName": "鐜嬩簲", "approveMode": "and" },
+ { "approveType": 1, "nodeOrder": 2, "approverId": 22, "approverName": "璧靛叚", "approveMode": "and" }
+]
+```
+
+- `approveMode`锛歚or`=鎴栫锛堜换涓�浜洪�氳繃鍗虫祦杞級锛宍and`=浼氱锛堥渶鎵�鏈変汉閫氳繃鎵嶆祦杞級
+- 浠讳竴瀹℃壒浜烘嫆缁� 鈫� 鏁翠釜娴佺▼绔嬪嵆椹冲洖锛堟垨绛�/浼氱涓�鑷达紝涓�绁ㄥ惁鍐筹級
+
+### 瀹℃壒璇︽儏鍝嶅簲锛坉etails锛夋柊澧炲瓧娈�
+
+姣忎釜鑺傜偣鏂板锛�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| approveMode | String | 鑺傜偣瀹℃壒妯″紡锛歰r / and |
+| approveNodeOrder | Integer | 鑺傜偣搴忓彿锛堝悓涓�鑺傜偣鐨勫浜鸿褰曠浉鍚岋級 |
+| isShen | Boolean | 鏄惁涓哄綋鍓嶇櫥褰曚汉鐨勫緟瀹¤妭鐐� |
+
+娴佺▼绾ф柊澧烇紙鍒楄〃鎺ュ彛杩斿洖锛夛細`approveUserCurrentIds`銆乣approveUserCurrentNames`锛堝綋鍓嶈妭鐐瑰叏閮ㄥ緟瀹′汉锛夈��
+
+## 鍓嶇淇敼鐐�
+
+### 1. 閰嶇疆椤碉細鑺傜偣鍗$墖鏀寔澶氬鎵逛汉 + 鎴栫/浼氱
+
+`collaborativeApproval/approvalManagement/index.vue`
+
+瀹℃壒浜烘敼涓哄閫夛紝鏂板妯″紡鍗曢�夛細
+
+```html
+<el-form-item label="瀹℃壒浜�">
+ <el-select v-model="item.approverIds" multiple filterable placeholder="閫夋嫨瀹℃壒浜猴紙鍙閫夛級">
+ <el-option v-for="u in allUsers" :key="u.userId" :label="u.nickName" :value="u.userId" />
+ </el-select>
+</el-form-item>
+<el-form-item label="瀹℃壒妯″紡">
+ <el-radio-group v-model="item.approveMode">
+ <el-radio-button value="or">鎴栫锛堜换鎰忎竴浜洪�氳繃锛�</el-radio-button>
+ <el-radio-button value="and">浼氱锛堥渶鎵�鏈変汉閫氳繃锛�</el-radio-button>
+ </el-radio-group>
+</el-form-item>
+```
+
+`loadData`锛氬皢鎺ュ彛鎵佸钩閰嶇疆鎸� `nodeOrder` 鍚堝苟涓哄崱鐗囨暟鎹細
+
+```js
+const groupMap = new Map();
+(res.data || []).forEach(r => {
+ const order = r.nodeOrder;
+ if (!groupMap.has(order)) groupMap.set(order, { nodeOrder: order, approverIds: [], approverNames: [], approveMode: r.approveMode || 'or' });
+ const g = groupMap.get(order);
+ if (!g.approverIds.includes(r.approverId)) {
+ g.approverIds.push(r.approverId);
+ g.approverNames.push(r.approverName);
+ }
+});
+nodeCards.value = Array.from(groupMap.values()).sort((a, b) => a.nodeOrder - b.nodeOrder);
+```
+
+`handleSave`锛氭瘡寮犲崱鐗囨寜瀹℃壒浜哄睍寮�鎴愬琛� payload锛�
+
+```js
+const list = [];
+nodeCards.value.forEach(card => {
+ card.approverIds.forEach((uid, i) => {
+ list.push({
+ approveType: currentType,
+ nodeOrder: card.nodeOrder,
+ approverId: uid,
+ approverName: card.approverNames[i],
+ approveMode: card.approveMode || 'or',
+ });
+ });
+});
+await addApproveProcessConfigNodes(list);
+```
+
+### 2. 鍒楄〃椤碉細褰撳墠瀹℃壒浜哄垪 + 瀹℃牳鎸夐挳鏉冮檺
+
+`collaborativeApproval/approvalProcess/index.vue`銆乣safeProduction/safeWorkApproval/index.vue`
+
+褰撳墠瀹℃壒浜哄垪灞曠ず鍚岃妭鐐瑰叏閮ㄥ緟瀹′汉锛屽瘜鍖栬鏁版嵁锛�
+
+```js
+tableData.value = (res.data.records || []).map(r => ({
+ ...r,
+ currentApproverNames: r.approveUserCurrentNames || r.approveUserCurrentName || '-',
+ _canApprove:
+ String(r.approveUserCurrentIds ?? '').split(',').filter(Boolean).includes(String(userStore.id)) ||
+ r.approveUserCurrentId === userStore.id,
+}));
+```
+
+瀹℃牳鎸夐挳绂佺敤鏉′欢锛�
+
+```js
+disabled: row =>
+ row.approveStatus == 2 || row.approveStatus == 3 || row.approveStatus == 4 || !row._canApprove,
+```
+
+### 3. 瀹℃壒寮规锛氭寜鑺傜偣鍒嗙粍灞曠ず
+
+`collaborativeApproval/approvalProcess/components/approvalDia.vue`銆乣safeProduction/safeWorkApproval/components/approvalDia.vue`
+
+鏂板鍒嗙粍璁$畻灞炴�э紝鍚屼竴 `approveNodeOrder` 鐨勫鍚嶅鎵逛汉褰掍负涓�涓楠ょ粍锛�
+
+```js
+const stepGroups = computed(() => {
+ const map = new Map();
+ (activities.value || []).forEach(a => {
+ const order = a.approveNodeOrder;
+ if (!map.has(order)) map.set(order, []);
+ map.get(order).push(a);
+ });
+ return Array.from(map.values());
+});
+```
+
+`el-steps` 鏀逛负閬嶅巻 `stepGroups`锛屾瘡缁勪竴涓� `el-step`锛岀粍鍐呭惊鐜覆鏌撴垚鍛橈紙濮撳悕銆佺姸鎬併�佹剰瑙併�佺鍚嶃�佸緟瀹¤緭鍏ユ锛夛細
+
+```html
+<el-steps :active="getActiveStep()" ...>
+ <el-step v-for="(group, gIndex) in stepGroups" :key="gIndex" ...>
+ <template #title>
+ <span>{{ getNodeTitle(gIndex, stepGroups.length) }}</span>
+ <el-tag :type="group[0]?.approveMode === 'and' ? 'warning' : 'info'">
+ {{ group[0]?.approveMode === 'and' ? '浼氱' : '鎴栫' }}
+ </el-tag>
+ </template>
+ <template #description>
+ <div v-for="(activity, aIndex) in group" :key="activity.id || aIndex" class="node-user-item">
+ <!-- 鎴愬憳濮撳悕銆佺姸鎬併�佸鎵规剰瑙侊紱浠� isShen 鎴愬憳娓叉煋杈撳叆妗� -->
+ </div>
+ </template>
+ </el-step>
+</el-steps>
+```
+
+褰撳墠婵�娲绘楠ゅ彇鍚緟瀹℃垚鍛樼殑缁勪笅鏍囷細
+
+```js
+const getActiveStep = () => {
+ const idx = stepGroups.value.findIndex(group => group.some(a => a.isShen === true));
+ if (idx === -1) return stepGroups.value.length;
+ return idx;
+};
+```
+
+鎻愪氦瀹℃壒涓嶅啀浼� `isLast`锛�
+
+```js
+currentActivity.approveNodeStatus = status;
+updateApproveNode({ ...currentActivity }).then(() => {
+ proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ closeDia();
+});
+```
+
+## 绠$悊鍛樺鎵逛笉鍙楀鎵逛汉闄愬埗
+
+绠$悊鍛樿处鍙凤紙瑙掕壊涓� **鏅��2 / admin / 鏈�楂�1**锛夊彲瀹℃壒浠绘剰娴佺▼锛屼笉鍐嶅彈"瀹℃壒浜哄繀椤绘槸娴佺▼鑺傜偣閰嶇疆鐨勫鎵逛汉"闄愬埗銆�
+
+### 鍚庣
+
+- `details`锛氱鐞嗗憳瀵瑰綋鍓嶅緟瀹¤妭鐐圭殑棣栦釜寰呭璁板綍杩斿洖 `isShen=true`锛堜唬瀹℃暣鑺傜偣锛夈��
+- `updateApproveNode`锛氱鐞嗗憳璺宠繃"鏃犳潈瀹℃壒璇ヨ妭鐐�"鏍¢獙锛涘鎵归�氳繃鏃惰嚜鍔ㄥ皢鍚岃妭鐐瑰叾浣欏緟瀹¤褰曟爣璁颁负"绠$悊鍛樹唬瀹¢�氳繃"锛堟垨绛�/浼氱鍧囨暣鑺傜偣鏀捐锛夛紱鎷掔粷浠嶆寜涓�绁ㄥ惁鍐冲鐞嗐��
+
+### 鍓嶇
+
+涓や釜鍒楄〃椤碉紙approvalProcess / safeWorkApproval锛夋柊澧� `isAdminUser` 鍒ゆ柇锛�
+
+```js
+const isAdminUser = computed(() =>
+ ["admin", "鏅��2", "鏈�楂�1"].some(r => (userStore.roles || []).includes(r))
+);
+```
+
+`_canApprove` 瀵圭鐞嗗憳鎭掍负 `true`锛屽鏍告寜閽笉鍐嶇鐢細
+
+```js
+_canApprove: isAdminUser.value || String(r.approveUserCurrentIds ?? '').split(',').filter(Boolean).includes(String(userStore.id)) || r.approveUserCurrentId === userStore.id
+```
+
+### 娉ㄦ剰
+
+- 绠$悊鍛樹粛闇�鎸夎妭鐐归『搴忓鎵癸紙"灏氭湭杞埌璇ヨ妭鐐瑰鎵�"鏍¢獙淇濈暀锛夈��
+- 绠$悊鍛樻嫆缁濅换鎰忚妭鐐� = 涓�绁ㄥ惁鍐筹紝鏁翠釜娴佺▼绔嬪嵆椹冲洖銆�
+
+## 娉ㄦ剰浜嬮」
+
+- **鍚庣娴佽浆鍒ゆ柇**锛歚updateApproveNode` 宸叉敼涓烘寜鑺傜偣妯″紡锛堟垨绛�/浼氱锛夌粺璁″悓 order 鐨勫悓鎰忔暟鍐冲畾鏄惁娴佽浆锛屽墠绔�**蹇呴』鍒犻櫎 `isLast` 浼犲弬**锛屽惁鍒欐棫瀛楁浼氳蹇界暐锛堜笉褰卞搷缁撴灉锛屼絾鏃犳剰涔夛級銆�
+- **鎴栫鏀捐**锛氭垨绛句换涓�瀹℃壒浜洪�氳繃鍚庯紝鍚岃妭鐐瑰叾浣欐湭瀹¤褰曚細琚悗绔嚜鍔ㄦ爣璁颁负"宸插悓鎰忥紙鎴栫锛氬叾浠栧鎵逛汉宸查�氳繃锛�"锛屽墠绔棤闇�澶勭悊銆�
+- **浼氱閮ㄥ垎閫氳繃**锛氳妭鐐瑰仠鐣欏湪褰撳墠姝ラ锛屽垪琛�"褰撳墠瀹℃壒浜�"浼氭洿鏂颁负鍓╀綑鏈浜哄憳銆�
+- **鍘嗗彶鏁版嵁鍏煎**锛氬瓨閲忚妭鐐� `approveMode` 涓虹┖鎸�"鎴栫"澶勭悊锛涘瓨閲忔祦绋� `approveUserCurrentNames` 涓虹┖鏃跺墠绔厹搴� `approveUserCurrentName`銆�
+- **缂栬緫閲嶅缓**锛氭祦绋嬬紪杈戞椂鍚庣浼樺厛鎸� `approve_node_config` 蹇収閲嶅缓鑺傜偣浠ヤ繚鎸佸垎缁勪笌妯″紡锛屾棤蹇収鏃舵寜"涓�鑺傜偣涓�浜� or"鍥為��銆�
+- 閰嶇疆椤靛凡琛ュ厖 **type 8 鍗遍櫓浣滀笟瀹℃壒** 鐨勬爣绛句笌鍥炬爣銆�
diff --git a/docs/auto_quality_inspect_on_dispatch.md b/docs/auto_quality_inspect_on_dispatch.md
new file mode 100644
index 0000000..308d973
--- /dev/null
+++ b/docs/auto_quality_inspect_on_dispatch.md
@@ -0,0 +1,76 @@
+# 鎺掍骇涓嬭揪鑷姩瑙﹀彂绗竴閬撳伐搴忚川妫�
+
+## 鍔熻兘璇存槑
+
+鐢熶骇鎺掍骇涓嬭揪锛堢敓鎴愮敓浜у伐鍗曪級鍚庯紝鑻ョ涓�閬撳伐搴忛渶瑕佽川妫�锛坄isQuality=true`锛夛紝绯荤粺鑷姩涓鸿宸ュ簭棰勭敓鎴愪竴寮犺繃绋嬫楠岋紙鍗曢亾宸ュ簭鏃剁敓鎴愬嚭鍘傛锛夎川妫�鍗曘�傝川妫�鍛樻棤闇�绛夊緟璇ュ伐搴忔姤宸ュ鏍稿嵆鍙洿鎺ュ湪璐ㄦ鍒楄〃涓紑濮嬫楠屻��
+
+## 娑夊強椤甸潰
+
+- 鐢熶骇鎺掍骇/鐢熶骇璁㈠崟涓嬭揪
+- 璐ㄦ绠$悊锛堣繃绋嬫楠� / 鍑哄巶妫�楠� 鍒楄〃椤碉級
+
+## 琛屼负鍙樺寲锛堢函鍚庣锛屾棤鏂板鎺ュ彛锛�
+
+### 1. 鎺掍骇涓嬭揪鑷姩棰勭敓鎴愯川妫�鍗�
+
+瑕嗙洊鍏ㄩ儴宸ュ崟鍒涘缓鍏ュ彛锛�
+
+| 鍏ュ彛 | 璇存槑 |
+|------|------|
+| 鐢熶骇璁㈠崟缁戝畾宸ヨ壓璺嚎 `bindingRoute` | 鐢熸垚宸ュ崟鍚庨鐢熸垚璐ㄦ鍗� |
+| 閿�鍞彴璐︾敓鎴愮敓浜ф暟鎹� `addProductionData` | 鐢熸垚宸ュ崟鍚庨鐢熸垚璐ㄦ鍗� |
+| 鍗板埛鍗曢�嗘椂閽堝�掓帓 `processRouteAnticlockwise` | 鐢熸垚宸ュ崟鍚庨鐢熸垚璐ㄦ鍗� |
+
+棰勭敓鎴愯鍒欙細
+
+- 鍙栧伐鑹鸿矾绾跨涓�閬撳伐搴忥紙`dragSort` 鍗囧簭 + `id` 鍗囧簭锛夛紱
+- 绗竴閬撳伐搴� `isQuality` 闈� true 鍒欎笉鐢熸垚锛�
+- 浠呬竴閬撳伐搴� 鈫� `inspect_type=2`锛堝嚭鍘傛锛夛紝鍚﹀垯 `inspect_type=1`锛堣繃绋嬫锛宍process`=绗竴閬撳伐搴忓悕锛夛紱
+- `quantity`=宸ュ崟璁″垝鏁伴噺锛�
+- 鍏抽敭瀛楁锛歚work_order_id`=绗竴閬撳伐搴忓伐鍗� id锛宍product_main_id=NULL`锛堟湭鍏宠仈鎶ュ伐锛夈��
+
+> 璇嗗埆棰勭敓鎴愯川妫�鍗曪細`quality_inspect` 琛ㄤ腑 `product_main_id IS NULL` 涓� `work_order_id` 闈炵┖鐨勮褰曞嵆涓烘帓浜ч鐢熸垚鍗曘��
+
+### 2. 鎶ュ伐瀹℃牳鏃惰棰嗗苟鏇存柊
+
+绗竴閬撳伐搴忔姤宸ュ鏍革紙`nextAddProductMain`锛夋椂锛�
+
+- 鑻ヨ宸ュ崟瀛樺湪棰勭敓鎴愯川妫�鍗曪紙`work_order_id` 鍖归厤涓� `product_main_id IS NULL`锛夆啋 **璁ら**锛氬洖濉� `product_main_id`=鎶ュ伐 id锛宍quantity` 鏇存柊涓哄疄闄呭叆搴撴暟閲忥紙浜у嚭 + 鍔犳斁 - 鎶ュ簾锛夛紱
+- 鑻ヨ棰勭敓鎴愬崟宸叉彁浜わ紙`inspect_state=1`锛変笖涓哄嚭鍘傛 鈫� 鍏堝垹闄ゆ寜璁″垝鏁伴噺鍏ュ簱鐨勫簱瀛樿褰曪紝鍐嶆寜瀹為檯鏁伴噺閲嶆柊鍏ュ簱锛屼繚璇佸簱瀛樺噯纭紱
+- 鑻ヤ笉瀛樺湪棰勭敓鎴愬崟锛堝宸ュ簭涓存椂鏂板锛夆啋 璧板師鏈夐�昏緫鐢熸垚鏂拌川妫�鍗曘��
+
+鍚庣画宸ュ簭锛堥潪绗竴閬擄級鎶ュ伐瀹℃牳鏃朵粛鎸夊師鏈夐�昏緫鐢熸垚璐ㄦ鍗曘��
+
+### 3. 鍒楄〃/鎶ュ憡灞曠ず
+
+- 璐ㄦ鍒楄〃鐨勫伐鍗曞彿閫氳繃 `COALESCE(qi.work_order_id, ppm.work_order_id)` 鍏宠仈锛岄鐢熸垚鍗曪紙鏃犳姤宸ワ級涔熻兘姝e父鏄剧ず宸ュ崟鍙凤紱
+- 妫�楠屾姤鍛婁笅杞斤細棰勭敓鎴愬崟锛坄product_main_id=NULL`锛夋寜 `work_order_id` 鈫� 鐢熶骇璁㈠崟 鈫� 閿�鍞彴璐� 鍙嶆煡瀹㈡埛鍚嶇О銆�
+
+### 4. 鍒犻櫎娓呯悊
+
+鍒犻櫎鐢熶骇璁㈠崟 / 鍒犻櫎閿�鍞彴璐︾敓浜ф暟鎹� / 鍒犻櫎鐢熶骇宸ヨ壓璺嚎宸ュ簭椤规椂锛岃嚜鍔ㄦ竻鐞嗗搴旂殑棰勭敓鎴愯川妫�鍗曞強鍏舵楠屽弬鏁帮紱鑻ラ鐢熸垚鍗曞凡鎻愪氦锛坄inspect_state=1`锛夛紝鍒欐嫆缁濆垹闄ゅ苟鎻愮ず鈥滃凡鎻愪氦鐨勬楠屽崟涓嶈兘鍒犻櫎鈥濄��
+
+## API
+
+鏃犳柊澧炪�佹棤淇敼鎺ュ彛銆傚潎涓哄悗绔涓哄彉鏇淬��
+
+## 鍓嶇淇敼鐐�
+
+鏃犲墠绔唬鐮佹敼鍔ㄣ��
+
+鑻ラ渶鍦ㄧ晫闈㈠尯鍒嗏�滄帓浜ч鐢熸垚鈥濈殑璐ㄦ鍗曪紝鍙弬鑰� `work_order_id`锛堥潪绌猴級涓� `product_main_id` 涓虹┖杩涜鍒ゆ柇銆�
+
+## 鏁版嵁搴撳彉鏇�
+
+鎵ц `sql/quality_inspect_add_work_order_id.sql`锛�
+
+```sql
+ALTER TABLE quality_inspect ADD COLUMN work_order_id BIGINT NULL COMMENT '鐢熶骇宸ュ崟id(鎺掍骇棰勭敓鎴愯川妫�鍗曞叧鑱�)' AFTER product_main_id;
+ALTER TABLE quality_inspect ADD INDEX idx_quality_inspect_work_order_id (work_order_id);
+```
+
+## 娉ㄦ剰浜嬮」
+
+- 棰勭敓鎴愯川妫�鍗曚粎閽堝**绗竴閬撻渶璐ㄦ宸ュ簭**锛涗腑閫斿伐搴忎粛闇�鎶ュ伐瀹℃牳鍚庢墠鐢熸垚銆�
+- 璐ㄦ鍛樺彲鍦ㄦ姤宸ュ墠鐩存帴濉啓妫�楠岀粨鏋滃苟鎻愪氦锛涘嚭鍘傛鎻愪氦鏃朵細鎸夎鍒掓暟閲忓叆搴擄紝鎶ュ伐瀹℃牳鍚庤嚜鍔ㄦ寜瀹為檯鏁伴噺淇搴撳瓨銆�
+- 宸叉彁浜ょ殑棰勭敓鎴愯川妫�鍗曞湪鍒犻櫎璁㈠崟/宸ュ崟鏃朵細鎻愮ず鈥滃凡鎻愪氦鐨勬楠屽崟涓嶈兘鍒犻櫎鈥濓紝闇�鍏堟挙閿�鎴栧垹闄ゆ楠屽崟銆�
diff --git a/docs/production_report_person_detail.md b/docs/production_report_person_detail.md
new file mode 100644
index 0000000..c5f535a
--- /dev/null
+++ b/docs/production_report_person_detail.md
@@ -0,0 +1,181 @@
+# 鐢熶骇鎶ュ伐鎸夌彮缁勬媶鍒嗕骇閲�/鏃堕暱
+
+## 娑夊強椤甸潰
+
+- 鐢熶骇宸ュ崟鍒楄〃椤碉紙PDA 鎶ュ伐寮圭獥锛屾暟鎹潵婧� `/productWorkOrder/getProductWorkOrderById`锛�
+- 鎶ュ伐鎻愪氦锛坄POST /productionProductMain/addProductMain`锛�
+- 鐢熶骇鏄庣粏椤碉紙鐢熶骇鏄庣粏宸ユ椂鍥炴樉锛�
+
+## API
+
+### 1. 鎶ュ伐寮圭獥鏁版嵁鏌ヨ锛堣繑鍥為粯璁ょ彮缁勬垚鍛橈級
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /productWorkOrder/getProductWorkOrderById | 鎶ュ伐寮圭獥鏁版嵁锛屽搷搴旀柊澧� `reportPersons` 榛樿鐝粍鎴愬憳锛堝悎骞跺伐鍗曟帓浜т汉鍛樹笌鏈哄彴涓婃満鎿嶄綔鍛橈紝鍘婚噸锛� |
+
+**鍝嶅簲鏂板瀛楁锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| reportPersons | Array | 鎶ュ伐鐝粍榛樿鎴愬憳锛屾瘡椤� `{ userId, userName }`锛岀敤浣滄姤宸ュ脊绐楀綍鍏ヨ鍒濆鍖� |
+
+### 2. 鎶ュ伐鎻愪氦锛堟柊澧� personList 鏄庣粏锛�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /productionProductMain/addProductMain | 鎶ュ伐鎻愪氦锛岃姹備綋鏂板 `personList` 鐝粍浜哄憳浜ч噺鏃堕暱鏄庣粏 |
+
+**璇锋眰鍙傛暟鏂板锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| personList | Array | 鏄� | 鐝粍浜哄憳浜ч噺鏃堕暱鏄庣粏锛屾瘡椤� `{ userId, userName, quantity, workHour }` |
+| personList[].userId | Long | 鏄� | 鐢熶骇浜� id |
+| personList[].userName | String | 鏄� | 鐢熶骇浜哄鍚� |
+| personList[].quantity | BigDecimal | 鏄� | 璇ヤ汉鍛樹骇閲� |
+| personList[].workHour | Integer | 鏄� | 璇ヤ汉鍛樼敓浜ф椂闀匡紙鍒嗛挓锛� |
+
+**鏍¢獙瑙勫垯锛�** 鍚勪汉鍛� `quantity` 涔嬪拰蹇呴』绛変簬鎶ュ伐鎬讳骇閲� `quantity`锛屽惁鍒欏悗绔繑鍥� `鍚勭彮缁勪骇閲忎箣鍜屽繀椤荤瓑浜庢姤宸ユ�讳骇閲廯銆�
+
+### 3. 鐢熶骇鏄庣粏锛堝伐鏃跺洖鏄句慨姝o級
+
+`/productionProductMain/page` 瀵瑰簲鐨勭敓浜ф槑缁嗕腑锛屾柊澧� `personWorkMinutes` 瀛楁鐢ㄤ簬鍥炴樉鍚勭彮缁勪汉鍛樺綍鍏ョ殑宸ユ椂銆�
+
+**鍝嶅簲鏂板瀛楁锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| personWorkMinutes | String | 鍚勭彮缁勪汉鍛樺綍鍏ョ殑鐢熶骇鏃堕暱锛堝垎閽燂級锛屾潵婧� `sales_ledger_production_accounting.work_hour` |
+
+### 4. 鐢熶骇鏍哥畻/鏄庣粏鎶ヨ〃浜ч噺鍒椾慨姝�
+
+`GET /salesLedger/productionAccounting/listProductionDetails` 姣忚鎸夌敓浜т汉鐢熸垚锛堜竴浜轰竴琛岋級锛�"鐢熶骇鏁伴噺"瀛楁 `quantity` 淇涓猴細
+
+- 澶氫汉鎷嗗垎鎶ュ伐锛坄production_report_person` 鏈夎褰曪級锛氭樉绀�**璇ョ敓浜т汉鎶ュ伐鏃惰緭鍏ョ殑浜ч噺**锛坄production_report_person.quantity`锛夛紝涓嶅啀鏄剧ず鏁存潯鎶ュ伐鐨勬�讳骇閲忥紱
+- 鏈媶鍒�/鍗曚汉鎶ュ伐锛氬洖閫�鏄剧ず鏍哥畻 `finished_num`锛堝噣鍊硷級銆�
+
+```sql
+-- 淇鍚庡彇鍊�
+COALESCE(prp.quantity, slpa.finished_num) AS quantity
+-- 鍏宠仈鏉′欢锛歱rp.product_main_id = ppm.id AND prp.user_id = slpa.scheduling_user_id
+```
+
+`GET /salesLedger/productionAccounting/page`锛堝乏渚ф寜浜烘眹鎬诲彴璐︼級鐨�"浜ч噺"瀛楁 `output_num` 鍚屾牱淇锛�
+
+- 鍘燂細`SUM(ppout.quantity)`锛堣鐢熶骇浜哄叧鑱旀姤宸ョ殑**鎬讳骇閲忎箣鍜�**锛屽浜烘媶鍒嗘椂鏄�婚噺锛夛紱
+- 淇锛歚SUM(COALESCE(prp.quantity, slpa.finished_num))`锛堝悇浜烘姤宸ユ椂杈撳叆鐨勪骇閲忎箣鍜岋紝鏈媶鍒嗗洖閫�鍑�鍊硷級銆�
+
+```sql
+-- 淇鍚庡彇鍊�
+SUM(COALESCE(prp.quantity, slpa.finished_num)) AS output_num
+```
+
+**鍝嶅簲瀛楁 `quantity`/`output_num` 璇箟鍙樺寲锛�** 鐢�"鎶ュ伐鎬讳骇閲�"鏀逛负"璇ョ敓浜т汉鐨勪骇閲�"锛屽墠绔垪鏄剧ず鏃犻渶鏀瑰姩銆�
+
+## 鍓嶇淇敼鐐�
+
+### 1. data 鏁版嵁锛堟姤宸ュ脊绐楋級
+
+```js
+data() {
+ return {
+ reportForm: {
+ workOrderId: null,
+ productProcessRouteItemId: null,
+ quantity: null, // 浜у嚭鏁伴噺
+ addQty: 0, // 鍔犳斁鏁�
+ scrapQty: 0, // 鎶ュ簾鏁伴噺
+ },
+ personList: [], // 鐝粍浜哄憳浜ч噺鏃堕暱鏄庣粏 [{ userId, userName, quantity, workHour }]
+ teamList: [], // 鐝粍淇℃伅锛堝師鏈夛級
+ reportDialogVisible: false,
+ }
+}
+```
+
+### 2. 鎶ュ伐寮圭獥鎵撳紑鏃跺垵濮嬪寲鐝粍褰曞叆琛�
+
+```js
+openReport(row) {
+ // row 涓� getProductWorkOrderById 杩斿洖鐨勫伐鍗曟暟鎹�
+ this.reportForm.workOrderId = row.id;
+ this.reportForm.productProcessRouteItemId = row.productProcessRouteItemId;
+ this.reportForm.quantity = null;
+ this.reportForm.addQty = row.processRouteAddNum ? Number(row.processRouteAddNum) : 0;
+ this.reportForm.scrapQty = 0;
+ // 鐢ㄩ粯璁ょ彮缁勬垚鍛樺垵濮嬪寲閫愯褰曞叆锛堟暟閲�/鏃堕暱闇�閫愯濉啓锛�
+ this.personList = (row.reportPersons || []).map(p => ({
+ userId: p.userId,
+ userName: p.userName,
+ quantity: null,
+ workHour: null,
+ }));
+ // teamList 涔熷悓姝ヤ负榛樿鎴愬憳
+ this.teamList = (row.reportPersons || []).map(p => ({ userId: p.userId, userName: p.userName }));
+ this.reportDialogVisible = true;
+}
+```
+
+### 3. 鎶ュ伐寮圭獥閫愯褰曞叆浜ч噺/鏃堕暱琛ㄦ牸
+
+```html
+<el-table :data="personList" border>
+ <el-table-column label="鐢熶骇浜�" prop="userName" min-width="100" align="center" />
+ <el-table-column label="浜ч噺" min-width="120" align="center">
+ <template slot-scope="scope">
+ <el-input-number v-model="scope.row.quantity" :min="0" :controls="false" placeholder="浜ч噺" style="width: 100%" />
+ </template>
+ </el-table-column>
+ <el-table-column label="鐢熶骇鏃堕暱(鍒嗛挓)" min-width="140" align="center">
+ <template slot-scope="scope">
+ <el-input-number v-model="scope.row.workHour" :min="0" :controls="false" placeholder="鏃堕暱(鍒嗛挓)" style="width: 100%" />
+ </template>
+ </el-table-column>
+</el-table>
+```
+
+### 4. 鎶ュ伐鎻愪氦鍙傛暟锛堟惡甯� personList锛�
+
+```js
+handleSubmit() {
+ // 鏍¢獙鍚勭彮缁勪骇閲忎箣鍜� = 鎶ュ伐鎬讳骇閲�
+ const total = this.personList.reduce((sum, p) => sum + (Number(p.quantity) || 0), 0);
+ if (Number(this.reportForm.quantity) !== total) {
+ this.$message.error('鍚勭彮缁勪骇閲忎箣鍜屽繀椤荤瓑浜庢姤宸ユ�讳骇閲�');
+ return;
+ }
+ const params = {
+ workOrderId: this.reportForm.workOrderId,
+ productProcessRouteItemId: this.reportForm.productProcessRouteItemId,
+ quantity: this.reportForm.quantity,
+ addQty: this.reportForm.addQty,
+ scrapQty: this.reportForm.scrapQty,
+ teamList: this.teamList,
+ personList: this.personList,
+ deviceId: this.deviceId,
+ deviceName: this.deviceName,
+ startTime: this.startTime,
+ endTime: this.endTime,
+ };
+ // 璋冪敤 addProductMain ...
+}
+```
+
+### 5. 鐢熶骇鏄庣粏琛ㄦ柊澧炰汉鍛樺伐鏃跺垪
+
+```html
+<el-table-column prop="personWorkMinutes" label="浜哄憳宸ユ椂(鍒嗛挓)" width="120" align="center" />
+```
+
+## 娉ㄦ剰浜嬮」
+
+- `personList` 姣忎汉鐨� `quantity` 涔嬪拰蹇呴』绛変簬鎶ュ伐鎬讳骇閲� `quantity`锛屽墠绔彁浜ゅ墠闇�鏍¢獙锛屽悗绔篃浼氭嫤鎴��
+- `workHour` 涓哄垎閽熸暟锛岀敱鎿嶄綔鍛樺湪鎶ュ伐寮圭獥鎵嬪姩褰曞叆锛屽悇鐝粍鍚勮嚜璁板綍鐢熶骇鏃堕暱銆�
+- 瀹℃牳閫氳繃鍚庯紝鍚庣鎸� `personList` 姣忎汉鐢熸垚涓�鏉$敓浜ф牳绠楄褰曪紙`sales_ledger_production_accounting`锛夛細
+ - `finished_num`锛堝畬鎴愭暟閲忥級= 鏃犳姤搴熸椂鍙栦釜浜轰骇閲忥紱鏈夋姤搴熸椂鎸夊悇浜轰骇閲忓崰姣斿垎鎽婂噣鍊硷紙浜у嚭+鍔犳斁-鎶ュ簾锛夛紝鍥涜垗浜斿叆淇濈暀 2 浣嶃��
+ - `work_hour` = 璇ヤ汉鍛樺綍鍏ョ殑鐢熶骇鏃堕暱銆�
+ - 鍏ㄩ儴鐝粍鐨勫伐鏃惰嚜鍔ㄦ眹鎬诲埌鐢熶骇鏍哥畻锛岃浠�/璁℃椂宸ヨ祫鎸変汉缁熻銆�
+- 鍏煎鏃ф暟鎹細鑻ユ姤宸ユ湭浼� `personList`锛屽悗绔洖閫�涓烘寜鏈哄彴鎿嶄綔鍛樼敓鎴愭牳绠楋紙`finished_num`=鍑�鍊硷紝`work_hour`=鏈哄彴涓婃満鏃堕暱锛夛紝鏃ф姤宸ヤ笉鍙楀奖鍝嶃��
+- 鍘嗗彶鎶ュ伐鏁版嵁 `production_report_person` 涓虹┖锛岀敓浜ф槑缁嗕腑 `personWorkMinutes` 鍥為��灞曠ず鎶ュ伐鏃堕暱 `workMinutes`銆�
+- 鏀寔鍒嗘壒鎶ュ伐锛氬畬鎴愭暟閲忔湭杈捐鍒掓暟閲忔椂鍙娆℃姤宸ワ紝姣忔鎶ュ伐鐙珛鎸� `personList` 姣忎汉鐢熸垚涓�鏉℃牳绠楋紝涓嶅彈涓婃満璁板綍鎶ュ伐鐘舵�侀檺鍒讹紱宸ュ崟瀹為檯缁撴潫鏃堕棿浠呭湪瀹屾垚鏁伴噺杈惧埌璁″垝鏁伴噺鏃跺~鍏呫�傝瑙� `docs/work_order_batch_report_fix.md`銆�
diff --git a/docs/production_work_order_add_qty.md b/docs/production_work_order_add_qty.md
new file mode 100644
index 0000000..7328fce
--- /dev/null
+++ b/docs/production_work_order_add_qty.md
@@ -0,0 +1,94 @@
+# 鐢熶骇宸ュ崟甯﹀嚭鍔犳斁鏁� & 鎶ュ伐鏍哥畻缁熶竴
+
+## 娑夊強椤甸潰
+
+- 鐢熶骇宸ュ崟鍒楄〃椤碉紙`/productWorkOrder/page`锛�
+- PDA 鎶ュ伐寮圭獥锛堟暟鎹潵婧� `/productWorkOrder/getProductWorkOrderById`锛�
+
+## API
+
+### 1. 鐢熶骇宸ュ崟鍒嗛〉鏌ヨ
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /productWorkOrder/page | 鐢熶骇宸ュ崟鍒嗛〉鏌ヨ锛屽搷搴旀柊澧炲姞鏀炬暟瀛楁 |
+
+**鍝嶅簲鏂板瀛楁锛圥roductWorkOrderDto锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| processRouteAddNum | String | 鍔犳斁鏁帮紙鍙栬嚜宸ヨ壓璺嚎椤圭洰 `product_process_route_item.process_route_add_num`锛� |
+
+### 2. PDA 鎶ュ伐鏁版嵁鏌ヨ
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /productWorkOrder/getProductWorkOrderById | 鎶ュ伐寮圭獥鏁版嵁锛屽搷搴旀柊澧炲姞鏀炬暟瀛楁锛堢敤浣滄姤宸ュ脊绐楀姞鏀炬暟榛樿鍊硷級 |
+
+**鍝嶅簲鏂板瀛楁锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| processRouteAddNum | String | 鍔犳斁鏁� |
+
+## 鍓嶇淇敼鐐�
+
+### 1. 鐢熶骇宸ュ崟鍒楄〃琛ㄦ牸澧炲姞鍔犳斁鏁板垪
+
+```html
+<el-table-column prop="processRouteAddNum" label="鍔犳斁鏁�" width="100" align="center" />
+```
+
+### 2. data 鏁版嵁锛堟姤宸ュ脊绐楄〃鍗曪級
+
+```js
+data() {
+ return {
+ reportForm: {
+ quantity: null, // 浜у嚭鏁伴噺
+ addQty: 0, // 鍔犳斁鏁帮紙榛樿鍊硷紝鍙慨鏀癸級
+ scrapQty: 0, // 鎶ュ簾鏁伴噺
+ },
+ }
+}
+```
+
+### 3. 鎶ュ伐寮圭獥鎵撳紑鏃跺甫鍑哄姞鏀炬暟榛樿鍊�
+
+```js
+openReport(row) {
+ // row 涓� getProductWorkOrderById 杩斿洖鐨勫伐鍗曟暟鎹�
+ this.reportForm.quantity = null;
+ this.reportForm.addQty = row.processRouteAddNum ? Number(row.processRouteAddNum) : 0;
+ this.reportForm.scrapQty = 0;
+ this.reportDialogVisible = true;
+}
+```
+
+### 4. 鎶ュ伐鎻愪氦鍙傛暟
+
+璋冪敤 `POST /productionProductMain/addProductMain` 鏃讹紝璇锋眰浣撻渶鍖呭惈鍔犳斁鏁帮細
+
+```js
+const params = {
+ workOrderId: this.reportForm.workOrderId,
+ productProcessRouteItemId: this.reportForm.productProcessRouteItemId,
+ quantity: this.reportForm.quantity, // 浜у嚭鏁伴噺
+ addQty: this.reportForm.addQty, // 鍔犳斁鏁�
+ scrapQty: this.reportForm.scrapQty, // 鎶ュ簾鏁伴噺
+ teamList: this.teamList,
+ deviceId: this.deviceId,
+ deviceName: this.deviceName,
+ startTime: this.startTime,
+ endTime: this.endTime,
+};
+```
+
+## 娉ㄦ剰浜嬮」
+
+- 鍔犳斁鏁� `processRouteAddNum` 涓哄瓧绗︿覆绫诲瀷锛屽墠绔渶 `Number()` 杞崲鍚庡啀浣滀负榛樿鍊煎~鍏ャ��
+- 鎶ュ伐鏁伴噺鏍哥畻鍒嗕袱涓彛寰勶細
+ - **宸ュ簭娴佽浆 / 鎬讳骇閲�**锛堜笉鎵f姤搴燂紝淇濇寔杩炶疮锛夛細宸ュ崟瀹屾垚鏁伴噺銆佺敓浜ц鍗曞畬鎴愭暟閲忋�佸悇宸ュ簭鎬讳骇閲忕粺璁°�佹瘡鏃ヤ骇鍑虹粺璁� = **浜у嚭鏁伴噺 + 鍔犳斁鏁�**銆�
+ - **瀹為檯鍏ュ簱 / 璐ㄦ / 璁′欢宸ヨ祫**锛堟墸鎶ュ簾锛夛細鍏ュ簱鏁伴噺銆佽川妫�鏁伴噺銆佺敓浜ф牳绠楋紙璁′欢宸ヨ祫锛夈�佹瘡鏃ュ伐璧勭粺璁� = **浜у嚭鏁伴噺 + 鍔犳斁鏁� - 鎶ュ簾鏁伴噺**銆�
+- 鎶ュ伐鎶ュ簾锛坰crapQty锛変粎鍋氭暟鎹褰曪紝涓嶆墸鍑忓伐搴忔祦杞熀鏁帮紝鍚勫伐搴忔�讳骇閲忎繚鎸佽繛璐��
+- 鍘嗗彶鎶ュ伐鏁版嵁鑻� `add_qty` 涓虹┖锛屽悗绔寜 0 澶勭悊锛屼笉褰卞搷鏃ф暟鎹牳绠椼��
diff --git a/docs/sales_ledger_auto_invoice_receipt.md b/docs/sales_ledger_auto_invoice_receipt.md
new file mode 100644
index 0000000..16ac602
--- /dev/null
+++ b/docs/sales_ledger_auto_invoice_receipt.md
@@ -0,0 +1,109 @@
+# 鏂板閿�鍞彴璐﹁嚜鍔ㄥ紑绁ㄣ�佽嚜鍔ㄥ洖娆�
+
+鏂板閿�鍞彴璐︼紙椤甸潰鏂板銆丒xcel 瀵煎叆锛夋椂锛屽悗绔嚜鍔ㄦ寜鍚堝悓鍏ㄩ鐢熸垚寮�绁ㄧ櫥璁颁笌鍥炴鐧昏锛屾棤闇�鍓嶇鍐嶅崟鐙皟鐢ㄥ紑绁ㄣ�佸洖娆炬帴鍙c��
+
+## 娑夊強椤甸潰
+
+- 閿�鍞彴璐﹀垪琛ㄩ〉锛堟柊澧�/缂栬緫寮圭獥銆佸鍏ワ級
+- 寮�绁ㄧ櫥璁伴〉闈�
+- 鍥炴鐧昏椤甸潰
+- 璐㈠姟绠$悊 - 鏀跺叆绠$悊椤甸潰
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /sales/ledger/addOrUpdateSalesLedger | 鏂板/淇敼閿�鍞彴璐︼紙浠呮柊澧炴椂瑙﹀彂鑷姩寮�绁ㄣ�佸洖娆撅級 |
+| POST | /sales/ledger/import | 瀵煎叆閿�鍞彴璐︼紙姣忔潯瀵煎叆鎴愬姛鐨勫彴璐﹀潎瑙﹀彂鑷姩寮�绁ㄣ�佸洖娆撅級 |
+
+**璇锋眰鍙傛暟锛�** 涓庡師鏈夊畬鍏ㄤ竴鑷达紝鏃犳柊澧炲瓧娈点��
+
+`/sales/ledger/addOrUpdateSalesLedger` 璇锋眰绀轰緥锛�
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鍚� | 涓虹┖=鏂板锛堣Е鍙戣嚜鍔ㄥ紑绁ㄥ洖娆撅級锛岄潪绌�=淇敼锛堜笉瑙﹀彂锛� |
+| customerId | Long | 鏄� | 瀹㈡埛ID |
+| projectName | String | 鍚� | 椤圭洰鍚嶇О |
+| entryDate | String | 鍚� | 褰曞叆鏃ユ湡 yyyy-MM-dd |
+| salesman | String | 鍚� | 涓氬姟鍛� |
+| entryPerson | String | 鍚� | 褰曞叆浜� |
+| type | Integer | 鏄� | 鍙拌处绫诲瀷 1-閿�鍞� |
+| productData | Array | 鏄� | 浜у搧鍒楄〃锛坬uantity銆乼axInclusiveTotalPrice 绛夛紝鍚屽師缁撴瀯锛� |
+| tempFileIds | Array | 鍚� | 涓存椂闄勪欢ID鍒楄〃 |
+
+**鍝嶅簲锛�** `{ "code": 200, "msg": "鎿嶄綔鎴愬姛" }`锛堢粨鏋勪笉鍙橈級
+
+## 鍚庣鑷姩鐢熸垚鐨勬暟鎹紙鍓嶇鏃犻渶璋冪敤鎺ュ彛锛�
+
+鏂板鎴愬姛鍚庯紝鍚庣鍦ㄥ悓涓�浜嬪姟鍐呰嚜鍔ㄧ敓鎴愶細
+
+1. 寮�绁ㄧ櫥璁帮細`invoice_registration` 涓昏〃 + 姣忎釜浜у搧涓�鏉� `invoice_registration_product` + 姣忎釜浜у搧涓�鏉� `invoice_ledger`锛�
+ - 寮�绁ㄦ暟 = 浜у搧鏁伴噺锛屽紑绁ㄩ噾棰� = 浜у搧鍚◣鎬讳环锛堝叏棰濆紑绁級锛�
+ - 鍙戠エ鍙疯嚜鍔ㄧ敓鎴愶紝鏍煎紡 `AUTO + 鏃堕棿鎴� + 浜у搧ID`锛堝 `AUTO1755500000000123`锛夛紱
+ - 寮�绁ㄦ椂闂� = 褰撳ぉ锛屽紑绁ㄤ汉 = 褰撳墠鐧诲綍浜恒��
+2. 鍥炴鐧昏锛氭瘡涓骇鍝佷竴鏉� `receipt_payment`锛�
+ - 鍥炴閲戦 = 寮�绁ㄥ叏棰濓紝鍥炴褰㈠紡 = 0锛堢數姹囷級锛屾潵娆炬棩鏈� = 褰撳ぉ锛�
+ - 宸插叧鑱斿搴旂殑寮�绁ㄥ彴璐� `invoiceLedgerId`銆�
+3. 璐㈠姟鏀跺叆锛氭瘡鏉″洖娆惧搴斾竴鏉� `account_income`锛堟敹鍏ョ被鍨�=鍥炴鏀跺叆锛夈��
+
+## 鍓嶇淇敼鐐�
+
+### 1. 鏂板寮圭獥锛氭棤闇�鏀瑰姩璇锋眰鍙傛暟
+
+鏂板淇濆瓨鎴愬姛鍚庯紝寤鸿鎻愮ず璇腑璇存槑宸茶嚜鍔ㄥ紑绁ㄥ洖娆撅細
+
+```html
+<el-button type="primary" @click="submitForm">淇� 瀛�</el-button>
+```
+
+```js
+submitForm() {
+ addOrUpdateSalesLedger(this.form).then(res => {
+ if (res.code === 200) {
+ const msg = this.form.id ? '淇敼鎴愬姛' : '鏂板鎴愬姛锛屽凡鑷姩鍏ㄩ寮�绁ㄥ苟鍏ㄩ鍥炴';
+ this.$modal.msgSuccess(msg);
+ this.open = false;
+ this.getList();
+ }
+ });
+}
+```
+
+### 2. 鍒楄〃缂栬緫鎸夐挳锛氭柊澧炲悗榛樿涓嶅彲缂栬緫
+
+鍚庣鍒楄〃鎺ュ彛 `/sales/ledger/listPage` 杩斿洖鐨� `isEdit` 瑙勫垯涓嶅彉锛氬彧瑕佸瓨鍦ㄥ紑绁ㄦ垨鍥炴璁板綍鍗充负 `false`銆傜敱浜庢柊澧炲嵆鑷姩寮�绁ㄥ洖娆撅紝鏂板彴璐� `isEdit` 鎭掍负 `false`锛岀紪杈戞寜閽簲绂佺敤锛�
+
+```html
+<el-button
+ size="mini"
+ type="text"
+ :disabled="scope.row.isEdit === false"
+ @click="handleUpdate(scope.row)"
+>淇敼</el-button>
+```
+
+```js
+data() {
+ return {
+ // 鏃犻渶鏂板瀛楁锛屾部鐢� scope.row.isEdit
+ }
+}
+```
+
+濡傞渶淇敼宸茶嚜鍔ㄥ紑绁ㄥ洖娆剧殑鍙拌处锛岃璧般�屽垹闄ゃ�嶅悗閲嶆柊鏂板锛堝垹闄や細绾ц仈娓呯悊寮�绁ㄣ�佸洖娆惧強璐㈠姟鏀跺叆璁板綍锛夈��
+
+### 3. 寮�绁ㄧ櫥璁� / 鍥炴鐧昏椤甸潰
+
+- 寮�绁ㄧ櫥璁板垪琛ㄤ細鍑虹幇鑷姩鐢熸垚鐨勮褰曪紝鍙戠エ鍙峰甫 `AUTO` 鍓嶇紑锛屽彲鍋氬睍绀哄尯鍒嗭紱
+- 鏂板鍙拌处鍚庯紝瀵瑰簲浜у搧鐨勩�屾湭寮�绁ㄦ暟/鏈紑绁ㄩ噾棰濄�嶄负 0锛屽紑绁ㄧ櫥璁板脊绐椾腑璇ヤ骇鍝佷笉鍙啀娆″紑绁紙鍚庣浼氭牎楠岃秴閲忥級锛�
+- 鍥炴鐧昏椤甸潰鍚岀悊锛岃嚜鍔ㄥ洖娆捐褰曞凡鍏宠仈鍙戠エ鍙枫��
+
+## 娉ㄦ剰浜嬮」
+
+- 浠� `id` 涓虹┖鐨勬柊澧炰細瑙﹀彂鑷姩寮�绁ㄥ洖娆撅紝淇敼鍙拌处涓嶄細瑙﹀彂銆�
+- 鑷姩寮�绁ㄥ洖娆句粎閽堝 `type=1`锛堥攢鍞級鐨勪骇鍝併��
+- 浜у搧鏁伴噺鎴栧惈绋庢�讳环涓虹┖/涓� 0 鏃惰烦杩囪浜у搧鐨勮嚜鍔ㄥ紑绁ㄥ洖娆撅紝涓嶉樆鏂彴璐︽柊澧炪��
+- Excel 瀵煎叆涓庨〉闈㈡柊澧炶涓轰竴鑷达紝鍧囪嚜鍔ㄥ紑绁ㄥ洖娆俱��
+- 鍒犻櫎閿�鍞彴璐︿細绾ц仈鍒犻櫎鑷姩鐢熸垚鐨勫紑绁ㄧ櫥璁般�佸紑绁ㄥ彴璐︺�佸洖娆剧櫥璁板強瀵瑰簲璐㈠姟鏀跺叆璁板綍銆�
+- 鍙拌处鏂板鍚庡闇�璋冩暣锛岃鍒犻櫎閲嶅缓锛岄伩鍏嶇洿鎺ヤ慨鏀瑰鑷村紑绁�/鍥炴鏁版嵁涓嶄竴鑷淬��
diff --git a/docs/seal_approval_unified_review.md b/docs/seal_approval_unified_review.md
new file mode 100644
index 0000000..79862eb
--- /dev/null
+++ b/docs/seal_approval_unified_review.md
@@ -0,0 +1,177 @@
+# 鐢ㄥ嵃瀹℃壒鎺ュ叆鍗忓悓缁熶竴瀹℃牳
+
+## 娑夊強椤甸潰
+
+- `src/views/collaborativeApproval/sealManagement/index.vue`锛堢敤鍗扮鐞嗭細绉婚櫎椤甸潰鍐呭鎵癸紝鐢宠/閲嶆柊鐢宠鍙戣捣鍗忓悓瀹℃壒锛屾敮鎸侀檮浠朵笂浼狅級
+- `src/api/collaborativeApproval/sealManagement.js`锛堟柊澧� `getSealDetail` 鎺ュ彛锛�
+- `src/views/collaborativeApproval/approvalProcess/index.vue`锛堝崗鍚屽鎵癸細鏂板"鐢ㄥ嵃瀹℃壒"椤电锛�
+- `src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue`锛堝鎵�/璇︽儏寮规锛氬睍绀虹敤鍗拌鎯呬笌闄勪欢锛�
+- `src/views/collaborativeApproval/approvalManagement/index.vue`锛堝鎵圭鐞嗭細鏂板"鐢ㄥ嵃瀹℃壒"绫诲瀷閰嶇疆锛�
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /common/uploadsList | 閫氱敤澶氭枃浠朵笂浼狅紙鏈湴纾佺洏锛岄潪 minio锛夛紝杩斿洖鏂囦欢鍒楄〃 |
+| POST | /sealApplicationManagement/add | 鏂板/閲嶆柊鐢宠鐢ㄥ嵃锛屽彂璧� type=9 鍗忓悓瀹℃壒锛屼繚瀛橀檮浠� |
+| GET | /sealApplicationManagement/detail | 鎸夊鎵规祦绋� id 鏌ヨ鐢ㄥ嵃鐢宠璇︽儏锛堝惈闄勪欢锛� |
+| GET | /sealApplicationManagement/getList | 鍒嗛〉鏌ヨ锛堟瘡鏉¤褰曢檮甯� commonFileList 闄勪欢锛� |
+| POST | /approveProcess/exportNine | 鐢ㄥ嵃瀹℃壒瀵煎嚭 |
+
+### 1. 涓婁紶闄勪欢
+
+**POST /common/uploadsList**
+
+- 璇锋眰锛歚multipart/form-data`锛屽瓧娈靛悕 `files`锛堝彲澶氫釜锛�
+- 鍝嶅簲锛�
+```json
+{
+ "code": 200,
+ "msg": "鎿嶄綔鎴愬姛",
+ "data": [
+ { "name": "鍘熷鏂囦欢鍚�.pdf", "url": "http://host/profile/upload/xxx.pdf" }
+ ]
+}
+```
+
+### 2. 鏂板/閲嶆柊鐢宠鐢ㄥ嵃
+
+**POST /sealApplicationManagement/add**
+
+璇锋眰鍙傛暟锛圝SON锛夛細
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鍚� | 瀛樺湪鏃朵负閲嶆柊鐢宠锛堝鐢ㄦ棫鍗曢噸鏂板彂璧峰鎵癸級 |
+| applicationNum | String | 鏄� | 鐢宠缂栧彿 |
+| title | String | 鏄� | 鐢宠鏍囬 |
+| sealType | String | 鏄� | 鐢ㄥ嵃绫诲瀷锛坥fficial/contract/finance/legal锛� |
+| reason | String | 鏄� | 鐢宠鍘熷洜 |
+| urgency | String | 鍚� | 绱ф�ョ▼搴︼紙normal/urgent/very-urgent锛� |
+| deptId | Long | 鏄� | 鐢宠閮ㄩ棬 |
+| attachmentList | Array | 鍚� | 闄勪欢 `[{ name, url }]` |
+
+璇存槑锛�
+- 鏈嶅姟绔己鍒� `status = "pending"`锛岃嚜鍔ㄥ垱寤哄鎵规祦绋嬶紙`approveType = 9`锛夛紝娴佺▼鍙峰瓨鍥� `approve_process_id`銆�
+- 闄勪欢鍐欏叆 `common_file` 琛紙`common_id` = 瀹℃壒娴佺▼ DB 涓婚敭锛宍type` = 8 ApproveProcess锛夈��
+- 閲嶆柊鐢宠鏃朵細鍏堟竻鐞嗘棫瀹℃壒娴佺▼鍏宠仈鐨勯檮浠躲��
+
+### 3. 鐢ㄥ嵃璇︽儏
+
+**GET /sealApplicationManagement/detail?approveProcessId=xxx**
+
+- 璇锋眰鍙傛暟锛歚approveProcessId`锛堝鎵规祦绋� DB 涓婚敭锛屽嵆鍗忓悓瀹℃壒鍒楄〃璁板綍鐨� `id`锛�
+- 鍝嶅簲 data 涓虹敤鍗板崟瀵硅薄锛屽惈 `commonFileList` 闄勪欢鍒楄〃銆�
+
+## 鍓嶇淇敼鐐�
+
+### 1. 鐢ㄥ嵃绠$悊椤碉紙sealManagement/index.vue锛�
+
+- 绉婚櫎瀹℃壒浜洪�夋嫨銆佸鎵�/鎷掔粷鎿嶄綔鎸夐挳锛涙搷浣滃垪浠呬繚鐣�"閲嶆柊鐢宠"锛堢姸鎬佷负 rejected 涓斾负鏈汉鍒涘缓锛変笌"璇︽儏"銆�
+- 闄勪欢涓婁紶鏀逛负鍘熺敓 el-upload锛屾寚鍚� `/common/uploadsList`锛�
+
+```html
+<el-upload
+ multiple
+ :show-file-list="false"
+ :action="sealUploadUrl"
+ name="files"
+ :headers="uploadHeaders"
+ :limit="10"
+ :before-upload="handleBeforeUpload"
+ :on-success="handleUploadSuccess"
+ :on-error="handleUploadError"
+ :on-exceed="handleUploadExceed"
+>
+ <el-button type="primary">鐐瑰嚮涓婁紶闄勪欢</el-button>
+</el-upload>
+```
+
+```js
+const sealUploadUrl = import.meta.env.VITE_APP_BASE_API + '/common/uploadsList'
+const uploadHeaders = ref({ Authorization: 'Bearer ' + getToken() })
+const sealForm = reactive({
+ id: null, applicationNum: '', title: '', sealType: '',
+ reason: '', urgency: 'normal', status: 'pending', attachmentList: []
+})
+// 涓婁紶鎴愬姛鍥炶皟锛氭妸 {name, url} 杩藉姞杩� attachmentList
+const handleUploadSuccess = (res) => {
+ if (res.code === 200) {
+ const list = res.data || []
+ list.forEach(item => sealForm.attachmentList.push({ name: item.name, url: item.url }))
+ ElMessage.success('涓婁紶鎴愬姛')
+ } else {
+ ElMessage.error(res.msg || '涓婁紶澶辫触')
+ }
+}
+```
+
+- 鎻愪氦涓庨噸鏂扮敵璇峰潎璋冪敤 `addSealApplication(sealForm)`锛涢噸鏂扮敵璇烽濉� `attachmentList: row.commonFileList || []`銆�
+- 棰勮闄勪欢蹇呴』鐢ㄥ畬鏁� URL 鐩村紑锛歚filePreviewRef.value.openUrl(url)`锛堜笉瑕佺敤 `open`锛屼細鍐嶆嫾 host锛夈��
+- 涓嬭浇闄勪欢浣跨敤 `<a>` 閿氱偣鏂瑰紡锛坄download` 鎻掍欢鏃� `byUrl` 鏂规硶锛夈��
+
+### 2. API 灏佽锛坰ealManagement.js锛�
+
+```js
+// 鐢ㄥ嵃鐢宠璇︽儏锛堟寜瀹℃壒娴佺▼id锛�
+export function getSealDetail(approveProcessId) {
+ return request({
+ url: "/sealApplicationManagement/detail",
+ method: "get",
+ params: { approveProcessId },
+ });
+}
+```
+
+### 3. 鍗忓悓瀹℃壒椤碉紙approvalProcess/index.vue锛�
+
+- `approveTypes` 鏂板 `{ value: '9', label: '鐢ㄥ嵃瀹℃壒', icon: 'Stamp', color: '#7D3C98' }`銆�
+- 绫诲瀷 9 闅愯棌"鏂板/鍒犻櫎"鎸夐挳锛坄v-if` 鏉′欢杩藉姞 `&& currentApproveType !== 9`锛夈��
+- 绫诲瀷 9 鐨�"缂栬緫"鎸夐挳 `disabled`锛堜笌 5/6/7 涓�鑷达級銆�
+- 绫诲瀷 9 涓嶅睍绀�"闄勪欢"鎿嶄綔鎸夐挳锛堥檮浠惰蛋 common_file锛屼笉璧� storage_attachment锛夛細
+
+```js
+if (!isQuotationType && currentApproveType.value !== 9) {
+ actionOperations.push({ name: "闄勪欢", /* ... */ })
+}
+```
+
+- 瀵煎嚭鏄犲皠杩藉姞锛�
+
+```js
+const urlMap = { /* ... */ 9: "/approveProcess/exportNine" }
+const nameMap = { /* ... */ 9: "鐢ㄥ嵃瀹℃壒琛�" }
+```
+
+### 4. 瀹℃壒寮规锛坅pprovalDia.vue锛�
+
+- 鏂板 `isSealApproval = computed(() => Number(props.approveType) === 9)`銆�
+- 鐢ㄥ嵃瀹℃壒鎵撳紑寮规鏃讹紝鐢� `row.id`锛堝鎵规祦绋� DB 涓婚敭锛夎姹� `getSealDetail(row.id)`锛屾覆鏌撶敤鍗拌鎯� + 闄勪欢琛ㄦ牸锛堥瑙�/涓嬭浇锛夈��
+
+```js
+if (isSealApproval.value) {
+ sealLoading.value = true
+ getSealDetail(row.id).then((res) => {
+ currentSeal.value = res.data || {}
+ }).catch((err) => {
+ proxy.$modal.msgError('鏌ヨ鐢ㄥ嵃璇︽儏澶辫触')
+ }).finally(() => {
+ sealLoading.value = false
+ })
+}
+```
+
+- 闄勪欢棰勮/涓嬭浇涓庣敤鍗扮鐞嗛〉涓�鑷达紙`openUrl` + 閿氱偣涓嬭浇锛夈��
+
+### 5. 瀹℃壒绠$悊閰嶇疆椤碉紙approvalManagement/index.vue锛�
+
+- `approveTypes`銆乣approveTypeNameMap`銆乣typeIconMap`銆乣typeColorMap` 鍧囪拷鍔� type 9锛坙abel 鐢ㄥ嵃瀹℃壒锛宨con Stamp锛夈��
+
+## 娉ㄦ剰浜嬮」
+
+- 闄勪欢涓婁紶涓嶅厑璁镐娇鐢� minio锛岀粺涓�璧� `/common/uploadsList`锛堟湰鍦扮鐩樺瓨鍌級銆�
+- 鐢ㄥ嵃鍗曚笌瀹℃壒娴佺▼閫氳繃 `seal_application_management.approve_process_id`锛堝鎵规祦绋� DB 涓婚敭锛夊叧鑱旓紱鍗忓悓瀹℃壒鍒楄〃涓璁板綍鐨� `id` 鍗冲鎵规祦绋� DB 涓婚敭銆�
+- 鐢ㄥ嵃瀹℃壒绫诲瀷鍊间负 `approveType = 9`锛涘鎵硅妭鐐归厤缃渶鍦ㄥ鎵圭鐞�-鐢ㄥ嵃瀹℃壒椤电涓嬮厤缃紙DB 鍒濆鍖栬剼鏈 `sql/seal_application_add_approve_process.sql`锛夈��
+- 瀹℃壒閫氳繃/椹冲洖鍚庯紝鏈嶅姟绔細鍥炶皟鏇存柊鐢ㄥ嵃鍗� `status`锛坅pproved/rejected/pending锛夛紝鍓嶇鍒楄〃鍒锋柊鍗冲彲鐪嬪埌鏈�鏂扮姸鎬併��
+- `filePreview.open(url)` 浼氬湪 URL 鍓嶆嫾鎺� `window.location.host`锛屼紶鍏ュ畬鏁� URL 鏃跺繀椤讳娇鐢� `filePreview.openUrl(url)`銆�
+- 鍓嶇 `download` 鎻掍欢涓嶅瓨鍦� `byUrl` 鏂规硶锛屽畬鏁� URL 涓嬭浇璇蜂娇鐢� `<a>` 閿氱偣鏂瑰紡銆�
diff --git a/docs/stock_inventory_model_query.md b/docs/stock_inventory_model_query.md
new file mode 100644
index 0000000..9f68986
--- /dev/null
+++ b/docs/stock_inventory_model_query.md
@@ -0,0 +1,68 @@
+# 搴撳瓨姹囨�诲垪琛ㄨ鏍煎瀷鍙锋ā绯婃煡璇�
+
+## 娑夊強椤甸潰
+
+- 搴撳瓨绠$悊 - 搴撳瓨姹囨�诲垪琛�
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /stockInventory/pageListCombinedStockInventory | 搴撳瓨姹囨�诲垪琛ㄦ煡璇紝鏂板瑙勬牸鍨嬪彿妯$硦鏌ヨ鍙傛暟 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| model | String | 鍚� | 瑙勬牸鍨嬪彿锛屾ā绯婃煡璇紙鍖归厤 product_model.model锛� |
+| productName | String | 鍚� | 浜у搧鍚嶇О锛屾ā绯婃煡璇� |
+| topParentProductId | Long | 鍚� | 椤堕儴鐖朵骇鍝� id |
+
+**鍝嶅簲锛�** 鍝嶅簲缁撴瀯涓嶅彉锛屼粛涓� `StockInventoryDto` 鍒嗛〉鍒楄〃锛屽寘鍚� `model`銆乣productName`銆乣qualifiedQuantity`銆乣unQualifiedQuantity` 绛夊瓧娈点��
+
+## 鍓嶇淇敼鐐�
+
+### 1. 鎼滅储鏉′欢
+
+鍦ㄥ簱瀛樻眹鎬诲垪琛ㄩ〉闈㈢殑鎼滅储琛ㄥ崟涓鍔犺鏍煎瀷鍙疯緭鍏ユ锛�
+
+```html
+<el-form-item label="瑙勬牸鍨嬪彿" prop="model">
+ <el-input v-model="queryParams.model" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" clearable />
+</el-form-item>
+```
+
+### 2. data 鏁版嵁
+
+```js
+data() {
+ return {
+ queryParams: {
+ pageNum: 1,
+ pageSize: 10,
+ productName: undefined,
+ model: undefined,
+ topParentProductId: undefined,
+ },
+ }
+}
+```
+
+### 3. 鏂规硶
+
+```js
+/** 鏌ヨ搴撳瓨姹囨�诲垪琛� */
+function getList() {
+ loading.value = true
+ listStockInventory(queryParams.value).then(response => {
+ stockList.value = response.rows
+ total.value = response.total
+ loading.value = false
+ })
+}
+```
+
+## 娉ㄦ剰浜嬮」
+
+- `model` 涓哄彲閫夊弬鏁帮紝涓嶄紶鏃惰繑鍥炲叏閮ㄦ暟鎹紝涓嶅奖鍝嶅師鏈夋煡璇㈤�昏緫銆�
+- 瑙勬牸鍨嬪彿妯$硦鏌ヨ鍚屾椂浣滅敤浜庡悎鏍煎簱瀛橈紙stock_inventory锛夊拰涓嶅悎鏍煎簱瀛橈紙stock_uninventory锛夊悎骞跺悗鐨勬暟鎹��
diff --git a/docs/work_order_batch_report_fix.md b/docs/work_order_batch_report_fix.md
new file mode 100644
index 0000000..48f9c4d
--- /dev/null
+++ b/docs/work_order_batch_report_fix.md
@@ -0,0 +1,55 @@
+# 宸ュ崟鍒嗘壒鎶ュ伐淇
+
+## 闂鎻忚堪
+
+鎶ュ伐涓�娆″悗锛堝畬鎴愭暟閲忔湭杈捐鍒掓暟閲忥級锛�
+1. 宸ュ崟銆屽疄闄呯粨鏉熸椂闂淬�嶈鑷姩濉厖锛�
+2. 宸ュ崟鍒楄〃杩斿洖 `hasUnreportedMachine=true`锛屽墠绔�屾姤宸ャ�嶆寜閽绂佺敤锛屽鑷存棤娉曠户缁垎鎵规姤宸ョ洿鑷冲畬鎴愩��
+
+## 淇鍐呭锛堢函鍚庣琛屼负 + 鍓嶇涓�琛屾敼鍔紝鏃犳柊澧�/淇敼鎺ュ彛锛�
+
+### 1. 瀹為檯缁撴潫鏃堕棿浠呭湪瀹屾垚鍚庡~鍏�
+
+`ProductionProductMainServiceImpl.addProductMain`锛�
+
+- 鍘熼�昏緫锛氭瘡娆℃姤宸ユ棤鏉′欢璁剧疆宸ュ崟 `actualEndTime` / `endProductTime` 涓烘姤宸ョ粨鏉熸椂闂达紱
+- 淇敼鍚庯細浠呭綋 `completeQuantity >= planQuantity` 鏃跺~鍏咃紱鏈畬鎴愭椂鏄惧紡娓呯┖ `actualEndTime`/`endProductTime`锛堣嚜鎰堝巻鍙茶剰鏁版嵁锛宍updateById` 榛樿蹇界暐 null 瀛楁锛屾晠鐢� `UpdateWrapper` 鏄惧紡缃┖锛夈��
+
+### 2. 鍒嗘壒鎶ュ伐鏍哥畻鎸夋瘡娆℃姤宸ョ嫭绔嬬敓鎴�
+
+`ProductionProductMainServiceImpl.nextAddProductMain`锛�
+
+- 鍘熼�昏緫锛氱敓浜ф牳绠楀湪銆屾湭鎶ュ伐鐨勪笂鏈鸿褰曘�嶅惊鐜唴鐢熸垚锛岀涓�娆℃姤宸ュ悗涓婃満璁板綍 `report_status=true`锛岀浜屾鎶ュ伐琚� `continue` 璺宠繃锛�**涓嶄細鐢熸垚鏍哥畻**锛�
+- 淇敼鍚庯細鎶ュ伐浼犱簡鐝粍浜哄憳鏄庣粏 `personList` 鏃讹紝**姣忔鎶ュ伐鐙珛鎸変汉鐢熸垚涓�鏉℃牳绠�**锛坄finished_num` 鎸夊悇浜烘瘺浜ч噺鍗犳瘮鍒嗘憡鍑�鍊硷紝`work_hour` 鍙栧綍鍏ユ椂闀匡級锛屼笉鍐嶅彈涓婃満璁板綍鎶ュ伐鐘舵�佸奖鍝嶏紱璁惧鍙栨姤宸ュ崟 `deviceId`锛屾湭濉啓鏃跺洖閫�鍙栬宸ュ崟浠讳竴鍙版満銆�
+- 鏈紶 `personList`锛堟棫鏁版嵁锛夋椂浠嶅洖閫�鎸夋湭鎶ュ伐鐨勬満鍙颁笂鏈烘搷浣滃憳鐢熸垚鏍哥畻锛屽師閫昏緫淇濈暀銆�
+
+### 3. 鍓嶇鎶ュ伐鎸夐挳涓嶅啀鍙椾笂鏈鸿褰曟姤宸ョ姸鎬侀檺鍒�
+
+`src/views/productionManagement/workOrder/index.vue`锛�
+
+```js
+// 淇敼鍓�
+disabled: row =>
+ row.completeQuantity >= row.planQuantity ||
+ !canCurrentUserReport(row) ||
+ row.hasUnreportedMachine,
+
+// 淇敼鍚�
+disabled: row =>
+ row.completeQuantity >= row.planQuantity ||
+ !canCurrentUserReport(row),
+```
+
+銆屾姤宸ャ�嶆寜閽粎鍦ㄣ�屽畬鎴愭暟閲� >= 璁″垝鏁伴噺銆嶆垨銆屽綋鍓嶇敤鎴蜂笉鍦ㄥ伐鍗�/宸ュ簭鎶ュ伐浜轰腑銆嶆椂绂佺敤锛屽厑璁稿垎鎵规姤宸ョ洿鑷冲畬鎴愩��
+
+## 鏁版嵁搴撹涓洪獙璇�
+
+浠ュ伐鍗� `DC26081690001`锛堣鍒� 16037锛変负渚嬶紝鎶ュ伐 100 鍚庯細
+- `actual_end_time` 涓嶅啀濉厖锛坄completeQuantity=100 < 16037`锛夛紱
+- 宸ュ崟鍒楄〃 `has_unreported_machine` 浠嶄細璁$畻杩斿洖锛堝墠绔凡涓嶅啀鐢ㄤ簬绂佺敤鎶ュ伐鎸夐挳锛夛紱
+- 缁х画鎶ュ伐鏃讹紝绗簩娆℃姤宸ヤ篃浼氭甯哥敓鎴愮敓浜ф牳绠椼��
+
+## 娑夊強鏂囦欢
+
+- `src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java`
+- `src/views/productionManagement/workOrder/index.vue`
diff --git a/sql/production_report_person_create.sql b/sql/production_report_person_create.sql
new file mode 100644
index 0000000..8013e96
--- /dev/null
+++ b/sql/production_report_person_create.sql
@@ -0,0 +1,16 @@
+# 鎶ュ伐鐝粍浜哄憳浜ч噺鏃堕暱鏄庣粏琛�
+create table if not exists production_report_person
+(
+ id bigint auto_increment primary key,
+ product_main_id bigint not null comment '鎶ュ伐涓昏〃id(production_product_main.id)',
+ user_id bigint not null comment '鐢熶骇浜篿d',
+ user_name varchar(50) null comment '鐢熶骇浜哄鍚�',
+ quantity decimal(16,4) not null default 0 comment '璇ヤ汉鍛樹骇鍑烘暟閲�',
+ work_hour int not null default 0 comment '璇ヤ汉鍛樼敓浜ф椂闀�(鍒嗛挓)',
+ tenant_id bigint null comment '绉熸埛id',
+ create_time datetime null comment '鍒涘缓鏃堕棿',
+ update_time datetime null comment '鏇存柊鏃堕棿',
+ create_user bigint null comment '鍒涘缓浜�',
+ update_user bigint null comment '鏇存柊浜�',
+ dept_id bigint null comment '閮ㄩ棬id'
+) engine=innodb default charset=utf8mb4 comment='鎶ュ伐鐝粍浜哄憳浜ч噺鏃堕暱鏄庣粏';
diff --git a/sql/quality_inspect_add_work_order_id.sql b/sql/quality_inspect_add_work_order_id.sql
new file mode 100644
index 0000000..b42845d
--- /dev/null
+++ b/sql/quality_inspect_add_work_order_id.sql
@@ -0,0 +1,5 @@
+# 璐ㄩ噺妫�楠岃〃澧炲姞鐢熶骇宸ュ崟id锛岀敤浜庢帓浜т笅杈炬椂棰勭敓鎴愮涓�閬撳伐搴忚川妫�鍗曪紙姝ゆ椂鏃犳姤宸d锛�
+alter table quality_inspect
+ add column work_order_id bigint null comment '鐢熶骇宸ュ崟id(鎺掍骇涓嬭揪棰勭敓鎴愯川妫�鍗曠敤)' after product_main_id;
+
+create index idx_quality_inspect_work_order_id on quality_inspect (work_order_id);
diff --git a/sql/seal_application_add_approve_process.sql b/sql/seal_application_add_approve_process.sql
new file mode 100644
index 0000000..7c7eff1
--- /dev/null
+++ b/sql/seal_application_add_approve_process.sql
@@ -0,0 +1,16 @@
+-- 鐢ㄥ嵃鐢宠绠$悊鎺ュ叆鍗忓悓缁熶竴瀹℃牳
+-- 浣跨敤鍓嶇‘璁ゅ綋鍓嶆暟鎹簱涓轰笟鍔″簱锛堝 product-inventory-management-ygcy锛�
+
+-- 1. 鐢ㄥ嵃鐢宠鍗曞鍔犲叧鑱斿崗鍚屽鎵规祦绋媔d瀛楁
+ALTER TABLE seal_application_management
+ ADD COLUMN approve_process_id bigint DEFAULT NULL COMMENT '鍏宠仈鍗忓悓瀹℃壒娴佺▼id(approve_process.id)' AFTER approve_user_id;
+
+-- 2. 瀹℃壒娴佺▼閰嶇疆锛氭柊澧炩�滅敤鍗板鎵光��(approve_type=9)瀹℃壒鑺傜偣
+-- 榛樿瀹℃壒浜猴細绗竴绾� 绠$悊閮�(236)锛岀浜岀骇 璧垫嫢鍐�(214)锛屽彲鍦ㄣ�愬鎵圭鐞嗐�戣彍鍗曡皟鏁�
+INSERT INTO approve_process_config_node (approve_type, node_order, approver_id, approver_name, timeout_hours, tenant_id, create_user, create_time, update_user, update_time, delete_flag, dept_id)
+SELECT 9, 1, 236, '绠$悊閮�', NULL, 1, 1, NOW(), 1, NOW(), 0, NULL FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM approve_process_config_node WHERE approve_type = 9 AND delete_flag = 0);
+
+INSERT INTO approve_process_config_node (approve_type, node_order, approver_id, approver_name, timeout_hours, tenant_id, create_user, create_time, update_user, update_time, delete_flag, dept_id)
+SELECT 9, 2, 214, '璧垫嫢鍐�', NULL, 1, 1, NOW(), 1, NOW(), 0, NULL FROM DUAL
+WHERE NOT EXISTS (SELECT 1 FROM approve_process_config_node WHERE approve_type = 9 AND node_order = 2 AND delete_flag = 0);
diff --git a/src/main/java/com/ruoyi/approve/controller/ApproveProcessController.java b/src/main/java/com/ruoyi/approve/controller/ApproveProcessController.java
index 56940f4..a252c87 100644
--- a/src/main/java/com/ruoyi/approve/controller/ApproveProcessController.java
+++ b/src/main/java/com/ruoyi/approve/controller/ApproveProcessController.java
@@ -188,4 +188,14 @@
ExcelUtil<ApproveProcess> util = new ExcelUtil<ApproveProcess>(ApproveProcess.class);
util.exportExcel(response, accountExpenses, "鍗遍櫓浣滀笟瀹℃壒瀵煎嚭");
}
+
+ @ApiOperation(value = "鐢ㄥ嵃瀹℃壒瀵煎嚭")
+ @PostMapping("/exportNine")
+ public void exportNine(HttpServletResponse response) {
+ List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
+ .eq(ApproveProcess::getApproveDelete, 0)
+ .eq(ApproveProcess::getApproveType, 9));
+ ExcelUtil<ApproveProcess> util = new ExcelUtil<ApproveProcess>(ApproveProcess.class);
+ util.exportExcel(response, accountExpenses, "鐢ㄥ嵃瀹℃壒瀵煎嚭");
+ }
}
diff --git a/src/main/java/com/ruoyi/approve/pojo/ApproveNode.java b/src/main/java/com/ruoyi/approve/pojo/ApproveNode.java
index 09e3f02..b47919e 100644
--- a/src/main/java/com/ruoyi/approve/pojo/ApproveNode.java
+++ b/src/main/java/com/ruoyi/approve/pojo/ApproveNode.java
@@ -48,6 +48,11 @@
private Integer approveNodeOrder;
/**
+ * 瀹℃壒妯″紡锛歰r=鎴栫锛宎nd=浼氱
+ */
+ private String approveMode;
+
+ /**
* 瀹℃壒鑺傜偣鐢ㄦ埛鍚嶅瓧
*/
private String approveNodeUser;
diff --git a/src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java b/src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java
index 278cf61..7a47f0f 100644
--- a/src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java
+++ b/src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java
@@ -92,6 +92,21 @@
private String approveUserCurrentName;
/**
+ * 褰撳墠鑺傜偣寰呭浜篒D锛堥�楀彿鍒嗛殧锛屾垨绛�/浼氱澶氫汉锛�
+ */
+ private String approveUserCurrentIds;
+
+ /**
+ * 褰撳墠鑺傜偣寰呭浜哄鍚嶏紙閫楀彿鍒嗛殧锛�
+ */
+ private String approveUserCurrentNames;
+
+ /**
+ * 鑺傜偣缁撴瀯蹇収JSON锛歔{nodeOrder,mode,userIds,userNames}]锛岀敤浜庣紪杈戦噸寤哄鎵硅妭鐐�
+ */
+ private String approveNodeConfig;
+
+ /**
* 鐢宠鏃ユ湡
*/
@JsonFormat(pattern = "yyyy-MM-dd")
diff --git a/src/main/java/com/ruoyi/approve/pojo/ApproveProcessConfigNode.java b/src/main/java/com/ruoyi/approve/pojo/ApproveProcessConfigNode.java
index bcb3635..065efe5 100644
--- a/src/main/java/com/ruoyi/approve/pojo/ApproveProcessConfigNode.java
+++ b/src/main/java/com/ruoyi/approve/pojo/ApproveProcessConfigNode.java
@@ -49,6 +49,12 @@
@ApiModelProperty("鑺傜偣椤哄簭")
private Integer nodeOrder;
+ /**
+ * 瀹℃壒妯″紡锛歰r=鎴栫锛宎nd=浼氱
+ */
+ @ApiModelProperty("瀹℃壒妯″紡锛歰r=鎴栫锛宎nd=浼氱")
+ private String approveMode;
+
/**
* 瀹℃壒浜篒Ds锛堝涓敤閫楀彿鍒嗛殧锛�
diff --git a/src/main/java/com/ruoyi/approve/service/IApproveNodeService.java b/src/main/java/com/ruoyi/approve/service/IApproveNodeService.java
index eac4cce..ab9dadc 100644
--- a/src/main/java/com/ruoyi/approve/service/IApproveNodeService.java
+++ b/src/main/java/com/ruoyi/approve/service/IApproveNodeService.java
@@ -1,6 +1,7 @@
package com.ruoyi.approve.service;
import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.approve.bean.vo.ApproveProcessConfigNodeVo;
import com.ruoyi.approve.pojo.ApproveNode;
import java.io.IOException;
@@ -9,6 +10,11 @@
public interface IApproveNodeService extends IService<ApproveNode> {
void initApproveNodes(String approveUserIds,String approveID,Long tenantId);
+
+ /**
+ * 鎸夐厤缃妭鐐瑰垪琛ㄥ垵濮嬪寲瀹℃壒鑺傜偣锛堟敮鎸佸悓鑺傜偣澶氬鎵逛汉涓庢垨绛�/浼氱妯″紡锛�
+ */
+ void initApproveNodes(List<ApproveProcessConfigNodeVo> config, String approveID, Long tenantId);
/**
* 璇︽儏
* @param id
diff --git a/src/main/java/com/ruoyi/approve/service/IApproveProcessService.java b/src/main/java/com/ruoyi/approve/service/IApproveProcessService.java
index abad2d3..d306c1b 100644
--- a/src/main/java/com/ruoyi/approve/service/IApproveProcessService.java
+++ b/src/main/java/com/ruoyi/approve/service/IApproveProcessService.java
@@ -15,8 +15,9 @@
/**
* 娣诲姞瀹℃壒娴佺▼
* @param approveProcessVO 瀹℃壒娴佺▼VO瀵硅薄
+ * @return 瀹℃壒娴佺▼涓婚敭id
*/
- void addApprove(ApproveProcessVO approveProcessVO) throws Exception;
+ Long addApprove(ApproveProcessVO approveProcessVO) throws Exception;
/**
* 鏍规嵁閮ㄩ棬id鏌ヨ閮ㄩ棬淇℃伅
* @param deptIds 閮ㄩ棬ID鏁扮粍
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
index 17666b7..28eff5e 100644
--- a/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
@@ -6,11 +6,14 @@
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.approve.bean.vo.ApproveProcessConfigNodeVo;
import com.ruoyi.approve.mapper.ApproveNodeMapper;
import com.ruoyi.approve.mapper.ApproveProcessMapper;
import com.ruoyi.approve.pojo.ApproveNode;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.service.IApproveNodeService;
+import com.ruoyi.collaborativeApproval.mapper.SealApplicationManagementMapper;
+import com.ruoyi.collaborativeApproval.pojo.SealApplicationManagement;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
@@ -47,10 +50,13 @@
import java.io.IOException;
import java.time.LocalDateTime;
-import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@@ -73,6 +79,7 @@
private final QualityTestStandardMapper qualityTestStandardMapper;
private final QualityTestStandardParamMapper qualityTestStandardParamMapper;
private final QualityInspectParamMapper qualityInspectParamMapper;
+ private final SealApplicationManagementMapper sealApplicationManagementMapper;
public ApproveProcess getApproveById(String id) {
@@ -87,25 +94,69 @@
@Override
public void initApproveNodes(String approveUserIds, String approveID, Long tenantId) {
+ if (approveUserIds == null || approveUserIds.isEmpty()) {
+ return;
+ }
+ List<ApproveProcessConfigNodeVo> config = new ArrayList<>();
+ String[] ids = approveUserIds.split(",");
+ for (int i = 0; i < ids.length; i++) {
+ if (ids[i] == null || ids[i].trim().isEmpty()) {
+ continue;
+ }
+ ApproveProcessConfigNodeVo vo = new ApproveProcessConfigNodeVo();
+ vo.setNodeOrder(i + 1);
+ vo.setApproverId(Long.parseLong(ids[i].trim()));
+ vo.setApproveMode("or");
+ config.add(vo);
+ }
+ initApproveNodes(config, approveID, tenantId);
+ }
+
+ @Override
+ public void initApproveNodes(List<ApproveProcessConfigNodeVo> config, String approveID, Long tenantId) {
Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
- String[] names = approveUserIds.split(",");
- for (int i = 0; i < names.length; i++) {
- SysUser sysUser = sysUserMapper.selectUserById(Long.parseLong(names[i]));
- if (sysUser == null) continue;
- ApproveNode approveNode = new ApproveNode();
- approveNode.setApproveProcessId(approveID);
- approveNode.setApproveNodeOrder(i + 1);
- approveNode.setApproveNodeUser(sysUser.getNickName());
- approveNode.setApproveNodeUserId(sysUser.getUserId());
- approveNode.setApproveNodeTime(new Date());
- approveNode.setApproveNodeStatus(0);
- approveNode.setTenantId(tenantId);
- approveNode.setDeleteFlag(0);
- approveNode.setCreateUser(userId);
- approveNode.setUpdateUser(userId);
- approveNode.setCreateTime(LocalDateTime.now());
- approveNode.setUpdateTime(LocalDateTime.now());
- approveNodeMapper.insert(approveNode);
+ // 鎸夎妭鐐归『搴忓垎缁勶紝淇濊瘉鍚屼竴鑺傜偣鐨勫瀹℃壒浜哄啓鍏ョ浉鍚� approveNodeOrder
+ Map<Integer, List<ApproveProcessConfigNodeVo>> groups = new LinkedHashMap<>();
+ for (ApproveProcessConfigNodeVo vo : config) {
+ if (vo.getApproverId() == null) {
+ continue;
+ }
+ int order = vo.getNodeOrder() == null ? groups.size() + 1 : vo.getNodeOrder();
+ groups.computeIfAbsent(order, k -> new ArrayList<>()).add(vo);
+ }
+ for (Map.Entry<Integer, List<ApproveProcessConfigNodeVo>> entry : groups.entrySet()) {
+ Integer order = entry.getKey();
+ String mode = entry.getValue().get(0).getApproveMode();
+ if (mode == null || mode.isEmpty()) {
+ mode = "or";
+ }
+ List<Long> seenIds = new ArrayList<>();
+ for (ApproveProcessConfigNodeVo vo : entry.getValue()) {
+ Long approverId = vo.getApproverId();
+ if (seenIds.contains(approverId)) {
+ continue;
+ }
+ seenIds.add(approverId);
+ SysUser sysUser = sysUserMapper.selectUserById(approverId);
+ if (sysUser == null) {
+ continue;
+ }
+ ApproveNode approveNode = new ApproveNode();
+ approveNode.setApproveProcessId(approveID);
+ approveNode.setApproveNodeOrder(order);
+ approveNode.setApproveMode(mode);
+ approveNode.setApproveNodeUser(sysUser.getNickName());
+ approveNode.setApproveNodeUserId(sysUser.getUserId());
+ approveNode.setApproveNodeTime(new Date());
+ approveNode.setApproveNodeStatus(0);
+ approveNode.setTenantId(tenantId);
+ approveNode.setDeleteFlag(0);
+ approveNode.setCreateUser(userId);
+ approveNode.setUpdateUser(userId);
+ approveNode.setCreateTime(LocalDateTime.now());
+ approveNode.setUpdateTime(LocalDateTime.now());
+ approveNodeMapper.insert(approveNode);
+ }
}
}
@@ -114,76 +165,56 @@
LambdaQueryWrapper<ApproveNode> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ApproveNode::getApproveProcessId, id);
queryWrapper.eq(ApproveNode::getDeleteFlag, 0);
-// queryWrapper.eq(ApproveNode::getApproveNodeStatus, 0);
List<ApproveNode> list = list(queryWrapper);
- // 鎸夌収 approveNodeOrder 瀛楁鍗囧簭鎺掑簭
- list.sort(Comparator.comparingInt(ApproveNode::getApproveNodeOrder));
- LambdaQueryWrapper<ApproveProcess> approveProcessLambdaQueryWrapper = new LambdaQueryWrapper<>();
- approveProcessLambdaQueryWrapper.eq(ApproveProcess::getApproveId, id)
- .eq(ApproveProcess::getApproveDelete, 0)
-// .eq(ApproveProcess::getApproveStatus, 0)
- .last("limit 1");
- ApproveProcess approveProcess = approveProcessMapper.selectOne(approveProcessLambdaQueryWrapper);
- if (approveProcess != null && approveProcess.getApproveStatus() == 3) {
+ // 鎸� approveNodeOrder 鍗囧簭銆佸悓鑺傜偣鎸� id 鍗囧簭锛屼繚璇佸悓鑺傜偣澶氬鎵逛汉椤哄簭绋冲畾
+ list.sort(Comparator.comparingInt(ApproveNode::getApproveNodeOrder).thenComparingLong(ApproveNode::getId));
+ bindNodeUrl(list);
+ ApproveProcess approveProcess = getApproveById(id);
+ if (approveProcess == null || approveProcess.getApproveStatus() == 2
+ || approveProcess.getApproveStatus() == 3 || approveProcess.getApproveStatus() == 4) {
+ // 娴佺▼宸插畬鎴�/椹冲洖/閲嶆柊鎻愪氦锛屽叏閮ㄥ彧璇�
return list;
}
+ Integer currentOrder = pendingOrder(id);
+ Long loginUserId = SecurityUtils.getLoginUser().getUser().getUserId();
+ boolean admin = SecurityUtils.isAdmin(loginUserId);
+ boolean ishenSet = false;
for (ApproveNode approveNode : list) {
- List<CommonFile> commonFiles = fileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
- .eq(CommonFile::getCommonId, approveNode.getId())
- .eq(CommonFile::getType, FileNameType.ApproveNode.getValue()));
- if (!CollectionUtils.isEmpty(commonFiles)) {
- approveNode.setUrl(commonFiles.get(0).getUrl());
+ // 绠$悊鍛樹笉鍙楀鎵逛汉闄愬埗锛氫唬瀹″綋鍓嶈妭鐐归涓緟瀹¤褰曪紙鏁磋妭鐐逛唬瀹$敱 updateApproveNode 瀹屾垚锛�
+ if (approveNode.getApproveNodeOrder().equals(currentOrder)
+ && approveNode.getApproveNodeStatus() == 0
+ && (admin || approveNode.getApproveNodeUserId().equals(loginUserId))
+ && !ishenSet) {
+ approveNode.setIsShen(true);
+ ishenSet = true;
}
- if (approveNode.getApproveNodeStatus() == 1) {
- continue;
- }
- approveNode.setIsShen(true);
- break;
}
return list;
}
- public void updateApproveProcessStatus(ApproveNode approveNode, Integer status) throws IOException {
- LambdaQueryWrapper<ApproveProcess> approveProcessLambdaQueryWrapper = new LambdaQueryWrapper<>();
- approveProcessLambdaQueryWrapper.eq(ApproveProcess::getApproveId, approveNode.getApproveProcessId())
- .eq(ApproveProcess::getApproveDelete, 0)
- .last("limit 1");
- ApproveProcess approveProcess = approveProcessMapper.selectOne(approveProcessLambdaQueryWrapper);
- if (approveProcess == null) throw new RuntimeException("瀹℃壒涓嶅瓨鍦�");
- LambdaQueryWrapper<ApproveNode> approveNodeLambdaQueryWrapper = new LambdaQueryWrapper<>();
- approveNodeLambdaQueryWrapper.eq(ApproveNode::getApproveProcessId, approveNode.getApproveProcessId())
- .eq(ApproveNode::getApproveNodeOrder, approveNode.getApproveNodeOrder() + 1)
- .eq(ApproveNode::getDeleteFlag, 0)
- .eq(ApproveNode::getApproveNodeStatus, 0)
- .last("limit 1");
- ApproveNode approveNode1 = approveNodeMapper.selectOne(approveNodeLambdaQueryWrapper);
- approveProcess.setApproveStatus(status);
- if (approveNode1 != null) {
- approveProcess.setApproveUserCurrentId(approveNode1.getApproveNodeUserId());
- approveProcess.setApproveUserCurrentName(approveNode1.getApproveNodeUser());
- }
- if (approveProcess.getApproveStatus().equals(2) || approveProcess.getApproveStatus().equals(3) || approveProcess.getApproveStatus().equals(4)) {
- approveProcess.setApproveOverTime(new Date());
- }
- approveProcessMapper.updateById(approveProcess);
-
- //閲囪喘瀹℃牳
- if(approveProcess.getApproveType().equals(5)){
+ /**
+ * 涓氬姟鍥炶皟锛氭寜瀹℃壒绫诲瀷鏇存柊瀵瑰簲涓氬姟鍗曟嵁鐘舵��
+ * status: 1=瀹℃牳涓�(浼氱閮ㄥ垎閫氳繃), 2=瀹℃牳瀹屾垚(鍚屾剰), 3=瀹℃牳鏈�氳繃(鎷掔粷)
+ */
+ private void applyBusinessStatus(ApproveProcess approveProcess, Integer status) {
+ // 閲囪喘瀹℃壒
+ if (approveProcess.getApproveType().equals(5)) {
PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
.eq(PurchaseLedger::getPurchaseContractNumber, approveProcess.getApproveReason())
.last("limit 1"));
- if(purchaseLedger != null) {
+ if (purchaseLedger != null) {
if (status.equals(2)) {
- // 鍚屾剰
+ // 鍚屾剰锛氱敓鎴愯川妫�/鍏ュ簱
purchaseLedger.setApprovalStatus(3);
List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(new QueryWrapper<SalesLedgerProduct>()
.lambda().eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId()).eq(SalesLedgerProduct::getType, 2));
for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
- // 璐ㄦ
if (salesLedgerProduct.getIsChecked()) {
- addQualityInspect(purchaseLedger, salesLedgerProduct); }else {
- //鐩存帴鍏ュ簱
- stockUtils.addStock(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId(),"-","-","-");
+ addQualityInspect(purchaseLedger, salesLedgerProduct);
+ } else {
+ // 鐩存帴鍏ュ簱
+ stockUtils.addStock(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(),
+ StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId(), "-", "-", "-");
}
}
} else if (status.equals(3)) {
@@ -197,78 +228,265 @@
}
}
// 閿�鍞姤浠风姸鎬佷慨鏀�
- if(approveProcess.getApproveType().equals(6)){
+ if (approveProcess.getApproveType().equals(6)) {
SalesQuotation salesQuote = salesQuotationMapper.selectOne(new LambdaQueryWrapper<SalesQuotation>()
.eq(SalesQuotation::getQuotationNo, approveProcess.getApproveReason())
.last("limit 1"));
- // 鍚屾剰
- if(status.equals(2) && salesQuote != null){
- salesQuote.setStatus("閫氳繃");
- }else if(status.equals(3) && salesQuote != null){
- salesQuote.setStatus("鎷掔粷");
- }else if(status.equals(1) && salesQuote != null){
- salesQuote.setStatus("瀹℃牳涓�");
- }
- salesQuotationMapper.updateById(salesQuote);
- }
- // 鍑哄簱瀹℃壒淇敼
- if(approveProcess.getApproveType().equals(7)){
- String[] split = approveProcess.getApproveReason().split(":");
- ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
- .eq(ShippingInfo::getShippingNo, split[1])
- .orderByDesc(ShippingInfo::getCreateTime)
- .last("limit 1"));
- if(shippingInfo != null){
- if(status.equals(2)){
- shippingInfo.setStatus("瀹℃牳閫氳繃");
- }else if(status.equals(3)){
- shippingInfo.setStatus("瀹℃牳鎷掔粷");
- }else if(status.equals(1)){
- shippingInfo.setStatus("瀹℃牳涓�");
+ if (salesQuote != null) {
+ if (status.equals(2)) {
+ salesQuote.setStatus("閫氳繃");
+ } else if (status.equals(3)) {
+ salesQuote.setStatus("鎷掔粷");
+ } else if (status.equals(1)) {
+ salesQuote.setStatus("瀹℃牳涓�");
}
- shippingInfoMapper.updateById(shippingInfo);
+ salesQuotationMapper.updateById(salesQuote);
}
-
}
- // 缁戝畾闄勪欢
- if(!CollectionUtils.isEmpty(approveNode.getTempFileIds()) && approveNode.getApproveNodeStatus() == 1){
- tempFileService.migrateTempFilesToFormal(approveNode.getId(), approveNode.getTempFileIds(), FileNameType.ApproveNode.getValue());
+ // 鍑哄簱瀹℃壒鐘舵�佷慨鏀�
+ if (approveProcess.getApproveType().equals(7)) {
+ String[] split = approveProcess.getApproveReason().split(":");
+ if (split.length > 1) {
+ ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
+ .eq(ShippingInfo::getShippingNo, split[1])
+ .orderByDesc(ShippingInfo::getCreateTime)
+ .last("limit 1"));
+ if (shippingInfo != null) {
+ if (status.equals(2)) {
+ shippingInfo.setStatus("瀹℃牳閫氳繃");
+ } else if (status.equals(3)) {
+ shippingInfo.setStatus("瀹℃牳鎷掔粷");
+ } else if (status.equals(1)) {
+ shippingInfo.setStatus("瀹℃牳涓�");
+ }
+ shippingInfoMapper.updateById(shippingInfo);
+ }
+ }
+ }
+ // 鐢ㄥ嵃瀹℃壒鐘舵�佷慨鏀�
+ if (approveProcess.getApproveType().equals(9)) {
+ SealApplicationManagement seal = sealApplicationManagementMapper.selectOne(new LambdaQueryWrapper<SealApplicationManagement>()
+ .eq(SealApplicationManagement::getApproveProcessId, approveProcess.getId())
+ .last("limit 1"));
+ if (seal != null) {
+ if (status.equals(2)) {
+ seal.setStatus("approved");
+ } else if (status.equals(3)) {
+ seal.setStatus("rejected");
+ } else if (status.equals(1)) {
+ seal.setStatus("pending");
+ }
+ sealApplicationManagementMapper.updateById(seal);
+ }
+ }
+ }
+
+ /**
+ * 褰撳墠寰呭鎵硅妭鐐瑰簭鍙凤細鎵�鏈夋湭瀹℃壒鑺傜偣涓渶灏忕殑 approveNodeOrder
+ */
+ private Integer pendingOrder(String approveId) {
+ return list(new LambdaQueryWrapper<ApproveNode>()
+ .eq(ApproveNode::getApproveProcessId, approveId)
+ .eq(ApproveNode::getDeleteFlag, 0)
+ .eq(ApproveNode::getApproveNodeStatus, 0))
+ .stream()
+ .map(ApproveNode::getApproveNodeOrder)
+ .min(Integer::compareTo)
+ .orElse(null);
+ }
+
+ /**
+ * 鏌ヨ鎸囧畾娴佺▼鎸囧畾鑺傜偣鐨勫叏閮ㄥ鎵硅妭鐐癸紙鍚岃妭鐐瑰瀹℃壒浜猴紝鎸� id 鍗囧簭锛�
+ */
+ private List<ApproveNode> nodesByOrder(String approveId, Integer order) {
+ return list(new LambdaQueryWrapper<ApproveNode>()
+ .eq(ApproveNode::getApproveProcessId, approveId)
+ .eq(ApproveNode::getApproveNodeOrder, order)
+ .eq(ApproveNode::getDeleteFlag, 0)
+ .orderByAsc(ApproveNode::getId));
+ }
+
+ /**
+ * 璁剧疆娴佺▼褰撳墠鑺傜偣寰呭浜猴紙澶氬�奸�楀彿鍒嗛殧锛屽吋瀹规棫鐨勫崟鍊煎瓧娈碉級
+ */
+ private void setCurrentApprovers(ApproveProcess approveProcess, List<ApproveNode> nodes) {
+ if (nodes == null || nodes.isEmpty()) {
+ approveProcess.setApproveUserCurrentIds(null);
+ approveProcess.setApproveUserCurrentNames(null);
+ approveProcess.setApproveUserCurrentId(null);
+ approveProcess.setApproveUserCurrentName(null);
+ return;
+ }
+ approveProcess.setApproveUserCurrentIds(nodes.stream().map(ApproveNode::getApproveNodeUserId)
+ .map(String::valueOf).collect(Collectors.joining(",")));
+ approveProcess.setApproveUserCurrentNames(nodes.stream().map(ApproveNode::getApproveNodeUser)
+ .collect(Collectors.joining(",")));
+ approveProcess.setApproveUserCurrentId(nodes.get(0).getApproveNodeUserId());
+ approveProcess.setApproveUserCurrentName(nodes.get(0).getApproveNodeUser());
+ }
+
+ /**
+ * 閫氱煡褰撳墠鑺傜偣鍏ㄩ儴寰呭浜�
+ */
+ private void notifyApproveUsers(List<Long> userIds, ApproveProcess approveProcess) {
+ if (userIds == null || userIds.isEmpty()) {
+ return;
+ }
+ List<Long> ids = userIds.stream().distinct().collect(Collectors.toList());
+ if (approveProcess.getApproveType() == 8) {
+ sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
+ approveProcess.getApproveId() + "娴佺▼缂栧彿鐨勫鎵归渶瑕佹偍瀹℃牳!!!!!",
+ ids,
+ "/safeProduction/safeWorkApproval?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
+ } else {
+ sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
+ approveProcess.getApproveId() + "娴佺▼缂栧彿鐨勫鎵归渶瑕佹偍瀹℃牳!!!!!",
+ ids,
+ "/collaborativeApproval/approvalProcess?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
+ }
+ }
+
+ /**
+ * 濉厖瀹℃壒鑺傜偣闄勪欢 url
+ */
+ private void bindNodeUrl(List<ApproveNode> nodes) {
+ if (CollectionUtils.isEmpty(nodes)) {
+ return;
+ }
+ for (ApproveNode approveNode : nodes) {
+ List<CommonFile> commonFiles = fileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
+ .eq(CommonFile::getCommonId, approveNode.getId())
+ .eq(CommonFile::getType, FileNameType.ApproveNode.getValue()));
+ if (!CollectionUtils.isEmpty(commonFiles)) {
+ approveNode.setUrl(commonFiles.get(0).getUrl());
+ }
+ }
+ }
+
+ /**
+ * 涓存椂闄勪欢杩佺Щ涓烘寮忛檮浠�
+ */
+ private void bindNodeFiles(ApproveNode approveNode, List<String> tempFileIds) throws IOException {
+ if (!CollectionUtils.isEmpty(tempFileIds) && approveNode.getApproveNodeStatus() != null
+ && approveNode.getApproveNodeStatus() == 1) {
+ tempFileService.migrateTempFilesToFormal(approveNode.getId(), tempFileIds, FileNameType.ApproveNode.getValue());
}
}
@Override
public void updateApproveNode(ApproveNode approveNode) throws IOException {
- // 瀹℃壒鑺傜偣鐘舵��:1鍚屾剰锛�2鎷掔粷锛�0灏氭湭瀹℃牳
- switch (approveNode.getApproveNodeStatus()){
- case 1:
- updateApproveProcessStatus(approveNode, Boolean.TRUE.equals(approveNode.getIsLast()) ? 2 : 1);
- /*娑堟伅閫氱煡*/
- Integer nodeOrder = approveNode.getApproveNodeOrder();
- ApproveProcess approveProcess = approveProcessMapper.selectList(Wrappers.<ApproveProcess>lambdaQuery()
- .eq(ApproveProcess::getApproveId, approveNode.getApproveProcessId())).get(0);
- if (approveProcess.getApproveUserIds().split(",").length > nodeOrder){
- String id = approveProcess.getApproveUserIds().split(",")[nodeOrder];
- if (approveProcess.getApproveType()==8){
- sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
- approveProcess.getApproveId() + "娴佺▼缂栧彿鐨勫鎵归渶瑕佹偍瀹℃牳!!!!!",
- Arrays.asList(Long.valueOf(id)),
- "/safeProduction/safeWorkApproval?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
- }else {
- sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
- approveProcess.getApproveId() + "娴佺▼缂栧彿鐨勫鎵归渶瑕佹偍瀹℃牳!!!!!",
- Arrays.asList(Long.valueOf(id)),
- "/collaborativeApproval/approvalProcess?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
- }
- }
- break;
- case 2:
- updateApproveProcessStatus(approveNode, 3);
- break;
- default:
- break;
+ ApproveNode db = approveNodeMapper.selectById(approveNode.getId());
+ if (db == null || db.getDeleteFlag() != null && db.getDeleteFlag() == 1) {
+ throw new RuntimeException("瀹℃壒鑺傜偣涓嶅瓨鍦�");
}
- approveNode.setApproveNodeTime(new Date());
- approveNodeMapper.updateById(approveNode);
+ if (db.getApproveNodeStatus() != null && db.getApproveNodeStatus() != 0) {
+ throw new RuntimeException("璇ヨ妭鐐瑰凡瀹℃壒锛岃鍕块噸澶嶆搷浣�");
+ }
+ Long loginUserId = SecurityUtils.getLoginUser().getUser().getUserId();
+ boolean admin = SecurityUtils.isAdmin(loginUserId);
+ // 绠$悊鍛樹笉鍙楀鎵逛汉闄愬埗
+ if (!admin && !db.getApproveNodeUserId().equals(loginUserId)) {
+ throw new RuntimeException("鏃犳潈瀹℃壒璇ヨ妭鐐�");
+ }
+ ApproveProcess approveProcess = getApproveById(db.getApproveProcessId());
+ if (approveProcess == null) {
+ throw new RuntimeException("瀹℃壒娴佺▼涓嶅瓨鍦�");
+ }
+ if (approveProcess.getApproveStatus() == null
+ || (approveProcess.getApproveStatus() != 0 && approveProcess.getApproveStatus() != 1)) {
+ throw new RuntimeException("褰撳墠娴佺▼鐘舵�佷笉鍏佽瀹℃壒");
+ }
+ Integer curOrder = pendingOrder(db.getApproveProcessId());
+ if (!db.getApproveNodeOrder().equals(curOrder)) {
+ throw new RuntimeException("灏氭湭杞埌璇ヨ妭鐐瑰鎵�");
+ }
+
+ db.setApproveNodeStatus(approveNode.getApproveNodeStatus());
+ db.setApproveNodeReason(approveNode.getApproveNodeReason());
+ db.setApproveNodeRemark(approveNode.getApproveNodeRemark());
+ db.setApproveNodeTime(new Date());
+ db.setUpdateUser(loginUserId);
+ approveNodeMapper.updateById(db);
+
+ // 鎷掔粷锛氫竴绁ㄥ惁鍐筹紝绔嬪嵆椹冲洖鏁翠釜娴佺▼
+ if (db.getApproveNodeStatus() != null && db.getApproveNodeStatus() == 2) {
+ approveProcess.setApproveStatus(3);
+ approveProcess.setApproveOverTime(new Date());
+ approveProcessMapper.updateById(approveProcess);
+ applyBusinessStatus(approveProcess, 3);
+ bindNodeFiles(db, approveNode.getTempFileIds());
+ return;
+ }
+
+ String mode = db.getApproveMode();
+ if (mode == null || mode.isEmpty()) {
+ mode = "or";
+ }
+ List<ApproveNode> sameOrder = nodesByOrder(db.getApproveProcessId(), db.getApproveNodeOrder());
+ // 绠$悊鍛樹唬瀹★細褰撳墠鑺傜偣鍏朵綑寰呭璁板綍涓�骞堕�氳繃
+ if (admin) {
+ for (ApproveNode n : sameOrder) {
+ if (n.getApproveNodeStatus() == null || n.getApproveNodeStatus() == 0) {
+ n.setApproveNodeStatus(1);
+ n.setApproveNodeRemark("绠$悊鍛樹唬瀹¢�氳繃");
+ n.setApproveNodeTime(new Date());
+ approveNodeMapper.updateById(n);
+ }
+ }
+ }
+ boolean released;
+ if ("and".equals(mode)) {
+ // 浼氱锛氳妭鐐瑰唴鎵�鏈変汉鍧囧悓鎰忔墠鏀捐
+ released = sameOrder.stream().allMatch(n -> n.getApproveNodeStatus() != null && n.getApproveNodeStatus() == 1);
+ } else {
+ // 鎴栫锛氳妭鐐瑰唴浠讳竴鍚屾剰鍗虫斁琛�
+ released = sameOrder.stream().anyMatch(n -> n.getApproveNodeStatus() != null && n.getApproveNodeStatus() == 1);
+ }
+
+ // 浼氱鏈斁琛岋細鑺傜偣鍐呬粛鏈夊緟瀹′汉锛屾祦绋嬩繚鎸佸鏍镐腑
+ if (!released) {
+ List<ApproveNode> pending = sameOrder.stream()
+ .filter(n -> n.getApproveNodeStatus() == null || n.getApproveNodeStatus() == 0)
+ .collect(Collectors.toList());
+ approveProcess.setApproveStatus(1);
+ setCurrentApprovers(approveProcess, pending);
+ approveProcessMapper.updateById(approveProcess);
+ applyBusinessStatus(approveProcess, 1);
+ bindNodeFiles(db, approveNode.getTempFileIds());
+ return;
+ }
+
+ // 鎴栫鏀捐锛氬悓鑺傜偣鍏朵綑鏈鎵硅褰曟爣璁颁负鑷姩鍚屾剰
+ if (!"and".equals(mode)) {
+ for (ApproveNode n : sameOrder) {
+ if (n.getApproveNodeStatus() == null || n.getApproveNodeStatus() == 0) {
+ n.setApproveNodeStatus(1);
+ n.setApproveNodeRemark("鎴栫锛氬叾浠栧鎵逛汉宸查�氳繃");
+ n.setApproveNodeTime(new Date());
+ approveNodeMapper.updateById(n);
+ }
+ }
+ }
+
+ List<ApproveNode> next = nodesByOrder(db.getApproveProcessId(), db.getApproveNodeOrder() + 1).stream()
+ .filter(n -> n.getApproveNodeStatus() == null || n.getApproveNodeStatus() == 0)
+ .collect(Collectors.toList());
+ if (next.isEmpty()) {
+ // 鏈�鍚庤妭鐐规斁琛岋細娴佺▼瀹屾垚
+ approveProcess.setApproveStatus(2);
+ approveProcess.setApproveOverTime(new Date());
+ setCurrentApprovers(approveProcess, new ArrayList<>());
+ approveProcessMapper.updateById(approveProcess);
+ applyBusinessStatus(approveProcess, 2);
+ } else {
+ // 娴佽浆涓嬩竴鑺傜偣
+ approveProcess.setApproveStatus(1);
+ setCurrentApprovers(approveProcess, next);
+ approveProcessMapper.updateById(approveProcess);
+ applyBusinessStatus(approveProcess, 1);
+ notifyApproveUsers(next.stream().map(ApproveNode::getApproveNodeUserId).collect(Collectors.toList()), approveProcess);
+ }
+ bindNodeFiles(db, approveNode.getTempFileIds());
}
@Override
@@ -304,6 +522,8 @@
return "鍙戣揣瀹℃壒";
case 8:
return "鍗遍櫓浣滀笟瀹℃壒";
+ case 9:
+ return "鐢ㄥ嵃瀹℃壒";
}
return null;
}
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessConfigNodeServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessConfigNodeServiceImpl.java
index 6d4124c..e067fc4 100644
--- a/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessConfigNodeServiceImpl.java
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessConfigNodeServiceImpl.java
@@ -27,7 +27,10 @@
private final ApproveProcessConfigNodeMapper approveProcessConfigNodeMapper;
@Override
public List<ApproveProcessConfigNodeVo> listNode(Integer type) {
- List<ApproveProcessConfigNode> approveProcessConfigNodes = approveProcessConfigNodeMapper.selectList(new QueryWrapper<ApproveProcessConfigNode>().lambda().eq(ApproveProcessConfigNode::getApproveType, type).orderByAsc(ApproveProcessConfigNode::getNodeOrder));
+ List<ApproveProcessConfigNode> approveProcessConfigNodes = approveProcessConfigNodeMapper.selectList(new QueryWrapper<ApproveProcessConfigNode>().lambda()
+ .eq(ApproveProcessConfigNode::getApproveType, type)
+ .orderByAsc(ApproveProcessConfigNode::getNodeOrder)
+ .orderByAsc(ApproveProcessConfigNode::getId));
return approveProcessConfigNodes.stream()
.map(node -> {
ApproveProcessConfigNodeVo vo = new ApproveProcessConfigNodeVo();
@@ -43,6 +46,10 @@
approveProcessConfigNodeMapper.delete(new QueryWrapper<ApproveProcessConfigNode>().lambda().eq(ApproveProcessConfigNode::getApproveType, approveProcessConfigNodes.get(0).getApproveType()));
//鏂板 鏁版嵁
for (ApproveProcessConfigNode node : approveProcessConfigNodes) {
+ // 榛樿鎴栫妯″紡锛屽吋瀹规湭浼� approve_mode 鐨勬棫鏁版嵁
+ if (node.getApproveMode() == null || node.getApproveMode().isEmpty()) {
+ node.setApproveMode("or");
+ }
approveProcessConfigNodeMapper.insert(node);
}
return true;
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
index c152605..2ac1bae 100644
--- a/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -16,6 +16,8 @@
import com.ruoyi.approve.service.ApproveProcessConfigNodeService;
import com.ruoyi.approve.service.IApproveNodeService;
import com.ruoyi.approve.service.IApproveProcessService;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
@@ -50,6 +52,7 @@
@RequiredArgsConstructor
public class ApproveProcessServiceImpl extends ServiceImpl<ApproveProcessMapper, ApproveProcess> implements IApproveProcessService {
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final SysDeptMapper sysDeptMapper;
private final IApproveNodeService approveNodeService;
@@ -65,10 +68,14 @@
private final ApproveProcessConfigNodeService approveProcessConfigNodeService;
@Override
- public void addApprove(ApproveProcessVO approveProcessVO) throws Exception {
+ public Long addApprove(ApproveProcessVO approveProcessVO) throws Exception {
SysUser sysUser = SecurityUtils.getLoginUser().getUser();
SysDept sysDept = sysDeptMapper.selectDeptById(SecurityUtils.getLoginUser().getCurrentDeptId());
- List<ApproveProcessConfigNodeVo> list = approveProcessConfigNodeService.listNode( approveProcessVO.getApproveType());
+ List<ApproveProcessConfigNodeVo> list = approveProcessConfigNodeService.listNode(approveProcessVO.getApproveType());
+ // 鎸夎妭鐐归『搴忓垎缁勶紙鍚岃妭鐐瑰瀹℃壒浜猴級
+ List<List<ApproveProcessConfigNodeVo>> groups = groupByOrder(list);
+ if (groups.isEmpty()) throw new RuntimeException("娣诲姞瀹℃壒鑺傜偣鍜屼汉鍛�");
+ List<ApproveProcessConfigNodeVo> firstGroup = groups.get(0);
List<Long> nodeIds = list.stream()
.map(ApproveProcessConfigNodeVo::getApproverId)
.collect(Collectors.toList());
@@ -104,40 +111,23 @@
approveProcess.setApproveType(approveProcessVO.getApproveType());
approveProcess.setCreateTime(LocalDateTime.now());
approveProcess.setTenantId(approveProcessVO.getApproveDeptId());
- approveProcess.setApproveUserCurrentId(nodeIds.get(0));
- approveProcess.setApproveUserCurrentName(sysUsers
- .stream()
- .filter(SysUser -> SysUser.getUserId().equals(nodeIds.get(0)))
- .collect(Collectors.toList())
- .get(0)
- .getNickName());
+ setFirstNodeApprovers(approveProcess, firstGroup, sysUsers);
// 璁剧疆鐘舵�佷负閲嶆柊鎻愪氦
if (approveProcessVO.getId() != null) {
ApproveProcess approveProcess1 = approveProcessMapper.selectById(approveProcessVO.getId());
approveProcess1.setApproveStatus(4);
approveProcessMapper.updateById(approveProcess1);
}
+ // 鑺傜偣缁撴瀯蹇収锛堜緵缂栬緫閲嶅缓瀹℃壒鑺傜偣鏃朵繚鍒嗙粍涓庢ā寮忥級
+ approveProcess.setApproveNodeConfig(buildNodeConfig(list));
save(approveProcess);
- //鍒濆鍖栧鎵硅妭鐐�
- String nodeIdStr = nodeIds.stream()
- .map(String::valueOf)
- .collect(Collectors.joining(","));
- approveNodeService.initApproveNodes(nodeIdStr, no, approveProcessVO.getApproveDeptId());
+ //鍒濆鍖栧鎵硅妭鐐癸紙鎸夐厤缃妭鐐瑰垎缁勶紝鏀寔鍚岃妭鐐瑰瀹℃壒浜�/鎴栫浼氱锛�
+ approveNodeService.initApproveNodes(list, no, approveProcessVO.getApproveDeptId());
// 闄勪欢缁戝畾
tempFileService.migrateTempFilesToFormal(approveProcess.getId(), approveProcessVO.getTempFileIds(), FileNameType.ApproveProcess.getValue());
- /*娑堟伅閫氱煡*/
- Long id = nodeIds.get(0);
- if (approveProcess.getApproveType()==8){
- sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
- approveProcess.getApproveId() + "娴佺▼缂栧彿鐨勫鎵归渶瑕佹偍瀹℃牳!!!!!",
- Collections.singletonList(id),
- "/safeProduction/safeWorkApproval?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
- }else {
- sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
- approveProcess.getApproveId() + "娴佺▼缂栧彿鐨勫鎵归渶瑕佹偍瀹℃牳!!!!!",
- Collections.singletonList(id),
- "/collaborativeApproval/approvalProcess?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
- }
+ /*娑堟伅閫氱煡锛堥�氱煡绗竴鑺傜偣鍏ㄩ儴瀹℃壒浜猴級*/
+ notifyApproveUsers(approveProcess, firstGroupUserIds(approveProcess));
+ return approveProcess.getId();
}
@Override
@@ -297,112 +287,254 @@
.last("limit 1");
ApproveProcess approveProcess = approveProcessMapper.selectOne(approveProcessLambdaQueryWrapper);
if (approveProcess == null) throw new RuntimeException("璇烽�夋嫨瀹℃壒浜�");
- String[] split = approveGetAndUpdateVo.getApproveUserIds().split(",");
- if (split.length == 0) {
+ String newIds = approveGetAndUpdateVo.getApproveUserIds();
+ if (newIds == null || newIds.trim().isEmpty()) {
throw new RuntimeException("璇烽�夋嫨瀹℃壒浜�");
}
- List<SysUser> sysUsers = sysUserMapper.selectUserByIds(Arrays.asList(split).stream().map(Long::parseLong).collect(Collectors.toList()));
+ List<Long> newIdList = parseUserIds(newIds);
+ List<SysUser> sysUsers = sysUserMapper.selectUserByIds(newIdList);
if (CollectionUtils.isEmpty(sysUsers)) throw new RuntimeException("璇烽�夋嫨瀹℃壒浜�");
- //瀹℃牳涓笉鍙互缂栬緫瀹℃牳浜�
- if (approveProcess.getApproveStatus() != 1) {
- approveProcess.setApproveUserCurrentId(Long.parseLong(split[0]));
- approveProcess.setApproveUserCurrentName(sysUsers.stream().filter(user -> user.getUserId().equals(Long.parseLong(split[0]))).collect(Collectors.toList()).get(0).getNickName());
- }
+ approveProcess.setApproveUserIds(newIds);
+ approveProcess.setApproveUserNames(sysUsers.stream().map(SysUser::getNickName).collect(Collectors.joining(",")));
if (approveGetAndUpdateVo.getApproveStatus() != null) {
approveProcess.setApproveStatus(approveGetAndUpdateVo.getApproveStatus());
}
- approveProcess.setApproveUserIds(approveGetAndUpdateVo.getApproveUserIds());
- updateById(approveProcess);
- //淇敼瀹℃壒浜�
- // 鍏堝垹闄� 鍚庢柊澧�
- LambdaQueryWrapper<ApproveNode> approveNodeLambdaQueryWrapper = new LambdaQueryWrapper<>();
- approveNodeLambdaQueryWrapper.eq(ApproveNode::getApproveProcessId, approveProcess.getApproveId())
- .eq(ApproveNode::getDeleteFlag, 0)
-// .eq(ApproveNode::getTenantId, SecurityUtils.getLoginUser().getTenantId())
- .orderByAsc(ApproveNode::getApproveNodeOrder);
- approveNodeMapper.delete(approveNodeLambdaQueryWrapper);
- approveNodeService.initApproveNodes(approveGetAndUpdateVo.getApproveUserIds(), approveProcess.getApproveId(), approveProcess.getTenantId());
- /*娑堟伅閫氱煡*/
- String id = approveProcess.getApproveUserIds().split(",")[0];
- if (approveProcess.getApproveType()==8){
- sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
- approveProcess.getApproveId() + "娴佺▼缂栧彿鐨勫鎵归渶瑕佹偍瀹℃牳!!!!!",
- Arrays.asList(Long.valueOf(id)),
- "/safeProduction/safeWorkApproval?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
- }else {
- sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
- approveProcess.getApproveId() + "娴佺▼缂栧彿鐨勫鎵归渶瑕佹偍瀹℃牳!!!!!",
- Arrays.asList(Long.valueOf(id)),
- "/collaborativeApproval/approvalProcess?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
+ // 鍏堝垹闄� 鍚庢柊澧烇紙浼樺厛鎸� approve_node_config 蹇収淇濆垎缁勪笌妯″紡锛�
+ approveNodeMapper.delete(new LambdaQueryWrapper<ApproveNode>()
+ .eq(ApproveNode::getApproveProcessId, approveProcess.getApproveId())
+ .eq(ApproveNode::getDeleteFlag, 0));
+ rebuildApproveNodes(approveProcess, newIds);
+ // 瀹℃牳涓笉鍙互缂栬緫瀹℃牳浜�
+ if (approveProcess.getApproveStatus() != 1) {
+ refreshCurrentApprovers(approveProcess);
+ } else {
+ approveProcessMapper.updateById(approveProcess);
}
+ /*娑堟伅閫氱煡锛堥�氱煡绗竴鑺傜偣鍏ㄩ儴瀹℃壒浜猴級*/
+ notifyApproveUsers(approveProcess, firstGroupUserIds(approveProcess));
}
@Override
public void updateByApproveId(ApproveGetAndUpdateVo approveGetAndUpdateVo) throws IOException {
ApproveProcess approve = approveProcessMapper.selectById(approveGetAndUpdateVo.getId());
+ Integer oldStatus = approve.getApproveStatus();
BeanUtils.copyProperties(approveGetAndUpdateVo, approve);
- approve.setApproveUserIds(approveGetAndUpdateVo.getApproveUserIds());
+ // BeanUtils 浼氭嫹璐� null锛岄槻姝㈡妸瀹℃壒鐘舵�佽鐩栦负绌�
+ if (approveGetAndUpdateVo.getApproveStatus() == null) {
+ approve.setApproveStatus(oldStatus);
+ }
approve.setApproveReason(approveGetAndUpdateVo.getApproveReason());
- SysUser sysUser = sysUserMapper.selectUserById(approveGetAndUpdateVo.getApproveUser());
- String[] split = approveGetAndUpdateVo.getApproveUserIds().split(",");
- if (split.length == 0) {
+ String newIds = approveGetAndUpdateVo.getApproveUserIds();
+ if (newIds == null || newIds.trim().isEmpty()) {
throw new RuntimeException("璇烽�夋嫨瀹℃壒浜�");
}
- List<SysUser> sysUsers = sysUserMapper.selectUserByIds(Arrays.asList(split).stream().map(Long::parseLong).collect(Collectors.toList()));
+ List<Long> newIdList = parseUserIds(newIds);
+ List<SysUser> sysUsers = sysUserMapper.selectUserByIds(newIdList);
if (CollectionUtils.isEmpty(sysUsers)) throw new RuntimeException("璇烽�夋嫨瀹℃壒浜�");
+ SysUser sysUser = sysUserMapper.selectUserById(approveGetAndUpdateVo.getApproveUser());
if (sysUser == null) throw new RuntimeException("鐢宠浜轰笉瀛樺湪");
approve.setApproveUserName(sysUser.getNickName());
approve.setApproveUser(sysUser.getUserId());
- //瀹℃牳涓笉鍙互缂栬緫瀹℃牳浜�
+ approve.setApproveUserIds(newIds);
+ approve.setApproveUserNames(sysUsers.stream().map(SysUser::getNickName).collect(Collectors.joining(",")));
+ //淇敼瀹℃壒浜猴細鍏堝垹闄ゅ悗鏂板锛堜紭鍏堟寜 approve_node_config 蹇収淇濆垎缁勪笌妯″紡锛�
+ approveNodeMapper.delete(new LambdaQueryWrapper<ApproveNode>()
+ .eq(ApproveNode::getApproveProcessId, approve.getApproveId())
+ .eq(ApproveNode::getDeleteFlag, 0));
+ rebuildApproveNodes(approve, newIds);
+ // 瀹℃牳涓笉鍙互缂栬緫瀹℃牳浜�
if (approve.getApproveStatus() != 1) {
- approve.setApproveUserCurrentId(Long.parseLong(split[0]));
- approve.setApproveUserCurrentName(sysUsers.stream().filter(user -> user.getUserId().equals(Long.parseLong(split[0]))).collect(Collectors.toList()).get(0).getNickName());
+ refreshCurrentApprovers(approve);
+ } else {
+ approveProcessMapper.updateById(approve);
}
- updateById(approve);
- //淇敼瀹℃壒浜�
- // 鍏堝垹闄� 鍚庢柊澧�
- LambdaQueryWrapper<ApproveNode> approveNodeLambdaQueryWrapper = new LambdaQueryWrapper<>();
- approveNodeLambdaQueryWrapper.eq(ApproveNode::getApproveProcessId, approve.getApproveId())
- .eq(ApproveNode::getDeleteFlag, 0)
-// .eq(ApproveNode::getTenantId, SecurityUtils.getLoginUser().getTenantId())
- .orderByAsc(ApproveNode::getApproveNodeOrder);
- approveNodeMapper.delete(approveNodeLambdaQueryWrapper);
- approveNodeService.initApproveNodes(approveGetAndUpdateVo.getApproveUserIds(), approve.getApproveId(), approve.getTenantId());
-
-// int i = 0;
-// for (ApproveNode approveNode : list) {
-// int finalI = i;
-// if(i >= split.length){
-// approveNode.setDeleteFlag(1);
-// }else{
-// List<SysUser> collect = sysUsers.stream().filter(user -> user.getUserId().equals(Long.parseLong(split[finalI]))).collect(Collectors.toList());
-// if(CollectionUtils.isEmpty(collect)){
-// throw new RuntimeException("璇烽�夋嫨姝g‘鐨勫鎵逛汉");
-// }
-// approveNode.setApproveNodeUserId(collect.get(0).getUserId());
-// approveNode.setApproveNodeUser(collect.get(0).getNickName());
-// }
-// approveNodeMapper.updateById(approveNode);
-// i++;
-// }
tempFileService.migrateTempFilesToFormal(approve.getId(), approveGetAndUpdateVo.getTempFileIds(), FileNameType.ApproveProcess.getValue());
- /*娑堟伅閫氱煡*/
- String id = approve.getApproveUserIds().split(",")[0];
- if (approve.getApproveType()==8){
- sysNoticeService.simpleNoticeByUser(approveProcessType(approve.getApproveType()),
- approve.getApproveId() + "娴佺▼缂栧彿鐨勫鎵归渶瑕佹偍瀹℃牳!!!!!",
- Arrays.asList(Long.valueOf(id)),
- "/safeProduction/safeWorkApproval?approveType=" + approve.getApproveType() + "&approveId=" + approve.getApproveId());
- }else {
- sysNoticeService.simpleNoticeByUser(approveProcessType(approve.getApproveType()),
- approve.getApproveId() + "娴佺▼缂栧彿鐨勫鎵归渶瑕佹偍瀹℃牳!!!!!",
- Arrays.asList(Long.valueOf(id)),
- "/collaborativeApproval/approvalProcess?approveType=" + approve.getApproveType() + "&approveId=" + approve.getApproveId());
- }
-
+ /*娑堟伅閫氱煡锛堥�氱煡绗竴鑺傜偣鍏ㄩ儴瀹℃壒浜猴級*/
+ notifyApproveUsers(approve, firstGroupUserIds(approve));
}
+
+ /**
+ * 鎸夎妭鐐归『搴忓垎缁勶紙淇濆簭锛夛紝鍚岃妭鐐瑰瀹℃壒浜哄綊鍏ヤ竴缁�
+ */
+ private List<List<ApproveProcessConfigNodeVo>> groupByOrder(List<ApproveProcessConfigNodeVo> list) {
+ Map<Integer, List<ApproveProcessConfigNodeVo>> map = new LinkedHashMap<>();
+ for (ApproveProcessConfigNodeVo vo : list) {
+ if (vo.getApproverId() == null) {
+ continue;
+ }
+ int order = vo.getNodeOrder() == null ? map.size() + 1 : vo.getNodeOrder();
+ map.computeIfAbsent(order, k -> new ArrayList<>()).add(vo);
+ }
+ return new ArrayList<>(map.values());
+ }
+
+ /**
+ * 璁剧疆娴佺▼棣栬妭鐐瑰緟瀹′汉锛堝鍊奸�楀彿鍒嗛殧锛屽吋瀹规棫鐨勫崟鍊煎瓧娈碉級
+ */
+ private void setFirstNodeApprovers(ApproveProcess approveProcess, List<ApproveProcessConfigNodeVo> firstGroup, List<SysUser> sysUsers) {
+ List<Long> ids = firstGroup.stream()
+ .map(ApproveProcessConfigNodeVo::getApproverId)
+ .distinct()
+ .collect(Collectors.toList());
+ Map<Long, String> nameMap = sysUsers.stream()
+ .collect(Collectors.toMap(SysUser::getUserId, SysUser::getNickName, (a, b) -> a));
+ List<String> names = ids.stream().map(nameMap::get).filter(Objects::nonNull).collect(Collectors.toList());
+ approveProcess.setApproveUserCurrentIds(ids.stream().map(String::valueOf).collect(Collectors.joining(",")));
+ approveProcess.setApproveUserCurrentNames(String.join(",", names));
+ if (!ids.isEmpty()) {
+ approveProcess.setApproveUserCurrentId(ids.get(0));
+ approveProcess.setApproveUserCurrentName(names.isEmpty() ? null : names.get(0));
+ }
+ }
+
+ /**
+ * 鑺傜偣缁撴瀯蹇収JSON锛歔{nodeOrder,mode,userIds,userNames}]锛屼緵缂栬緫閲嶅缓瀹℃壒鑺傜偣
+ */
+ private String buildNodeConfig(List<ApproveProcessConfigNodeVo> list) {
+ try {
+ List<Map<String, Object>> nodes = new ArrayList<>();
+ for (List<ApproveProcessConfigNodeVo> group : groupByOrder(list)) {
+ String mode = group.get(0).getApproveMode();
+ if (mode == null || mode.isEmpty()) {
+ mode = "or";
+ }
+ List<Long> ids = group.stream().map(ApproveProcessConfigNodeVo::getApproverId).distinct().collect(Collectors.toList());
+ List<SysUser> users = sysUserMapper.selectUserByIds(ids);
+ Map<String, Object> node = new LinkedHashMap<>();
+ node.put("nodeOrder", group.get(0).getNodeOrder());
+ node.put("mode", mode);
+ node.put("userIds", ids.stream().map(String::valueOf).collect(Collectors.joining(",")));
+ node.put("userNames", users.stream().map(SysUser::getNickName).collect(Collectors.joining(",")));
+ nodes.add(node);
+ }
+ return OBJECT_MAPPER.writeValueAsString(nodes);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private List<String> splitIds(String ids) {
+ if (ids == null || ids.trim().isEmpty()) {
+ return new ArrayList<>();
+ }
+ return Arrays.stream(ids.split(",")).map(String::trim).filter(s -> !s.isEmpty()).collect(Collectors.toList());
+ }
+
+ private List<Long> parseUserIds(String ids) {
+ return splitIds(ids).stream().map(Long::parseLong).collect(Collectors.toList());
+ }
+
+ /**
+ * 閲嶅缓瀹℃壒鑺傜偣锛氫紭鍏堟寜 approve_node_config 蹇収淇濆垎缁勪笌妯″紡閲嶅缓锛�
+ * 鏂伴�夌殑鎵佸钩瀹℃壒浜轰覆鎸夎妭鐐规Ы浣嶉『搴忎緷娆″~鍏咃紱鏃犲揩鐓у垯鍥為��鎵佸钩閫昏緫锛堜竴鑺傜偣涓�浜� or锛�
+ */
+ private void rebuildApproveNodes(ApproveProcess approveProcess, String approveUserIds) {
+ List<ApproveProcessConfigNodeVo> config = null;
+ if (StringUtils.hasText(approveProcess.getApproveNodeConfig())) {
+ config = buildConfigFromSnapshot(approveProcess.getApproveNodeConfig(), approveUserIds);
+ }
+ if (config == null) {
+ approveNodeService.initApproveNodes(approveUserIds, approveProcess.getApproveId(), approveProcess.getTenantId());
+ } else {
+ approveNodeService.initApproveNodes(config, approveProcess.getApproveId(), approveProcess.getTenantId());
+ }
+ }
+
+ private List<ApproveProcessConfigNodeVo> buildConfigFromSnapshot(String snapshotJson, String approveUserIds) {
+ List<String> newIds = splitIds(approveUserIds);
+ try {
+ List<Map<String, Object>> nodes = OBJECT_MAPPER.readValue(snapshotJson,
+ new TypeReference<List<Map<String, Object>>>() {});
+ List<ApproveProcessConfigNodeVo> config = new ArrayList<>();
+ int cursor = 0;
+ for (Map<String, Object> node : nodes) {
+ Object orderObj = node.get("nodeOrder");
+ int nodeOrder = orderObj instanceof Number ? ((Number) orderObj).intValue() : 0;
+ String mode = node.get("mode") == null ? "or" : String.valueOf(node.get("mode"));
+ List<String> snapshotIds = splitIds(node.get("userIds") == null ? null : String.valueOf(node.get("userIds")));
+ int slot = Math.max(1, snapshotIds.size());
+ for (int j = 0; j < slot; j++) {
+ String uid = cursor < newIds.size() ? newIds.get(cursor++) : (j < snapshotIds.size() ? snapshotIds.get(j) : null);
+ if (uid == null) {
+ continue;
+ }
+ ApproveProcessConfigNodeVo vo = new ApproveProcessConfigNodeVo();
+ vo.setNodeOrder(nodeOrder);
+ vo.setApproverId(Long.parseLong(uid));
+ vo.setApproveMode(mode);
+ config.add(vo);
+ }
+ }
+ // 鏂伴�夊鎵逛汉鑻ュ浜庡揩鐓фЫ浣嶏紝杩藉姞鍒版渶鍚庝竴涓妭鐐�
+ while (cursor < newIds.size()) {
+ ApproveProcessConfigNodeVo vo = new ApproveProcessConfigNodeVo();
+ vo.setNodeOrder(nodes.isEmpty() ? 1 : ((Number) nodes.get(nodes.size() - 1).get("nodeOrder")).intValue());
+ vo.setApproverId(Long.parseLong(newIds.get(cursor++)));
+ vo.setApproveMode("or");
+ config.add(vo);
+ }
+ return config;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 鍒锋柊娴佺▼褰撳墠瀹℃壒浜轰负绗竴鑺傜偣寰呭浜猴紙鐢ㄤ簬缂栬緫鍚庨噸寤猴級
+ */
+ private void refreshCurrentApprovers(ApproveProcess approveProcess) {
+ List<ApproveNode> first = approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>()
+ .eq(ApproveNode::getApproveProcessId, approveProcess.getApproveId())
+ .eq(ApproveNode::getApproveNodeOrder, 1)
+ .eq(ApproveNode::getDeleteFlag, 0)
+ .orderByAsc(ApproveNode::getId));
+ if (first.isEmpty()) {
+ approveProcess.setApproveUserCurrentIds(null);
+ approveProcess.setApproveUserCurrentNames(null);
+ approveProcess.setApproveUserCurrentId(null);
+ approveProcess.setApproveUserCurrentName(null);
+ } else {
+ approveProcess.setApproveUserCurrentIds(first.stream()
+ .map(n -> String.valueOf(n.getApproveNodeUserId())).collect(Collectors.joining(",")));
+ approveProcess.setApproveUserCurrentNames(first.stream()
+ .map(ApproveNode::getApproveNodeUser).collect(Collectors.joining(",")));
+ approveProcess.setApproveUserCurrentId(first.get(0).getApproveNodeUserId());
+ approveProcess.setApproveUserCurrentName(first.get(0).getApproveNodeUser());
+ }
+ approveProcessMapper.updateById(approveProcess);
+ }
+
+ private List<Long> firstGroupUserIds(ApproveProcess approveProcess) {
+ List<ApproveNode> first = approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>()
+ .eq(ApproveNode::getApproveProcessId, approveProcess.getApproveId())
+ .eq(ApproveNode::getApproveNodeOrder, 1)
+ .eq(ApproveNode::getDeleteFlag, 0)
+ .orderByAsc(ApproveNode::getId));
+ return first.stream().map(ApproveNode::getApproveNodeUserId).distinct().collect(Collectors.toList());
+ }
+
+ /**
+ * 閫氱煡绗竴鑺傜偣鍏ㄩ儴寰呭浜�
+ */
+ private void notifyApproveUsers(ApproveProcess approveProcess, List<Long> userIds) {
+ if (userIds == null || userIds.isEmpty()) {
+ return;
+ }
+ List<Long> ids = userIds.stream().distinct().collect(Collectors.toList());
+ if (approveProcess.getApproveType() == 8) {
+ sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
+ approveProcess.getApproveId() + "娴佺▼缂栧彿鐨勫鎵归渶瑕佹偍瀹℃牳!!!!!",
+ ids,
+ "/safeProduction/safeWorkApproval?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
+ } else {
+ sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
+ approveProcess.getApproveId() + "娴佺▼缂栧彿鐨勫鎵归渶瑕佹偍瀹℃牳!!!!!",
+ ids,
+ "/collaborativeApproval/approvalProcess?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
+ }
+ }
//瀹℃壒绫诲瀷鑾峰彇(涓庡墠绔〉闈㈠搴�)
private String approveProcessType(Integer approveType) {
@@ -423,6 +555,8 @@
return "鍙戣揣瀹℃壒";
case 8:
return "鍗遍櫓浣滀笟瀹℃壒";
+ case 9:
+ return "鐢ㄥ嵃瀹℃壒";
}
return null;
}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java b/src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java
index 18a6f4e..aa1104a 100644
--- a/src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java
+++ b/src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java
@@ -1,25 +1,31 @@
package com.ruoyi.collaborativeApproval.controller;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.ruoyi.approve.pojo.KnowledgeBase;
+import com.ruoyi.approve.bean.vo.ApproveProcessVO;
+import com.ruoyi.approve.service.IApproveProcessService;
+import com.ruoyi.collaborativeApproval.dto.SealApplicationManagementDTO;
import com.ruoyi.collaborativeApproval.pojo.SealApplicationManagement;
import com.ruoyi.collaborativeApproval.service.SealApplicationManagementService;
-import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
-import com.ruoyi.project.system.service.ISysNoticeService;
+import com.ruoyi.sales.mapper.CommonFileMapper;
+import com.ruoyi.sales.pojo.CommonFile;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
-import java.util.Arrays;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.Date;
import java.util.List;
-import java.util.Map;
@AllArgsConstructor
@RestController
@@ -30,24 +36,65 @@
private SealApplicationManagementService sealApplicationManagementService;
@Autowired
- private ISysNoticeService sysNoticeService;
+ private IApproveProcessService approveProcessService;
+
+ @Autowired
+ private CommonFileMapper commonFileMapper;
@GetMapping("/getList")
@ApiOperation("鍒嗛〉鏌ヨ")
public AjaxResult listPage(Page page, SealApplicationManagement sealApplicationManagement){
- return AjaxResult.success(sealApplicationManagementService.listPage(page,sealApplicationManagement));
+ IPage<SealApplicationManagementDTO> result = sealApplicationManagementService.listPage(page,sealApplicationManagement);
+ for (SealApplicationManagementDTO record : result.getRecords()) {
+ if (record.getApproveProcessId() != null) {
+ record.setCommonFileList(commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
+ .eq(CommonFile::getCommonId, record.getApproveProcessId())
+ .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue())));
+ }
+ }
+ return AjaxResult.success(result);
}
@PostMapping("/add")
- @ApiOperation("鏂板")
- public AjaxResult add(@RequestBody SealApplicationManagement sealApplicationManagement){
- sealApplicationManagementService.save(sealApplicationManagement);
- //娑堟伅閫氱煡
- sysNoticeService.simpleNoticeByUser("鐢ㄥ嵃瀹℃壒",
- "鐢宠缂栧彿锛�"+sealApplicationManagement.getApplicationNum()+"\t"
- +"鐢宠鏍囬锛�"+sealApplicationManagement.getTitle(),
- Arrays.asList(sealApplicationManagement.getApproveUserId()),
- "/collaborativeApproval/sealManagement?applicationNum="+sealApplicationManagement.getApplicationNum());
+ @ApiOperation("鏂板/閲嶆柊鐢宠鐢ㄥ嵃")
+ @Transactional(rollbackFor = Exception.class)
+ public AjaxResult add(@RequestBody SealApplicationManagement sealApplicationManagement) throws Exception {
+ sealApplicationManagement.setStatus("pending");
+ if (sealApplicationManagement.getId() == null) {
+ sealApplicationManagementService.save(sealApplicationManagement);
+ } else {
+ // 閲嶆柊鐢宠锛氭竻鐞嗘棫瀹℃壒娴佺▼鍏宠仈鐨勯檮浠讹紝閬垮厤閬楃暀
+ SealApplicationManagement old = sealApplicationManagementService.getById(sealApplicationManagement.getId());
+ if (old != null && old.getApproveProcessId() != null) {
+ commonFileMapper.delete(new LambdaQueryWrapper<CommonFile>()
+ .eq(CommonFile::getCommonId, old.getApproveProcessId())
+ .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue()));
+ }
+ sealApplicationManagementService.updateById(sealApplicationManagement);
+ }
+ // 鍙戣捣鍗忓悓缁熶竴瀹℃牳娴佺▼锛堢敤鍗板鎵癸級
+ ApproveProcessVO vo = new ApproveProcessVO();
+ vo.setApproveType(9);
+ vo.setApproveReason(sealApplicationManagement.getApplicationNum() + "锛�" + sealApplicationManagement.getTitle());
+ vo.setApproveDeptId(sealApplicationManagement.getDeptId());
+ vo.setApproveTime(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
+ Long approveProcessId = approveProcessService.addApprove(vo);
+ // 鍥炲~瀹℃壒娴佺▼id锛屼緵瀹℃壒鍥炶皟鏇存柊鐘舵�併�佽鎯呮煡璇㈤檮浠�
+ sealApplicationManagement.setApproveProcessId(approveProcessId);
+ sealApplicationManagementService.updateById(sealApplicationManagement);
+ // 淇濆瓨鐢ㄥ嵃鐢宠闄勪欢锛屽叧鑱斿埌瀹℃壒娴佺▼
+ if (!CollectionUtils.isEmpty(sealApplicationManagement.getAttachmentList())) {
+ for (CommonFile cf : sealApplicationManagement.getAttachmentList()) {
+ CommonFile record = new CommonFile();
+ record.setCommonId(approveProcessId);
+ record.setName(cf.getName());
+ record.setUrl(cf.getUrl());
+ record.setType(FileNameType.ApproveProcess.getValue());
+ record.setCreateTime(LocalDateTime.now());
+ record.setUpdateTime(LocalDateTime.now());
+ commonFileMapper.insert(record);
+ }
+ }
return AjaxResult.success();
}
@@ -66,6 +113,21 @@
return AjaxResult.success(sealApplicationManagementService.removeBatchByIds(ids));
}
+ @GetMapping("/detail")
+ @ApiOperation("鐢ㄥ嵃鐢宠璇︽儏锛堟寜瀹℃壒娴佺▼id锛�")
+ public AjaxResult detail(Long approveProcessId){
+ SealApplicationManagement seal = sealApplicationManagementService.getOne(new LambdaQueryWrapper<SealApplicationManagement>()
+ .eq(SealApplicationManagement::getApproveProcessId, approveProcessId)
+ .last("limit 1"));
+ if (seal == null) {
+ throw new RuntimeException("鐢ㄥ嵃鐢宠涓嶅瓨鍦�");
+ }
+ seal.setCommonFileList(commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
+ .eq(CommonFile::getCommonId, approveProcessId)
+ .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue())));
+ return AjaxResult.success(seal);
+ }
+
@ApiOperation(value = "鐢ㄥ嵃鐢宠绠$悊瀵煎嚭")
@PostMapping("/export")
public void export(HttpServletResponse response) {
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/pojo/SealApplicationManagement.java b/src/main/java/com/ruoyi/collaborativeApproval/pojo/SealApplicationManagement.java
index 0acac2d..d7a267f 100644
--- a/src/main/java/com/ruoyi/collaborativeApproval/pojo/SealApplicationManagement.java
+++ b/src/main/java/com/ruoyi/collaborativeApproval/pojo/SealApplicationManagement.java
@@ -3,11 +3,13 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import com.ruoyi.sales.pojo.CommonFile;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
+import java.util.List;
@Data
@TableName("seal_application_management")
@@ -56,6 +58,24 @@
* 瀹℃壒浜�
*/
private Long approveUserId;
+
+ /**
+ * 鍏宠仈鐨勫崗鍚屽鎵规祦绋嬩富閿甶d锛坅pprove_process.id锛�
+ */
+ @ApiModelProperty("鍗忓悓瀹℃壒娴佺▼id")
+ private Long approveProcessId;
+
+ /**
+ * 闄勪欢鍒楄〃锛堟彁浜ょ敤锛屾潵鑷墠绔� /common/upload 涓婁紶缁撴灉锛�
+ */
+ @TableField(exist = false)
+ private List<CommonFile> attachmentList;
+
+ /**
+ * 闄勪欢鍒楄〃锛堟煡璇㈠睍绀虹敤锛�
+ */
+ @TableField(exist = false)
+ private List<CommonFile> commonFileList;
/**
* 鍒涘缓鑰�
*/
diff --git a/src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java b/src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java
index f44742a..e0f3554 100644
--- a/src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java
+++ b/src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java
@@ -38,6 +38,8 @@
}
}
+ wrapper.orderByDesc(CustomerVisits::getPurposeDate);
+
return customerVisitsMapper.selectPage(page, wrapper);
}
diff --git a/src/main/java/com/ruoyi/production/dto/ProductWorkOrderDto.java b/src/main/java/com/ruoyi/production/dto/ProductWorkOrderDto.java
index a528151..ddcec72 100644
--- a/src/main/java/com/ruoyi/production/dto/ProductWorkOrderDto.java
+++ b/src/main/java/com/ruoyi/production/dto/ProductWorkOrderDto.java
@@ -6,6 +6,7 @@
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
+import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
@@ -55,4 +56,17 @@
private Boolean hasUnreportedMachine;
private String finalProductModel;
+
+ //鍔犳斁鏁�
+ @ApiModelProperty(value = "鍔犳斁鏁�")
+ private String processRouteAddNum;
+
+ @ApiModelProperty("鎶ュ伐鐝粍榛樿鎴愬憳(id+濮撳悕)")
+ private List<ReportPerson> reportPersons;
+
+ @Data
+ public static class ReportPerson {
+ private Long userId;
+ private String userName;
+ }
}
diff --git a/src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java b/src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java
index 7481074..7121e25 100644
--- a/src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java
+++ b/src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java
@@ -83,8 +83,12 @@
private Long deviceId;
private String deviceName;
private String workMinutes;
+ private String personWorkMinutes;
private List<Team> teamList;
+
+ @ApiModelProperty("鎶ュ伐鐝粍浜哄憳浜ч噺鏃堕暱鏄庣粏")
+ private List<Person> personList;
@@ -97,4 +101,14 @@
private Long userId;
private String userName;
}
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public static class Person {
+ private Long userId;
+ private String userName;
+ private BigDecimal quantity; // 璇ヤ汉鍛樹骇閲�
+ private Integer workHour; // 璇ヤ汉鍛樼敓浜ф椂闀�(鍒嗛挓)
+ }
}
diff --git a/src/main/java/com/ruoyi/production/mapper/ProductionReportPersonMapper.java b/src/main/java/com/ruoyi/production/mapper/ProductionReportPersonMapper.java
new file mode 100644
index 0000000..554db4b
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/mapper/ProductionReportPersonMapper.java
@@ -0,0 +1,9 @@
+package com.ruoyi.production.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.production.pojo.ProductionReportPerson;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ProductionReportPersonMapper extends BaseMapper<ProductionReportPerson> {
+}
diff --git a/src/main/java/com/ruoyi/production/pojo/ProductionReportPerson.java b/src/main/java/com/ruoyi/production/pojo/ProductionReportPerson.java
new file mode 100644
index 0000000..eb552d6
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/pojo/ProductionReportPerson.java
@@ -0,0 +1,58 @@
+package com.ruoyi.production.pojo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 鎶ュ伐鐝粍浜哄憳浜ч噺鏃堕暱鏄庣粏
+ */
+@Data
+@TableName("production_report_person")
+@ApiModel(value = "ProductionReportPerson瀵硅薄", description = "鎶ュ伐鐝粍浜哄憳浜ч噺鏃堕暱鏄庣粏琛�")
+public class ProductionReportPerson {
+
+ @TableId(value = "id", type = IdType.AUTO)
+ private Long id;
+
+ @ApiModelProperty("鎶ュ伐涓昏〃id(production_product_main.id)")
+ private Long productMainId;
+
+ @ApiModelProperty("鐢熶骇浜篿d")
+ private Long userId;
+
+ @ApiModelProperty("鐢熶骇浜哄鍚�")
+ private String userName;
+
+ @ApiModelProperty("璇ヤ汉鍛樹骇鍑烘暟閲�")
+ private BigDecimal quantity;
+
+ @ApiModelProperty("璇ヤ汉鍛樼敓浜ф椂闀�(鍒嗛挓)")
+ private Integer workHour;
+
+ @TableField(fill = FieldFill.INSERT)
+ private Long tenantId;
+
+ @TableField(fill = FieldFill.INSERT)
+ private LocalDateTime createTime;
+
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private LocalDateTime updateTime;
+
+ @TableField(fill = FieldFill.INSERT)
+ private Long createUser;
+
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private Long updateUser;
+
+ @TableField(fill = FieldFill.INSERT)
+ private Long deptId;
+}
diff --git a/src/main/java/com/ruoyi/production/service/ProductionProductMainService.java b/src/main/java/com/ruoyi/production/service/ProductionProductMainService.java
index e09893d..c81213b 100644
--- a/src/main/java/com/ruoyi/production/service/ProductionProductMainService.java
+++ b/src/main/java/com/ruoyi/production/service/ProductionProductMainService.java
@@ -24,4 +24,14 @@
void nextAddProductMain(@NotNull ProductionProductOutput productionProductOutput);
ArrayList<Long> listMain(List<Long> idList);
+
+ /**
+ * 鎺掍骇涓嬭揪鍚庯紝涓虹涓�閬撻渶璐ㄦ宸ュ簭棰勭敓鎴愯繃绋�/鍑哄巶妫�楠岃川妫�鍗�
+ */
+ void preGenerateQualityInspectForOrder(Long productOrderId);
+
+ /**
+ * 娓呯悊鎺掍骇棰勭敓鎴愮殑璐ㄦ鍗曪紙鏈叧鑱旀姤宸ワ級
+ */
+ void cleanPreGeneratedQualityInspects(List<Long> workOrderIds);
}
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProcessRouteServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProcessRouteServiceImpl.java
index 9c40694..fe197f0 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProcessRouteServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProcessRouteServiceImpl.java
@@ -52,6 +52,8 @@
private ProductOrderService productOrderService;
@Autowired
private ProductProcessService productProcessService;
+ @Autowired
+ private ProductionProductMainService productionProductMainService;
@Override
public IPage<ProcessRouteDto> pageProcessRouteDto(Page<ProcessRouteDto> page, ProcessRouteDto processRouteDto) {
@@ -114,6 +116,8 @@
// 6. 鏂板鐢熶骇宸ヨ壓璺嚎瀛愯〃
buildProductProcessRouteItems(processRouteAnticlockwiseDtos, productProcessRoute.getId(), productModelId, productOrderId);
+ // 鎺掍骇涓嬭揪鍚庤嚜鍔ㄨЕ鍙戠涓�閬撻渶璐ㄦ宸ュ簭鐨勮繃绋嬫楠�
+ productionProductMainService.preGenerateQualityInspectForOrder(productOrderId);
return processRoute.getId();
}
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java
index 58f14d8..b7063e4 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java
@@ -18,6 +18,7 @@
import com.ruoyi.production.service.ProductProcessService;
import com.ruoyi.production.service.ProductWorkOrderService;
import com.ruoyi.production.service.ProductionPrintOrderService;
+import com.ruoyi.production.service.ProductionProductMainService;
import com.ruoyi.quality.mapper.QualityInspectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
@@ -68,6 +69,8 @@
private SalesLedgerProductionAccountingMapper salesLedgerProductionAccountingMapper;
@Autowired
private StockUtils stockUtils;
+ @Autowired
+ private ProductionProductMainService productionProductMainService;
@Override
public IPage<ProductOrderDto> pageProductOrder(Page page, ProductOrderDto productOrder) {
@@ -116,6 +119,7 @@
productProcessRouteItem.setProductOrderId(productOrder.getId());
productProcessRouteItem.setProductRouteId(productProcessRoute.getId());
productProcessRouteItem.setDragSort(processRouteItem.getDragSort());
+ productProcessRouteItem.setIsQuality(processRouteItem.getIsQuality());
int insert = productProcessRouteItemMapper.insert(productProcessRouteItem);
if (insert > 0) {
@@ -129,6 +133,8 @@
productWorkOrderMapper.insert(productWorkOrder);
}
}
+ // 鎺掍骇涓嬭揪鍚庤嚜鍔ㄨЕ鍙戠涓�閬撻渶璐ㄦ宸ュ簭鐨勮繃绋嬫楠�
+ productionProductMainService.preGenerateQualityInspectForOrder(productOrder.getId());
return productOrderMapper.updateById(productOrder);
}
@@ -166,6 +172,9 @@
if (productionProductMains.size() > 0) {
throw new RuntimeException("鐢熶骇璁㈠崟宸茬粡寮�濮嬬敓浜�,涓嶈兘鍒犻櫎");
}
+ // 娓呯悊鎺掍骇棰勭敓鎴愮殑璐ㄦ鍗�
+ productionProductMainService.cleanPreGeneratedQualityInspects(
+ productWorkOrders.stream().map(ProductWorkOrder::getId).collect(Collectors.toList()));
//鍒犻櫎宸ュ崟
productWorkOrderMapper.delete(Wrappers.<ProductWorkOrder>lambdaQuery().in(ProductWorkOrder::getProductOrderId, ids));
}
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductProcessRouteItemServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductProcessRouteItemServiceImpl.java
index 2e97872..4d8df0f 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductProcessRouteItemServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductProcessRouteItemServiceImpl.java
@@ -48,6 +48,7 @@
private ProductionProductMainMapper productionProductMainMapper;
private SalesLedgerProductionAccountingMapper salesLedgerProductionAccountingMapper;
+ private ProductionReportPersonMapper productionReportPersonMapper;
private ProductWorkOrderMapper productWorkOrderMapper;
@@ -102,10 +103,15 @@
// .eq(QualityInspect::getProductMainId, mainId));
salesLedgerProductionAccountingMapper.delete(new LambdaQueryWrapper<SalesLedgerProductionAccounting>()
.eq(SalesLedgerProductionAccounting::getProductMainId, main.getId()));
+ productionReportPersonMapper.delete(new LambdaQueryWrapper<ProductionReportPerson>()
+ .eq(ProductionReportPerson::getProductMainId, main.getId()));
}
}
// 鏌ヨ璁㈠崟 + 鍒犻櫎鏍哥畻
+ // 娓呯悊鎺掍骇棰勭敓鎴愮殑璐ㄦ鍗�
+ productionProductMainService.cleanPreGeneratedQualityInspects(Collections.singletonList(workOrderId));
+
// 鍒犻櫎鍏宠仈宸ュ崟
productWorkOrderMapper.delete(new LambdaQueryWrapper<ProductWorkOrder>()
.eq(ProductWorkOrder::getProductProcessRouteItemId, routeItemId));
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductWorkOrderServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductWorkOrderServiceImpl.java
index 411a462..3b37e40 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductWorkOrderServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductWorkOrderServiceImpl.java
@@ -16,9 +16,13 @@
import com.ruoyi.production.dto.ProductWorkOrderDto;
import com.ruoyi.production.mapper.ProductWorkOrderFileMapper;
import com.ruoyi.production.mapper.ProductWorkOrderMapper;
+import com.ruoyi.production.mapper.ProductionMachineRecordMapper;
import com.ruoyi.production.pojo.ProductWorkOrder;
import com.ruoyi.production.pojo.ProductWorkOrderFile;
+import com.ruoyi.production.pojo.ProductionMachineRecord;
import com.ruoyi.production.service.ProductWorkOrderService;
+import com.ruoyi.project.system.domain.SysUser;
+import com.ruoyi.project.system.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@@ -32,8 +36,10 @@
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
@Service
@Transactional(rollbackFor = Exception.class)
@@ -43,6 +49,10 @@
private ProductWorkOrderMapper productWorkOrderMapper;
@Autowired
private ProductWorkOrderFileMapper productWorkOrderFileMapper;
+ @Autowired
+ private ProductionMachineRecordMapper productionMachineRecordMapper;
+ @Autowired
+ private SysUserMapper sysUserMapper;
@Value("${file.temp-dir}")
private String tempDir;
@@ -127,7 +137,43 @@
@Override
public ProductWorkOrderDto getProductWorkOrderById(Long id) {
- return productWorkOrderMapper.getProductWorkOrderFlowCard(id);
+ ProductWorkOrderDto productWorkOrderDto = productWorkOrderMapper.getProductWorkOrderFlowCard(id);
+ // 鍚堝苟宸ュ崟鎺掍骇浜哄憳涓庢満鍙颁笂鏈烘搷浣滃憳锛屽幓閲嶅悗浣滀负鎶ュ伐寮圭獥榛樿鐝粍鎴愬憳
+ Set<Long> userIdSet = new LinkedHashSet<>();
+ if (productWorkOrderDto != null && StrUtil.isNotBlank(productWorkOrderDto.getUserIds())) {
+ for (String uid : productWorkOrderDto.getUserIds().split(",")) {
+ if (StrUtil.isNotBlank(uid)) {
+ userIdSet.add(Long.parseLong(uid.trim()));
+ }
+ }
+ }
+ List<ProductionMachineRecord> machineRecords = productionMachineRecordMapper.selectList(
+ Wrappers.<ProductionMachineRecord>lambdaQuery()
+ .eq(ProductionMachineRecord::getWorkOrderId, id)
+ .isNotNull(ProductionMachineRecord::getOperatorId));
+ for (ProductionMachineRecord machineRecord : machineRecords) {
+ if (StrUtil.isNotBlank(machineRecord.getOperatorId())) {
+ for (String uid : machineRecord.getOperatorId().split(",")) {
+ if (StrUtil.isNotBlank(uid)) {
+ userIdSet.add(Long.parseLong(uid.trim()));
+ }
+ }
+ }
+ }
+ List<ProductWorkOrderDto.ReportPerson> reportPersons = new ArrayList<>();
+ for (Long userId : userIdSet) {
+ ProductWorkOrderDto.ReportPerson reportPerson = new ProductWorkOrderDto.ReportPerson();
+ reportPerson.setUserId(userId);
+ SysUser sysUser = sysUserMapper.selectUserById(userId);
+ if (sysUser != null) {
+ reportPerson.setUserName(sysUser.getNickName());
+ }
+ reportPersons.add(reportPerson);
+ }
+ if (productWorkOrderDto != null) {
+ productWorkOrderDto.setReportPersons(reportPersons);
+ }
+ return productWorkOrderDto;
}
@Override
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
index ba4efc4..3f8c05f 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -90,6 +90,8 @@
private SalesLedgerProductionAccountingMapper salesLedgerProductionAccountingMapper;
+ private ProductionReportPersonMapper productionReportPersonMapper;
+
private StockUtils stockUtils;
@@ -176,6 +178,26 @@
productionProductMain.setAuditUserId(SecurityUtils.getUserId());
productionProductMainMapper.insert(productionProductMain);
+ /*淇濆瓨鎶ュ伐鐝粍浜哄憳浜ч噺鏃堕暱鏄庣粏*/
+ List<ProductionProductMainDto.Person> personList = dto.getPersonList();
+ BigDecimal personSum = BigDecimal.ZERO;
+ if (CollUtil.isNotEmpty(personList)) {
+ for (ProductionProductMainDto.Person person : personList) {
+ BigDecimal personQty = person.getQuantity() == null ? BigDecimal.ZERO : person.getQuantity();
+ personSum = personSum.add(personQty);
+ ProductionReportPerson reportPerson = new ProductionReportPerson();
+ reportPerson.setProductMainId(productionProductMain.getId());
+ reportPerson.setUserId(person.getUserId());
+ reportPerson.setUserName(person.getUserName());
+ reportPerson.setQuantity(personQty);
+ reportPerson.setWorkHour(person.getWorkHour() != null ? person.getWorkHour() : 0);
+ productionReportPersonMapper.insert(reportPerson);
+ }
+ }
+ BigDecimal totalQuantity = dto.getQuantity() == null ? BigDecimal.ZERO : dto.getQuantity();
+ if (CollUtil.isNotEmpty(personList) && personSum.compareTo(totalQuantity) != 0) {
+ throw new ServiceException("鍚勭彮缁勪骇閲忎箣鍜屽繀椤荤瓑浜庢姤宸ユ�讳骇閲�");
+ }
/*鏂板鎶ュ伐鎶曞叆琛�*/
List<ProductStructureDto> productStructureDtos = productStructureMapper.listBybomAndProcess(Optional.ofNullable(productProcessRoute.getBomId()).orElse(0), productProcess.getId());
if (productStructureDtos.isEmpty()) {
@@ -202,9 +224,10 @@
productionProductOutput.setReplenishQty(dto.getReplenishQty());
productionProductOutput.setAddQty(dto.getAddQty() != null ? dto.getAddQty() : BigDecimal.ZERO);
productionProductOutputMapper.insert(productionProductOutput);
- //鍚堟牸鏁伴噺=鎶ュ伐鏁伴噺-鎶ュ簾鏁伴噺
- BigDecimal productQty = productionProductOutput.getQuantity().subtract(productionProductOutput.getScrapQty());
- //鍙湁鍚堟牸鏁伴噺>0鎵嶈兘澧炲姞鐩稿簲鏁版嵁
+ //宸ュ簭娴佽浆鏁伴噺=浜у嚭鏁伴噺+鍔犳斁鏁帮紙鎶ュ簾浠呭仛鏁版嵁璁板綍锛屼笉鎵e噺宸ュ簭娴佽浆鍩烘暟锛屽悇宸ュ簭鎬讳骇閲忎繚鎸佽繛璐級
+ BigDecimal addQty = productionProductOutput.getAddQty() == null ? BigDecimal.ZERO : productionProductOutput.getAddQty();
+ BigDecimal productQty = productionProductOutput.getQuantity().add(addQty);
+ //鍙湁宸ュ簭娴佽浆鏁伴噺>0鎵嶈兘澧炲姞鐩稿簲鏁版嵁
if (productQty.compareTo(BigDecimal.ZERO) > 0) {
List<ProductProcessRouteItem> productProcessRouteItems = productProcessRouteItemMapper.selectList(Wrappers.<ProductProcessRouteItem>lambdaQuery().eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()));
/*鏇存柊宸ュ崟鍜岀敓浜ц鍗�*/
@@ -219,10 +242,18 @@
if (!machineRecords.isEmpty()) {
productWorkOrder.setActualStartTime(machineRecords.get(0).getMachineStartTime());
}
- // 瀹為檯缁撴潫鏃堕棿 = 鎶ュ伐缁撴潫鏃堕棿
- productWorkOrder.setActualEndTime(now);//瀹為檯缁撴潫鏃堕棿涓烘姤宸ョ粨鏉熸椂闂�
- productWorkOrder.setEndProductTime(now);
- productWorkOrderMapper.updateById(productWorkOrder);
+ // 浠呭畬鎴愭暟閲忚揪鍒拌鍒掓暟閲忔椂鎵嶅~鍏呭疄闄呯粨鏉熸椂闂达紝鏈畬鎴愬垯娓呯┖宸插~鐨勭粨鏉熸椂闂达紙鍏佽缁х画鍒嗘壒鎶ュ伐锛�
+ if (productWorkOrder.getCompleteQuantity().compareTo(productWorkOrder.getPlanQuantity()) >= 0) {
+ productWorkOrder.setActualEndTime(now);
+ productWorkOrder.setEndProductTime(now);
+ productWorkOrderMapper.updateById(productWorkOrder);
+ } else {
+ // updateById 榛樿蹇界暐 null 瀛楁锛岄渶鐢� UpdateWrapper 鏄惧紡娓呯┖锛堣嚜鎰堝巻鍙茶剰鏁版嵁锛�
+ productWorkOrderMapper.update(null, Wrappers.<ProductWorkOrder>lambdaUpdate()
+ .eq(ProductWorkOrder::getId, productWorkOrder.getId())
+ .set(ProductWorkOrder::getActualEndTime, null)
+ .set(ProductWorkOrder::getEndProductTime, null));
+ }
//鐢熶骇璁㈠崟
ProductOrder productOrder = productOrderMapper.selectById(productWorkOrder.getProductOrderId());
if (ObjectUtils.isNull(productOrder.getStartTime())) {
@@ -281,10 +312,13 @@
// 鍑忓皯 鏇存柊宸ュ崟鍜岀敓浜ц鍗�
ProductWorkOrder productWorkOrder = productWorkOrderMapper.selectById(productionProductMain.getWorkOrderId());
ProductOrder productOrder = productOrderMapper.selectById(productWorkOrder.getProductOrderId());
+ //宸ュ簭娴佽浆鏁伴噺=浜у嚭鏁伴噺+鍔犳斁鏁帮紙涓庢姤宸ョ疮鍔犲彛寰勪竴鑷达紝鎶ュ簾涓嶆墸鍑忔祦杞熀鏁帮級
+ BigDecimal processQty = productionProductOutput.getQuantity().add(
+ productionProductOutput.getAddQty() == null ? BigDecimal.ZERO : productionProductOutput.getAddQty());
productWorkOrder.setCompleteQuantity(
productWorkOrder.getCompleteQuantity()
- .subtract(productionProductOutput.getQuantity())
+ .subtract(processQty)
.max(BigDecimal.ZERO)
);
List<ProductProcessRouteItem> productProcessRouteItems = productProcessRouteItemMapper.selectList(Wrappers.<ProductProcessRouteItem>lambdaQuery().eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()));
@@ -294,16 +328,16 @@
//濡傛灉鏄渶鍚庝竴閬撳伐搴忔姤宸ヤ箣鍚庣敓浜ц鍗� 闇�瑕佹墸闄�
productOrder.setCompleteQuantity(
productOrder.getCompleteQuantity()
- .subtract(productionProductOutput.getQuantity())
+ .subtract(processQty)
.max(BigDecimal.ZERO)
);
- if (productOrder.getCompleteQuantity().compareTo(productionProductOutput.getQuantity()) > 0) {
+ if (productOrder.getCompleteQuantity().compareTo(processQty) > 0) {
productOrder.setEndTime(null);
}
productOrderMapper.updateById(productOrder);
}
- if (productWorkOrder.getCompleteQuantity().compareTo(productionProductOutput.getQuantity()) > 0) {
+ if (productWorkOrder.getCompleteQuantity().compareTo(processQty) > 0) {
productWorkOrder.setActualEndTime(null);
}
@@ -326,9 +360,10 @@
@Override
@Transactional(rollbackFor = Exception.class)
public void nextAddProductMain(@NotNull ProductionProductOutput productionProductOutput) {
- //鍚堟牸鏁伴噺=鎶ュ伐鏁伴噺-鎶ュ簾鏁伴噺
+ //瀹為檯鍏ュ簱鏁伴噺=浜у嚭鏁伴噺+鍔犳斁鏁�-鎶ュ簾鏁伴噺
ProductionProductMain productionProductMain = productionProductMainMapper.selectById(productionProductOutput.getProductMainId());
- BigDecimal productQty = productionProductOutput.getQuantity().subtract(productionProductOutput.getScrapQty());
+ BigDecimal addQty = productionProductOutput.getAddQty() == null ? BigDecimal.ZERO : productionProductOutput.getAddQty();
+ BigDecimal productQty = productionProductOutput.getQuantity().add(addQty).subtract(productionProductOutput.getScrapQty());
ProductProcessRouteItem productProcessRouteItem = productProcessRouteItemMapper.selectById(productionProductMain.getProductProcessRouteItemId());
ProductProcess productProcess = productProcessMapper.selectById(productProcessRouteItem.getProcessId());
//宸ヨ壓璺嚎涓綋鍓嶅伐搴忓搴旂殑浜у嚭瑙勬牸鍨嬪彿
@@ -348,32 +383,24 @@
inspectType = 2;
process = null;
}
- Product product = productMapper.selectById(productModel.getProductId());
- QualityInspect qualityInspect = new QualityInspect();
- qualityInspect.setProductId(product.getId());
- qualityInspect.setProductName(product.getProductName());
- qualityInspect.setModel(productModel.getModel());
- qualityInspect.setUnit(productModel.getUnit());
- qualityInspect.setQuantity(productQty);
- qualityInspect.setProcess(process);
- qualityInspect.setInspectState(0);
- qualityInspect.setInspectType(inspectType);
- qualityInspect.setProductMainId(productionProductMain.getId());
- qualityInspect.setProductModelId(productModel.getId());
- qualityInspectMapper.insert(qualityInspect);
- List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(product.getId(), inspectType, process);
- if (!qualityTestStandard.isEmpty()) {
- qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
- qualityInspectMapper.updateById(qualityInspect);
- qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
- .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.get(0).getId()))//榛樿鑾峰彇鏈�鏂扮殑
- .forEach(qualityTestStandardParam -> {
- QualityInspectParam param = new QualityInspectParam();
- BeanUtils.copyProperties(qualityTestStandardParam, param);
- param.setId(null);
- param.setInspectId(qualityInspect.getId());
- qualityInspectParamMapper.insert(param);
- });
+ // 鎺掍骇涓嬭揪鏃跺凡涓鸿宸ュ崟棰勭敓鎴愯川妫�鍗曪紙product_main_id IS NULL锛夛紝鎶ュ伐瀹℃牳鏃惰棰嗗苟鏇存柊涓哄疄闄呮暟閲�
+ QualityInspect preInspect = qualityInspectMapper.selectOne(
+ Wrappers.<QualityInspect>lambdaQuery()
+ .eq(QualityInspect::getWorkOrderId, productionProductMain.getWorkOrderId())
+ .isNull(QualityInspect::getProductMainId)
+ .last("limit 1"));
+ if (preInspect != null) {
+ preInspect.setProductMainId(productionProductMain.getId());
+ preInspect.setQuantity(productQty);
+ if (preInspect.getInspectState() != null && preInspect.getInspectState() == 1
+ && preInspect.getInspectType() != null && preInspect.getInspectType() == 2) {
+ // 宸叉彁浜や笖涓哄嚭鍘傛锛氬垹闄ゆ寜璁″垝鏁伴噺鍏ュ簱鐨勮褰曪紝鎸夊疄闄呮暟閲忛噸鏂板叆搴�
+ stockUtils.deleteStockInRecord(preInspect.getId(), StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode());
+ stockUtils.addStock(productModel.getId(), productQty, StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode(), preInspect.getId(), "-", "-", "-");
+ }
+ qualityInspectMapper.updateById(preInspect);
+ } else {
+ insertQualityInspect(productProcessRouteItem, inspectType, process, productionProductMain.getId(), productionProductMain.getWorkOrderId(), productQty);
}
} else {
//鐩存帴鍏ュ簱
@@ -386,7 +413,49 @@
/*娣诲姞鐢熶骇鏍哥畻 鍖哄垎宸ュ簭鏄浠惰繕鏄鏃�*/
BigDecimal workHours = productProcess.getSalaryQuota();
List<ProductionMachineRecord> productionMachineRecords = productionMachineRecordMapper.selectList(Wrappers.<ProductionMachineRecord>lambdaQuery().eq(ProductionMachineRecord::getWorkOrderId, productionProductMain.getWorkOrderId()));
- if (ObjectUtils.isNotEmpty(productionMachineRecords)) {
+ // 鎶ュ伐鐝粍浜哄憳浜ч噺鏃堕暱鏄庣粏锛堟湁鏄庣粏鍒欐寜浜虹敓鎴愭牳绠楋紝鏃犳槑缁嗗垯鍥為��鏈哄彴鎿嶄綔鍛橀�昏緫锛�
+ List<ProductionReportPerson> personList = productionReportPersonMapper.selectList(Wrappers.<ProductionReportPerson>lambdaQuery().eq(ProductionReportPerson::getProductMainId, productionProductMain.getId()));
+ // 璁惧鍙栨姤宸ュ崟璁惧锛屾湭濉啓鏃跺洖閫�鍙栬宸ュ崟浠讳竴鍙版満
+ Long reportDeviceId = productionProductMain.getDeviceId() != null
+ ? productionProductMain.getDeviceId()
+ : productionMachineRecords.stream().map(ProductionMachineRecord::getMachineId).filter(Objects::nonNull).findFirst().orElse(null);
+ if (CollUtil.isNotEmpty(personList)) {
+ // 姣忔鎶ュ伐鐙珛鎸変汉鐢熸垚鏍哥畻锛堟敮鎸佸垎鎵规姤宸ョ洿鑷冲畬鎴愶級锛屼笉鍙椾笂鏈鸿褰曟姤宸ョ姸鎬佸奖鍝�
+ BigDecimal netQty = productQty;
+ BigDecimal rawQuantity = productionProductOutput.getQuantity() == null ? BigDecimal.ZERO : productionProductOutput.getQuantity();
+ for (ProductionReportPerson person : personList) {
+ BigDecimal personQty = person.getQuantity() == null ? BigDecimal.ZERO : person.getQuantity();
+ BigDecimal finishedNum;
+ if (netQty.compareTo(rawQuantity) == 0 || rawQuantity.compareTo(BigDecimal.ZERO) == 0) {
+ // 鏃犳姤搴燂紙鎴栨瘺浜ч噺涓�0锛夋椂鍑�鍊煎嵆姣涗骇閲忥紝鐩存帴鍙栦釜浜轰骇閲�
+ finishedNum = personQty;
+ } else {
+ // 鏈夋姤搴熸椂鎸夊悇浜烘瘺浜ч噺鍗犳瘮鍒嗘憡鍑�鍊硷紝鍥涜垗浜斿叆淇濈暀2浣�
+ finishedNum = personQty.multiply(netQty).divide(rawQuantity, 2, BigDecimal.ROUND_HALF_UP);
+ }
+ SalesLedgerProductionAccounting salesLedgerProductionAccounting = SalesLedgerProductionAccounting.builder()
+ .productMainId(productionProductMain.getId())
+ .schedulingUserId(person.getUserId())
+ .schedulingUserName(person.getUserName())
+ .finishedNum(finishedNum)
+ .workHours(workHours)
+ .process(productProcess.getName())
+ .schedulingDate(LocalDate.now())
+ .tenantId(productionProductOutput.getTenantId())
+ .deviceId(reportDeviceId)
+ .workHour(person.getWorkHour() == null ? 0L : person.getWorkHour().longValue())
+ .build();
+ salesLedgerProductionAccountingMapper.insert(salesLedgerProductionAccounting);
+ }
+ // 璇ュ伐鍗曚笂鏈鸿褰曟湰娆″凡鎶ュ伐
+ for (ProductionMachineRecord productionMachineRecord : productionMachineRecords) {
+ if (!productionMachineRecord.getReportStatus()) {
+ productionMachineRecord.setReportStatus(true);
+ productionMachineRecordMapper.updateById(productionMachineRecord);
+ }
+ }
+ } else if (ObjectUtils.isNotEmpty(productionMachineRecords)) {
+ // 鍏煎鏃ф暟鎹細鏈紶浜哄憳鏄庣粏鍒欏洖閫�鎸夋湭鎶ュ伐鐨勬満鍙颁笂鏈烘搷浣滃憳鐢熸垚鏍哥畻
for (ProductionMachineRecord productionMachineRecord : productionMachineRecords) {
//璇存槑宸茬粡娣诲姞杩囦簡锛屼笉娣诲姞浜�
if (productionMachineRecord.getReportStatus()) {
@@ -418,6 +487,115 @@
}
}
+ /**
+ * 鐢熸垚璐ㄦ鍗曞苟鍥炲~妫�娴嬫爣鍑�/妫�楠屽弬鏁�
+ */
+ private void insertQualityInspect(ProductProcessRouteItem routeItem, Integer inspectType, String process,
+ Long productMainId, Long workOrderId, BigDecimal quantity) {
+ ProductModel productModel = productModelMapper.selectById(routeItem.getProductModelId());
+ Product product = productMapper.selectById(productModel.getProductId());
+ QualityInspect qualityInspect = new QualityInspect();
+ qualityInspect.setProductId(product.getId());
+ qualityInspect.setProductName(product.getProductName());
+ qualityInspect.setModel(productModel.getModel());
+ qualityInspect.setUnit(productModel.getUnit());
+ qualityInspect.setQuantity(quantity);
+ qualityInspect.setProcess(process);
+ qualityInspect.setInspectState(0);
+ qualityInspect.setInspectType(inspectType);
+ qualityInspect.setProductMainId(productMainId);
+ qualityInspect.setWorkOrderId(workOrderId);
+ qualityInspect.setProductModelId(productModel.getId());
+ qualityInspectMapper.insert(qualityInspect);
+ List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(product.getId(), inspectType, process);
+ if (!qualityTestStandard.isEmpty()) {
+ qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
+ qualityInspectMapper.updateById(qualityInspect);
+ qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
+ .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.get(0).getId()))//榛樿鑾峰彇鏈�鏂扮殑
+ .forEach(qualityTestStandardParam -> {
+ QualityInspectParam param = new QualityInspectParam();
+ BeanUtils.copyProperties(qualityTestStandardParam, param);
+ param.setId(null);
+ param.setInspectId(qualityInspect.getId());
+ qualityInspectParamMapper.insert(param);
+ });
+ }
+ }
+
+ @Override
+ public void preGenerateQualityInspectForOrder(Long productOrderId) {
+ if (productOrderId == null) {
+ return;
+ }
+ ProductProcessRoute productProcessRoute = productProcessRouteMapper.selectOne(
+ Wrappers.<ProductProcessRoute>lambdaQuery()
+ .eq(ProductProcessRoute::getProductOrderId, productOrderId));
+ if (productProcessRoute == null) {
+ return;
+ }
+ List<ProductProcessRouteItem> items = productProcessRouteItemMapper.selectList(
+ Wrappers.<ProductProcessRouteItem>lambdaQuery()
+ .eq(ProductProcessRouteItem::getProductRouteId, productProcessRoute.getId())
+ .orderByAsc(ProductProcessRouteItem::getDragSort)
+ .orderByAsc(ProductProcessRouteItem::getId));
+ if (items.isEmpty()) {
+ return;
+ }
+ // 绗竴閬撳伐搴忛渶璐ㄦ鎵嶉鐢熸垚
+ ProductProcessRouteItem first = items.get(0);
+ if (!Boolean.TRUE.equals(first.getIsQuality())) {
+ return;
+ }
+ ProductWorkOrder workOrder = productWorkOrderMapper.selectOne(
+ Wrappers.<ProductWorkOrder>lambdaQuery()
+ .eq(ProductWorkOrder::getProductProcessRouteItemId, first.getId()));
+ if (workOrder == null) {
+ return;
+ }
+ // 璇ュ伐鍗曞凡瀛樺湪棰勭敓鎴愯川妫�鍗曞垯涓嶉噸澶嶇敓鎴�
+ Long existCount = qualityInspectMapper.selectCount(
+ Wrappers.<QualityInspect>lambdaQuery()
+ .eq(QualityInspect::getWorkOrderId, workOrder.getId())
+ .isNull(QualityInspect::getProductMainId));
+ if (existCount != null && existCount > 0) {
+ return;
+ }
+ boolean onlyOne = items.size() == 1;
+ int inspectType = onlyOne ? 2 : 1;
+ String process = null;
+ if (!onlyOne) {
+ ProductProcess processEntity = productProcessMapper.selectById(first.getProcessId());
+ if (processEntity != null) {
+ process = processEntity.getName();
+ }
+ }
+ insertQualityInspect(first, inspectType, process, null, workOrder.getId(), workOrder.getPlanQuantity());
+ }
+
+ @Override
+ public void cleanPreGeneratedQualityInspects(List<Long> workOrderIds) {
+ if (CollUtil.isEmpty(workOrderIds)) {
+ return;
+ }
+ List<QualityInspect> preInspects = qualityInspectMapper.selectList(
+ Wrappers.<QualityInspect>lambdaQuery()
+ .in(QualityInspect::getWorkOrderId, workOrderIds)
+ .isNull(QualityInspect::getProductMainId));
+ if (CollUtil.isEmpty(preInspects)) {
+ return;
+ }
+ for (QualityInspect pre : preInspects) {
+ if (pre.getInspectState() != null && pre.getInspectState() == 1) {
+ throw new ServiceException("宸叉彁浜ょ殑妫�楠屽崟涓嶈兘鍒犻櫎");
+ }
+ }
+ List<Long> inspectIds = preInspects.stream().map(QualityInspect::getId).collect(Collectors.toList());
+ qualityInspectParamMapper.delete(
+ Wrappers.<QualityInspectParam>lambdaQuery().in(QualityInspectParam::getInspectId, inspectIds));
+ qualityInspectMapper.deleteBatchIds(inspectIds);
+ }
+
@Override
public Boolean removeProductMain(Long id) {
//鍒ゆ柇璇ユ潯鎶ュ伐鏄惁涓嶅悎鏍煎鐞�,濡傛灉涓嶅悎鏍煎鐞嗕簡锛屽垯涓嶅厑璁稿垹闄�
@@ -438,17 +616,22 @@
new LambdaQueryWrapper<SalesLedgerProductionAccounting>()
.eq(SalesLedgerProductionAccounting::getProductMainId, productionProductMain.getId())
);
+ /*鍒犻櫎鎶ュ伐鐝粍浜哄憳浜ч噺鏃堕暱鏄庣粏*/
+ productionReportPersonMapper.delete(
+ new LambdaQueryWrapper<ProductionReportPerson>()
+ .eq(ProductionReportPerson::getProductMainId, productionProductMain.getId())
+ );
/*鏇存柊宸ュ崟鍜岀敓浜ц鍗�*/
ProductWorkOrder productWorkOrder = productWorkOrderMapper.selectById(productionProductMain.getWorkOrderId());
if (productWorkOrder != null && productionProductOutput != null) {
BigDecimal outputQty = productionProductOutput.getQuantity() == null ? BigDecimal.ZERO : productionProductOutput.getQuantity();
- BigDecimal scrapQty = productionProductOutput.getScrapQty() == null ? BigDecimal.ZERO : productionProductOutput.getScrapQty();
+ BigDecimal addQty = productionProductOutput.getAddQty() == null ? BigDecimal.ZERO : productionProductOutput.getAddQty();
BigDecimal completeQty = productWorkOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productWorkOrder.getCompleteQuantity();
// 蹇呴』涓哄鏍搁�氳繃鐨勬墠浼氬噺灏戞暟閲�
- BigDecimal validQuantity = outputQty.subtract(scrapQty);
+ BigDecimal validQuantity = outputQty.add(addQty);
if (productionProductMain.getAuditStatus() != 2) {
productWorkOrder.setCompleteQuantity(completeQty.subtract(validQuantity).max(BigDecimal.ZERO));
}
@@ -464,8 +647,8 @@
if (productOrder != null) {
BigDecimal orderCompleteQty = productOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productOrder.getCompleteQuantity();
BigDecimal totalQty = productionProductOutput.getQuantity() != null ? productionProductOutput.getQuantity() : BigDecimal.ZERO;
- BigDecimal scrapQty = productionProductOutput.getScrapQty() != null ? productionProductOutput.getScrapQty() : BigDecimal.ZERO;
- BigDecimal actualQualifiedQty = totalQty.subtract(scrapQty);
+ BigDecimal addQty = productionProductOutput.getAddQty() != null ? productionProductOutput.getAddQty() : BigDecimal.ZERO;
+ BigDecimal actualQualifiedQty = totalQty.add(addQty);
BigDecimal newCompleteQty = orderCompleteQty.subtract(actualQualifiedQty);
productOrder.setCompleteQuantity(newCompleteQty.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : newCompleteQty);
productOrder.setEndTime(null);
diff --git a/src/main/java/com/ruoyi/project/common/CommonController.java b/src/main/java/com/ruoyi/project/common/CommonController.java
index 21fa5ec..a5c1fb1 100644
--- a/src/main/java/com/ruoyi/project/common/CommonController.java
+++ b/src/main/java/com/ruoyi/project/common/CommonController.java
@@ -2,7 +2,9 @@
import java.io.File;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -153,6 +155,40 @@
}
/**
+ * 閫氱敤涓婁紶璇锋眰锛堝涓紝杩斿洖鏂囦欢鍒楄〃锛屼緵闄勪欢涓婁紶缁勪欢浣跨敤锛�
+ */
+ @PostMapping("/uploadsList")
+ @ApiOperation(value = "閫氱敤涓婁紶璇锋眰锛堝涓紝杩斿洖鏂囦欢鍒楄〃锛�")
+ public AjaxResult uploadsList(List<MultipartFile> files) throws Exception
+ {
+ try
+ {
+ if (files == null || files.isEmpty())
+ {
+ return AjaxResult.error("鏈�夋嫨鏂囦欢");
+ }
+ // 涓婁紶鏂囦欢璺緞
+ String filePath = RuoYiConfig.getUploadPath();
+ List<Map<String, String>> result = new ArrayList<>();
+ for (MultipartFile file : files)
+ {
+ // 涓婁紶骞惰繑鍥炴柊鏂囦欢鍚嶇О
+ String fileName = FileUploadUtils.upload(filePath, file);
+ String url = serverConfig.getUrl() + fileName;
+ Map<String, String> item = new LinkedHashMap<>();
+ item.put("name", file.getOriginalFilename());
+ item.put("url", url);
+ result.add(item);
+ }
+ return AjaxResult.success(result);
+ }
+ catch (Exception e)
+ {
+ return AjaxResult.error(e.getMessage());
+ }
+ }
+
+ /**
* 鏈湴璧勬簮閫氱敤涓嬭浇
*/
@GetMapping("/download/resource")
diff --git a/src/main/java/com/ruoyi/quality/pojo/QualityInspect.java b/src/main/java/com/ruoyi/quality/pojo/QualityInspect.java
index 8a5f425..960e371 100644
--- a/src/main/java/com/ruoyi/quality/pojo/QualityInspect.java
+++ b/src/main/java/com/ruoyi/quality/pojo/QualityInspect.java
@@ -145,6 +145,11 @@
*/
private Long productMainId;
+ /**
+ * 鐢熶骇宸ュ崟id(鎺掍骇涓嬭揪棰勭敓鎴愯川妫�鍗曠敤锛屾鏃舵棤鎶ュ伐id)
+ */
+ private Long workOrderId;
+
private Long productModelId;
//涓嶅悎鏍肩幇璞�
private String defectivePhenomena;
diff --git a/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java b/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
index cad0da6..a32b3c6 100644
--- a/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
+++ b/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
@@ -163,18 +163,22 @@
} else {
// 杩囩▼妫�楠�/鍑哄巶妫�楠岋細閫氳繃鍏宠仈鍏崇郴鏌ヨ閿�鍞彴璐︾殑瀹㈡埛
customerValue = inspect.getCustomer(); // 榛樿浣跨敤鐩存帴瀛樺偍鐨勫鎴�
+ Long workOrderId = inspect.getWorkOrderId();
if (inspect.getProductMainId() != null) {
// 閫氳繃鎶ュ伐ID鏌ヨ宸ュ崟
ProductionProductMain productMain = productionProductMainMapper.selectById(inspect.getProductMainId());
- if (productMain != null && productMain.getWorkOrderId() != null) {
- ProductWorkOrder workOrder = productWorkOrderMapper.selectById(productMain.getWorkOrderId());
- if (workOrder != null && workOrder.getProductOrderId() != null) {
- ProductOrder productOrder = productOrderMapper.selectById(workOrder.getProductOrderId());
- if (productOrder != null && productOrder.getSalesLedgerId() != null) {
- SalesLedger salesLedger = salesLedgerMapper.selectById(productOrder.getSalesLedgerId());
- if (salesLedger != null && salesLedger.getCustomerName() != null) {
- customerValue = salesLedger.getCustomerName();
- }
+ if (productMain != null) {
+ workOrderId = productMain.getWorkOrderId();
+ }
+ }
+ if (workOrderId != null) {
+ ProductWorkOrder workOrder = productWorkOrderMapper.selectById(workOrderId);
+ if (workOrder != null && workOrder.getProductOrderId() != null) {
+ ProductOrder productOrder = productOrderMapper.selectById(workOrder.getProductOrderId());
+ if (productOrder != null && productOrder.getSalesLedgerId() != null) {
+ SalesLedger salesLedger = salesLedgerMapper.selectById(productOrder.getSalesLedgerId());
+ if (salesLedger != null && salesLedger.getCustomerName() != null) {
+ customerValue = salesLedger.getCustomerName();
}
}
}
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
index b756ed2..e8de010 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -18,6 +18,7 @@
import com.ruoyi.production.pojo.*;
import com.ruoyi.production.service.ProductProcessService;
import com.ruoyi.production.service.ProductWorkOrderService;
+import com.ruoyi.production.service.ProductionProductMainService;
import com.ruoyi.production.service.impl.ProductOrderServiceImpl;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
@@ -63,8 +64,10 @@
private final ProductWorkOrderService productWorkOrderService;
private final ProductProcessService productProcessService;
+ private ProductionProductMainService productionProductMainService;
private SalesLedgerProductMapper salesLedgerProductMapper;
private SalesLedgerProductionAccountingMapper salesLedgerProductionAccountingMapper;
+ private ProductionReportPersonMapper productionReportPersonMapper;
private SalesLedgerMapper salesLedgerMapper;
private PurchaseLedgerMapper purchaseLedgerMapper;
private ProductOrderMapper productOrderMapper;
@@ -316,6 +319,7 @@
productProcessRouteItem.setProductOrderId(productOrder.getId());
productProcessRouteItem.setProductRouteId(productProcessRoute.getId());
productProcessRouteItem.setDragSort(processRouteItem.getDragSort());
+ productProcessRouteItem.setIsQuality(processRouteItem.getIsQuality());
int insert = productProcessRouteItemMapper.insert(productProcessRouteItem);
if (insert > 0) {
ProductWorkOrder productWorkOrder = new ProductWorkOrder();
@@ -329,6 +333,8 @@
}
}
+ // 鎺掍骇涓嬭揪鍚庤嚜鍔ㄨЕ鍙戠涓�閬撻渶璐ㄦ宸ュ簭鐨勮繃绋嬫楠�
+ productionProductMainService.preGenerateQualityInspectForOrder(productOrder.getId());
productOrder.setRouteId(processRoute.getId());
productOrderMapper.updateById(productOrder);
}
@@ -400,9 +406,13 @@
qualityInspectMapper.deleteByProductMainIds(productMainIds);
salesLedgerProductionAccountingMapper.delete(new LambdaQueryWrapper<SalesLedgerProductionAccounting>()
.in(SalesLedgerProductionAccounting::getProductMainId, productMainIds));
+ productionReportPersonMapper.delete(new LambdaQueryWrapper<ProductionReportPerson>()
+ .in(ProductionReportPerson::getProductMainId, productMainIds));
}
productionProductMainMapper.deleteByWorkOrderIds(workOrderIds);
+ // 娓呯悊鎺掍骇棰勭敓鎴愮殑璐ㄦ鍗�
+ productionProductMainService.cleanPreGeneratedQualityInspects(workOrderIds);
productWorkOrderMapper.delete(new LambdaQueryWrapper<ProductWorkOrder>()
.in(ProductWorkOrder::getProductOrderId, orderIds));
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index 3f2b037..4a98e72 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -74,9 +74,9 @@
druid:
# 涓诲簱鏁版嵁婧�
master:
- url: jdbc:mysql://localhost:3300/product-inventory-management-ygcy?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+ url: jdbc:mysql://localhost:3306/product-inventory-management-ygcy?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
- password: root
+ password: 123456
# 浠庡簱鏁版嵁婧�
slave:
# 浠庢暟鎹簮寮�鍏�/榛樿鍏抽棴
@@ -154,7 +154,7 @@
database: 0
# 瀵嗙爜
# password: root2022!
- password: 123456
+# password:
# 杩炴帴瓒呮椂鏃堕棿
timeout: 10s
diff --git a/src/main/resources/mapper/production/ProductProcessMapper.xml b/src/main/resources/mapper/production/ProductProcessMapper.xml
index 9213760..3122774 100644
--- a/src/main/resources/mapper/production/ProductProcessMapper.xml
+++ b/src/main/resources/mapper/production/ProductProcessMapper.xml
@@ -25,7 +25,7 @@
pp.name AS processName,
SUM(pi.quantity) AS totalInput,
SUM(distinct ppo.scrap_qty) AS totalScrap,
- SUM(distinct (ppo.quantity - ppo.scrap_qty)) AS totalOutput
+ SUM(distinct (ppo.quantity + IFNULL(ppo.add_qty, 0))) AS totalOutput
FROM
production_product_output ppo
INNER JOIN production_product_main ppm ON ppo.product_main_id = ppm.id
diff --git a/src/main/resources/mapper/production/ProductWorkOrderMapper.xml b/src/main/resources/mapper/production/ProductWorkOrderMapper.xml
index 3a92a65..cf0e13f 100644
--- a/src/main/resources/mapper/production/ProductWorkOrderMapper.xml
+++ b/src/main/resources/mapper/production/ProductWorkOrderMapper.xml
@@ -39,7 +39,8 @@
END AS work_order_type,
pp.device_name,
pp.user_ids as process_user_ids,
- t8.product_name as final_product_model
+ t8.product_name as final_product_model,
+ ppri.process_route_add_num as processRouteAddNum
FROM
product_work_order pwo
LEFT JOIN product_process_route_item ppri ON ppri.id = pwo.product_process_route_item_id
@@ -68,7 +69,8 @@
sum(ppo.scrap_qty) scrapQty,
pp.device_id,
pp.device_name,
- t8.product_name as final_product_model
+ t8.product_name as final_product_model,
+ ppri.process_route_add_num as processRouteAddNum
FROM
product_work_order pwo
LEFT JOIN product_process_route_item ppri ON ppri.id = pwo.product_process_route_item_id
@@ -88,7 +90,8 @@
pm.model,
pm.unit,
p.product_name,
- po.nps_no
+ po.nps_no,
+ ppri.process_route_add_num
</select>
<select id="selectWorkOrderStartStats" resultType="com.ruoyi.production.dto.ProductWorkOrderDto">
SELECT
diff --git a/src/main/resources/mapper/production/ProductionProductMainMapper.xml b/src/main/resources/mapper/production/ProductionProductMainMapper.xml
index df33e70..8abeb57 100644
--- a/src/main/resources/mapper/production/ProductionProductMainMapper.xml
+++ b/src/main/resources/mapper/production/ProductionProductMainMapper.xml
@@ -76,11 +76,11 @@
pm.model as product_model_name,
pm.unit,
slpa.process,
- ppo.quantity,
+ COALESCE(prp.quantity, slpa.finished_num) AS quantity,
slpa.work_hours,
slpa.work_hours * slpa.finished_num AS wages,
dl.device_name,
- slpa.work_hour as work_minutes,
+ slpa.work_hour as person_work_minutes,
IFNULL(
TIMESTAMPDIFF(MINUTE, ppm.start_time, ppm.end_time),
0
@@ -91,6 +91,7 @@
FROM
production_product_main ppm
LEFT JOIN sales_ledger_production_accounting slpa ON slpa.product_main_id = ppm.id
+ LEFT JOIN production_report_person prp ON prp.product_main_id = ppm.id AND prp.user_id = slpa.scheduling_user_id
LEFT JOIN production_product_output ppo ON ppm.id = ppo.product_main_id
LEFT JOIN product_work_order pwo ON pwo.id = ppm.work_order_id
LEFT JOIN product_order po ON po.id = pwo.product_order_id
diff --git a/src/main/resources/mapper/production/ProductionProductOutputMapper.xml b/src/main/resources/mapper/production/ProductionProductOutputMapper.xml
index 3b44135..19b519b 100644
--- a/src/main/resources/mapper/production/ProductionProductOutputMapper.xml
+++ b/src/main/resources/mapper/production/ProductionProductOutputMapper.xml
@@ -49,7 +49,7 @@
<select id="selectDailyOutputStats" resultType="java.util.Map">
SELECT
DATE_FORMAT(create_time, '%Y-%m-%d') as date,
- SUM(quantity-scrap_qty) as quantity
+ SUM(quantity + IFNULL(add_qty, 0)) as quantity
FROM
production_product_output
WHERE
diff --git a/src/main/resources/mapper/production/SalesLedgerProductionAccountingMapper.xml b/src/main/resources/mapper/production/SalesLedgerProductionAccountingMapper.xml
index 556f6f0..3739b31 100644
--- a/src/main/resources/mapper/production/SalesLedgerProductionAccountingMapper.xml
+++ b/src/main/resources/mapper/production/SalesLedgerProductionAccountingMapper.xml
@@ -54,7 +54,7 @@
SELECT
slpa.scheduling_user_name,
- SUM(ppout.quantity) AS output_num,
+ SUM(COALESCE(prp.quantity, slpa.finished_num)) AS output_num,
CONCAT(
ROUND(
CASE
@@ -75,6 +75,8 @@
FROM sales_ledger_production_accounting slpa
LEFT JOIN production_product_main ppm
ON slpa.product_main_id = ppm.id
+ LEFT JOIN production_report_person prp
+ ON prp.product_main_id = ppm.id AND prp.user_id = slpa.scheduling_user_id
LEFT JOIN production_product_output ppout
ON ppm.id = ppout.product_main_id
left join device_ledger dl on dl.id = slpa.device_id
@@ -96,10 +98,10 @@
<select id="selectDailyWagesStats" resultType="java.util.Map">
SELECT DATE(ppout.create_time) AS dateStr,
- SUM(ppout.quantity - IFNULL(ppout.scrap_qty, 0)) AS numberOfCompleted,
- SUM((ppout.quantity - IFNULL(ppout.scrap_qty, 0)) * IFNULL(pp.salary_quota, 0)) AS amount,
+ SUM(ppout.quantity + IFNULL(ppout.add_qty, 0) - IFNULL(ppout.scrap_qty, 0)) AS numberOfCompleted,
+ SUM((ppout.quantity + IFNULL(ppout.add_qty, 0) - IFNULL(ppout.scrap_qty, 0)) * IFNULL(pp.salary_quota, 0)) AS amount,
ROUND(
- SUM(ppout.quantity - IFNULL(ppout.scrap_qty, 0)) * 100 / NULLIF(SUM(ppout.quantity), 0),
+ SUM(ppout.quantity + IFNULL(ppout.add_qty, 0) - IFNULL(ppout.scrap_qty, 0)) * 100 / NULLIF(SUM(ppout.quantity), 0),
2
) AS passRate
FROM production_product_output ppout
diff --git a/src/main/resources/mapper/quality/QualityInspectMapper.xml b/src/main/resources/mapper/quality/QualityInspectMapper.xml
index 7b883c3..3e01e97 100644
--- a/src/main/resources/mapper/quality/QualityInspectMapper.xml
+++ b/src/main/resources/mapper/quality/QualityInspectMapper.xml
@@ -20,7 +20,7 @@
</when>
<otherwise>
LEFT JOIN production_product_main ppm ON qi.product_main_id = ppm.id
- LEFT JOIN product_work_order pwo ON ppm.work_order_id = pwo.id
+ LEFT JOIN product_work_order pwo ON pwo.id = COALESCE(qi.work_order_id, ppm.work_order_id)
</otherwise>
</choose>
WHERE
@@ -88,7 +88,7 @@
</when>
<otherwise>
LEFT JOIN production_product_main ppm ON qi.product_main_id = ppm.id
- LEFT JOIN product_work_order pwo ON ppm.work_order_id = pwo.id
+ LEFT JOIN product_work_order pwo ON pwo.id = COALESCE(qi.work_order_id, ppm.work_order_id)
</otherwise>
</choose>
WHERE qi.inspect_type=#{qualityInspect.inspectType}
--
Gitblit v1.9.3