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 < 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 >= #{startDate}
+ AND sir.create_time <= #{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 >= #{startDate}
+ AND register_date <= #{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 >= #{startDate}
+ AND slp.register_date <= #{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