From f0efc10af370936b4db47b8428eb1ca4686431f9 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 06 八月 2026 15:11:56 +0800
Subject: [PATCH] feat(home): 添加报表日期范围筛选功能并完善设备维护管理

---
 src/main/java/com/ruoyi/home/mapper/HomeMapper.java                                                        |   12 
 src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java                         |   46 +
 docs/spare_parts_outbound.md                                                                               |  130 ++++
 src/main/java/com/ruoyi/measuringinstrumentledger/dto/SparePartsOutboundDto.java                           |   21 
 src/main/java/com/ruoyi/home/controller/HomeController.java                                                |   42 
 src/main/resources/mapper/device/DeviceMaintenanceMapper.xml                                               |    1 
 src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java                                               |    3 
 src/main/java/com/ruoyi/common/enums/SparePartsRequisitionRecordSourceTypeEnum.java                        |    3 
 src/main/java/com/ruoyi/measuringinstrumentledger/service/SparePartsRequisitionRecordService.java          |    3 
 .claude/skills/project-conventions.md                                                                      |   51 +
 src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java                                             |  253 ++++++--
 .claude/commands/feature-development.md                                                                    |   32 +
 src/main/resources/mapper/sales/SalesLedgerProductMapper.xml                                               |   37 +
 doc/旭宝峰系统功能优化需求整理.md                                                                                       |   23 
 docs/dashboard_optimization.md                                                                             |  177 +++++
 src/main/java/com/ruoyi/purchase/controller/AccountingReportController.java                                |    7 
 src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java                              |   49 +
 src/main/resources/application-dev.yml                                                                     |    2 
 src/main/java/com/ruoyi/purchase/mapper/InvoicePurchaseMapper.java                                         |    2 
 src/main/resources/mapper/home/HomeMapper.xml                                                              |   24 
 src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java                                 |   11 
 src/main/java/com/ruoyi/purchase/dto/VatDto.java                                                           |   28 
 src/main/java/com/ruoyi/device/service/IDeviceMaintenanceService.java                                      |    4 
 src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java                                            |    3 
 .claude/identity.json                                                                                      |    9 
 src/main/java/com/ruoyi/purchase/service/IInvoicePurchaseService.java                                      |    2 
 src/main/java/com/ruoyi/purchase/service/impl/InvoicePurchaseServiceImpl.java                              |    5 
 docs/inspection_task_complete.md                                                                           |  155 +++++
 .claude/rules/frontend-docs.md                                                                             |   63 ++
 .claude/settings.local.json                                                                                |   29 
 src/main/java/com/ruoyi/measuringinstrumentledger/service/impl/SparePartsRequisitionRecordServiceImpl.java |   29 
 src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java                                  |    4 
 src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java                            |   20 
 docs/vat_comparison_optimization.md                                                                        |  189 ++++++
 doc/20260806_inspection_task_add_status.sql                                                                |    2 
 src/main/java/com/ruoyi/home/service/HomeService.java                                                      |   14 
 src/main/java/com/ruoyi/measuringinstrumentledger/controller/SparePartsRequisitionRecordController.java    |   10 
 .claude/commands/database-migration.md                                                                     |   34 +
 docs/maintenance_task_complete.md                                                                          |  142 ++++
 src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java                                         |    4 
 .claude/rules/security-guardrails.md                                                                       |   37 +
 src/main/resources/mapper/purchase/InvoicePurchaseMapper.xml                                               |   51 +
 42 files changed, 1,668 insertions(+), 95 deletions(-)

diff --git a/.claude/commands/database-migration.md b/.claude/commands/database-migration.md
new file mode 100644
index 0000000..46c9860
--- /dev/null
+++ b/.claude/commands/database-migration.md
@@ -0,0 +1,34 @@
+# /鏁版嵁搴撹縼绉�
+
+鍦ㄨ繘琛�**鏁版嵁搴撹縼绉�**鏃朵娇鐢ㄦ宸ヤ綔娴併��
+
+## 鐩爣
+
+瀹夊叏鐨勬暟鎹簱杩佺Щ宸ヤ綔娴�
+
+## 甯哥敤鏂囦欢
+
+- `migrations/*`
+- `schemas/*`
+- `**/models/**`
+- `**/db/**`
+
+## 寤鸿姝ラ
+
+1. 鏌ョ湅鐜版湁杩佺Щ浠ヤ簡瑙fā寮忋��
+2. 鍒涘缓甯︽湁鏃堕棿鎴冲墠缂�鐨勬柊杩佺Щ鏂囦欢銆�
+3. 缂栧啓鏀寔鍥炴粴鐨勮縼绉汇��
+4. 鍦ㄦ湰鍦扮幆澧冧腑娴嬭瘯杩佺Щ銆�
+5. 璁板綍浠讳綍鐮村潖鎬у彉鏇淬��
+
+## 鍏稿瀷鎻愪氦淇℃伅
+
+- 涓篬鍔熻兘]娣诲姞杩佺Щ
+- 淇杩佺Щ[鎻忚堪]
+- 鏇存柊鏋舵瀯鏂囨。
+
+## 娉ㄦ剰浜嬮」
+
+- 濮嬬粓鍦ㄥ壇鏈笂娴嬭瘯杩佺Щ銆�
+- 纭繚鍥炴粴鑴氭湰涔熺粡杩囨祴璇曘��
+- 娌℃湁澶囦唤鐨勬儏鍐典笅閬垮厤鐮村潖鎬ф搷浣溿��
diff --git a/.claude/commands/feature-development.md b/.claude/commands/feature-development.md
new file mode 100644
index 0000000..ca75e0a
--- /dev/null
+++ b/.claude/commands/feature-development.md
@@ -0,0 +1,32 @@
+# /鍔熻兘寮�鍙�
+
+鍦ㄨ繘琛�**鍔熻兘寮�鍙�**鏃朵娇鐢ㄦ宸ヤ綔娴併��
+
+## 鐩爣
+
+鏍囧噯鍖栧姛鑳藉疄鐜板伐浣滄祦
+
+## 甯哥敤鏂囦欢
+
+- `manifests/*`
+- `schemas/*`
+- `**/*.test.*`
+- `**/api/**`
+
+## 寤鸿姝ラ
+
+1. 鍦ㄧ紪杈戝墠浜嗚В褰撳墠鐘舵�佸拰澶辫触妯″紡銆�
+2. 杩涜鏈�灏忎笖杩炶疮鐨勬洿鏀逛互婊¤冻宸ヤ綔娴佺洰鏍囥��
+3. 瀵逛慨鏀圭殑鏂囦欢杩愯鏈�鐩稿叧鐨勯獙璇併��
+4. 鎬荤粨鏇存敼鍐呭浠ュ強浠嶉渶瀹℃煡鐨勯儴鍒嗐��
+
+## 鍏稿瀷鎻愪氦淇℃伅
+
+- 娣诲姞鍔熻兘瀹炵幇
+- 涓哄姛鑳芥坊鍔犳祴璇�
+- 鏇存柊鏂囨。
+
+## 娉ㄦ剰浜嬮」
+
+- 灏嗘瑙嗕负鑴氭墜鏋讹紝鑰岄潪纭紪鐮佽剼鏈��
+- 濡傛灉宸ヤ綔娴佹湁閲嶅ぇ鍙樺寲锛屾洿鏂版鍛戒护銆�
diff --git a/.claude/identity.json b/.claude/identity.json
new file mode 100644
index 0000000..d55b516
--- /dev/null
+++ b/.claude/identity.json
@@ -0,0 +1,9 @@
+{
+  "name": "product-inventory-management",
+  "description": "Product inventory management system with JDK 25+",
+  "language": "Java",
+  "framework": "Spring Boot",
+  "database": "MySQL",
+  "orm": "MyBatis",
+  "javaVersion": "25"
+}
diff --git a/.claude/rules/frontend-docs.md b/.claude/rules/frontend-docs.md
new file mode 100644
index 0000000..8016dee
--- /dev/null
+++ b/.claude/rules/frontend-docs.md
@@ -0,0 +1,63 @@
+# 鎺ュ彛鍙樻洿鍓嶇鏂囨。瑙勫垯
+
+## 瑙勫垯
+
+- 姣忔娑夊強鎺ュ彛鍙樻洿锛堟柊澧炴帴鍙c�佷慨鏀硅姹傚弬鏁般�佷慨鏀瑰搷搴旂粨鏋勶級鏃讹紝蹇呴』杈撳嚭涓�浠藉墠绔慨鏀规枃妗e埌 `docs/` 鐩綍銆�
+- 鏂囨。浠� Markdown 鏍煎紡缂栧啓锛屾枃浠跺悕浣跨敤灏忓啓瀛楁瘝鍜屼笅鍒掔嚎锛屽 `docs/customer_handover.md`銆�
+- 鏂囨。鍐呭鑷冲皯鍖呭惈锛欰PI 璇存槑锛堣矾寰勩�佹柟娉曘�佽姹傚弬鏁般�佸搷搴旀牸寮忥級銆佹秹鍙婄殑鍓嶇椤甸潰銆佸叿浣撶殑淇敼浠g爜绀轰緥銆�
+
+## 鏂囨。妯℃澘
+
+```markdown
+# 鍔熻兘鍚嶇О
+
+## 娑夊強椤甸潰
+
+- 椤甸潰1
+- 椤甸潰2
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /xxx/xxx | 璇存槑 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| xxx | String | 鏄� | xxx |
+
+**鍝嶅簲锛�** `{ "code": 200, "msg": "鎿嶄綔鎴愬姛" }`
+
+## 鍓嶇淇敼鐐�
+
+### 1. 鎸夐挳/寮圭獥/琛ㄦ牸绛�
+
+\`\`\`html
+<el-button>xxx</el-button>
+\`\`\`
+
+### 2. data 鏁版嵁
+
+\`\`\`js
+data() {
+  return {
+    xxx: '',
+  }
+}
+\`\`\`
+
+### 3. 鏂规硶
+
+\`\`\`js
+handleXxx() {
+  // ...
+}
+\`\`\`
+
+## 娉ㄦ剰浜嬮」
+
+- 娉ㄦ剰浜嬮」1
+- 娉ㄦ剰浜嬮」2
+```
\ No newline at end of file
diff --git a/.claude/rules/security-guardrails.md b/.claude/rules/security-guardrails.md
new file mode 100644
index 0000000..21348c2
--- /dev/null
+++ b/.claude/rules/security-guardrails.md
@@ -0,0 +1,37 @@
+# 瀹夊叏鎶ゆ爮
+
+## SQL 娉ㄥ叆闃叉姢
+
+- 姘歌繙涓嶈灏嗙敤鎴疯緭鍏ョ洿鎺ユ嫾鎺ュ埌 SQL 鏌ヨ涓��
+- 濮嬬粓浣跨敤鍙傛暟鍖栨煡璇㈡垨 ORM 鏂规硶銆�
+- 鍦ㄦ暟鎹簱鎿嶄綔鍓嶉獙璇佸拰娓呯悊鎵�鏈夎緭鍏ャ��
+
+## XSS 闃叉姢
+
+- 鍦� HTML 涓覆鏌撳墠杞箟鎵�鏈夌敤鎴风敓鎴愮殑鍐呭銆�
+- 浣跨敤妯℃澘鑷姩杞箟鍔熻兘銆�
+- 鍦ㄥ鎴风鍜屾湇鍔″櫒绔兘杩涜杈撳叆楠岃瘉鍜屾竻鐞嗐��
+
+## 瀵嗛挜绠$悊
+
+- 姘歌繙涓嶈鍦ㄦ簮浠g爜涓‖缂栫爜瀵嗛挜銆丄PI 瀵嗛挜鎴栧瘑鐮併��
+- 浣跨敤鐜鍙橀噺鎴栧畨鍏ㄤ繚闄╁簱瀛樺偍鏁忔劅鏁版嵁銆�
+- 瀹氭湡杞崲瀵嗛挜锛岄伩鍏嶅湪鏃ュ織涓褰曞畠浠��
+
+## 杈撳叆楠岃瘉
+
+- 鍦ㄧ郴缁熻竟鐣屽楠岃瘉鎵�鏈夎緭鍏ャ��
+- 浣跨敤鐧藉悕鍗曡�岄潪榛戝悕鍗曡繘琛岄獙璇併��
+- 鍦ㄥ鐞嗕笂浼犲墠妫�鏌ユ枃浠剁被鍨嬪拰澶у皬銆�
+
+## 閿欒澶勭悊
+
+- 涓嶈鍦ㄩ敊璇秷鎭腑鏆撮湶鏁忔劅淇℃伅銆�
+- 鍦ㄥ唴閮ㄨ褰曡缁嗛敊璇紝鍚戠敤鎴疯繑鍥為�氱敤娑堟伅銆�
+- 澶勭悊鎵�鏈夊紓甯镐互闃叉淇℃伅娉勯湶銆�
+
+## 渚濊禆绠$悊
+
+- 淇濇寔渚濊禆椤规洿鏂般��
+- 瀹¤渚濊禆椤逛腑鐨勫凡鐭ユ紡娲炪��
+- 鍥哄畾鐗堟湰浠ラ伩鍏嶆剰澶栫殑鐮村潖鎬у彉鏇淬��
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..05837a5
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,29 @@
+{
+  "permissions": {
+    "allow": [
+      "Read",
+      "Glob",
+      "Grep",
+      "Write",
+      "Edit",
+      "Bash(git status)",
+      "Bash(git diff)",
+      "Bash(npm test)",
+      "Bash(npm run build)",
+      "Bash(pytest *)",
+      "mcp__ide__getDiagnostics",
+      "Bash(mvn compile *)",
+      "Bash(sed -i '269,271d' src/main/resources/mapper/system/SysUserMapper.xml)",
+      "Bash(sed -i '268a\\\\\\\\t\\\\t<update id=\"resetAllUserPwd\">\\\\n\\\\t\\\\t\\\\tupdate sys_user set password = #{password} where del_flag = '\\\\''0'\\\\''\\\\n\\\\t\\\\t</update>' src/main/resources/mapper/system/SysUserMapper.xml)",
+      "Bash(sed -i '269s/^.*$/\\\\t\\\\t<update id=\"resetAllUserPwd\">/' src/main/resources/mapper/system/SysUserMapper.xml)",
+      "Bash(sed -i '270s/^.*$/\\\\t\\\\t\\\\tupdate sys_user set password = #{password} where del_flag = '\\\\''0'\\\\''/' src/main/resources/mapper/system/SysUserMapper.xml)",
+      "Bash(sed -i '271s/^.*$/\\\\t\\\\t<\\\\/update>/' src/main/resources/mapper/system/SysUserMapper.xml)"
+    ],
+    "deny": [
+      "Bash(rm -rf *)",
+      "Bash(curl * | bash)",
+      "Bash(git push --force *)",
+      "Bash(sudo *)"
+    ]
+  }
+}
diff --git a/.claude/skills/project-conventions.md b/.claude/skills/project-conventions.md
new file mode 100644
index 0000000..28ac0ce
--- /dev/null
+++ b/.claude/skills/project-conventions.md
@@ -0,0 +1,51 @@
+# 椤圭洰寮�鍙戠害瀹�
+
+## 姒傝堪
+
+姝ゆ妧鑳藉畾涔変簡鏈」鐩殑寮�鍙戞ā寮忓拰绾﹀畾銆�
+
+## 鎶�鏈爤
+
+- **涓昏璇█**: Java (JDK 25+)
+- **鏋舵瀯**: Spring Boot + MyBatis
+- **娴嬭瘯浣嶇疆**: `src/test/java`
+
+## 鎻愪氦绾﹀畾
+
+閬靛惊绾﹀畾寮忔彁浜ゆ牸寮忥細
+
+### 浣跨敤鐨勫墠缂�
+
+- `feat` - 鏂板姛鑳�
+- `fix` - 閿欒淇
+- `docs` - 鏂囨。鍙樻洿
+- `test` - 娴嬭瘯娣诲姞鎴栨洿鏂�
+- `refactor` - 浠g爜閲嶆瀯
+- `style` - 浠g爜椋庢牸鍙樻洿锛堟牸寮忓寲銆佸垎鍙风瓑锛�
+- `chore` - 鏋勫缓杩囩▼鎴栬緟鍔╁伐鍏峰彉鏇�
+
+### 娑堟伅瑙勮寖
+
+- 淇濇寔绗竴琛岀畝娲佷笖鏈夋弿杩版�э紙灏戜簬 70 涓瓧绗︼級
+- 浣跨敤绁堜娇璇皵锛�"娣诲姞鍔熻兘"鑰岄潪"娣诲姞浜嗗姛鑳�"锛�
+- 鐩稿叧鏃跺寘鍚寖鍥达細`feat(鑼冨洿): 鎻忚堪`
+
+## 浠g爜椋庢牸
+
+- 閬靛惊 Java 鍛藉悕绾﹀畾
+- 浣跨敤鏈夋剰涔夌殑鍙橀噺鍜屾柟娉曞悕
+- 涓哄叕鍏� API 娣诲姞 Javadoc
+- 淇濇寔鏂规硶涓撴敞涓斿崟涓�鑱岃矗
+
+## 娴嬭瘯
+
+- 涓轰笟鍔¢�昏緫缂栧啓鍗曞厓娴嬭瘯
+- 涓烘暟鎹簱鎿嶄綔浣跨敤闆嗘垚娴嬭瘯
+- 淇濇寔娴嬭瘯瑕嗙洊鐜囧湪 80% 浠ヤ笂
+
+## 鍚堝苟璇锋眰瑙勮寖
+
+- 淇濇寔鍚堝苟璇锋眰涓撴敞涓斿皬
+- 涓烘柊鍔熻兘鍖呭惈娴嬭瘯
+- 鏍规嵁闇�瑕佹洿鏂版枃妗�
+- 鍚堝苟鍓嶅鏌ヤ唬鐮�
diff --git a/doc/20260806_inspection_task_add_status.sql b/doc/20260806_inspection_task_add_status.sql
new file mode 100644
index 0000000..1fdd845
--- /dev/null
+++ b/doc/20260806_inspection_task_add_status.sql
@@ -0,0 +1,2 @@
+-- inspection_task 琛ㄦ柊澧� status 瀛楁
+ALTER TABLE inspection_task ADD COLUMN status TINYINT DEFAULT 0 COMMENT '鐘舵�� 0寰呭贰妫� 1宸插畬鎴�';
diff --git "a/doc/\346\227\255\345\256\235\345\263\260\347\263\273\347\273\237\345\212\237\350\203\275\344\274\230\345\214\226\351\234\200\346\261\202\346\225\264\347\220\206.md" "b/doc/\346\227\255\345\256\235\345\263\260\347\263\273\347\273\237\345\212\237\350\203\275\344\274\230\345\214\226\351\234\200\346\261\202\346\225\264\347\220\206.md"
new file mode 100644
index 0000000..7332c0c
--- /dev/null
+++ "b/doc/\346\227\255\345\256\235\345\263\260\347\263\273\347\273\237\345\212\237\350\203\275\344\274\230\345\214\226\351\234\200\346\261\202\346\225\264\347\220\206.md"
@@ -0,0 +1,23 @@
+# 涓�銆佽澶囩鐞嗘ā鍧�
+
+## 1\. 澶囦欢绠$悊 - 澶囦欢棰嗙敤璁板綍
+
+鐜版湁澶囦欢鍑哄簱浠呰兘渚濇墭璁惧妫�淇満鏅紝鍑哄簱娓犻亾瀛樺湪灞�闄愶紝闇�鏂板鐙珛澶囦欢鍑哄簱鍔熻兘鍏ュ彛锛屾敮鎸佺洿鎺ュ畬鎴愬浠堕鐢ㄥ嚭搴撴搷浣溿��
+
+## 澶囦欢棰嗙敤璁板綍椤甸潰2. 璁惧淇濆吇 - 淇濆吇浠诲姟
+
+淇濆吇浠诲姟涓嬪彂鍚庯紝椤甸潰鍙傜収閿�鍞� / 閲囪喘璁㈠崟鏍峰紡锛屾敮鎸佹寜鏃ユ湡鍒嗙粍鎶樺彔灞曠ず锛屽垎缁勬潯鐩彲灞曞紑鏌ョ湅鏄庣粏锛涘崟鏉℃槑缁嗚鏈熬澧炲姞銆愪繚鍏诲畬鎴愩�戞搷浣滄寜閽紝鍙崟鐙姙缁撳崟鏉′繚鍏讳换鍔★紱鎶樺彔姹囨�荤姸鎬佷笅锛屾柊澧炪�愪竴閿繚鍏诲畬鎴愩�戞壒閲忔搷浣滄寜閽紝蹇�熷姙缁撳綋鍓嶅垎缁勫叏閮ㄤ繚鍏婚」銆�
+
+## 淇濆吇浠诲姟椤甸潰3. 璁惧宸℃ - 宸℃浠诲姟
+
+宸℃浠诲姟涓嬪彂瀹屾垚鍚庯紝椤甸潰鍙傜収閿�鍞� / 閲囪喘璁㈠崟甯冨眬锛屾寜鏃ユ湡鍒嗙粍鎶樺彔灞曠ず锛屽垎缁勬敮鎸佸睍寮�鏌ョ湅鏄庣粏锛涙瘡鏉″贰妫�鏄庣粏鍚庡璁俱�愬贰妫�瀹屾垚銆戞寜閽紝鏀寔鍗曠嫭鍔炵粨鍗曟潯宸℃璁板綍锛涘垎缁勬姌鍙犳湭灞曞紑鐘舵�佷笅锛屾彁渚涖�愪竴閿贰妫�瀹屾垚銆戞壒閲忔搷浣滐紝缁熶竴鍔炵粨璇ュ垎缁勪笅鎵�鏈夊贰妫�浠诲姟銆�
+
+# 浜屻�佸喅绛栧垎鏋愭ā鍧�
+
+## 1\. 澧炲�肩◣瀵规瘮椤甸潰
+
+褰撳墠椤甸潰鏌ヨ鏃犳暟鎹紝鎸夌洃鐞嗚姹備紭鍖栨暟鎹睍绀洪�昏緫锛屽垪琛ㄥ睍绀哄悇璁㈠崟澧炲�肩◣鏄庣粏锛涢〉闈㈠彸渚ф柊澧炴煴鐘跺彲瑙嗗寲鍥捐〃锛岀洿瑙傚姣旇繘閿�椤瑰鍊肩◣鏁版嵁銆�
+
+## 澧炲�肩◣瀵规瘮椤甸潰2. 绠$悊椹鹃┒鑸憋紙PSI 鏁版嵁鍒嗘瀽澶у睆锛�
+
+鎸囨爣淇敼锛氬皢椤堕儴銆岄攢鍞骇鍝佹暟銆嶈皟鏁翠负銆岄攢鍞鍗曟暟銆嶏紱鏃堕棿绛涢�夛細椤甸潰宸︿笂瑙掓柊澧炴棩鏈熻寖鍥翠笅鎷夌瓫閫夋帶浠讹紝鎵�鏈夌粺璁℃寚鏍囨爣娉ㄥ綋鍓嶇瓫閫夋椂娈碉紱鏁版嵁淇锛氳В鍐炽�岄噰璐搧鍒嗗竷銆嶇幆褰㈠浘鏃犳暟鎹睍绀洪棶棰樸��
\ No newline at end of file
diff --git a/docs/dashboard_optimization.md b/docs/dashboard_optimization.md
new file mode 100644
index 0000000..4f38a28
--- /dev/null
+++ b/docs/dashboard_optimization.md
@@ -0,0 +1,177 @@
+# 绠$悊椹鹃┒鑸憋紙PSI 鏁版嵁鍒嗘瀽澶у睆锛変紭鍖�
+
+## 娑夊強椤甸潰
+
+- 绠$悊椹鹃┒鑸� / PSI 鏁版嵁鍒嗘瀽澶у睆
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /home/salesPurchaseStorageProductCount | 閿�鍞�-閲囪喘-鍌ㄥ瓨缁熻锛堟柊澧炴棩鏈熺瓫閫夛級 |
+| GET | /home/productCategoryDistribution | 浜у搧澶х被鍒嗗竷锛堜慨澶嶆暟鎹級 |
+
+### 1. 閿�鍞�-閲囪喘-鍌ㄥ瓨缁熻鎺ュ彛
+
+**璇锋眰鍙傛暟锛堟柊澧烇級锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| startDate | String | 鍚� | 寮�濮嬫棩鏈燂紙yyyy-MM-dd锛� |
+| endDate | String | 鍚� | 缁撴潫鏃ユ湡锛坹yyy-MM-dd锛� |
+
+涓嶄紶鏃ユ湡鍙傛暟鏃堕粯璁ょ粺璁″綋鍓嶆湀 vs 涓婃湀銆�
+
+**鍝嶅簲鏍煎紡鍙樺寲锛�**
+- 绗竴涓寚鏍囧悕绉颁粠 `"閿�鍞骇鍝佹暟"` 鏀逛负 `"閿�鍞鍗曟暟"`
+- 鏁板�肩粺璁″彛寰勪粠"閿�鍞骇鍝佽褰曟暟"鏀逛负"閿�鍞鍗曟暟锛坰ales_ledger 琛ㄨ褰曟暟锛�"
+
+```json
+{
+  "code": 200,
+  "data": [
+    { "name": "閿�鍞鍗曟暟", "value": "156", "rate": "12.50" },
+    { "name": "閲囪喘浜у搧鏁�", "value": "89", "rate": "-5.30" },
+    { "name": "鍌ㄥ瓨浜у搧鏁�", "value": "234", "rate": "8.20" }
+  ]
+}
+```
+
+### 2. 浜у搧澶х被鍒嗗竷鎺ュ彛
+
+淇浜嗗皬绫� value/rate 鏁版嵁涓� 0 鐨勯棶棰橈紝鐜板湪杩斿洖瀹為檯鐨勯噰璐暟鎹��
+
+**鍝嶅簲鏍煎紡锛�**
+```json
+{
+  "code": 200,
+  "data": {
+    "items": [
+      {
+        "name": "鍘熸潗鏂�",
+        "value": "45",
+        "rate": "40.50",
+        "children": [
+          { "name": "閽㈡潗", "value": "20", "rate": "44.44", "children": [] },
+          { "name": "濉戞枡", "value": "15", "rate": "33.33", "children": [] },
+          { "name": "鐢靛瓙鍏冧欢", "value": "10", "rate": "22.22", "children": [] }
+        ]
+      }
+    ]
+  }
+}
+```
+
+## 鍓嶇淇敼鐐�
+
+### 1. 椤堕儴鎸囨爣淇敼 - "閿�鍞骇鍝佹暟" 鈫� "閿�鍞鍗曟暟"
+
+```html
+<div class="stat-card">
+  <div class="stat-label">閿�鍞鍗曟暟</div>
+  <div class="stat-value">{{ salesOrderCount.value }}</div>
+  <div class="stat-rate" :class="{ up: salesOrderCount.rate > 0, down: salesOrderCount.rate < 0 }">
+    鐜瘮 {{ salesOrderCount.rate }}%
+  </div>
+</div>
+```
+
+### 2. 鏃ユ湡鑼冨洿绛涢�夋帶浠�
+
+鍦ㄩ〉闈㈠乏涓婅鏂板鏃ユ湡鑼冨洿涓嬫媺绛涢�夈��
+
+```html
+<el-date-picker
+  v-model="dateRange"
+  type="daterange"
+  range-separator="鑷�"
+  start-placeholder="寮�濮嬫棩鏈�"
+  end-placeholder="缁撴潫鏃ユ湡"
+  value-format="yyyy-MM-dd"
+  @change="handleDateChange"
+  style="margin-right: 16px;" />
+```
+
+### 3. data 鏁版嵁
+
+```js
+data() {
+  return {
+    dateRange: [],
+    salesOrderCount: { value: 0, rate: 0 },
+    purchaseProductCount: { value: 0, rate: 0 },
+    storageProductCount: { value: 0, rate: 0 },
+    productCategoryData: [],
+  }
+}
+```
+
+### 4. 鏂规硶
+
+```js
+async loadSalesPurchaseStorageCount() {
+  const params = {};
+  if (this.dateRange && this.dateRange.length === 2) {
+    params.startDate = this.dateRange[0];
+    params.endDate = this.dateRange[1];
+  }
+  const res = await this.$http.get('/home/salesPurchaseStorageProductCount', { params });
+  const data = res.data || [];
+  this.salesOrderCount = this.extractStatData(data[0]);
+  this.purchaseProductCount = this.extractStatData(data[1]);
+  this.storageProductCount = this.extractStatData(data[2]);
+},
+
+extractStatData(item) {
+  return item ? {
+    value: parseInt(item.value) || 0,
+    rate: parseFloat(item.rate) || 0,
+  } : { value: 0, rate: 0 };
+},
+
+handleDateChange(val) {
+  this.loadSalesPurchaseStorageCount();
+},
+
+async loadProductCategoryDistribution() {
+  const res = await this.$http.get('/home/productCategoryDistribution');
+  this.productCategoryData = res.data.items || [];
+  this.renderCategoryChart();
+},
+
+renderCategoryChart() {
+  const chart = this.$echarts.init(this.$refs.categoryChart);
+  const seriesData = [];
+  const legendData = [];
+
+  this.productCategoryData.forEach(major => {
+    legendData.push(major.name);
+    seriesData.push({
+      name: major.name,
+      value: parseInt(major.value) || 0,
+      children: (major.children || []).map(child => ({
+        name: child.name,
+        value: parseInt(child.value) || 0,
+      }))
+    });
+  });
+
+  chart.setOption({
+    tooltip: { trigger: 'item' },
+    legend: { data: legendData, orient: 'vertical', right: 10, top: 20 },
+    series: [{
+      type: 'sunburst',
+      data: seriesData,
+      radius: [0, '90%'],
+      label: { rotate: 'radial' }
+    }]
+  });
+},
+```
+
+## 娉ㄦ剰浜嬮」
+
+- "閿�鍞鍗曟暟"缁熻鐨勬槸 sales_ledger 琛ㄤ腑鐨勮鍗曡褰曟暟锛堟寜 entry_date锛夛紝鑰岄潪 sales_ledger_product 琛ㄤ腑鐨勪骇鍝佽褰曟暟
+- 鏃ユ湡鑼冨洿绛涢�変负绌烘椂锛岄粯璁ゅ睍绀哄綋鍓嶆湀 vs 涓婃湀鐨勬暟鎹�
+- 閲囪喘鍝佸垎甯冪幆褰㈠浘涓悇灏忕被鐨� value 鐜板湪鏉ヨ嚜瀹為檯鐨勯噰璐暟鎹紙sales_ledger_product type=2锛夛紝鑰岄潪鍐欐鐨� 0
+- 鎵�鏈夌粺璁℃寚鏍囦笂鏍囨敞褰撳墠绛涢�夋椂娈碉紙鍓嶇鐢� dateRange 灞曠ず锛�
diff --git a/docs/inspection_task_complete.md b/docs/inspection_task_complete.md
new file mode 100644
index 0000000..4264650
--- /dev/null
+++ b/docs/inspection_task_complete.md
@@ -0,0 +1,155 @@
+# 宸℃浠诲姟瀹屾垚鍔熻兘
+
+## 娑夊強椤甸潰
+
+- 璁惧宸℃ - 宸℃浠诲姟椤甸潰
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| PUT | /inspectionTask/complete/{id} | 鍗曟潯宸℃瀹屾垚 |
+| PUT | /inspectionTask/batchComplete | 涓�閿贰妫�瀹屾垚 |
+
+### 1. 宸℃瀹屾垚
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 宸℃浠诲姟ID锛堣矾寰勫弬鏁帮級 |
+
+**鍝嶅簲锛�**
+```json
+{ "code": 200, "msg": "鎿嶄綔鎴愬姛" }
+```
+
+### 2. 涓�閿贰妫�瀹屾垚
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| dateStr | String | 鏄� | 鏃ユ湡锛堟牸寮忥細yyyy-MM-dd锛� |
+
+**鍝嶅簲锛�**
+```json
+{ "code": 200, "msg": "鎿嶄綔鎴愬姛" }
+```
+
+## 鍓嶇淇敼鐐�
+
+### 1. 宸℃浠诲姟鍒楄〃鏀归�� - 鎸夋棩鏈熷垎缁勬姌鍙�
+
+鍙傜収閿�鍞�/閲囪喘璁㈠崟甯冨眬锛屽皢宸℃璁板綍鎸夋棩鏈熷垎缁勬姌鍙犲睍绀恒��
+
+```html
+<el-collapse v-model="activeDateGroups" accordion>
+  <el-collapse-item v-for="group in groupedInspectionList" :key="group.dateStr" :name="group.dateStr">
+    <template slot="title">
+      <div class="group-header">
+        <span>{{ group.dateStr }}</span>
+        <span>鍏� {{ group.items.length }} 鏉�</span>
+        <el-button type="success" size="small" @click.stop="handleBatchComplete(group.dateStr)">涓�閿贰妫�瀹屾垚</el-button>
+      </div>
+    </template>
+    <el-table :data="group.items" border>
+      <el-table-column prop="taskName" label="宸℃浠诲姟鍚嶇О" />
+      <el-table-column prop="inspectionProject" label="宸℃椤圭洰" />
+      <el-table-column prop="inspector" label="宸℃浜�" />
+      <el-table-column prop="inspectionResult" label="宸℃缁撴灉">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.inspectionResult === '1'" type="success">姝e父</el-tag>
+          <el-tag v-else-if="scope.row.inspectionResult === '0'" type="danger">寮傚父</el-tag>
+          <el-tag v-else type="info">鏈畬鎴�</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="status" label="鐘舵��">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.status === 0 ? 'warning' : 'success'">
+            {{ scope.row.status === 0 ? '寰呭贰妫�' : '宸插畬鎴�' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="鎿嶄綔" width="120">
+        <template slot-scope="scope">
+          <el-button v-if="scope.row.status === 0" type="primary" size="small" @click="handleComplete(scope.row.id)">
+            宸℃瀹屾垚
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </el-collapse-item>
+</el-collapse>
+```
+
+### 2. data 鏁版嵁
+
+```js
+data() {
+  return {
+    activeDateGroups: [],
+    inspectionList: [],
+  }
+}
+```
+
+### 3. 璁$畻灞炴��
+
+```js
+computed: {
+  groupedInspectionList() {
+    const groups = {};
+    this.inspectionList.forEach(item => {
+      const dateStr = item.dateStr; // 鍚庣宸茶繑鍥� dateStr 瀛楁
+      if (!groups[dateStr]) {
+        groups[dateStr] = { dateStr, items: [] };
+      }
+      groups[dateStr].items.push(item);
+    });
+    return Object.values(groups).sort((a, b) => b.dateStr.localeCompare(a.dateStr));
+  }
+}
+```
+
+### 4. 鏂规硶
+
+```js
+async handleComplete(id) {
+  try {
+    await this.$confirm('纭璇ュ贰妫�浠诲姟宸插畬鎴愶紵', '鎻愮ず', { type: 'warning' });
+    await this.$http.put(`/inspectionTask/complete/${id}`);
+    this.$message.success('宸℃瀹屾垚');
+    this.loadList();
+  } catch (e) {
+    if (e !== 'cancel') {
+      this.$message.error(e.msg || '鎿嶄綔澶辫触');
+    }
+  }
+},
+
+async handleBatchComplete(dateStr) {
+  try {
+    await this.$confirm(`纭灏� ${dateStr} 鐨勬墍鏈夊緟宸℃浠诲姟涓�閿畬鎴愶紵`, '鎻愮ず', { type: 'warning' });
+    await this.$http.put('/inspectionTask/batchComplete', null, { params: { dateStr } });
+    this.$message.success('鎵归噺宸℃瀹屾垚');
+    this.loadList();
+  } catch (e) {
+    if (e !== 'cancel') {
+      this.$message.error(e.msg || '鎿嶄綔澶辫触');
+    }
+  }
+},
+
+async loadList() {
+  const res = await this.$http.get('/inspectionTask/list', { params: this.queryParams });
+  this.inspectionList = res.data.records || [];
+}
+```
+
+## 娉ㄦ剰浜嬮」
+
+- 宸插畬鎴愶紙status=1锛夌殑宸℃璁板綍涓嶆樉绀�"宸℃瀹屾垚"鎸夐挳
+- 涓�閿贰妫�瀹屾垚浠呭鐞嗗綋澶╂墍鏈夊緟宸℃锛坰tatus=0锛夌殑璁板綍
+- 宸℃瀹屾垚鏃惰嫢鏈缃贰妫�缁撴灉锛岄粯璁よ涓�"姝e父"
+- 鏁版嵁搴撴柊澧炰簡 status 瀛楁锛岄儴缃插墠闇�鎵ц杩佺Щ SQL
diff --git a/docs/maintenance_task_complete.md b/docs/maintenance_task_complete.md
new file mode 100644
index 0000000..5fc9d32
--- /dev/null
+++ b/docs/maintenance_task_complete.md
@@ -0,0 +1,142 @@
+# 淇濆吇浠诲姟瀹屾垚鍔熻兘
+
+## 娑夊強椤甸潰
+
+- 璁惧淇濆吇 - 淇濆吇浠诲姟椤甸潰
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| PUT | /device/maintenance/complete/{id} | 鍗曟潯淇濆吇瀹屾垚 |
+| PUT | /device/maintenance/batchComplete | 涓�閿繚鍏诲畬鎴� |
+
+### 1. 淇濆吇瀹屾垚
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 淇濆吇璁板綍ID锛堣矾寰勫弬鏁帮級 |
+
+**鍝嶅簲锛�** `{ "code": 200, "msg": "鎿嶄綔鎴愬姛" }`
+
+### 2. 涓�閿繚鍏诲畬鎴�
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| dateStr | String | 鏄� | 鏃ユ湡锛堟牸寮忥細yyyy-MM-dd锛� |
+
+**鍝嶅簲锛�** `{ "code": 200, "msg": "宸插畬鎴� N 鏉′繚鍏昏褰�" }`
+
+## 鍓嶇淇敼鐐�
+
+### 1. 淇濆吇浠诲姟鍒楄〃鏀归�� - 鎸夋棩鏈熷垎缁勬姌鍙�
+
+鍙傜収閿�鍞�/閲囪喘璁㈠崟鏍峰紡锛屽皢淇濆吇璁板綍鎸夋棩鏈熷垎缁勬姌鍙犲睍绀恒��
+
+```html
+<el-collapse v-model="activeDateGroups" accordion>
+  <el-collapse-item v-for="group in groupedMaintenanceList" :key="group.dateStr" :name="group.dateStr">
+    <template slot="title">
+      <div class="group-header">
+        <span>{{ group.dateStr }}</span>
+        <span>鍏� {{ group.items.length }} 鏉�</span>
+        <el-button type="success" size="small" @click.stop="handleBatchComplete(group.dateStr)">涓�閿繚鍏诲畬鎴�</el-button>
+      </div>
+    </template>
+    <el-table :data="group.items" border>
+      <el-table-column prop="deviceName" label="璁惧鍚嶇О" />
+      <el-table-column prop="deviceModel" label="瑙勬牸鍨嬪彿" />
+      <el-table-column prop="machineryCategory" label="璁惧椤圭洰" />
+      <el-table-column prop="maintenanceActuallyName" label="淇濆吇浜�" />
+      <el-table-column prop="status" label="鐘舵��">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.status === 0 ? 'warning' : 'success'">
+            {{ scope.row.status === 0 ? '寰呬繚鍏�' : '宸插畬缁�' }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="鎿嶄綔" width="120">
+        <template slot-scope="scope">
+          <el-button v-if="scope.row.status === 0" type="primary" size="small" @click="handleComplete(scope.row.id)">
+            淇濆吇瀹屾垚
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </el-collapse-item>
+</el-collapse>
+```
+
+### 2. data 鏁版嵁
+
+```js
+data() {
+  return {
+    activeDateGroups: [],
+    maintenanceList: [],
+  }
+}
+```
+
+### 3. 璁$畻灞炴��
+
+```js
+computed: {
+  groupedMaintenanceList() {
+    const groups = {};
+    this.maintenanceList.forEach(item => {
+      const dateStr = item.createTime.substring(0, 10); // 鍙栨棩鏈熼儴鍒� yyyy-MM-dd
+      if (!groups[dateStr]) {
+        groups[dateStr] = { dateStr, items: [] };
+      }
+      groups[dateStr].items.push(item);
+    });
+    return Object.values(groups).sort((a, b) => b.dateStr.localeCompare(a.dateStr));
+  }
+}
+```
+
+### 4. 鏂规硶
+
+```js
+async handleComplete(id) {
+  try {
+    await this.$confirm('纭璇ヤ繚鍏讳换鍔″凡瀹屾垚锛�', '鎻愮ず', { type: 'warning' });
+    await this.$http.put(`/device/maintenance/complete/${id}`);
+    this.$message.success('淇濆吇瀹屾垚');
+    this.loadList();
+  } catch (e) {
+    if (e !== 'cancel') {
+      this.$message.error(e.msg || '鎿嶄綔澶辫触');
+    }
+  }
+},
+
+async handleBatchComplete(dateStr) {
+  try {
+    await this.$confirm(`纭灏� ${dateStr} 鐨勬墍鏈夊緟淇濆吇浠诲姟涓�閿畬鎴愶紵`, '鎻愮ず', { type: 'warning' });
+    await this.$http.put('/device/maintenance/batchComplete', null, { params: { dateStr } });
+    this.$message.success('鎵归噺淇濆吇瀹屾垚');
+    this.loadList();
+  } catch (e) {
+    if (e !== 'cancel') {
+      this.$message.error(e.msg || '鎿嶄綔澶辫触');
+    }
+  }
+},
+
+async loadList() {
+  const res = await this.$http.get('/device/maintenance/page', { params: this.queryParams });
+  this.maintenanceList = res.data.records || [];
+}
+```
+
+## 娉ㄦ剰浜嬮」
+
+- 宸插畬鎴愶紙status=1锛夌殑淇濆吇璁板綍涓嶆樉绀�"淇濆吇瀹屾垚"鎸夐挳
+- 涓�閿繚鍏诲畬鎴愪粎澶勭悊褰撳ぉ鎵�鏈夊緟淇濆吇锛坰tatus=0锛夌殑璁板綍
+- 淇濆吇瀹屾垚鍚� status 鍙樹负 1锛屽悓鏃惰褰曞綋鍓嶆搷浣滀汉涓哄疄闄呬繚鍏讳汉
diff --git a/docs/spare_parts_outbound.md b/docs/spare_parts_outbound.md
new file mode 100644
index 0000000..3776774
--- /dev/null
+++ b/docs/spare_parts_outbound.md
@@ -0,0 +1,130 @@
+# 澶囦欢鐙珛鍑哄簱
+
+## 娑夊強椤甸潰
+
+- 澶囦欢棰嗙敤璁板綍椤甸潰
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /sparePartsRequisitionRecord/outbound | 澶囦欢鐙珛鍑哄簱 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| sparePartsId | Long | 鏄� | 澶囦欢ID |
+| quantity | Integer | 鏄� | 鍑哄簱鏁伴噺 |
+| deviceLedgerId | Long | 鍚� | 璁惧ID |
+| remark | String | 鍚� | 澶囨敞 |
+
+**璇锋眰绀轰緥锛�**
+```json
+{
+  "sparePartsId": 1,
+  "quantity": 5,
+  "deviceLedgerId": 10,
+  "remark": "鐩存帴棰嗙敤"
+}
+```
+
+**鍝嶅簲锛�** `{ "code": 200, "msg": "鎿嶄綔鎴愬姛" }`
+
+## 鍓嶇淇敼鐐�
+
+### 1. 鏂板鍑哄簱鎸夐挳/寮圭獥
+
+鍦ㄥ浠堕鐢ㄨ褰曢〉闈㈡柊澧炪�愬浠跺嚭搴撱�戞寜閽紝鐐瑰嚮鍚庡脊鍑哄嚭搴撹〃鍗曘��
+
+```html
+<el-button type="primary" @click="handleOpenOutbound">澶囦欢鍑哄簱</el-button>
+
+<el-dialog title="澶囦欢鍑哄簱" :visible.sync="outboundDialogVisible" width="500px">
+  <el-form :model="outboundForm" :rules="outboundRules" ref="outboundFormRef" label-width="100px">
+    <el-form-item label="閫夋嫨澶囦欢" prop="sparePartsId">
+      <el-select v-model="outboundForm.sparePartsId" placeholder="璇烽�夋嫨澶囦欢" filterable>
+        <el-option v-for="item in sparePartsList" :key="item.id" :label="item.name" :value="item.id" />
+      </el-select>
+    </el-form-item>
+    <el-form-item label="鍑哄簱鏁伴噺" prop="quantity">
+      <el-input-number v-model="outboundForm.quantity" :min="1" />
+    </el-form-item>
+    <el-form-item label="鍏宠仈璁惧" prop="deviceLedgerId">
+      <el-select v-model="outboundForm.deviceLedgerId" placeholder="璇烽�夋嫨璁惧锛堝彲閫夛級" filterable clearable>
+        <el-option v-for="item in deviceList" :key="item.id" :label="item.deviceName" :value="item.id" />
+      </el-select>
+    </el-form-item>
+    <el-form-item label="澶囨敞" prop="remark">
+      <el-input v-model="outboundForm.remark" type="textarea" />
+    </el-form-item>
+  </el-form>
+  <span slot="footer">
+    <el-button @click="outboundDialogVisible = false">鍙栨秷</el-button>
+    <el-button type="primary" @click="handleOutboundSubmit">纭鍑哄簱</el-button>
+  </span>
+</el-dialog>
+```
+
+### 2. data 鏁版嵁
+
+```js
+data() {
+  return {
+    outboundDialogVisible: false,
+    outboundForm: {
+      sparePartsId: null,
+      quantity: 1,
+      deviceLedgerId: null,
+      remark: '',
+    },
+    outboundRules: {
+      sparePartsId: [{ required: true, message: '璇烽�夋嫨澶囦欢', trigger: 'change' }],
+      quantity: [{ required: true, message: '璇疯緭鍏ュ嚭搴撴暟閲�', trigger: 'blur' }],
+    },
+    sparePartsList: [],
+    deviceList: [],
+  }
+}
+```
+
+### 3. 鏂规硶
+
+```js
+handleOpenOutbound() {
+  this.outboundForm = { sparePartsId: null, quantity: 1, deviceLedgerId: null, remark: '' };
+  this.outboundDialogVisible = true;
+  this.loadSparePartsList();
+  this.loadDeviceList();
+},
+
+handleOutboundSubmit() {
+  this.$refs.outboundFormRef.validate(async (valid) => {
+    if (!valid) return;
+    try {
+      await this.$http.post('/sparePartsRequisitionRecord/outbound', this.outboundForm);
+      this.$message.success('鍑哄簱鎴愬姛');
+      this.outboundDialogVisible = false;
+      this.loadList(); // 鍒锋柊棰嗙敤璁板綍鍒楄〃
+    } catch (e) {
+      this.$message.error(e.msg || '鍑哄簱澶辫触');
+    }
+  });
+},
+
+async loadSparePartsList() {
+  const res = await this.$http.get('/spareParts/listPage');
+  this.sparePartsList = res.data.records || [];
+},
+
+async loadDeviceList() {
+  const res = await this.$http.get('/device/ledger/page');
+  this.deviceList = res.data.records || [];
+},
+```
+
+## 娉ㄦ剰浜嬮」
+
+- 鍑哄簱浼氭墸鍑忓浠跺簱瀛橈紝璇风‘璁ゆ暟閲忓噯纭�
+- 棰嗙敤璁板綍鏉ユ簮绫诲瀷浼氭爣璁颁负"鐩存帴鍑哄簱"
+- 澶囦欢搴撳瓨涓嶈冻鏃舵帴鍙d細杩斿洖閿欒鎻愮ず
diff --git a/docs/vat_comparison_optimization.md b/docs/vat_comparison_optimization.md
new file mode 100644
index 0000000..489ea10
--- /dev/null
+++ b/docs/vat_comparison_optimization.md
@@ -0,0 +1,189 @@
+# 澧炲�肩◣瀵规瘮椤甸潰浼樺寲
+
+## 娑夊強椤甸潰
+
+- 鍐崇瓥鍒嗘瀽 - 澧炲�肩◣瀵规瘮椤甸潰
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /purchase/report/listVat | 澧炲�肩◣鎸夋湀姹囨�伙紙鍘熸湁锛� |
+| GET | /purchase/report/listVatDetail | 澧炲�肩◣鏄庣粏-鎸夊彂绁�/璁㈠崟灞曠ず锛堟柊澧烇級 |
+
+### 澧炲�肩◣鏄庣粏鎺ュ彛
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| page | Page | 鍚� | 鍒嗛〉鍙傛暟 |
+| month | String | 鍚� | 鏈堜唤绛涢�夛紙yyyy-MM锛夛紝涓虹┖鍒欐煡璇㈠叏閮� |
+
+**鍝嶅簲鏍煎紡锛�**
+```json
+{
+  "code": 200,
+  "msg": "鎿嶄綔鎴愬姛",
+  "data": {
+    "records": [
+      {
+        "invoiceNo": "FP20260801001",
+        "salesContractNo": "HT20260801",
+        "supplierName": "渚涘簲鍟咥",
+        "customerName": null,
+        "orderType": "杩涢」",
+        "invoiceDate": "2026-08-01",
+        "taxRate": 13.00,
+        "jTaxAmount": 1300.00,
+        "xTaxAmount": 0.00,
+        "taxAmount": 1300.00
+      },
+      {
+        "invoiceNo": "XS20260801001",
+        "salesContractNo": "XS-HT20260801",
+        "supplierName": null,
+        "customerName": "瀹㈡埛B",
+        "orderType": "閿�椤�",
+        "invoiceDate": "2026-08-03",
+        "taxRate": 13.00,
+        "jTaxAmount": 0.00,
+        "xTaxAmount": 2600.00,
+        "taxAmount": 2600.00
+      }
+    ],
+    "total": 100
+  }
+}
+```
+
+## 鍓嶇淇敼鐐�
+
+### 1. 椤甸潰甯冨眬鏀归�� - 宸︿晶鍒楄〃 + 鍙充晶鏌辩姸鍥�
+
+```html
+<el-row :gutter="20">
+  <!-- 宸︿晶锛氬鍊肩◣鏄庣粏鍒楄〃 -->
+  <el-col :span="14">
+    <el-card>
+      <div slot="header">
+        <span>澧炲�肩◣鏄庣粏</span>
+        <el-select v-model="queryMonth" placeholder="閫夋嫨鏈堜唤" clearable style="float: right; width: 150px;">
+          <el-option v-for="m in monthOptions" :key="m" :label="m" :value="m" />
+        </el-select>
+      </div>
+      <el-table :data="vatDetailList" border v-loading="loading">
+        <el-table-column prop="orderType" label="绫诲瀷" width="60">
+          <template slot-scope="scope">
+            <el-tag :type="scope.row.orderType === '杩涢」' ? 'info' : 'warning'" size="small">
+              {{ scope.row.orderType }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="invoiceNo" label="鍙戠エ鍙�" width="140" />
+        <el-table-column prop="salesContractNo" label="鍚堝悓鍙�" width="140" />
+        <el-table-column prop="supplierName" label="渚涘簲鍟�" />
+        <el-table-column prop="customerName" label="瀹㈡埛" />
+        <el-table-column prop="invoiceDate" label="寮�绁ㄦ棩鏈�" width="110" />
+        <el-table-column prop="taxRate" label="绋庣巼(%)" width="80" />
+        <el-table-column prop="taxAmount" label="绋庨" width="120">
+          <template slot-scope="scope">
+            <span :style="{ color: scope.row.orderType === '杩涢」' ? '#409EFF' : '#E6A23C' }">
+              {{ scope.row.taxAmount }}
+            </span>
+          </template>
+        </el-table-column>
+      </el-table>
+      <el-pagination
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+        :current-page="page.current"
+        :page-sizes="[10, 20, 50]"
+        :page-size="page.size"
+        :total="page.total"
+        layout="total, sizes, prev, pager, next" />
+    </el-card>
+  </el-col>
+
+  <!-- 鍙充晶锛氭煴鐘跺姣斿浘 -->
+  <el-col :span="10">
+    <el-card>
+      <div slot="header"><span>杩涢攢椤瑰鍊肩◣瀵规瘮</span></div>
+      <div ref="vatChart" style="height: 400px;"></div>
+    </el-card>
+  </el-col>
+</el-row>
+```
+
+### 2. data 鏁版嵁
+
+```js
+data() {
+  return {
+    queryMonth: '',
+    monthOptions: [],
+    vatDetailList: [],
+    loading: false,
+    page: { current: 1, size: 20, total: 0 },
+  }
+}
+```
+
+### 3. 鏂规硶
+
+```js
+async loadVatDetail() {
+  this.loading = true;
+  try {
+    const res = await this.$http.get('/purchase/report/listVatDetail', {
+      params: {
+        current: this.page.current,
+        size: this.page.size,
+        month: this.queryMonth || null,
+      }
+    });
+    this.vatDetailList = res.data.records || [];
+    this.page.total = res.data.total || 0;
+    this.renderChart();
+  } finally {
+    this.loading = false;
+  }
+},
+
+renderChart() {
+  const chart = this.$echarts.init(this.$refs.vatChart);
+  // 鎸夎鍗曠被鍨嬪垎缁勭粺璁�
+  const inputItems = this.vatDetailList.filter(i => i.orderType === '杩涢」');
+  const outputItems = this.vatDetailList.filter(i => i.orderType === '閿�椤�');
+  chart.setOption({
+    tooltip: { trigger: 'axis' },
+    legend: { data: ['杩涢」绋庨', '閿�椤圭◣棰�'] },
+    xAxis: { type: 'category', data: this.vatDetailList.map(i => i.invoiceNo) },
+    yAxis: { type: 'value', name: '绋庨(鍏�)' },
+    series: [
+      {
+        name: '杩涢」绋庨',
+        type: 'bar',
+        data: this.vatDetailList.map(i => i.orderType === '杩涢」' ? i.taxAmount : 0),
+        itemStyle: { color: '#409EFF' }
+      },
+      {
+        name: '閿�椤圭◣棰�',
+        type: 'bar',
+        data: this.vatDetailList.map(i => i.orderType === '閿�椤�' ? i.taxAmount : 0),
+        itemStyle: { color: '#E6A23C' }
+      }
+    ]
+  });
+},
+
+handleSizeChange(val) { this.page.size = val; this.loadVatDetail(); },
+handleCurrentChange(val) { this.page.current = val; this.loadVatDetail(); },
+```
+
+## 娉ㄦ剰浜嬮」
+
+- 鍒楄〃灞曠ず姣忔潯鍙戠エ/璁㈠崟绾у埆鐨勭◣棰濇槑缁嗭紙鑰岄潪浠ュ墠鍙寜鏈堟眹鎬伙級
+- 杩涢」绋庢潵鑷噰璐彂绁紙invoice_ledger锛夛紝閿�椤圭◣鏉ヨ嚜閿�鍞紑绁紙ticket_registration锛�
+- 鍙充晶鏌辩姸鍥句娇鐢� ECharts 瀹炵幇锛屾寜鍙戠エ鍙峰垎缁勫睍绀鸿繘閿�瀵规瘮
+- month 绛涢�夊弬鏁颁负绌烘椂鏌ヨ鍏ㄩ儴鏁版嵁
diff --git a/src/main/java/com/ruoyi/common/enums/SparePartsRequisitionRecordSourceTypeEnum.java b/src/main/java/com/ruoyi/common/enums/SparePartsRequisitionRecordSourceTypeEnum.java
index b9acc40..d853453 100644
--- a/src/main/java/com/ruoyi/common/enums/SparePartsRequisitionRecordSourceTypeEnum.java
+++ b/src/main/java/com/ruoyi/common/enums/SparePartsRequisitionRecordSourceTypeEnum.java
@@ -5,7 +5,8 @@
 @Getter
 public enum SparePartsRequisitionRecordSourceTypeEnum implements BaseEnum<Integer> {
     SparePartsRequisitionRecordSourceTypeRepair(0, "缁翠慨"),
-    SparePartsRequisitionRecordSourceTypeMaintenance(1, "淇濆吇");
+    SparePartsRequisitionRecordSourceTypeMaintenance(1, "淇濆吇"),
+    SparePartsRequisitionRecordSourceTypeDirectOutbound(2, "鐩存帴鍑哄簱");
 
     private final Integer code;
     private final String value;
diff --git a/src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java b/src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java
index e34fc37..d776137 100644
--- a/src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java
+++ b/src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java
@@ -79,5 +79,16 @@
         deviceMaintenanceService.export(response, ids);
     }
 
+    @PutMapping("/complete/{id}")
+    @Operation(summary = "淇濆吇瀹屾垚")
+    public AjaxResult complete(@PathVariable Long id) {
+        return deviceMaintenanceService.complete(id);
+    }
+
+    @PutMapping("/batchComplete")
+    @Operation(summary = "涓�閿繚鍏诲畬鎴�")
+    public AjaxResult batchComplete(String dateStr) {
+        return deviceMaintenanceService.batchComplete(dateStr);
+    }
 
 }
diff --git a/src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java b/src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java
index 127d82b..81d34a0 100644
--- a/src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java
+++ b/src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java
@@ -53,5 +53,8 @@
     @Schema(description = "淇濆吇鍥剧墖")
     private List<StorageBlobDTO> storageBlobDTOs;
 
+    @Schema(description = "鏃ユ湡鍒嗙粍瀛楁(yyyy-MM-dd)")
+    private String dateStr;
+
 }
 
diff --git a/src/main/java/com/ruoyi/device/service/IDeviceMaintenanceService.java b/src/main/java/com/ruoyi/device/service/IDeviceMaintenanceService.java
index e5a4080..d44155b 100644
--- a/src/main/java/com/ruoyi/device/service/IDeviceMaintenanceService.java
+++ b/src/main/java/com/ruoyi/device/service/IDeviceMaintenanceService.java
@@ -21,4 +21,8 @@
     void export(HttpServletResponse response, Long[] ids);
 
     DeviceMaintenanceVo detailById(Long id);
+
+    AjaxResult complete(Long id);
+
+    AjaxResult batchComplete(String dateStr);
 }
diff --git a/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java b/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java
index fbf03ad..29b9c3a 100644
--- a/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java
+++ b/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java
@@ -6,6 +6,7 @@
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.basic.enums.RecordTypeEnum;
 import com.ruoyi.basic.utils.FileUtil;
+import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.device.dto.DeviceMaintenanceDto;
@@ -27,6 +28,9 @@
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -124,4 +128,49 @@
         vo.setStorageBlobVOs(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.DEVICE_MAINTENANCE, id));
         return vo;
     }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public AjaxResult complete(Long id) {
+        DeviceMaintenance record = this.getById(id);
+        if (record == null) {
+            return AjaxResult.error("淇濆吇璁板綍涓嶅瓨鍦�");
+        }
+        if (record.getStatus() != null && record.getStatus() == 1) {
+            return AjaxResult.error("璇ヤ繚鍏昏褰曞凡瀹屾垚");
+        }
+        record.setStatus(1);
+        record.setMaintenanceActuallyName(SecurityUtils.getLoginUser().getNickName());
+        record.setMaintenanceActuallyTime(LocalDateTime.now());
+        this.updateById(record);
+        return AjaxResult.success();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public AjaxResult batchComplete(String dateStr) {
+        if (StringUtils.isBlank(dateStr)) {
+            return AjaxResult.error("鏃ユ湡鍙傛暟涓嶈兘涓虹┖");
+        }
+        LocalDate date = LocalDate.parse(dateStr);
+        LocalDateTime startOfDay = date.atStartOfDay();
+        LocalDateTime endOfDay = date.atTime(LocalTime.MAX);
+        List<DeviceMaintenance> records = this.lambdaQuery()
+                .eq(DeviceMaintenance::getStatus, 0)
+                .ge(DeviceMaintenance::getCreateTime, startOfDay)
+                .le(DeviceMaintenance::getCreateTime, endOfDay)
+                .list();
+        if (records.isEmpty()) {
+            return AjaxResult.error("璇ユ棩鏈熶笅娌℃湁寰呭畬鎴愮殑淇濆吇璁板綍");
+        }
+        String nickName = SecurityUtils.getLoginUser().getNickName();
+        LocalDateTime now = LocalDateTime.now();
+        for (DeviceMaintenance record : records) {
+            record.setStatus(1);
+            record.setMaintenanceActuallyName(nickName);
+            record.setMaintenanceActuallyTime(now);
+        }
+        this.updateBatchById(records);
+        return AjaxResult.success("宸插畬鎴� " + records.size() + " 鏉′繚鍏昏褰�");
+    }
 }
diff --git a/src/main/java/com/ruoyi/home/controller/HomeController.java b/src/main/java/com/ruoyi/home/controller/HomeController.java
index 41ae224..737b4af 100644
--- a/src/main/java/com/ruoyi/home/controller/HomeController.java
+++ b/src/main/java/com/ruoyi/home/controller/HomeController.java
@@ -73,8 +73,10 @@
     /********************************************************钀ラ攢閲囪喘绫�**************************************************/
     @GetMapping("/supplierPurchaseRanking")
     @Operation(summary = "渚涘簲鍟嗛噰璐帓鍚�")
-    public AjaxResult supplierPurchaseRanking(@DefaultType Integer type) {
-        List<SupplierPurchaseRankingDto> list = homeService.supplierPurchaseRanking(type);
+    public AjaxResult supplierPurchaseRanking(@DefaultType Integer type,
+            @RequestParam(required = false) String startDate,
+            @RequestParam(required = false) String endDate) {
+        List<SupplierPurchaseRankingDto> list = homeService.supplierPurchaseRanking(type, startDate, endDate);
         return AjaxResult.success(list);
     }
 
@@ -87,22 +89,28 @@
 
     @GetMapping("/customerContributionRanking")
     @Operation(summary = "瀹㈡埛閲戦璐$尞鎺掑悕")
-    public AjaxResult customerContributionRanking(@DefaultType Integer type) {
-        List<CustomerContributionRankingDto> list = homeService.customerContributionRanking(type);
+    public AjaxResult customerContributionRanking(@DefaultType Integer type,
+            @RequestParam(required = false) String startDate,
+            @RequestParam(required = false) String endDate) {
+        List<CustomerContributionRankingDto> list = homeService.customerContributionRanking(type, startDate, endDate);
         return AjaxResult.success(list);
     }
 
     @GetMapping("/productSalesAnalysis")
     @Operation(summary = "鍚勪骇鍝侀攢鍞噾棰濆垎鏋�")
-    public AjaxResult productSalesAnalysis() {
-        List<MapDto> list = homeService.productSalesAnalysis();
+    public AjaxResult productSalesAnalysis(
+            @RequestParam(required = false) String startDate,
+            @RequestParam(required = false) String endDate) {
+        List<MapDto> list = homeService.productSalesAnalysis(startDate, endDate);
         return AjaxResult.success(list);
     }
 
     @GetMapping("/rawMaterialPurchaseAmountRatio")
     @Operation(summary = "鍘熸潗鏂欓噰璐噾棰濆崰姣�")
-    public AjaxResult rawMaterialPurchaseAmountRatio(){
-        List<MapDto> list = homeService.rawMaterialPurchaseAmountRatio();
+    public AjaxResult rawMaterialPurchaseAmountRatio(
+            @RequestParam(required = false) String startDate,
+            @RequestParam(required = false) String endDate){
+        List<MapDto> list = homeService.rawMaterialPurchaseAmountRatio(startDate, endDate);
         return AjaxResult.success(list);
     }
 
@@ -302,22 +310,28 @@
 
     @GetMapping("/salesPurchaseStorageProductCount")
     @Operation(summary = "閿�鍞�-閲囪喘-鍌ㄥ瓨浜у搧鏁�")
-    public AjaxResult salesPurchaseStorageProductCount(){
-        List<MapDto> list = homeService.salesPurchaseStorageProductCount();
+    public AjaxResult salesPurchaseStorageProductCount(
+            @RequestParam(required = false) String startDate,
+            @RequestParam(required = false) String endDate){
+        List<MapDto> list = homeService.salesPurchaseStorageProductCount(startDate, endDate);
         return AjaxResult.success(list);
     }
 
     @GetMapping("/productInOutAnalysis")
     @Operation(summary = "浜у搧鍑哄叆搴撳垎鏋�")
-    public AjaxResult productInOutAnalysis(@DefaultType Integer type){
-        List<Map<String, Object>> result = homeService.productInOutAnalysis(type);
+    public AjaxResult productInOutAnalysis(@DefaultType Integer type,
+            @RequestParam(required = false) String startDate,
+            @RequestParam(required = false) String endDate){
+        List<Map<String, Object>> result = homeService.productInOutAnalysis(type, startDate, endDate);
         return AjaxResult.success(result);
     }
 
     @GetMapping("/productTurnoverDays")
     @Operation(summary = "浜у搧鍛ㄨ浆澶╂暟")
-    public AjaxResult productTurnoverDays(){
-        List<MapDto> list = homeService.productTurnoverDays();
+    public AjaxResult productTurnoverDays(
+            @RequestParam(required = false) String startDate,
+            @RequestParam(required = false) String endDate){
+        List<MapDto> list = homeService.productTurnoverDays(startDate, endDate);
         return AjaxResult.success(list);
     }
 
diff --git a/src/main/java/com/ruoyi/home/mapper/HomeMapper.java b/src/main/java/com/ruoyi/home/mapper/HomeMapper.java
index 1bf30e4..b339c6b 100644
--- a/src/main/java/com/ruoyi/home/mapper/HomeMapper.java
+++ b/src/main/java/com/ruoyi/home/mapper/HomeMapper.java
@@ -2,6 +2,7 @@
 
 import com.ruoyi.dto.MapDto;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 import java.util.List;
 
 /**
@@ -13,8 +14,17 @@
 public interface HomeMapper {
     /**
      * 鏌ヨ浜у搧鍛ㄨ浆澶╂暟
-     * 
+     *
      * @return 浜у搧鍛ㄨ浆澶╂暟鍒楄〃
      */
     List<MapDto> productTurnoverDays();
+
+    List<MapDto> productTurnoverDaysByDate(@Param("startDate") String startDate, @Param("endDate") String endDate);
+
+    /**
+     * 鏌ヨ閲囪喘鍝佸ぇ绫诲垎甯冿紙鍚勪骇鍝佸皬绫荤殑閲囪喘鏁伴噺/閲戦锛�
+     *
+     * @return 閲囪喘鍒嗗竷鍒楄〃
+     */
+    List<MapDto> selectPurchaseCategoryDistribution();
 }
diff --git a/src/main/java/com/ruoyi/home/service/HomeService.java b/src/main/java/com/ruoyi/home/service/HomeService.java
index 2c3c89b..ce335b2 100644
--- a/src/main/java/com/ruoyi/home/service/HomeService.java
+++ b/src/main/java/com/ruoyi/home/service/HomeService.java
@@ -38,23 +38,23 @@
 
     HomeSummaryDto summaryStatistics();
 
-    List<SupplierPurchaseRankingDto> supplierPurchaseRanking(Integer type);
+    List<SupplierPurchaseRankingDto> supplierPurchaseRanking(Integer type, String startDate, String endDate);
 
     CustomerRevenueAnalysisDto customerRevenueAnalysis(Long customerId, Integer type);
 
     ProductCategoryDistributionDto productCategoryDistribution();
 
-    List<CustomerContributionRankingDto> customerContributionRanking(Integer type);
+    List<CustomerContributionRankingDto> customerContributionRanking(Integer type, String startDate, String endDate);
 
-    List<MapDto> productSalesAnalysis();
+    List<MapDto> productSalesAnalysis(String startDate, String endDate);
 
-    List<MapDto> rawMaterialPurchaseAmountRatio();
+    List<MapDto> rawMaterialPurchaseAmountRatio(String startDate, String endDate);
 
-    List<MapDto> salesPurchaseStorageProductCount();
+    List<MapDto> salesPurchaseStorageProductCount(String startDate, String endDate);
 
-    List<Map<String, Object>> productInOutAnalysis(Integer type);
+    List<Map<String, Object>> productInOutAnalysis(Integer type, String startDate, String endDate);
 
-    List<MapDto> productTurnoverDays();
+    List<MapDto> productTurnoverDays(String startDate, String endDate);
 
     List<Map<String, Object>> incomeExpenseAnalysis(Integer type);
 
diff --git a/src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java b/src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
index e01927c..ac502b8 100644
--- a/src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
+++ b/src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
@@ -694,36 +694,39 @@
     }
 
     @Override
-    public List<SupplierPurchaseRankingDto> supplierPurchaseRanking(Integer type) {
-        LocalDate today = LocalDate.now();
+    public List<SupplierPurchaseRankingDto> supplierPurchaseRanking(Integer type, String startDateStr, String endDateStr) {
         LocalDate startDate;
         LocalDate endDate;
-        switch (type) {
-            case 1: // 鍛�
-                startDate = today.with(DayOfWeek.MONDAY);
-                endDate = today.with(DayOfWeek.SUNDAY);
-                break;
-            case 2: // 鏈�
-                startDate = today.with(TemporalAdjusters.firstDayOfMonth());
-                endDate = today.with(TemporalAdjusters.lastDayOfMonth());
-                break;
-            case 3: // 瀛e害
-                Month currentMonth = today.getMonth();
-                Month firstMonthOfQuarter = currentMonth.firstMonthOfQuarter();
-                Month lastMonthOfQuarter = Month.of(firstMonthOfQuarter.getValue() + 2);
-
-                startDate = today.withMonth(firstMonthOfQuarter.getValue()).with(TemporalAdjusters.firstDayOfMonth());
-                endDate = today.withMonth(lastMonthOfQuarter.getValue()).with(TemporalAdjusters.lastDayOfMonth());
-                break;
-            default:
-                return new ArrayList<>();
+        if (com.ruoyi.common.utils.StringUtils.isNotBlank(startDateStr) && com.ruoyi.common.utils.StringUtils.isNotBlank(endDateStr)) {
+            startDate = LocalDate.parse(startDateStr);
+            endDate = LocalDate.parse(endDateStr);
+        } else {
+            LocalDate today = LocalDate.now();
+            switch (type) {
+                case 1: // 鍛�
+                    startDate = today.with(DayOfWeek.MONDAY);
+                    endDate = today.with(DayOfWeek.SUNDAY);
+                    break;
+                case 2: // 鏈�
+                    startDate = today.with(TemporalAdjusters.firstDayOfMonth());
+                    endDate = today.with(TemporalAdjusters.lastDayOfMonth());
+                    break;
+                case 3: // 瀛e害
+                    Month currentMonth = today.getMonth();
+                    Month firstMonthOfQuarter = currentMonth.firstMonthOfQuarter();
+                    Month lastMonthOfQuarter = Month.of(firstMonthOfQuarter.getValue() + 2);
+                    startDate = today.withMonth(firstMonthOfQuarter.getValue()).with(TemporalAdjusters.firstDayOfMonth());
+                    endDate = today.withMonth(lastMonthOfQuarter.getValue()).with(TemporalAdjusters.lastDayOfMonth());
+                    break;
+                default:
+                    return new ArrayList<>();
+            }
         }
 
         QueryWrapper<PurchaseLedger> queryWrapper = new QueryWrapper<>();
         queryWrapper.select("supplier_name", "SUM(contract_amount) as total_amount")
                 .ge("entry_date", startDate)
                 .le("entry_date", endDate)
-                // .ne("approval_status", 3)
                 .groupBy("supplier_name")
                 .orderByDesc("total_amount")
                 .last("LIMIT 5");
@@ -859,17 +862,81 @@
                 majorDto.setRate("0.00");
             }
 
+            // 鏌ヨ鍚勪骇鍝佸皬绫荤殑閲囪喘鏁伴噺
+            Map<String, BigDecimal> purchaseDataMap = new java.util.HashMap<>();
+            List<com.ruoyi.dto.MapDto> purchaseDistribution = homeMapper.selectPurchaseCategoryDistribution();
+            if (purchaseDistribution != null) {
+                for (com.ruoyi.dto.MapDto item : purchaseDistribution) {
+                    if (item.getName() != null && item.getValue() != null) {
+                        purchaseDataMap.put(item.getName(), new BigDecimal(item.getValue()));
+                    }
+                }
+            }
+
+            BigDecimal majorTotalChildrenValue = BigDecimal.ZERO;
             List<ProductCategoryDistributionDto.MinorCategoryDto> minorDtos = new ArrayList<>();
             for (Product minor : children) {
                 ProductCategoryDistributionDto.MinorCategoryDto minorDto = new ProductCategoryDistributionDto.MinorCategoryDto();
                 minorDto.setName(minor.getProductName());
-                minorDto.setValue("0");
+                BigDecimal minorValue = purchaseDataMap.getOrDefault(minor.getProductName(), BigDecimal.ZERO);
+                minorDto.setValue(minorValue.toString());
                 minorDto.setRate("0.00");
                 minorDto.setChildren(new ArrayList<>());
                 minorDtos.add(minorDto);
+                majorTotalChildrenValue = majorTotalChildrenValue.add(minorValue);
             }
             majorDto.setChildren(minorDtos);
+            // 浣跨敤瀹為檯閲囪喘鏁版嵁鏇存柊澶х被鐨� value
+            if (majorTotalChildrenValue.compareTo(BigDecimal.ZERO) > 0) {
+                majorDto.setValue(majorTotalChildrenValue.toString());
+            }
             majorItems.add(majorDto);
+
+            // 璁$畻澶х被鍗犳瘮
+            BigDecimal totalAllValue = BigDecimal.ZERO;
+            for (ProductCategoryDistributionDto.MajorCategoryDto m : majorItems) {
+                try {
+                    totalAllValue = totalAllValue.add(new BigDecimal(m.getValue()));
+                } catch (NumberFormatException ignored) {
+                }
+            }
+            if (totalAllValue.compareTo(BigDecimal.ZERO) > 0) {
+                for (ProductCategoryDistributionDto.MajorCategoryDto m : majorItems) {
+                    try {
+                        BigDecimal val = new BigDecimal(m.getValue());
+                        m.setRate(val.divide(totalAllValue, 4, RoundingMode.HALF_UP)
+                                .multiply(new BigDecimal("100"))
+                                .setScale(2, RoundingMode.HALF_UP)
+                                .toString());
+                    } catch (NumberFormatException ignored) {
+                    }
+                }
+            }
+            // 璁$畻灏忕被鍗犳瘮锛堝湪鍚屽ぇ绫诲唴锛�
+            for (ProductCategoryDistributionDto.MajorCategoryDto m : majorItems) {
+                if (m.getChildren() == null || m.getChildren().isEmpty()) {
+                    continue;
+                }
+                BigDecimal parentVal = BigDecimal.ZERO;
+                for (ProductCategoryDistributionDto.MinorCategoryDto child : m.getChildren()) {
+                    try {
+                        parentVal = parentVal.add(new BigDecimal(child.getValue()));
+                    } catch (NumberFormatException ignored) {
+                    }
+                }
+                if (parentVal.compareTo(BigDecimal.ZERO) > 0) {
+                    for (ProductCategoryDistributionDto.MinorCategoryDto child : m.getChildren()) {
+                        try {
+                            BigDecimal childVal = new BigDecimal(child.getValue());
+                            child.setRate(childVal.divide(parentVal, 4, RoundingMode.HALF_UP)
+                                    .multiply(new BigDecimal("100"))
+                                    .setScale(2, RoundingMode.HALF_UP)
+                                    .toString());
+                        } catch (NumberFormatException ignored) {
+                        }
+                    }
+                }
+            }
         }
 
         dto.setItems(majorItems);
@@ -877,38 +944,42 @@
     }
 
     @Override
-    public List<CustomerContributionRankingDto> customerContributionRanking(Integer type) {
-        LocalDate today = LocalDate.now();
-        LocalDate startDate = null;
-        LocalDate endDate = null;
-        switch (type) {
-            case 1:
-                startDate = today.with(DayOfWeek.MONDAY);
-                endDate = today.with(DayOfWeek.SUNDAY);
-                break;
-            case 2:
-                startDate = today.with(TemporalAdjusters.firstDayOfMonth());
-                endDate = today.with(TemporalAdjusters.lastDayOfMonth());
-                break;
-            case 3:
-                Month currentMonth = today.getMonth();
-                Month firstMonthOfQuarter = currentMonth.firstMonthOfQuarter();
-                Month lastMonthOfQuarter = Month.of(firstMonthOfQuarter.getValue() + 2);
-                startDate = today.withMonth(firstMonthOfQuarter.getValue()).with(TemporalAdjusters.firstDayOfMonth());
-                endDate = today.withMonth(lastMonthOfQuarter.getValue()).with(TemporalAdjusters.lastDayOfMonth());
-                break;
+    public List<CustomerContributionRankingDto> customerContributionRanking(Integer type, String startDateStr, String endDateStr) {
+        LocalDate startDate;
+        LocalDate endDate;
+        if (com.ruoyi.common.utils.StringUtils.isNotBlank(startDateStr) && com.ruoyi.common.utils.StringUtils.isNotBlank(endDateStr)) {
+            startDate = LocalDate.parse(startDateStr);
+            endDate = LocalDate.parse(endDateStr);
+        } else {
+            LocalDate today = LocalDate.now();
+            switch (type) {
+                case 1:
+                    startDate = today.with(DayOfWeek.MONDAY);
+                    endDate = today.with(DayOfWeek.SUNDAY);
+                    break;
+                case 2:
+                    startDate = today.with(TemporalAdjusters.firstDayOfMonth());
+                    endDate = today.with(TemporalAdjusters.lastDayOfMonth());
+                    break;
+                case 3:
+                    Month currentMonth = today.getMonth();
+                    Month firstMonthOfQuarter = currentMonth.firstMonthOfQuarter();
+                    Month lastMonthOfQuarter = Month.of(firstMonthOfQuarter.getValue() + 2);
+                    startDate = today.withMonth(firstMonthOfQuarter.getValue()).with(TemporalAdjusters.firstDayOfMonth());
+                    endDate = today.withMonth(lastMonthOfQuarter.getValue()).with(TemporalAdjusters.lastDayOfMonth());
+                    break;
+                default:
+                    return new ArrayList<>();
+            }
         }
 
         QueryWrapper<SalesLedger> queryWrapper = new QueryWrapper<>();
         queryWrapper.select("customer_name", "SUM(contract_amount) as total_amount")
                 .isNotNull("customer_name")
+                .between("entry_date", startDate, endDate)
                 .groupBy("customer_name")
                 .orderByDesc("total_amount")
                 .last("LIMIT 5");
-
-        if (startDate != null && endDate != null) {
-            queryWrapper.between("entry_date", startDate, endDate);
-        }
 
         List<Map<String, Object>> maps = salesLedgerMapper.selectMaps(queryWrapper);
         List<CustomerContributionRankingDto> result = new ArrayList<>();
@@ -924,9 +995,13 @@
     }
 
     @Override
-    public List<MapDto> productSalesAnalysis() {
-        // 鑾峰彇鎵�鏈変骇鍝佸ぇ绫荤殑閿�鍞
-        List<Map<String, Object>> analysisResults = salesLedgerProductMapper.selectProductSalesAnalysis();
+    public List<MapDto> productSalesAnalysis(String startDateStr, String endDateStr) {
+        List<Map<String, Object>> analysisResults;
+        if (com.ruoyi.common.utils.StringUtils.isNotBlank(startDateStr) && com.ruoyi.common.utils.StringUtils.isNotBlank(endDateStr)) {
+            analysisResults = salesLedgerProductMapper.selectProductSalesAnalysisByDate(startDateStr, endDateStr);
+        } else {
+            analysisResults = salesLedgerProductMapper.selectProductSalesAnalysis();
+        }
         if (CollectionUtils.isEmpty(analysisResults)) {
             return new ArrayList<>();
         }
@@ -961,9 +1036,13 @@
     }
 
     @Override
-    public List<MapDto> rawMaterialPurchaseAmountRatio() {
-        // 鑾峰彇鎵�鏈夊師鏉愭枡鐨勯噰璐
-        List<Map<String, Object>> analysisResults = salesLedgerProductMapper.selectRawMaterialPurchaseAnalysis();
+    public List<MapDto> rawMaterialPurchaseAmountRatio(String startDateStr, String endDateStr) {
+        List<Map<String, Object>> analysisResults;
+        if (com.ruoyi.common.utils.StringUtils.isNotBlank(startDateStr) && com.ruoyi.common.utils.StringUtils.isNotBlank(endDateStr)) {
+            analysisResults = salesLedgerProductMapper.selectRawMaterialPurchaseAnalysisByDate(startDateStr, endDateStr);
+        } else {
+            analysisResults = salesLedgerProductMapper.selectRawMaterialPurchaseAnalysis();
+        }
         if (CollectionUtils.isEmpty(analysisResults)) {
             return new ArrayList<>();
         }
@@ -998,25 +1077,47 @@
     }
 
     @Override
-    public List<MapDto> salesPurchaseStorageProductCount() {
+    public List<MapDto> salesPurchaseStorageProductCount(String startDate, String endDate) {
         LocalDateTime now = LocalDateTime.now();
-        LocalDateTime currentMonthStart = now.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
-        LocalDateTime lastMonth = now.minusMonths(1);
-        LocalDateTime lastMonthStart = lastMonth.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
-        LocalDateTime lastMonthEnd = lastMonth.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
+        LocalDateTime currentMonthStart;
+        LocalDateTime currentMonthEnd;
+        LocalDateTime lastMonthStart;
+        LocalDateTime lastMonthEnd;
 
-        //  閿�鍞�
-        int currentSales = salesLedgerProductMapper.selectProductCountByTypeAndDate(1, currentMonthStart, now);
-        int lastSales = salesLedgerProductMapper.selectProductCountByTypeAndDate(1, lastMonthStart, lastMonthEnd);
-        //  閲囪喘
-        int currentPurchase = salesLedgerProductMapper.selectProductCountByTypeAndDate(2, currentMonthStart, now);
+        if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) {
+            LocalDate start = LocalDate.parse(startDate);
+            LocalDate end = LocalDate.parse(endDate);
+            currentMonthStart = start.atStartOfDay();
+            currentMonthEnd = end.atTime(LocalTime.MAX);
+            long daysBetween = java.time.temporal.ChronoUnit.DAYS.between(start, end) + 1;
+            LocalDate lastStart = start.minusDays(daysBetween);
+            LocalDate lastEnd = start.minusDays(1);
+            lastMonthStart = lastStart.atStartOfDay();
+            lastMonthEnd = lastEnd.atTime(LocalTime.MAX);
+        } else {
+            currentMonthStart = now.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
+            currentMonthEnd = now;
+            LocalDateTime lastMonth = now.minusMonths(1);
+            lastMonthStart = lastMonth.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
+            lastMonthEnd = lastMonth.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
+        }
+
+        // 閿�鍞鍗曟暟锛堢粺璁ales_ledger琛級
+        int currentSales = Math.toIntExact(salesLedgerMapper.selectCount(new LambdaQueryWrapper<SalesLedger>()
+                .ge(SalesLedger::getEntryDate, currentMonthStart)
+                .le(SalesLedger::getEntryDate, currentMonthEnd)));
+        int lastSales = Math.toIntExact(salesLedgerMapper.selectCount(new LambdaQueryWrapper<SalesLedger>()
+                .ge(SalesLedger::getEntryDate, lastMonthStart)
+                .le(SalesLedger::getEntryDate, lastMonthEnd)));
+        // 閲囪喘浜у搧鏁�
+        int currentPurchase = salesLedgerProductMapper.selectProductCountByTypeAndDate(2, currentMonthStart, currentMonthEnd);
         int lastPurchase = salesLedgerProductMapper.selectProductCountByTypeAndDate(2, lastMonthStart, lastMonthEnd);
-        //  鍌ㄥ瓨
-        int currentStorage = stockInventoryMapper.selectStorageProductCountByDate(currentMonthStart, now);
+        // 鍌ㄥ瓨浜у搧鏁�
+        int currentStorage = stockInventoryMapper.selectStorageProductCountByDate(currentMonthStart, currentMonthEnd);
         int lastStorage = stockInventoryMapper.selectStorageProductCountByDate(lastMonthStart, lastMonthEnd);
 
         List<MapDto> list = new ArrayList<>();
-        list.add(createMapDto("閿�鍞骇鍝佹暟", currentSales, lastSales));
+        list.add(createMapDto("閿�鍞鍗曟暟", currentSales, lastSales));
         list.add(createMapDto("閲囪喘浜у搧鏁�", currentPurchase, lastPurchase));
         list.add(createMapDto("鍌ㄥ瓨浜у搧鏁�", currentStorage, lastStorage));
 
@@ -1045,7 +1146,7 @@
     }
 
     @Override
-    public List<Map<String, Object>> productInOutAnalysis(Integer type) {
+    public List<Map<String, Object>> productInOutAnalysis(Integer type, String startDateStr, String endDateStr) {
         String targetName;
         if (type == 1) {
             targetName = "鍘熸潗鏂�";
@@ -1070,8 +1171,17 @@
         LocalDate now = LocalDate.now();
         DateTimeFormatter displayDtf = DateTimeFormatter.ofPattern("M/d");
 
-        String startDate = now.minusDays(6).toString();
-        String endDate = now.toString();
+        LocalDate start;
+        LocalDate end;
+        if (com.ruoyi.common.utils.StringUtils.isNotBlank(startDateStr) && com.ruoyi.common.utils.StringUtils.isNotBlank(endDateStr)) {
+            start = LocalDate.parse(startDateStr);
+            end = LocalDate.parse(endDateStr);
+        } else {
+            end = now;
+            start = now.minusDays(6);
+        }
+        String startDate = start.toString();
+        String endDate = end.toString();
 
         List<Map<String, Object>> inCounts = stockInventoryMapper.selectDailyStockInCounts(rootCategoryId, startDate,
                 endDate + " 23:59:59");
@@ -1108,7 +1218,10 @@
     }
 
     @Override
-    public List<MapDto> productTurnoverDays() {
+    public List<MapDto> productTurnoverDays(String startDateStr, String endDateStr) {
+        if (com.ruoyi.common.utils.StringUtils.isNotBlank(startDateStr) && com.ruoyi.common.utils.StringUtils.isNotBlank(endDateStr)) {
+            return homeMapper.productTurnoverDaysByDate(startDateStr, endDateStr);
+        }
         return homeMapper.productTurnoverDays();
     }
 
diff --git a/src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java b/src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java
index be8dda5..9b6e93f 100644
--- a/src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java
+++ b/src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java
@@ -73,4 +73,24 @@
         return R.ok(inspectionTaskService.delByIds(ids));
     }
 
+    /**
+     * 宸℃瀹屾垚
+     */
+    @PutMapping("/complete/{id}")
+    @Operation(summary = "宸℃瀹屾垚")
+    @Transactional(rollbackFor = Exception.class)
+    public R complete(@PathVariable Long id) {
+        return R.ok(inspectionTaskService.complete(id));
+    }
+
+    /**
+     * 涓�閿贰妫�瀹屾垚
+     */
+    @PutMapping("/batchComplete")
+    @Operation(summary = "涓�閿贰妫�瀹屾垚")
+    @Transactional(rollbackFor = Exception.class)
+    public R batchComplete(String dateStr) {
+        return R.ok(inspectionTaskService.batchComplete(dateStr));
+    }
+
 }
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
index 4e7efad..d5b265a 100644
--- a/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
@@ -82,6 +82,9 @@
     @Schema(description = "宸℃鍦扮偣璇︾粏鎻忚堪")
     private String inspectionLocation;
 
+    @Schema(description = "鐘舵�� 0=寰呭贰妫�, 1=宸插畬鎴�")
+    private Integer status;
+
     @Schema(description = "杞垹闄ゆ爣蹇楋紝0=鏈垹闄わ紝1=宸插垹闄�")
     private Integer deleted;
 
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java b/src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java
index de175f2..077de86 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java
@@ -17,4 +17,8 @@
     int addOrEditInspectionTask(InspectionTaskDto inspectionTaskDto);
 
     int delByIds(Long[] ids);
+
+    int complete(Long id);
+
+    int batchComplete(String dateStr);
 }
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
index 5826fa7..9705038 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
@@ -378,6 +378,52 @@
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    public int complete(Long id) {
+        InspectionTask task = inspectionTaskMapper.selectById(id);
+        if (task == null) {
+            throw new IllegalArgumentException("宸℃浠诲姟涓嶅瓨鍦�");
+        }
+        if (task.getStatus() != null && task.getStatus() == 1) {
+            throw new IllegalArgumentException("璇ュ贰妫�浠诲姟宸插畬鎴�");
+        }
+        task.setStatus(1);
+        if (task.getInspectionResult() == null) {
+            task.setInspectionResult("1");
+        }
+        return inspectionTaskMapper.updateById(task);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int batchComplete(String dateStr) {
+        if (StringUtils.isBlank(dateStr)) {
+            throw new IllegalArgumentException("鏃ユ湡鍙傛暟涓嶈兘涓虹┖");
+        }
+        java.time.LocalDate date = java.time.LocalDate.parse(dateStr);
+        java.time.LocalDateTime startOfDay = date.atStartOfDay();
+        java.time.LocalDateTime endOfDay = date.atTime(java.time.LocalTime.MAX);
+        List<InspectionTask> tasks = inspectionTaskMapper.selectList(
+                new LambdaQueryWrapper<InspectionTask>()
+                        .eq(InspectionTask::getStatus, 0)
+                        .ge(InspectionTask::getCreateTime, startOfDay)
+                        .le(InspectionTask::getCreateTime, endOfDay));
+        if (tasks.isEmpty()) {
+            throw new IllegalArgumentException("璇ユ棩鏈熶笅娌℃湁寰呭畬鎴愮殑宸℃璁板綍");
+        }
+        int count = 0;
+        for (InspectionTask task : tasks) {
+            task.setStatus(1);
+            if (task.getInspectionResult() == null) {
+                task.setInspectionResult("1");
+            }
+            inspectionTaskMapper.updateById(task);
+            count++;
+        }
+        return count;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
     public int delByIds(Long[] ids) {
         // 妫�鏌ュ弬鏁�
         if (ids == null || ids.length == 0) {
diff --git a/src/main/java/com/ruoyi/measuringinstrumentledger/controller/SparePartsRequisitionRecordController.java b/src/main/java/com/ruoyi/measuringinstrumentledger/controller/SparePartsRequisitionRecordController.java
index 26195a4..785315a 100644
--- a/src/main/java/com/ruoyi/measuringinstrumentledger/controller/SparePartsRequisitionRecordController.java
+++ b/src/main/java/com/ruoyi/measuringinstrumentledger/controller/SparePartsRequisitionRecordController.java
@@ -3,12 +3,15 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.measuringinstrumentledger.dto.SparePartsOutboundDto;
 import com.ruoyi.measuringinstrumentledger.dto.SparePartsRequisitionRecordDto;
 import com.ruoyi.measuringinstrumentledger.service.SparePartsRequisitionRecordService;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
 import lombok.AllArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -33,4 +36,11 @@
         IPage<SparePartsRequisitionRecordDto> listPage = sparePartsRequisitionRecordService.listPage(page, sparePartsRequisitionRecordDto);
         return AjaxResult.success(listPage);
     }
+
+    @PostMapping("/outbound")
+    @Operation(summary = "澶囦欢鐙珛鍑哄簱")
+    public AjaxResult outbound(@RequestBody SparePartsOutboundDto dto){
+        sparePartsRequisitionRecordService.outbound(dto);
+        return AjaxResult.success();
+    }
 }
diff --git a/src/main/java/com/ruoyi/measuringinstrumentledger/dto/SparePartsOutboundDto.java b/src/main/java/com/ruoyi/measuringinstrumentledger/dto/SparePartsOutboundDto.java
new file mode 100644
index 0000000..fbd62fa
--- /dev/null
+++ b/src/main/java/com/ruoyi/measuringinstrumentledger/dto/SparePartsOutboundDto.java
@@ -0,0 +1,21 @@
+package com.ruoyi.measuringinstrumentledger.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+@Schema(name = "澶囦欢鍑哄簱璇锋眰")
+public class SparePartsOutboundDto {
+
+    @Schema(description = "澶囦欢ID", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Long sparePartsId;
+
+    @Schema(description = "鍑哄簱鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Integer quantity;
+
+    @Schema(description = "璁惧ID锛堝彲閫夛級")
+    private Long deviceLedgerId;
+
+    @Schema(description = "澶囨敞锛堝彲閫夛級")
+    private String remark;
+}
diff --git a/src/main/java/com/ruoyi/measuringinstrumentledger/service/SparePartsRequisitionRecordService.java b/src/main/java/com/ruoyi/measuringinstrumentledger/service/SparePartsRequisitionRecordService.java
index 2306a3a..8eb0848 100644
--- a/src/main/java/com/ruoyi/measuringinstrumentledger/service/SparePartsRequisitionRecordService.java
+++ b/src/main/java/com/ruoyi/measuringinstrumentledger/service/SparePartsRequisitionRecordService.java
@@ -3,6 +3,7 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.measuringinstrumentledger.dto.SparePartsOutboundDto;
 import com.ruoyi.measuringinstrumentledger.dto.SparePartsRequisitionRecordDto;
 import com.ruoyi.measuringinstrumentledger.pojo.SparePartsRequisitionRecord;
 
@@ -16,4 +17,6 @@
  */
 public interface SparePartsRequisitionRecordService extends IService<SparePartsRequisitionRecord> {
     IPage<SparePartsRequisitionRecordDto> listPage(Page page, SparePartsRequisitionRecordDto sparePartsRequisitionRecordDto);
+
+    void outbound(SparePartsOutboundDto dto);
 }
diff --git a/src/main/java/com/ruoyi/measuringinstrumentledger/service/impl/SparePartsRequisitionRecordServiceImpl.java b/src/main/java/com/ruoyi/measuringinstrumentledger/service/impl/SparePartsRequisitionRecordServiceImpl.java
index 19746f5..10311a5 100644
--- a/src/main/java/com/ruoyi/measuringinstrumentledger/service/impl/SparePartsRequisitionRecordServiceImpl.java
+++ b/src/main/java/com/ruoyi/measuringinstrumentledger/service/impl/SparePartsRequisitionRecordServiceImpl.java
@@ -4,12 +4,18 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.common.enums.SparePartsRequisitionRecordSourceTypeEnum;
+import com.ruoyi.measuringinstrumentledger.dto.SparePartsOutboundDto;
 import com.ruoyi.measuringinstrumentledger.dto.SparePartsRequisitionRecordDto;
+import com.ruoyi.measuringinstrumentledger.mapper.SparePartsMapper;
 import com.ruoyi.measuringinstrumentledger.mapper.SparePartsRequisitionRecordMapper;
+import com.ruoyi.measuringinstrumentledger.pojo.SpareParts;
 import com.ruoyi.measuringinstrumentledger.pojo.SparePartsRequisitionRecord;
 import com.ruoyi.measuringinstrumentledger.service.SparePartsRequisitionRecordService;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
 
 /**
  * <p>
@@ -23,6 +29,7 @@
 @RequiredArgsConstructor
 public class SparePartsRequisitionRecordServiceImpl extends ServiceImpl<SparePartsRequisitionRecordMapper, SparePartsRequisitionRecord> implements SparePartsRequisitionRecordService {
     private final SparePartsRequisitionRecordMapper sparePartsRequisitionRecordMapper;
+    private final SparePartsMapper sparePartsMapper;
 
     @Override
     public IPage<SparePartsRequisitionRecordDto> listPage(Page page, SparePartsRequisitionRecordDto sparePartsRequisitionRecordDto) {
@@ -35,4 +42,26 @@
         });
         return result;
     }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void outbound(SparePartsOutboundDto dto) {
+        SpareParts spareParts = sparePartsMapper.selectById(dto.getSparePartsId());
+        if (spareParts == null) {
+            throw new IllegalArgumentException("澶囦欢涓嶅瓨鍦�");
+        }
+        if (spareParts.getQuantity().compareTo(new BigDecimal(dto.getQuantity())) < 0) {
+            throw new IllegalArgumentException("澶囦欢 " + spareParts.getName() + " 搴撳瓨涓嶈冻");
+        }
+        // 鎵e噺搴撳瓨
+        spareParts.setQuantity(spareParts.getQuantity().subtract(new BigDecimal(dto.getQuantity())));
+        sparePartsMapper.updateById(spareParts);
+        // 鍒涘缓棰嗙敤璁板綍
+        SparePartsRequisitionRecord record = new SparePartsRequisitionRecord();
+        record.setSourceType(SparePartsRequisitionRecordSourceTypeEnum.SparePartsRequisitionRecordSourceTypeDirectOutbound.getCode());
+        record.setSparePartsId(dto.getSparePartsId());
+        record.setQuantity(dto.getQuantity());
+        record.setDeviceLedgerId(dto.getDeviceLedgerId());
+        this.save(record);
+    }
 }
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/purchase/controller/AccountingReportController.java b/src/main/java/com/ruoyi/purchase/controller/AccountingReportController.java
index 813fb44..85c136b 100644
--- a/src/main/java/com/ruoyi/purchase/controller/AccountingReportController.java
+++ b/src/main/java/com/ruoyi/purchase/controller/AccountingReportController.java
@@ -65,4 +65,11 @@
         ExcelUtil<VatDto> util = new ExcelUtil<VatDto>(VatDto.class);
         util.exportExcel(response, result.getRecords() , "澧炲�肩◣姣斿");
     }
+
+    @GetMapping("/listVatDetail")
+    @Operation(summary = "澧炲�肩◣鏄庣粏-鎸夊彂绁�/璁㈠崟灞曠ず")
+    public AjaxResult listVatDetail(Page page, String month) {
+        IPage<VatDto> result = invoicePurchaseService.listVatDetail(page, month);
+        return AjaxResult.success(result);
+    }
 }
diff --git a/src/main/java/com/ruoyi/purchase/dto/VatDto.java b/src/main/java/com/ruoyi/purchase/dto/VatDto.java
index c70ebeb..06d866f 100644
--- a/src/main/java/com/ruoyi/purchase/dto/VatDto.java
+++ b/src/main/java/com/ruoyi/purchase/dto/VatDto.java
@@ -1,15 +1,17 @@
 package com.ruoyi.purchase.dto;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.framework.aspectj.lang.annotation.Excel;
 import lombok.Data;
 
 import java.math.BigDecimal;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 
 @Data
 public class VatDto {
 
-    //鏈堜唤
+    //鏈堜唤锛堟眹鎬昏鍥撅級
     @Excel(name = "鏈堜唤")
     private String month ;
 
@@ -24,4 +26,28 @@
     @Excel(name = "閿�-杩�")
     private BigDecimal taxAmount;
 
+    // ========== 鏄庣粏瑙嗗浘瀛楁 ==========
+
+    @Excel(name = "鍙戠エ鍙�")
+    private String invoiceNo;
+
+    @Excel(name = "閿�鍞悎鍚屽彿")
+    private String salesContractNo;
+
+    @Excel(name = "瀹㈡埛鍚嶇О")
+    private String customerName;
+
+    @Excel(name = "渚涘簲鍟嗗悕绉�")
+    private String supplierName;
+
+    @Excel(name = "绫诲瀷")
+    private String orderType;
+
+    @Excel(name = "寮�绁ㄦ棩鏈�")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private LocalDate invoiceDate;
+
+    @Excel(name = "绋庣巼")
+    private BigDecimal taxRate;
+
 }
diff --git a/src/main/java/com/ruoyi/purchase/mapper/InvoicePurchaseMapper.java b/src/main/java/com/ruoyi/purchase/mapper/InvoicePurchaseMapper.java
index b1dc03c..0434723 100644
--- a/src/main/java/com/ruoyi/purchase/mapper/InvoicePurchaseMapper.java
+++ b/src/main/java/com/ruoyi/purchase/mapper/InvoicePurchaseMapper.java
@@ -25,4 +25,6 @@
     IPage<VatDto> listVat(Page page,@Param("month") String month);
 
     List<VatDto>  listVat1();
+
+    IPage<VatDto> listVatDetail(Page page, @Param("month") String month);
 }
diff --git a/src/main/java/com/ruoyi/purchase/service/IInvoicePurchaseService.java b/src/main/java/com/ruoyi/purchase/service/IInvoicePurchaseService.java
index cc17fc2..a162c3d 100644
--- a/src/main/java/com/ruoyi/purchase/service/IInvoicePurchaseService.java
+++ b/src/main/java/com/ruoyi/purchase/service/IInvoicePurchaseService.java
@@ -32,4 +32,6 @@
     IPage<InvoicePurchaseReportDto> listPurchaseReport(Page page, InvoicePurchaseReportDto invoicePurchaseReportDto);
 
     IPage<VatDto> listVat(Page page,String month);
+
+    IPage<VatDto> listVatDetail(Page page, String month);
 }
diff --git a/src/main/java/com/ruoyi/purchase/service/impl/InvoicePurchaseServiceImpl.java b/src/main/java/com/ruoyi/purchase/service/impl/InvoicePurchaseServiceImpl.java
index 8b76b1a..933ba9e 100644
--- a/src/main/java/com/ruoyi/purchase/service/impl/InvoicePurchaseServiceImpl.java
+++ b/src/main/java/com/ruoyi/purchase/service/impl/InvoicePurchaseServiceImpl.java
@@ -175,4 +175,9 @@
         });
         return vatDtos;
     }
+
+    @Override
+    public IPage<VatDto> listVatDetail(Page page, String month) {
+        return invoicePurchaseMapper.listVatDetail(page, month);
+    }
 }
diff --git a/src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java b/src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java
index f8e22b9..95be0a6 100644
--- a/src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java
+++ b/src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java
@@ -37,8 +37,12 @@
 
     List<Map<String, Object>> selectProductSalesAnalysis();
 
+    List<Map<String, Object>> selectProductSalesAnalysisByDate(@Param("startDate") String startDate, @Param("endDate") String endDate);
+
     List<Map<String, Object>> selectRawMaterialPurchaseAnalysis();
 
+    List<Map<String, Object>> selectRawMaterialPurchaseAnalysisByDate(@Param("startDate") String startDate, @Param("endDate") String endDate);
+
     int selectProductCountByTypeAndDate(@Param("type") Integer type, @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate);
 
     BigDecimal selectRawMaterialExpense();
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index 6d34f6b..21973ca 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -28,7 +28,7 @@
 # 寮�鍙戠幆澧冮厤缃�
 server:
   # 鏈嶅姟鍣ㄧ殑HTTP绔彛锛岄粯璁や负8080
-  port: 7006
+  port: 7005
   servlet:
     # 搴旂敤鐨勮闂矾寰�
     context-path: /
diff --git a/src/main/resources/mapper/device/DeviceMaintenanceMapper.xml b/src/main/resources/mapper/device/DeviceMaintenanceMapper.xml
index e21669f..9806e3b 100644
--- a/src/main/resources/mapper/device/DeviceMaintenanceMapper.xml
+++ b/src/main/resources/mapper/device/DeviceMaintenanceMapper.xml
@@ -50,6 +50,7 @@
                 and dm.maintenance_actually_time &lt; date_add(str_to_date(#{deviceMaintenanceDto.maintenanceActuallyTime}, '%Y-%m-%d'), interval 1 day)
             </if>
         </where>
+        ORDER BY dm.create_time DESC
     </select>
     <select id="detailById" resultType="com.ruoyi.device.vo.DeviceMaintenanceVo">
         select dm.id,
diff --git a/src/main/resources/mapper/home/HomeMapper.xml b/src/main/resources/mapper/home/HomeMapper.xml
index 060099a..71a4df6 100644
--- a/src/main/resources/mapper/home/HomeMapper.xml
+++ b/src/main/resources/mapper/home/HomeMapper.xml
@@ -13,4 +13,28 @@
         GROUP BY p.id, p.product_name
     </select>
 
+    <select id="productTurnoverDaysByDate" resultType="com.ruoyi.dto.MapDto">
+        SELECT p.product_name                                                      AS name,
+               DATEDIFF(IFNULL(MAX(sor.create_time), NOW()), MAX(sir.create_time)) AS value
+        FROM product p
+                 JOIN product_model pm ON p.id = pm.product_id
+                 JOIN stock_in_record sir ON pm.id = sir.product_model_id
+                        AND sir.create_time &gt;= #{startDate}
+                        AND sir.create_time &lt;= #{endDate}
+                 LEFT JOIN stock_out_record sor ON pm.id = sor.product_model_id
+        GROUP BY p.id, p.product_name
+    </select>
+
+    <select id="selectPurchaseCategoryDistribution" resultType="com.ruoyi.dto.MapDto">
+        SELECT p2.product_name   AS name,
+               COUNT(DISTINCT slp.id) AS value
+        FROM sales_ledger_product slp
+                 LEFT JOIN product p ON p.id = slp.product_category
+                 LEFT JOIN product p2 ON p2.id = p.parent_id
+        WHERE slp.type = 2
+          AND p.parent_id IS NOT NULL
+        GROUP BY p2.product_name
+        ORDER BY value DESC
+    </select>
+
 </mapper>
diff --git a/src/main/resources/mapper/purchase/InvoicePurchaseMapper.xml b/src/main/resources/mapper/purchase/InvoicePurchaseMapper.xml
index c5a99f9..5f4f0f4 100644
--- a/src/main/resources/mapper/purchase/InvoicePurchaseMapper.xml
+++ b/src/main/resources/mapper/purchase/InvoicePurchaseMapper.xml
@@ -143,4 +143,55 @@
 #         GROUP BY DATE_FORMAT(a.issue_date, '%Y-%m')
     </select>
 
+    <select id="listVatDetail" resultType="com.ruoyi.purchase.dto.VatDto">
+        SELECT *
+        FROM (
+            -- 杩涢」绋庢槑缁嗭紙閲囪喘璁㈠崟锛�
+            SELECT
+                pl.purchase_contract_number AS invoiceNo,
+                pl.sales_contract_no AS salesContractNo,
+                pl.supplier_name AS supplierName,
+                NULL AS customerName,
+                '杩涢」' AS orderType,
+                pl.entry_date AS invoiceDate,
+                slp.tax_rate AS taxRate,
+                ROUND(SUM(slp.tax_inclusive_total_price - slp.tax_exclusive_total_price), 2) AS jTaxAmount,
+                0 AS xTaxAmount,
+                ROUND(SUM(slp.tax_inclusive_total_price - slp.tax_exclusive_total_price), 2) AS taxAmount
+            FROM sales_ledger_product slp
+            LEFT JOIN purchase_ledger pl ON pl.id = slp.sales_ledger_id
+            WHERE slp.type = 2
+              AND slp.tax_rate IS NOT NULL
+              AND slp.tax_rate > 0
+            GROUP BY pl.id
+
+            UNION ALL
+
+            -- 閿�椤圭◣鏄庣粏锛堥攢鍞鍗曪級
+            SELECT
+                sl.sales_contract_no AS invoiceNo,
+                sl.sales_contract_no AS salesContractNo,
+                NULL AS supplierName,
+                sl.customer_name AS customerName,
+                '閿�椤�' AS orderType,
+                sl.entry_date AS invoiceDate,
+                slp.tax_rate AS taxRate,
+                0 AS jTaxAmount,
+                ROUND(SUM(slp.tax_inclusive_total_price - slp.tax_exclusive_total_price), 2) AS xTaxAmount,
+                ROUND(SUM(slp.tax_inclusive_total_price - slp.tax_exclusive_total_price), 2) AS taxAmount
+            FROM sales_ledger_product slp
+            LEFT JOIN sales_ledger sl ON sl.id = slp.sales_ledger_id
+            WHERE slp.type = 1
+              AND slp.tax_rate IS NOT NULL
+              AND slp.tax_rate > 0
+            GROUP BY sl.id
+        ) a
+        <where>
+            <if test="month != null and month != ''">
+                AND DATE_FORMAT(a.invoiceDate, '%Y-%m') = #{month}
+            </if>
+        </where>
+        ORDER BY a.invoiceDate DESC
+    </select>
+
 </mapper>
diff --git a/src/main/resources/mapper/sales/SalesLedgerProductMapper.xml b/src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
index 7e352d8..960e7f5 100644
--- a/src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
+++ b/src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -240,6 +240,23 @@
         LIMIT 5
     </select>
 
+    <select id="selectProductSalesAnalysisByDate" resultType="java.util.Map">
+        SELECT
+            product_category as name,
+            SUM( tax_inclusive_total_price ) AS value
+        FROM
+            sales_ledger_product
+        WHERE
+            type = 1
+            AND register_date &gt;= #{startDate}
+            AND register_date &lt;= #{endDate}
+        GROUP BY
+            product_category
+        ORDER BY
+            value DESC
+        LIMIT 5
+    </select>
+
     <select id="selectRawMaterialPurchaseAnalysis" resultType="java.util.Map">
         SELECT
             pr.product_name AS name,
@@ -258,6 +275,26 @@
         LIMIT 5
     </select>
 
+    <select id="selectRawMaterialPurchaseAnalysisByDate" resultType="java.util.Map">
+        SELECT
+            pr.product_name AS name,
+            SUM( slp.tax_inclusive_total_price ) AS value
+        FROM
+            sales_ledger_product slp
+            JOIN product pr ON slp.product_id = pr.id
+        WHERE
+            slp.type = 2
+            AND slp.register_date &gt;= #{startDate}
+            AND slp.register_date &lt;= #{endDate}
+            AND pr.parent_id = ( SELECT id FROM product WHERE product_name = '鍘熸潗鏂�' )
+        GROUP BY
+            pr.id,
+            pr.product_name
+        ORDER BY
+            value DESC
+        LIMIT 5
+    </select>
+
     <select id="selectProductCountByTypeAndDate" resultType="int">
         SELECT IFNULL(COUNT(*), 0)
         FROM sales_ledger_product

--
Gitblit v1.9.3