From 00e4d71bd9d504ed25339595690ebe074146b7e7 Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期二, 01 九月 2026 15:27:19 +0800
Subject: [PATCH] feat(mes): 新增管理驾驶舱与统计分析模块
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dashboard/MesDashboardServiceImpl.java | 404 ++++++++++++++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardQualityRespVO.java | 23
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardIndicatorAvgRespVO.java | 34 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/MesDashboardController.java | 275 +++++++++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardTraceRespVO.java | 30 +
yudao-module-mes/src/main/resources/mapper/dashboard/MesDashboardMapper.xml | 187 ++++++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardGenericExcelVO.java | 31 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardDailyCountRespVO.java | 29 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardOverviewExcelVO.java | 26
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dashboard/MesDashboardService.java | 64 ++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardOutboundRespVO.java | 23
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardProductionExcelVO.java | 29 +
sql/mysql/mes_dashboard_module_init.sql | 52 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dashboard/MesDashboardMapper.java | 81 ++
docs/management_cockpit_module.md | 96 +++
sql/mysql/mes_dashboard_test_data.sql | 96 +++
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardInventoryRespVO.java | 26
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardNameCountRespVO.java | 27
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardProductionTrendRespVO.java | 36 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardSummaryRespVO.java | 90 +++
20 files changed, 1,659 insertions(+), 0 deletions(-)
diff --git a/docs/management_cockpit_module.md b/docs/management_cockpit_module.md
new file mode 100644
index 0000000..ca020e5
--- /dev/null
+++ b/docs/management_cockpit_module.md
@@ -0,0 +1,96 @@
+# 绠$悊椹鹃┒鑸变笌缁熻鍒嗘瀽妯″潡
+
+## 1. 妯″潡姒傝堪
+
+瀵瑰簲鎷涙爣闇�姹備功绗� 12 鏉★細**鏁村悎鐢熶骇銆佽川妫�銆佸簱瀛樸�佸嚭搴撱�佽拷婧簲澶х淮搴︽牳蹇冩寚鏍囷紝鎻愪緵鍙鍖栧浘琛ㄥ睍绀猴紝鏀寔 Excel/PDF 鏍煎紡鎶ヨ〃瀵煎嚭鍜屾墦鍗板瓨妗c��**
+
+- 浜斿ぇ缁村害锛�
+ - **鐢熶骇**锛氱敓浜ф壒娆°�佽鐮侊紙涓�琚嬩竴鐮侊級銆佹墭鐩樼爜銆佷粖鏃ユ壂鐮佸叆搴撹鏁般�佺敓浜ц秼鍔匡紱
+ - **璐ㄦ**锛氱瀛愬洓椤规寚鏍囷紙绾害/鍑�搴�/鍙戣娊鐜�/姘村垎锛夋楠岀粨鏋滃垎甯冦�佸潎鍊间笌鍚堟牸鐜囷紱
+ - **搴撳瓨**锛氭寜浠撳簱/鍝佺/搴撳瓨鐘舵�侊紙鏆傚瓨搴�/鍚堟牸搴�/宸插嚭搴擄級鍒嗗竷銆佷綆搴撳瓨棰勮鏁帮紱
+ - **鍑哄簱**锛氬嚭搴撴寜鏃ヨ秼鍔裤�佹寜缁忛攢鍟嗗嚭搴� TOP20锛�
+ - **杩芥函**锛氭秷璐硅�呮壂鐮佽秼鍔裤�佺渷浠藉垎甯冦�佹笭閬撳垎甯冦�佺獪璐ч璀︽眹鎬汇��
+
+- 瀵煎嚭鑳藉姏锛�
+ - **Excel**锛氬悗绔� EasyExcel锛坒astexcel锛夌敓鎴愬 sheet 宸ヤ綔绨匡紝鍏� 6 涓� sheet锛堥┚椹惰埍鎬昏 / 鐢熶骇缁熻 / 璐ㄦ缁熻 / 搴撳瓨缁熻 / 鍑哄簱缁熻 / 杩芥函缁熻锛夛紱
+ - **PDF**锛氬墠绔� `html2canvas` + `jspdf` 灏嗛┚椹惰埍鎶ヨ〃鍖哄煙鐢熸垚涓哄彲鐩存帴涓嬭浇鐨� PDF 鏂囦欢锛�
+ - **鎵撳嵃**锛氬墠绔� `vue3-print-nb` 鎵撳嵃鎶ヨ〃鍖哄煙鐢ㄤ簬瀛樻。銆�
+
+## 2. 鏁版嵁婧愯姹傦紙閲嶈锛�
+
+**绉嶅瓙涓氬姟鏁版嵁琛ㄤ綅浜� `mom-thzy` 搴�**锛堝惈鐢熶骇鎵规 `mes_pro_batch`銆佽鐮� `mes_pro_bag_code`銆佹墭鐩� `mes_pro_pallet`銆佺瀛愯川妫� `mes_qc_seed_quality`銆佹壂鐮佷簨浠� `mes_trace_consumer_scan_event` 绛夊叏閮ㄤ笟鍔¤〃锛夈��
+
+> 鉁� 宸插鐞嗭細`yudao-server/src/main/resources/application-local.yaml` 涓绘暟鎹簮宸叉寚鍥� `jdbc:mysql://192.168.197.128:3306/mom-thzy`锛坮oot / sugaozhixing.cn锛夈�傞┚椹惰埍鍙婁竴琚嬩竴鐮佺瓑绉嶅瓙涓氬姟鍔熻兘**蹇呴』杩炴帴 `mom-thzy` 搴�**鎵嶈兘姝e父缁熻锛岃鍕垮啀鍒囧洖 `ruoyi-vue-pro`锛堣搴撲笉鍚瀛愪笟鍔¤〃锛夈��
+
+鑻ョ洰鏍囧簱缂哄皯绉嶅瓙涓氬姟琛紝闇�鍏堟墽琛� `sql/mysql/` 涓嬬殑鍒濆鍖栬剼鏈細
+- `mes_pro_batch_bag_code_init.sql`锛堢敓浜ф壒娆�/琚嬬爜/鎵樼洏锛�
+- `mes_seed_quality_init.sql`锛堢瀛愯川妫�锛�
+- `mes_trace_consumer_scan_event.sql`锛堟秷璐硅�呮壂鐮佷簨浠讹級
+- `mes_trace_anti_cross_region_init.sql`锛堢獪璐ч璀︼級
+- `mes_pro_scan_inbound_init.sql`锛堢敓浜х嚎鎵爜鍏ュ簱锛�
+
+## 3. 鍚庣瀹炵幇
+
+鍚庣浣嶄簬 `yudao-module-mes`锛屾柊澧炲寘锛�
+
+| 鏂囦欢 | 璇存槑 |
+|---|---|
+| `controller/admin/dashboard/MesDashboardController.java` | `/mes/dashboard` 缁熻鎺ュ彛 + Excel 澶� sheet 瀵煎嚭 |
+| `controller/admin/dashboard/vo/*.java` | 缁熻鍝嶅簲 VO 涓� Excel 瀵煎嚭 VO |
+| `service/dashboard/MesDashboardService(.java|Impl)` | 浜旂淮搴︾粺璁¤仛鍚堬紙鍚寜鏃ヨˉ闆讹級 |
+| `dal/mysql/dashboard/MesDashboardMapper.java` | MyBatis Mapper 鎺ュ彛 |
+| `resources/mapper/dashboard/MesDashboardMapper.xml` | 鎵嬪啓缁熻 SQL |
+
+鎺ュ彛娓呭崟锛堟潈闄� `mes:dashboard:query` / `mes:dashboard:export`锛夛細
+
+| 鎺ュ彛 | 璇存槑 |
+|---|---|
+| `GET /mes/dashboard/summary` | 浜旂淮搴� KPI 姹囨�� |
+| `GET /mes/dashboard/production-trend?days=` | 鐢熶骇瓒嬪娍 |
+| `GET /mes/dashboard/quality-statistics` | 璐ㄦ缁村害缁熻 |
+| `GET /mes/dashboard/inventory-statistics` | 搴撳瓨缁村害缁熻 |
+| `GET /mes/dashboard/outbound-statistics?days=` | 鍑哄簱缁村害缁熻 |
+| `GET /mes/dashboard/trace-statistics?days=` | 杩芥函缁村害缁熻锛堝鐢ㄦ壂鐮佺粺璁′笌绐滆揣棰勮鏈嶅姟锛� |
+| `GET /mes/dashboard/export-excel` | Excel 澶� sheet 鎶ヨ〃瀵煎嚭 |
+
+璁捐瑕佺偣锛�
+- 杩芥函缁村害**澶嶇敤** `MesTraceConsumerScanStatisticsService`銆乣MesTraceCrossRegionService`锛岄伩鍏嶉噸澶� SQL锛�
+- 搴撳瓨浣庨璀︽暟**澶嶇敤** `MesWmStockWarningService.getCurrentWarnings()`锛�
+- 绌烘暟鎹〃锛堝璐ㄦ/搴撳瓨/鍑哄簱灏氭棤鏁版嵁锛夎繑鍥� 0 鎴栫┖鏁扮粍锛屽墠绔浘琛ㄦ甯告覆鏌擄紱Excel 瀵煎嚭绌� sheet 鑷姩琛ャ�屾殏鏃犳暟鎹�嶈锛屼繚璇佽〃澶村彲鎺ㄦ柇銆�
+
+## 4. 鍓嶇瀹炵幇
+
+鍓嶇浣嶄簬 `mom-pro2-before`锛屾柊澧烇細
+
+| 鏂囦欢 | 璇存槑 |
+|---|---|
+| `src/api/mes/management-cockpit/index.ts` | 椹鹃┒鑸� API锛坄requestClient`锛� |
+| `src/views/management-cockpit/index.vue` | 椹鹃┒鑸变富椤甸潰锛堝伐鍏锋爮 + 鎶ヨ〃鍖哄煙锛� |
+| `src/views/management-cockpit/chart-options.ts` | ECharts 閰嶇疆宸ュ巶 |
+| `src/views/management-cockpit/modules/*.vue` | KPI 鍗� + 鐢熶骇/璐ㄦ/搴撳瓨/鍑哄簱/杩芥函鍥捐〃缁勪欢 |
+
+椤甸潰鑳藉姏锛�
+- **鍒锋柊**锛氭暣浣撳埛鏂板叏閮ㄧ淮搴︽暟鎹紱
+- **瀵煎嚭 Excel**锛歚requestClient.download` 涓嬭浇鍚庣澶� sheet 宸ヤ綔绨匡紱
+- **瀵煎嚭 PDF**锛歚html2canvas` 鎴彇鎶ヨ〃鍖哄煙 + `jspdf` 鐢熸垚鍙笅杞� PDF锛�
+- **鎵撳嵃**锛歚vue3-print-nb` 鎵撳嵃鎶ヨ〃鍖哄煙锛岀敤浜庡瓨妗c��
+
+鏂板渚濊禆锛堟牴 `package.json`锛夛細`html2canvas`銆乣jspdf`銆�
+
+## 5. 鑿滃崟涓庢潈闄�
+
+鎵ц `sql/mysql/mes_dashboard_module_init.sql`锛堝箓绛夛級娉ㄥ唽锛�
+
+- 椤剁骇鐩綍銆岀粺璁″垎鏋愩�嶏紙id=602700锛屽凡瀛樺湪鏃跺鐢級锛�
+- 椤甸潰銆岀鐞嗛┚椹惰埍銆嶏紙id=602701锛宍component = management-cockpit/index`锛夛紱
+- 鎸夐挳鏉冮檺 `mes:dashboard:query`銆乣mes:dashboard:export`銆�
+
+鑿滃崟鐢卞悗绔� `system_menu` 鍔ㄦ�佷笅鍙戯紝鍓嶇 `component` 瀛楁鏄犲皠鍒� `src/views/management-cockpit/index.vue`銆�
+
+## 6. 楠岃瘉
+
+1. 鍚庣缂栬瘧锛歚mvn compile -pl yudao-module-mes -am -q`
+2. 鍓嶇绫诲瀷妫�鏌ワ細`pnpm typecheck`锛堟湰娆℃柊澧炴枃浠舵棤绫诲瀷閿欒锛�
+3. 鎵ц `mes_dashboard_module_init.sql` 鍚庣櫥褰曠郴缁燂紝鑿滃崟鍑虹幇銆岀粺璁″垎鏋� 鈫� 绠$悊椹鹃┒鑸便��
+4. 鏍稿浜斿ぇ缁村害鍥捐〃鏁版嵁涓� `mom-thzy` 搴撳疄闄呮暟鎹竴鑷�
+5. 鍒嗗埆楠岃瘉 Excel 涓嬭浇銆丳DF 涓嬭浇銆佹墦鍗颁笁涓姛鑳�
diff --git a/sql/mysql/mes_dashboard_module_init.sql b/sql/mysql/mes_dashboard_module_init.sql
new file mode 100644
index 0000000..fa50112
--- /dev/null
+++ b/sql/mysql/mes_dashboard_module_init.sql
@@ -0,0 +1,52 @@
+-- =============================================
+-- 绠$悊椹鹃┒鑸变笌缁熻鍒嗘瀽妯″潡 鍒濆鍖栬剼鏈�
+-- 鍖呭惈锛氥�岀鐞嗛┚椹惰埍銆嶉〉闈㈠強鎸夐挳鏉冮檺锛堟煡璇�/瀵煎嚭锛�
+-- 璇存槑锛氶《绾х洰褰曘�岀粺璁″垎鏋愩��(id=602700) 宸插瓨鍦紝鏈剼鏈粎鍦ㄥ叾涓嬫寕杞介〉闈€��
+-- 閫傜敤搴擄細mom-thzy锛堢瀛愪笟鍔′富搴擄紝鍚敓浜�/璐ㄦ/搴撳瓨/鍑哄簱/杩芥函鍏ㄩ儴涓氬姟琛級
+-- 娉ㄦ剰锛氳嫢鐩爣搴撶己灏戠瀛愪笟鍔¤〃锛岄渶鍏堟墽琛屼互涓嬭剼鏈細
+-- mes_pro_batch_bag_code_init.sql 锛堢敓浜ф壒娆�/琚嬬爜/鎵樼洏锛�
+-- mes_seed_quality_init.sql 锛堢瀛愯川妫�锛�
+-- mes_trace_consumer_scan_event.sql 锛堟秷璐硅�呮壂鐮佷簨浠讹級
+-- mes_trace_anti_cross_region_init.sql 锛堢獪璐ч璀︼級
+-- mes_pro_scan_inbound_init.sql 锛堢敓浜х嚎鎵爜鍏ュ簱锛�
+-- MySQL 8.4 閫傜敤
+-- =============================================
+
+SET NAMES utf8mb4;
+
+-- ----------------------------
+-- 1. 椤甸潰锛氱鐞嗛┚椹惰埍锛堟寕鍒般�岀粺璁″垎鏋愩�嶇洰褰� 602700 涓嬶級
+-- ----------------------------
+INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name,
+ status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+SELECT 602701, '绠$悊椹鹃┒鑸�', '', 2, 1, 602700, 'management-cockpit', 'ep:monitor', 'management-cockpit/index', 'ManagementCockpit',
+ 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM DUAL
+WHERE NOT EXISTS (
+ SELECT 1 FROM system_menu
+ WHERE (id = 602701 OR (parent_id = 602700 AND path = 'management-cockpit' AND deleted = 0)) AND deleted = 0
+)
+ AND EXISTS (SELECT 1 FROM system_menu WHERE id = 602700 AND deleted = 0);
+
+-- ----------------------------
+-- 2. 鎸夐挳鏉冮檺锛氭煡璇� / 瀵煎嚭
+-- ----------------------------
+INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name,
+ status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+SELECT 602702, '椹鹃┒鑸辨煡璇�', 'mes:dashboard:query', 3, 1, 602701, '', '', '', '',
+ 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM DUAL
+WHERE NOT EXISTS (
+ SELECT 1 FROM system_menu
+ WHERE (id = 602702 OR (parent_id = 602701 AND permission = 'mes:dashboard:query' AND deleted = 0)) AND deleted = 0
+);
+
+INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name,
+ status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
+SELECT 602703, '椹鹃┒鑸卞鍑�', 'mes:dashboard:export', 3, 2, 602701, '', '', '', '',
+ 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM DUAL
+WHERE NOT EXISTS (
+ SELECT 1 FROM system_menu
+ WHERE (id = 602703 OR (parent_id = 602701 AND permission = 'mes:dashboard:export' AND deleted = 0)) AND deleted = 0
+);
diff --git a/sql/mysql/mes_dashboard_test_data.sql b/sql/mysql/mes_dashboard_test_data.sql
new file mode 100644
index 0000000..96a1477
--- /dev/null
+++ b/sql/mysql/mes_dashboard_test_data.sql
@@ -0,0 +1,96 @@
+-- =============================================
+-- 绠$悊椹鹃┒鑸� 娴嬭瘯鏁版嵁鑴氭湰
+-- 鐩爣搴擄細mom-thzy
+-- 浣滅敤锛氫负璐ㄦ / 搴撳瓨 / 鍑哄簱 / 绐滆揣棰勮 鍥涗釜褰撳墠涓虹┖鐨勭淮搴﹁ˉ鍏呮紨绀烘暟鎹�
+-- 鍏宠仈宸叉湁鍩虹鏁版嵁锛氫粨搴�(浜у搧搴�/鐗╂枡搴�/铏氭嫙绾胯竟浠撳簱)銆佸搧绉�(mdm_item#1)銆�
+-- 缁忛攢鍟�(crm_customer#2/4/6)銆佹壒娆�(1/102)銆佽鐮�(0001~0007)
+-- 鏁版嵁鏃ユ湡锛�2026-08-28 ~ 2026-09-01
+-- 娉ㄦ剰锛歩d 浣跨敤 1001+ 娈甸伩鍏嶄笌鏃㈡湁鏁版嵁鍐茬獊锛涢噸澶嶆墽琛屽墠璇峰厛娓呯悊鎴栧浠�
+-- =============================================
+
+SET NAMES utf8mb4;
+
+-- ----------------------------
+-- 1. 绉嶅瓙璐ㄦ涓昏〃锛�7 鏉★細4 鍚堟牸 / 2 涓嶅悎鏍� / 1 澶勭悊涓級
+-- ----------------------------
+INSERT INTO `mes_qc_seed_quality` (id, code, item_id, batch_code, result, reason, inspector_id, inspected_at, creator, create_time, updater, update_time, deleted) VALUES
+(1001, 'PC20260819twoITEM_001-0001', 1, 'PC20260819twoITEM_001', 1, NULL, 1, '2026-08-28 09:00:00', '1', '2026-08-28 09:00:00', '1', '2026-08-28 09:00:00', b'0'),
+(1002, 'PC20260819twoITEM_001-0002', 1, 'PC20260819twoITEM_001', 1, NULL, 111, '2026-08-29 10:00:00', '1', '2026-08-29 10:00:00', '1', '2026-08-29 10:00:00', b'0'),
+(1003, 'PC20260819twoITEM_001-0003', 1, 'PC20260819twoITEM_001', 2, '鍙戣娊鐜囦綆浜庢爣鍑嗗��', 1, '2026-08-29 14:30:00', '1', '2026-08-29 14:30:00', '1', '2026-08-29 14:30:00', b'0'),
+(1004, 'PC20260819twoITEM_001-0004', 1, 'PC20260819twoITEM_001', 1, NULL, 111, '2026-08-30 09:20:00', '1', '2026-08-30 09:20:00', '1', '2026-08-30 09:20:00', b'0'),
+(1005, 'PC20260819twoITEM_001-0005', 1, 'PC20260819twoITEM_001', 0, NULL, 1, NULL, '1', '2026-08-30 11:00:00', '1', '2026-08-30 11:00:00', b'0'),
+(1006, 'PC20260819twoITEM_001-0006', 1, 'PC20260819twoITEM_001', 1, NULL, 1, '2026-08-31 15:00:00', '1', '2026-08-31 15:00:00', '1', '2026-08-31 15:00:00', b'0'),
+(1007, 'PC20260819twoITEM_001-0007', 1, 'PC20260819twoITEM_001', 2, '姘村垎鍚噺瓒呮爣', 111, '2026-09-01 09:10:00', '1', '2026-09-01 09:10:00', '1', '2026-09-01 09:10:00', b'0');
+
+-- ----------------------------
+-- 2. 绉嶅瓙璐ㄦ鎸囨爣缁撴灉锛堟瘡鏉¤川妫� 4 椤癸細绾害/鍑�搴�/鍙戣娊鐜�/姘村垎锛�
+-- 鏍囧噯鍖洪棿锛氱函搴96,100] 鍑�搴98,100] 鍙戣娊鐜嘯85,100] 姘村垎[0,13]
+-- ----------------------------
+INSERT INTO `mes_qc_seed_quality_indicator` (id, quality_id, indicator_code, value, min_value, max_value, creator, create_time, updater, update_time, deleted) VALUES
+(1001, 1001, 'purity', 98.500000, 96, 100, '1', '2026-08-28 09:00:00', '1', '2026-08-28 09:00:00', b'0'),
+(1002, 1001, 'cleanliness', 99.200000, 98, 100, '1', '2026-08-28 09:00:00', '1', '2026-08-28 09:00:00', b'0'),
+(1003, 1001, 'germination_rate', 92.000000, 85, 100, '1', '2026-08-28 09:00:00', '1', '2026-08-28 09:00:00', b'0'),
+(1004, 1001, 'moisture', 10.500000, 0, 13, '1', '2026-08-28 09:00:00', '1', '2026-08-28 09:00:00', b'0'),
+(1005, 1002, 'purity', 98.100000, 96, 100, '1', '2026-08-29 10:00:00', '1', '2026-08-29 10:00:00', b'0'),
+(1006, 1002, 'cleanliness', 99.000000, 98, 100, '1', '2026-08-29 10:00:00', '1', '2026-08-29 10:00:00', b'0'),
+(1007, 1002, 'germination_rate', 93.500000, 85, 100, '1', '2026-08-29 10:00:00', '1', '2026-08-29 10:00:00', b'0'),
+(1008, 1002, 'moisture', 10.200000, 0, 13, '1', '2026-08-29 10:00:00', '1', '2026-08-29 10:00:00', b'0'),
+(1009, 1003, 'purity', 98.200000, 96, 100, '1', '2026-08-29 14:30:00', '1', '2026-08-29 14:30:00', b'0'),
+(1010, 1003, 'cleanliness', 99.000000, 98, 100, '1', '2026-08-29 14:30:00', '1', '2026-08-29 14:30:00', b'0'),
+(1011, 1003, 'germination_rate', 80.000000, 85, 100, '1', '2026-08-29 14:30:00', '1', '2026-08-29 14:30:00', b'0'),
+(1012, 1003, 'moisture', 11.200000, 0, 13, '1', '2026-08-29 14:30:00', '1', '2026-08-29 14:30:00', b'0'),
+(1013, 1004, 'purity', 98.800000, 96, 100, '1', '2026-08-30 09:20:00', '1', '2026-08-30 09:20:00', b'0'),
+(1014, 1004, 'cleanliness', 99.400000, 98, 100, '1', '2026-08-30 09:20:00', '1', '2026-08-30 09:20:00', b'0'),
+(1015, 1004, 'germination_rate', 95.000000, 85, 100, '1', '2026-08-30 09:20:00', '1', '2026-08-30 09:20:00', b'0'),
+(1016, 1004, 'moisture', 9.800000, 0, 13, '1', '2026-08-30 09:20:00', '1', '2026-08-30 09:20:00', b'0'),
+(1017, 1005, 'purity', 98.000000, 96, 100, '1', '2026-08-30 11:00:00', '1', '2026-08-30 11:00:00', b'0'),
+(1018, 1005, 'cleanliness', 98.800000, 98, 100, '1', '2026-08-30 11:00:00', '1', '2026-08-30 11:00:00', b'0'),
+(1019, 1005, 'germination_rate', 90.000000, 85, 100, '1', '2026-08-30 11:00:00', '1', '2026-08-30 11:00:00', b'0'),
+(1020, 1005, 'moisture', 11.000000, 0, 13, '1', '2026-08-30 11:00:00', '1', '2026-08-30 11:00:00', b'0'),
+(1021, 1006, 'purity', 98.600000, 96, 100, '1', '2026-08-31 15:00:00', '1', '2026-08-31 15:00:00', b'0'),
+(1022, 1006, 'cleanliness', 99.100000, 98, 100, '1', '2026-08-31 15:00:00', '1', '2026-08-31 15:00:00', b'0'),
+(1023, 1006, 'germination_rate', 91.500000, 85, 100, '1', '2026-08-31 15:00:00', '1', '2026-08-31 15:00:00', b'0'),
+(1024, 1006, 'moisture', 10.100000, 0, 13, '1', '2026-08-31 15:00:00', '1', '2026-08-31 15:00:00', b'0'),
+(1025, 1007, 'purity', 97.500000, 96, 100, '1', '2026-09-01 09:10:00', '1', '2026-09-01 09:10:00', b'0'),
+(1026, 1007, 'cleanliness', 98.800000, 98, 100, '1', '2026-09-01 09:10:00', '1', '2026-09-01 09:10:00', b'0'),
+(1027, 1007, 'germination_rate', 90.000000, 85, 100, '1', '2026-09-01 09:10:00', '1', '2026-09-01 09:10:00', b'0'),
+(1028, 1007, 'moisture', 14.500000, 0, 13, '1', '2026-09-01 09:10:00', '1', '2026-09-01 09:10:00', b'0');
+
+-- ----------------------------
+-- 3. 搴撳瓨锛�4 鏉★細鍚堟牸搴�2 / 鏆傚瓨搴�2锛屽垎甯冧簬浜у搧搴�/鐗╂枡搴�/铏氭嫙绾胯竟浠撳簱锛�
+-- 搴撳瓨鐘舵�侊細10-鏆傚瓨搴� 20-鍚堟牸搴� 30-宸插嚭搴�
+-- ----------------------------
+INSERT INTO `mes_wm_material_stock` (id, item_type_id, item_id, batch_id, batch_code, warehouse_id, location_id, area_id, vendor_id, quantity, frozen_quantity, reserved_quantity, in_transit_quantity, stock_state, erp_time, receipt_time, frozen, remark, creator, create_time, updater, update_time, deleted) VALUES
+(1001, NULL, 1, 1, 'PC20260819twoITEM_001', 1, NULL, NULL, NULL, 1000.000000, 0, 0, 0, 20, '2026-08-20 08:00:00', '2026-08-20 08:00:00', b'0', '椹鹃┒鑸辨紨绀烘暟鎹�', '1', '2026-08-20 08:00:00', '1', '2026-08-20 08:00:00', b'0'),
+(1002, NULL, 1, 102, 'PC20260828twoITEM_001', 1, NULL, NULL, NULL, 500.000000, 0, 0, 0, 20, '2026-08-28 09:00:00', '2026-08-28 09:00:00', b'0', '椹鹃┒鑸辨紨绀烘暟鎹�', '1', '2026-08-28 09:00:00', '1', '2026-08-28 09:00:00', b'0'),
+(1003, NULL, 1, 1, 'PC20260819twoITEM_001', 2, NULL, NULL, NULL, 800.000000, 0, 0, 0, 10, '2026-08-20 08:30:00', '2026-08-20 08:30:00', b'0', '椹鹃┒鑸辨紨绀烘暟鎹�', '1', '2026-08-20 08:30:00', '1', '2026-08-20 08:30:00', b'0'),
+(1004, NULL, 1, 102, 'PC20260828twoITEM_001', 3, NULL, NULL, NULL, 300.000000, 0, 0, 0, 10, '2026-08-28 10:00:00', '2026-08-28 10:00:00', b'0', '椹鹃┒鑸辨紨绀烘暟鎹�', '1', '2026-08-28 10:00:00', '1', '2026-08-28 10:00:00', b'0');
+
+-- ----------------------------
+-- 4. 閿�鍞嚭搴撳崟锛�4 寮狅紝3 宸插畬鎴� + 1 寰呮墽琛屽嚭搴擄級
+-- 鍑哄簱鐘舵�侊細0鑽夌 1寰呮娴� 2寰呮嫞璐� 3寰呮墽琛屽嚭搴� 4宸插畬鎴�
+-- ----------------------------
+INSERT INTO `mes_wm_product_sales` (id, code, name, client_id, sale_order_id, notice_id, sales_order_code, sales_date, contact_name, contact_telephone, contact_address, carrier, shipping_number, status, remark, creator, create_time, updater, update_time, deleted) VALUES
+(1001, 'XSCK20260828001', '閿�鍞嚭搴撳崟-鍗楅�氬鎴�', 2, NULL, NULL, NULL, '2026-08-28 09:00:00', '寮犵粡鐞�', '13800000001', '姹熻嫃鐪佸崡閫氬競', '椤轰赴', 'SF1001', 4, '椹鹃┒鑸辨紨绀烘暟鎹�', '1', '2026-08-28 09:00:00', '1', '2026-08-28 09:00:00', b'0'),
+(1002, 'XSCK20260829001', '閿�鍞嚭搴撳崟-瀹㈡埛娌熼��', 4, NULL, NULL, NULL, '2026-08-29 10:00:00', '鏉庣粡鐞�', '13800000002', '鏂扮枂闃垮厠鑻�', '涓��', 'ZT2001', 4, '椹鹃┒鑸辨紨绀烘暟鎹�', '1', '2026-08-29 10:00:00', '1', '2026-08-29 10:00:00', b'0'),
+(1003, 'XSCK20260831001', '閿�鍞嚭搴撳崟-楂樼骇', 6, NULL, NULL, NULL, '2026-08-31 11:00:00', '鐜嬬粡鐞�', '13800000003', '鏂扮枂鍠�浠�', '鍦嗛��', 'YT3001', 3, '椹鹃┒鑸辨紨绀烘暟鎹�', '1', '2026-08-31 11:00:00', '1', '2026-08-31 11:00:00', b'0'),
+(1004, 'XSCK20260901001', '閿�鍞嚭搴撳崟-鍗楅�氬鎴�', 2, NULL, NULL, NULL, '2026-09-01 09:30:00', '寮犵粡鐞�', '13800000001', '姹熻嫃鐪佸崡閫氬競', '椤轰赴', 'SF1004', 4, '椹鹃┒鑸辨紨绀烘暟鎹�', '1', '2026-09-01 09:30:00', '1', '2026-09-01 09:30:00', b'0');
+
+-- ----------------------------
+-- 5. 閿�鍞嚭搴撳崟琛岋紙瀵瑰簲涓婇潰 4 寮犲嚭搴撳崟锛�
+-- ----------------------------
+INSERT INTO `mes_wm_product_sales_line` (id, sales_id, sale_order_item_id, notice_line_id, item_id, batch_id, batch_code, material_stock_id, quantity, oqc_check_flag, oqc_id, quality_status, remark, creator, create_time, updater, update_time, deleted) VALUES
+(1001, 1001, NULL, NULL, 1, 1, 'PC20260819twoITEM_001', NULL, 200.000000, b'0', NULL, NULL, '椹鹃┒鑸辨紨绀烘暟鎹�', '1', '2026-08-28 09:00:00', '1', '2026-08-28 09:00:00', b'0'),
+(1002, 1001, NULL, NULL, 1, 1, 'PC20260819twoITEM_001', NULL, 150.000000, b'0', NULL, NULL, '椹鹃┒鑸辨紨绀烘暟鎹�', '1', '2026-08-28 09:00:00', '1', '2026-08-28 09:00:00', b'0'),
+(1003, 1002, NULL, NULL, 1, 102, 'PC20260828twoITEM_001', NULL, 180.000000, b'0', NULL, NULL, '椹鹃┒鑸辨紨绀烘暟鎹�', '1', '2026-08-29 10:00:00', '1', '2026-08-29 10:00:00', b'0'),
+(1004, 1003, NULL, NULL, 1, 1, 'PC20260819twoITEM_001', NULL, 220.000000, b'0', NULL, NULL, '椹鹃┒鑸辨紨绀烘暟鎹�', '1', '2026-08-31 11:00:00', '1', '2026-08-31 11:00:00', b'0'),
+(1005, 1004, NULL, NULL, 1, 102, 'PC20260828twoITEM_001', NULL, 160.000000, b'0', NULL, NULL, '椹鹃┒鑸辨紨绀烘暟鎹�', '1', '2026-09-01 09:30:00', '1', '2026-09-01 09:30:00', b'0');
+
+-- ----------------------------
+-- 6. 绐滆揣棰勮锛�3 鏉★細璺ㄧ渷寰呭鐞� / 璺ㄥ競宸插鐞� / 璺ㄧ渷璇姤锛�
+-- 鐘舵�侊細10-寰呭鐞� 20-宸插鐞� 30-璇姤锛涚被鍨嬶細CROSS_PROVINCE 璺ㄧ渷 / CROSS_CITY 璺ㄥ競
+-- ----------------------------
+INSERT INTO `mes_trace_cross_region_warning` (id, warning_no, bag_code_id, bag_code, pallet_code_id, pallet_code, batch_id, batch_code, item_id, item_code, item_name, sales_id, sales_code, client_id, client_name, dealer_province, dealer_city, scan_province, scan_city, warning_type, scan_event_id, status, handle_user_id, handle_time, handle_remark, event_time, create_time, update_time, creator, updater, deleted) VALUES
+(1001, 'WARN20260901001', 1, 'PC20260819twoITEM_001-0001', NULL, NULL, 1, 'PC20260819twoITEM_001', 1, '001', '娴嬭瘯', 1001, 'XSCK20260828001', 2, '鍗楅�氬鎴�', '鏂扮枂', '闃垮厠鑻�', '娌冲崡', '閮戝窞', 'CROSS_PROVINCE', NULL, 10, NULL, NULL, NULL, '2026-08-30 12:00:00', '2026-08-30 12:00:00', '2026-08-30 12:00:00', '1', '1', b'0'),
+(1002, 'WARN20260901002', NULL, NULL, NULL, NULL, 102, 'PC20260828twoITEM_001', 1, '001', '娴嬭瘯', 1002, 'XSCK20260829001', 4, '瀹㈡埛娌熼��', '鏂扮枂', '鍠�浠�', '鏂扮枂', '涔岄瞾鏈ㄩ綈', 'CROSS_CITY', NULL, 20, 1, '2026-09-01 08:00:00', '宸叉牳瀹炲鐞�', '2026-08-31 18:00:00', '2026-08-31 18:00:00', '2026-09-01 08:00:00', '1', '1', b'0'),
+(1003, 'WARN20260901003', NULL, NULL, NULL, NULL, 1, 'PC20260819twoITEM_001', 1, '001', '娴嬭瘯', 1004, 'XSCK20260901001', 2, '鍗楅�氬鎴�', '鏂扮枂', '闃垮厠鑻�', '鐢樿們', '鍏板窞', 'CROSS_PROVINCE', NULL, 30, 1, '2026-09-01 10:00:00', '璇姤', '2026-09-01 09:00:00', '2026-09-01 09:00:00', '2026-09-01 10:00:00', '1', '1', b'0');
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/MesDashboardController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/MesDashboardController.java
new file mode 100644
index 0000000..dd2e581
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/MesDashboardController.java
@@ -0,0 +1,275 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dashboard;
+
+import cn.idev.excel.ExcelWriter;
+import cn.idev.excel.FastExcelFactory;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardDailyCountRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardGenericExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardIndicatorAvgRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardInventoryRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardNameCountRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardOutboundRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardOverviewExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardProductionExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardProductionTrendRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardQualityRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardSummaryRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardTraceRespVO;
+import cn.iocoder.yudao.module.mes.service.dashboard.MesDashboardService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * 绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸变笌缁熻鍒嗘瀽
+ * <p>
+ * 鏁村悎鐢熶骇銆佽川妫�銆佸簱瀛樸�佸嚭搴撱�佽拷婧簲澶х淮搴︽牳蹇冩寚鏍囷紝鎻愪緵鍙鍖栧浘琛ㄦ暟鎹笌 Excel 鎶ヨ〃瀵煎嚭銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Tag(name = "绠$悊鍚庡彴 - MES 绠$悊椹鹃┒鑸�")
+@RestController
+@RequestMapping("/mes/dashboard")
+@Validated
+public class MesDashboardController {
+
+ @Resource
+ private MesDashboardService dashboardService;
+
+ @GetMapping("/summary")
+ @Operation(summary = "鑾峰緱绠$悊椹鹃┒鑸变簲缁村害姹囨�荤粺璁�")
+ @PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
+ public CommonResult<MesDashboardSummaryRespVO> getSummary() {
+ return success(dashboardService.getSummary());
+ }
+
+ @GetMapping("/production-trend")
+ @Operation(summary = "鑾峰緱鐢熶骇瓒嬪娍")
+ @Parameter(name = "days", description = "澶╂暟", example = "30")
+ @PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
+ public CommonResult<List<MesDashboardProductionTrendRespVO>> getProductionTrend(
+ @RequestParam(value = "days", defaultValue = "30") @Min(1) @Max(365) Integer days) {
+ return success(dashboardService.getProductionTrend(days));
+ }
+
+ @GetMapping("/quality-statistics")
+ @Operation(summary = "鑾峰緱璐ㄦ缁村害缁熻")
+ @PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
+ public CommonResult<MesDashboardQualityRespVO> getQualityStatistics() {
+ return success(dashboardService.getQualityStatistics());
+ }
+
+ @GetMapping("/inventory-statistics")
+ @Operation(summary = "鑾峰緱搴撳瓨缁村害缁熻")
+ @PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
+ public CommonResult<MesDashboardInventoryRespVO> getInventoryStatistics() {
+ return success(dashboardService.getInventoryStatistics());
+ }
+
+ @GetMapping("/outbound-statistics")
+ @Operation(summary = "鑾峰緱鍑哄簱缁村害缁熻")
+ @Parameter(name = "days", description = "澶╂暟", example = "30")
+ @PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
+ public CommonResult<MesDashboardOutboundRespVO> getOutboundStatistics(
+ @RequestParam(value = "days", defaultValue = "30") @Min(1) @Max(365) Integer days) {
+ return success(dashboardService.getOutboundStatistics(days));
+ }
+
+ @GetMapping("/trace-statistics")
+ @Operation(summary = "鑾峰緱杩芥函缁村害缁熻")
+ @Parameter(name = "days", description = "澶╂暟", example = "30")
+ @PreAuthorize("@ss.hasPermission('mes:dashboard:query')")
+ public CommonResult<MesDashboardTraceRespVO> getTraceStatistics(
+ @RequestParam(value = "days", defaultValue = "30") @Min(1) @Max(365) Integer days) {
+ return success(dashboardService.getTraceStatistics(days));
+ }
+
+ @GetMapping("/export-excel")
+ @Operation(summary = "瀵煎嚭绠$悊椹鹃┒鑸� Excel 鎶ヨ〃")
+ @ApiAccessLog(operateType = EXPORT)
+ @PreAuthorize("@ss.hasPermission('mes:dashboard:export')")
+ public void exportExcel(HttpServletResponse response) throws IOException {
+ // 1. 缁勮鍚勭淮搴︽姤琛ㄦ暟鎹�
+ MesDashboardSummaryRespVO summary = dashboardService.getSummary();
+ List<MesDashboardProductionTrendRespVO> productionTrend = dashboardService.getProductionTrend(30);
+ MesDashboardQualityRespVO quality = dashboardService.getQualityStatistics();
+ MesDashboardInventoryRespVO inventory = dashboardService.getInventoryStatistics();
+ MesDashboardOutboundRespVO outbound = dashboardService.getOutboundStatistics(30);
+ MesDashboardTraceRespVO trace = dashboardService.getTraceStatistics(30);
+
+ // 2. 澶� sheet 鍐欏叆
+ ExcelWriter excelWriter = FastExcelFactory.write(response.getOutputStream())
+ .autoCloseStream(false)
+ .build();
+ try {
+ excelWriter.write(buildOverviewRows(summary), FastExcelFactory.writerSheet("椹鹃┒鑸辨�昏").build());
+ excelWriter.write(buildProductionRows(summary, productionTrend), FastExcelFactory.writerSheet("鐢熶骇缁熻").build());
+ excelWriter.write(buildQualityRows(quality), FastExcelFactory.writerSheet("璐ㄦ缁熻").build());
+ excelWriter.write(buildInventoryRows(inventory), FastExcelFactory.writerSheet("搴撳瓨缁熻").build());
+ excelWriter.write(buildOutboundRows(outbound), FastExcelFactory.writerSheet("鍑哄簱缁熻").build());
+ excelWriter.write(buildTraceRows(trace), FastExcelFactory.writerSheet("杩芥函缁熻").build());
+ } finally {
+ excelWriter.finish();
+ excelWriter.close();
+ }
+ response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8("绠$悊椹鹃┒鑸辩粺璁℃姤琛�.xls"));
+ response.setContentType("application/vnd.ms-excel;charset=UTF-8");
+ }
+
+ // ========== Excel 琛岀粍瑁� ==========
+
+ private List<MesDashboardOverviewExcelVO> buildOverviewRows(MesDashboardSummaryRespVO summary) {
+ List<MesDashboardOverviewExcelVO> rows = new ArrayList<>();
+ addOverview(rows, "鎵规鎬绘暟", summary.getBatchTotal());
+ addOverview(rows, "鐢熶骇涓壒娆�", summary.getBatchProducing());
+ addOverview(rows, "宸插畬鎴愭壒娆�", summary.getBatchFinished());
+ addOverview(rows, "宸蹭綔搴熸壒娆�", summary.getBatchCanceled());
+ addOverview(rows, "琚嬬爜鎬绘暟", summary.getBagCodeTotal());
+ addOverview(rows, "宸插鍑哄嵃鍒疯鐮�", summary.getBagCodePrinted());
+ addOverview(rows, "宸蹭綔搴熻鐮�", summary.getBagCodeCanceled());
+ addOverview(rows, "鎵樼洏鐮佹�绘暟", summary.getPalletTotal());
+ addOverview(rows, "浠婃棩鎵爜鍏ュ簱琚嬫暟", summary.getTodayInboundBagCount());
+ addOverview(rows, "妫�楠岃褰曟�绘暟", summary.getQualityTotal());
+ addOverview(rows, "鍚堟牸妫�楠岃褰�", summary.getQualityPass());
+ addOverview(rows, "涓嶅悎鏍兼楠岃褰�", summary.getQualityFail());
+ addOverview(rows, "妫�楠屽悎鏍肩巼(%)", summary.getQualityPassRate());
+ addOverview(rows, "褰撳墠搴撳瓨鎬婚噺", summary.getStockTotal());
+ addOverview(rows, "鏆傚瓨搴撳瓨閲�", summary.getStockTemporary());
+ addOverview(rows, "鍚堟牸搴撳瓨閲�", summary.getStockQualified());
+ addOverview(rows, "浣庡簱瀛橀璀︽暟", summary.getLowStockCount());
+ addOverview(rows, "鍑哄簱鍗曟�绘暟", summary.getOutboundOrderTotal());
+ addOverview(rows, "鍑哄簱鎬婚噺", summary.getOutboundQuantityTotal());
+ addOverview(rows, "浠婃棩鍑哄簱閲�", summary.getTodayOutboundQuantity());
+ addOverview(rows, "鎵爜鎬绘暟", summary.getScanTotal());
+ addOverview(rows, "浠婃棩鎵爜鏁�", summary.getTodayScanCount());
+ addOverview(rows, "绐滆揣棰勮鎬绘暟", summary.getCrossRegionWarningTotal());
+ addOverview(rows, "寰呭鐞嗙獪璐ч璀�", summary.getCrossRegionPendingCount());
+ return rows;
+ }
+
+ private List<MesDashboardProductionExcelVO> buildProductionRows(MesDashboardSummaryRespVO summary,
+ List<MesDashboardProductionTrendRespVO> trend) {
+ List<MesDashboardProductionExcelVO> rows = new ArrayList<>();
+ addProduction(rows, "鎵规", "鑽夌", summary.getBatchDraft());
+ addProduction(rows, "鎵规", "鐢熶骇涓�", summary.getBatchProducing());
+ addProduction(rows, "鎵规", "宸插畬鎴�", summary.getBatchFinished());
+ addProduction(rows, "鎵规", "宸蹭綔搴�", summary.getBatchCanceled());
+ addProduction(rows, "琚嬬爜", "宸茬敓鎴�", summary.getBagCodeGenerated());
+ addProduction(rows, "琚嬬爜", "宸插鍑哄嵃鍒�", summary.getBagCodePrinted());
+ addProduction(rows, "琚嬬爜", "宸蹭綔搴�", summary.getBagCodeCanceled());
+ addProduction(rows, "鎵樼洏", "鎬绘暟", summary.getPalletTotal());
+ addProduction(rows, "鍏ュ簱", "浠婃棩鎵爜鍏ュ簱琚嬫暟", summary.getTodayInboundBagCount());
+ for (MesDashboardProductionTrendRespVO item : trend) {
+ addProduction(rows, "瓒嬪娍-" + item.getDate(), "鏂板鎵规", item.getBatchCount());
+ addProduction(rows, "瓒嬪娍-" + item.getDate(), "鏂板琚嬬爜", item.getBagCodeCount());
+ addProduction(rows, "瓒嬪娍-" + item.getDate(), "鏂板鎵樼洏", item.getPalletCount());
+ addProduction(rows, "瓒嬪娍-" + item.getDate(), "鍏ュ簱琚嬫暟", item.getInboundBagQuantity() == null ? 0L
+ : item.getInboundBagQuantity().longValue());
+ }
+ return rows;
+ }
+
+ private List<MesDashboardGenericExcelVO> buildQualityRows(MesDashboardQualityRespVO quality) {
+ List<MesDashboardGenericExcelVO> rows = new ArrayList<>();
+ for (MesDashboardNameCountRespVO item : quality.getResultDistribution()) {
+ addGeneric(rows, "妫�楠岀粨鏋�", item.getName(), item.getCount());
+ }
+ for (MesDashboardIndicatorAvgRespVO item : quality.getIndicatorList()) {
+ addGeneric(rows, "鎸囨爣鍧囧��", item.getIndicatorName() + "锛堝钩鍧囧�硷級", item.getAverageValue());
+ addGeneric(rows, "鎸囨爣鍧囧��", item.getIndicatorName() + "锛堝悎鏍肩巼%锛�", item.getPassRate());
+ }
+ addEmptyGuard(rows);
+ return rows;
+ }
+
+ private List<MesDashboardGenericExcelVO> buildInventoryRows(MesDashboardInventoryRespVO inventory) {
+ List<MesDashboardGenericExcelVO> rows = new ArrayList<>();
+ for (MesDashboardNameCountRespVO item : inventory.getWarehouseDistribution()) {
+ addGeneric(rows, "鎸変粨搴�", item.getName(), item.getCount());
+ }
+ for (MesDashboardNameCountRespVO item : inventory.getItemDistribution()) {
+ addGeneric(rows, "鎸夊搧绉�", item.getName(), item.getCount());
+ }
+ for (MesDashboardNameCountRespVO item : inventory.getStateDistribution()) {
+ addGeneric(rows, "鎸夌姸鎬�", item.getName(), item.getCount());
+ }
+ addEmptyGuard(rows);
+ return rows;
+ }
+
+ private List<MesDashboardGenericExcelVO> buildOutboundRows(MesDashboardOutboundRespVO outbound) {
+ List<MesDashboardGenericExcelVO> rows = new ArrayList<>();
+ for (MesDashboardDailyCountRespVO item : outbound.getTrend()) {
+ addGeneric(rows, "鍑哄簱瓒嬪娍", item.getDate(), item.getCount());
+ }
+ for (MesDashboardNameCountRespVO item : outbound.getDealerDistribution()) {
+ addGeneric(rows, "鎸夌粡閿�鍟�", item.getName(), item.getCount());
+ }
+ addEmptyGuard(rows);
+ return rows;
+ }
+
+ private List<MesDashboardGenericExcelVO> buildTraceRows(MesDashboardTraceRespVO trace) {
+ List<MesDashboardGenericExcelVO> rows = new ArrayList<>();
+ for (MesDashboardDailyCountRespVO item : trace.getScanTrend()) {
+ addGeneric(rows, "鎵爜瓒嬪娍", item.getDate(), item.getCount());
+ }
+ for (MesDashboardNameCountRespVO item : trace.getProvinceDistribution()) {
+ addGeneric(rows, "鎵爜鐪佷唤", item.getName(), item.getCount());
+ }
+ for (MesDashboardNameCountRespVO item : trace.getChannelDistribution()) {
+ addGeneric(rows, "鎵爜娓犻亾", item.getName(), item.getCount());
+ }
+ if (trace.getCrossRegionSummary() != null) {
+ addGeneric(rows, "绐滆揣棰勮", "鎬绘暟", trace.getCrossRegionSummary().getTotal());
+ addGeneric(rows, "绐滆揣棰勮", "寰呭鐞�", trace.getCrossRegionSummary().getPending());
+ addGeneric(rows, "绐滆揣棰勮", "宸插鐞�", trace.getCrossRegionSummary().getHandled());
+ addGeneric(rows, "绐滆揣棰勮", "璇姤", trace.getCrossRegionSummary().getFalseAlarm());
+ addGeneric(rows, "绐滆揣棰勮", "璺ㄧ渷", trace.getCrossRegionSummary().getCrossProvince());
+ addGeneric(rows, "绐滆揣棰勮", "璺ㄥ競", trace.getCrossRegionSummary().getCrossCity());
+ }
+ addEmptyGuard(rows);
+ return rows;
+ }
+
+ /**
+ * 绌烘暟鎹厹搴曪細淇濊瘉姣忎釜 sheet 鑷冲皯鏈変竴琛岋紝閬垮厤 FastExcel 鏃犳硶鎺ㄦ柇琛ㄥご
+ */
+ private void addEmptyGuard(List<MesDashboardGenericExcelVO> rows) {
+ if (rows.isEmpty()) {
+ rows.add(new MesDashboardGenericExcelVO("鎻愮ず", "鏆傛棤鏁版嵁", "0"));
+ }
+ }
+
+ private void addOverview(List<MesDashboardOverviewExcelVO> rows, String indicator, Object value) {
+ rows.add(new MesDashboardOverviewExcelVO(indicator, value == null ? "0" : value.toString()));
+ }
+
+ private void addProduction(List<MesDashboardProductionExcelVO> rows, String category, String status, Object count) {
+ rows.add(new MesDashboardProductionExcelVO(category, status, count == null ? 0L : ((Number) count).longValue()));
+ }
+
+ private void addGeneric(List<MesDashboardGenericExcelVO> rows, String category, String name, Object value) {
+ rows.add(new MesDashboardGenericExcelVO(category, name, value == null ? "0" : value.toString()));
+ }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardDailyCountRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardDailyCountRespVO.java
new file mode 100644
index 0000000..d76f3a2
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardDailyCountRespVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * 绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸辨寜鏃ユ暟閲忚秼鍔块」 Response VO
+ * <p>
+ * 鐢ㄤ簬鍑哄簱鎸夋棩瓒嬪娍銆佽拷婧壂鐮佹寜鏃ヨ秼鍔跨瓑銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸辨寜鏃ユ暟閲忚秼鍔块」 Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesDashboardDailyCountRespVO {
+
+ @Schema(description = "鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-08-13")
+ private String date;
+
+ @Schema(description = "鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private BigDecimal count;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardGenericExcelVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardGenericExcelVO.java
new file mode 100644
index 0000000..b12f2da
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardGenericExcelVO.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸遍�氱敤瀵煎嚭 VO锛堢被鍒�-鍚嶇О-鏁伴噺 涓夊垪锛�
+ * <p>
+ * 鐢ㄤ簬璐ㄦ/搴撳瓨/鍑哄簱/杩芥函缁村害鐨勫垎甯冭〃瀵煎嚭銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ExcelIgnoreUnannotated
+public class MesDashboardGenericExcelVO {
+
+ @ExcelProperty("绫诲埆")
+ private String category;
+
+ @ExcelProperty("鍚嶇О")
+ private String name;
+
+ @ExcelProperty("鏁板��")
+ private String value;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardIndicatorAvgRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardIndicatorAvgRespVO.java
new file mode 100644
index 0000000..0e0125a
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardIndicatorAvgRespVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸辫川妫�鎸囨爣鍧囧�� Response VO
+ * <p>
+ * 绉嶅瓙鍥涢」鎸囨爣锛氱函搴︺�佸噣搴︺�佸彂鑺界巼銆佹按鍒�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸辫川妫�鎸囨爣鍧囧�� Response VO")
+@Data
+public class MesDashboardIndicatorAvgRespVO {
+
+ @Schema(description = "鎸囨爣缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "PURITY")
+ private String indicatorCode;
+
+ @Schema(description = "鎸囨爣鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "绾害")
+ private String indicatorName;
+
+ @Schema(description = "妫�楠屾鏁�", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Long checkCount;
+
+ @Schema(description = "鎸囨爣骞冲潎鍊�", requiredMode = Schema.RequiredMode.REQUIRED, example = "98.500000")
+ private BigDecimal averageValue;
+
+ @Schema(description = "鎸囨爣鍚堟牸鐜囷紙%锛夛紝钀藉湪鏍囧噯鍖洪棿鍐呯殑鍗犳瘮", requiredMode = Schema.RequiredMode.REQUIRED, example = "90.00")
+ private BigDecimal passRate;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardInventoryRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardInventoryRespVO.java
new file mode 100644
index 0000000..bdc6fca
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardInventoryRespVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸卞簱瀛樼淮搴︾粺璁� Response VO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸卞簱瀛樼淮搴︾粺璁� Response VO")
+@Data
+public class MesDashboardInventoryRespVO {
+
+ @Schema(description = "鎸変粨搴撳垎甯�", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List<MesDashboardNameCountRespVO> warehouseDistribution;
+
+ @Schema(description = "鎸夊搧绉嶅垎甯�", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List<MesDashboardNameCountRespVO> itemDistribution;
+
+ @Schema(description = "鎸夊簱瀛樼姸鎬佸垎甯冿紙鏆傚瓨搴�/鍚堟牸搴�/宸插嚭搴擄級", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List<MesDashboardNameCountRespVO> stateDistribution;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardNameCountRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardNameCountRespVO.java
new file mode 100644
index 0000000..afa77db
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardNameCountRespVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸遍�氱敤鍚嶇О-鏁伴噺鍒嗗竷椤� Response VO
+ * <p>
+ * 鐢ㄤ簬璐ㄦ缁撴灉鍒嗗竷銆佸簱瀛樻寜浠撳簱/鍝佺/鐘舵�佸垎甯冦�佸嚭搴撶粡閿�鍟嗗垎甯冦�佽拷婧渷浠�/娓犻亾鍒嗗竷绛夈��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸遍�氱敤鍚嶇О-鏁伴噺鍒嗗竷椤� Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesDashboardNameCountRespVO {
+
+ @Schema(description = "鍚嶇О锛堜粨搴撳悕/鍝佺鍚�/鐘舵�佸悕/缁忛攢鍟嗗悕/鐪佷唤绛夛級", requiredMode = Schema.RequiredMode.REQUIRED, example = "鍚堟牸搴�")
+ private String name;
+
+ @Schema(description = "鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private Long count;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardOutboundRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardOutboundRespVO.java
new file mode 100644
index 0000000..cfd2af3
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardOutboundRespVO.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸卞嚭搴撶淮搴︾粺璁� Response VO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸卞嚭搴撶淮搴︾粺璁� Response VO")
+@Data
+public class MesDashboardOutboundRespVO {
+
+ @Schema(description = "鍑哄簱鎸夋棩瓒嬪娍", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List<MesDashboardDailyCountRespVO> trend;
+
+ @Schema(description = "鎸夌粡閿�鍟嗗嚭搴撳垎甯冿紙TOP 20锛�", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List<MesDashboardNameCountRespVO> dealerDistribution;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardOverviewExcelVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardOverviewExcelVO.java
new file mode 100644
index 0000000..90e3fc4
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardOverviewExcelVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸辨�昏瀵煎嚭 VO锛堟寚鏍�-鏁板�� 涓ゅ垪锛�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ExcelIgnoreUnannotated
+public class MesDashboardOverviewExcelVO {
+
+ @ExcelProperty("鎸囨爣")
+ private String indicator;
+
+ @ExcelProperty("鏁板��")
+ private String value;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardProductionExcelVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardProductionExcelVO.java
new file mode 100644
index 0000000..a0c9485
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardProductionExcelVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸辩敓浜х淮搴﹀鍑� VO锛堢被鍒�-鐘舵��-鏁伴噺 涓夊垪锛�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ExcelIgnoreUnannotated
+public class MesDashboardProductionExcelVO {
+
+ @ExcelProperty("绫诲埆")
+ private String category;
+
+ @ExcelProperty("鐘舵��")
+ private String statusName;
+
+ @ExcelProperty("鏁伴噺")
+ private Long count;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardProductionTrendRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardProductionTrendRespVO.java
new file mode 100644
index 0000000..3dd9d3f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardProductionTrendRespVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * 绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸辩敓浜ц秼鍔� Response VO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸辩敓浜ц秼鍔� Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesDashboardProductionTrendRespVO {
+
+ @Schema(description = "鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-08-13")
+ private String date;
+
+ @Schema(description = "褰撴棩鏂板鎵规鏁�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long batchCount;
+
+ @Schema(description = "褰撴棩鏂板琚嬬爜鏁�", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private Long bagCodeCount;
+
+ @Schema(description = "褰撴棩鏂板鎵樼洏鐮佹暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Long palletCount;
+
+ @Schema(description = "褰撴棩鎵爜鍏ュ簱琚嬫暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private BigDecimal inboundBagQuantity;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardQualityRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardQualityRespVO.java
new file mode 100644
index 0000000..90af603
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardQualityRespVO.java
@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸辫川妫�缁村害缁熻 Response VO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸辫川妫�缁村害缁熻 Response VO")
+@Data
+public class MesDashboardQualityRespVO {
+
+ @Schema(description = "妫�楠岀粨鏋滃垎甯冿紙澶勭悊涓�/鍚堟牸/涓嶅悎鏍硷級", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List<MesDashboardNameCountRespVO> resultDistribution;
+
+ @Schema(description = "鍥涢」鎸囨爣鍧囧�间笌鍚堟牸鐜�", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List<MesDashboardIndicatorAvgRespVO> indicatorList;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardSummaryRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardSummaryRespVO.java
new file mode 100644
index 0000000..5ecb62f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardSummaryRespVO.java
@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸变簲缁村害姹囨�荤粺璁� Response VO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸辨眹鎬荤粺璁� Response VO")
+@Data
+public class MesDashboardSummaryRespVO {
+
+ // ========== 鐢熶骇缁村害 ==========
+
+ @Schema(description = "鎵规鎬绘暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "103")
+ private Long batchTotal;
+ @Schema(description = "鑽夌鎵规鏁�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long batchDraft;
+ @Schema(description = "鐢熶骇涓壒娆℃暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long batchProducing;
+ @Schema(description = "宸插畬鎴愭壒娆℃暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long batchFinished;
+ @Schema(description = "宸蹭綔搴熸壒娆℃暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long batchCanceled;
+ @Schema(description = "琚嬬爜鎬绘暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "1101")
+ private Long bagCodeTotal;
+ @Schema(description = "宸茬敓鎴愯鐮佹暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long bagCodeGenerated;
+ @Schema(description = "宸插鍑哄嵃鍒疯鐮佹暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long bagCodePrinted;
+ @Schema(description = "宸蹭綔搴熻鐮佹暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long bagCodeCanceled;
+ @Schema(description = "鎵樼洏鐮佹�绘暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
+ private Long palletTotal;
+ @Schema(description = "浠婃棩鎵爜鍏ュ簱琚嬫暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long todayInboundBagCount;
+
+ // ========== 璐ㄦ缁村害 ==========
+
+ @Schema(description = "妫�楠岃褰曟�绘暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long qualityTotal;
+ @Schema(description = "澶勭悊涓楠岃褰曟暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long qualityPending;
+ @Schema(description = "鍚堟牸妫�楠岃褰曟暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long qualityPass;
+ @Schema(description = "涓嶅悎鏍兼楠岃褰曟暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long qualityFail;
+ @Schema(description = "妫�楠屽悎鏍肩巼锛�%锛夛紝濡� 95.50", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+ private BigDecimal qualityPassRate;
+
+ // ========== 搴撳瓨缁村害 ==========
+
+ @Schema(description = "褰撳墠搴撳瓨鎬婚噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private BigDecimal stockTotal;
+ @Schema(description = "鏆傚瓨搴撳瓨閲�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private BigDecimal stockTemporary;
+ @Schema(description = "鍚堟牸搴撳瓨閲�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private BigDecimal stockQualified;
+ @Schema(description = "宸插嚭搴撴暟閲�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private BigDecimal stockOutbound;
+ @Schema(description = "褰撳墠浣庡簱瀛橀璀﹁褰曟暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long lowStockCount;
+
+ // ========== 鍑哄簱缁村害 ==========
+
+ @Schema(description = "鍑哄簱鍗曟�绘暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long outboundOrderTotal;
+ @Schema(description = "鍑哄簱鎬婚噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private BigDecimal outboundQuantityTotal;
+ @Schema(description = "浠婃棩鍑哄簱閲�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private BigDecimal todayOutboundQuantity;
+
+ // ========== 杩芥函缁村害 ==========
+
+ @Schema(description = "鎵爜鎬绘暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "7")
+ private Long scanTotal;
+ @Schema(description = "浠婃棩鎵爜鏁�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long todayScanCount;
+ @Schema(description = "鎵爜鎴愬姛鏁�", requiredMode = Schema.RequiredMode.REQUIRED, example = "6")
+ private Long scanSuccessCount;
+ @Schema(description = "绐滆揣棰勮鎬绘暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long crossRegionWarningTotal;
+ @Schema(description = "寰呭鐞嗙獪璐ч璀︽暟", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+ private Long crossRegionPendingCount;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardTraceRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardTraceRespVO.java
new file mode 100644
index 0000000..ec8b083
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dashboard/vo/MesDashboardTraceRespVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo;
+
+import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionSummaryRespVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸辫拷婧淮搴︾粺璁� Response VO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 绠$悊椹鹃┒鑸辫拷婧淮搴︾粺璁� Response VO")
+@Data
+public class MesDashboardTraceRespVO {
+
+ @Schema(description = "娑堣垂鑰呮壂鐮佹寜鏃ヨ秼鍔�", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List<MesDashboardDailyCountRespVO> scanTrend;
+
+ @Schema(description = "鎵爜鐪佷唤鍒嗗竷", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List<MesDashboardNameCountRespVO> provinceDistribution;
+
+ @Schema(description = "鎵爜娓犻亾鍒嗗竷锛堝井淇�/鏀粯瀹�/娴忚鍣級", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List<MesDashboardNameCountRespVO> channelDistribution;
+
+ @Schema(description = "绐滆揣棰勮姹囨��", requiredMode = Schema.RequiredMode.REQUIRED)
+ private MesTraceCrossRegionSummaryRespVO crossRegionSummary;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dashboard/MesDashboardMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dashboard/MesDashboardMapper.java
new file mode 100644
index 0000000..34e1a6f
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dashboard/MesDashboardMapper.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.dashboard;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 绠$悊椹鹃┒鑸辩粺璁� Mapper
+ * <p>
+ * 鎵嬪啓 SQL 鑱氬悎鐢熶骇銆佽川妫�銆佸簱瀛樸�佸嚭搴撱�佽拷婧簲澶х淮搴︽牳蹇冩寚鏍囥��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface MesDashboardMapper {
+
+ // ========== 鐢熶骇缁村害 ==========
+
+ /** 鎸夌姸鎬佺粺璁℃壒娆℃暟閲忥紝杩斿洖 [{ "status": 10, "count": 5 }] */
+ List<Map<String, Object>> selectBatchCountGroupByStatus();
+
+ /** 鎸夌姸鎬佺粺璁¤鐮佹暟閲忥紝杩斿洖 [{ "status": 10, "count": 5 }] */
+ List<Map<String, Object>> selectBagCodeCountGroupByStatus();
+
+ /** 缁熻鎵樼洏鐮佹�绘暟 */
+ Long selectPalletTotal();
+
+ /** 缁熻浠婃棩鎵爜鍏ュ簱琚嬫暟锛堟壂鐮佹垚鍔熶簨浠舵暟閲忎箣鍜岋級 */
+ Long selectTodayInboundBagCount(@Param("beginTime") LocalDateTime beginTime);
+
+ /** 鎸夊ぉ鑱氬悎鏂板鎵规鏁帮紝杩斿洖 [{ "date": "2026-08-13", "count": 1 }] */
+ List<Map<String, Object>> selectDailyBatchCount(@Param("beginTime") LocalDateTime beginTime,
+ @Param("endTime") LocalDateTime endTime);
+
+ /** 鎸夊ぉ鑱氬悎鏂板琚嬬爜鏁� */
+ List<Map<String, Object>> selectDailyBagCodeCount(@Param("beginTime") LocalDateTime beginTime,
+ @Param("endTime") LocalDateTime endTime);
+
+ /** 鎸夊ぉ鑱氬悎鏂板鎵樼洏鐮佹暟 */
+ List<Map<String, Object>> selectDailyPalletCount(@Param("beginTime") LocalDateTime beginTime,
+ @Param("endTime") LocalDateTime endTime);
+
+ /** 鎸夊ぉ鑱氬悎鎵爜鍏ュ簱琚嬫暟锛堟壂鐮佹垚鍔熶簨浠舵暟閲忎箣鍜岋級锛岃繑鍥� [{ "date": "...", "quantity": 100 }] */
+ List<Map<String, Object>> selectDailyInboundBagQuantity(@Param("beginTime") LocalDateTime beginTime,
+ @Param("endTime") LocalDateTime endTime);
+
+ // ========== 璐ㄦ缁村害 ==========
+
+ /** 鎸夋楠岀粨鏋滅粺璁℃暟閲忥紝杩斿洖 [{ "result": 1, "count": 5 }] */
+ List<Map<String, Object>> selectQualityCountGroupByResult();
+
+ /** 鎸囨爣鍧囧�间笌鍚堟牸鐜囷紝杩斿洖 [{ "indicatorCode", "checkCount", "averageValue", "passRate" }] */
+ List<Map<String, Object>> selectIndicatorStatistics();
+
+ // ========== 搴撳瓨缁村害 ==========
+
+ /** 鎸夊簱瀛樼姸鎬佹眹鎬绘暟閲忥紝杩斿洖 [{ "stockState": 10, "quantity": 100 }] */
+ List<Map<String, Object>> selectStockGroupByState();
+
+ /** 鎸変粨搴撴眹鎬绘暟閲忥紝杩斿洖 [{ "name": "浠撳簱A", "quantity": 100 }] */
+ List<Map<String, Object>> selectStockGroupByWarehouse();
+
+ /** 鎸夊搧绉嶆眹鎬绘暟閲忥紝杩斿洖 [{ "name": "妫夎姳", "quantity": 100 }] */
+ List<Map<String, Object>> selectStockGroupByItem();
+
+ // ========== 鍑哄簱缁村害 ==========
+
+ /** 鍑哄簱姹囨�伙紙鍗曟暟/鎬婚噺/浠婃棩鍑哄簱閲忥級锛岃繑鍥炲崟琛� Map锛歿 orderTotal, quantityTotal, todayQuantity } */
+ Map<String, Object> selectOutboundSummary(@Param("beginTime") LocalDateTime beginTime);
+
+ /** 鎸夊ぉ鑱氬悎鍑哄簱閲忥紝杩斿洖 [{ "date": "...", "quantity": 100 }] */
+ List<Map<String, Object>> selectOutboundDailyQuantity(@Param("beginTime") LocalDateTime beginTime,
+ @Param("endTime") LocalDateTime endTime);
+
+ /** 鎸夌粡閿�鍟嗘眹鎬诲嚭搴撻噺锛圱OP 20锛夛紝杩斿洖 [{ "name": "缁忛攢鍟咥", "quantity": 100 }] */
+ List<Map<String, Object>> selectOutboundGroupByClient();
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dashboard/MesDashboardService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dashboard/MesDashboardService.java
new file mode 100644
index 0000000..899fed7
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dashboard/MesDashboardService.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.mes.service.dashboard;
+
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardInventoryRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardOutboundRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardProductionTrendRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardQualityRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardSummaryRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardTraceRespVO;
+
+import java.util.List;
+
+/**
+ * 绠$悊椹鹃┒鑸辩粺璁� Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesDashboardService {
+
+ /**
+ * 鑾峰緱绠$悊椹鹃┒鑸变簲缁村害姹囨�荤粺璁�
+ *
+ * @return 姹囨�荤粺璁�
+ */
+ MesDashboardSummaryRespVO getSummary();
+
+ /**
+ * 鑾峰緱鐢熶骇瓒嬪娍锛堣繎 N 澶╋紝鎸夋棩锛�
+ *
+ * @param days 澶╂暟
+ * @return 鐢熶骇瓒嬪娍
+ */
+ List<MesDashboardProductionTrendRespVO> getProductionTrend(Integer days);
+
+ /**
+ * 鑾峰緱璐ㄦ缁村害缁熻
+ *
+ * @return 璐ㄦ缁熻
+ */
+ MesDashboardQualityRespVO getQualityStatistics();
+
+ /**
+ * 鑾峰緱搴撳瓨缁村害缁熻
+ *
+ * @return 搴撳瓨缁熻
+ */
+ MesDashboardInventoryRespVO getInventoryStatistics();
+
+ /**
+ * 鑾峰緱鍑哄簱缁村害缁熻锛堣繎 N 澶╋級
+ *
+ * @param days 澶╂暟
+ * @return 鍑哄簱缁熻
+ */
+ MesDashboardOutboundRespVO getOutboundStatistics(Integer days);
+
+ /**
+ * 鑾峰緱杩芥函缁村害缁熻锛堣繎 N 澶╋級
+ *
+ * @param days 澶╂暟
+ * @return 杩芥函缁熻
+ */
+ MesDashboardTraceRespVO getTraceStatistics(Integer days);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dashboard/MesDashboardServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dashboard/MesDashboardServiceImpl.java
new file mode 100644
index 0000000..11c41f0
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dashboard/MesDashboardServiceImpl.java
@@ -0,0 +1,404 @@
+package cn.iocoder.yudao.module.mes.service.dashboard;
+
+import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardDailyCountRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardIndicatorAvgRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardInventoryRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardNameCountRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardOutboundRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardProductionTrendRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardQualityRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardSummaryRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dashboard.vo.MesDashboardTraceRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanDistributionRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanSummaryRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.trace.consumer.vo.MesTraceConsumerScanTrendRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.trace.crossregion.vo.MesTraceCrossRegionSummaryRespVO;
+import cn.iocoder.yudao.module.mes.dal.mysql.dashboard.MesDashboardMapper;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum;
+import cn.iocoder.yudao.module.mes.service.trace.consumer.MesTraceConsumerScanStatisticsService;
+import cn.iocoder.yudao.module.mes.service.trace.crossregion.MesTraceCrossRegionService;
+import cn.iocoder.yudao.module.mes.service.wm.warning.MesWmStockWarningService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 绠$悊椹鹃┒鑸辩粺璁� Service 瀹炵幇绫�
+ * <p>
+ * 鏁村悎鐢熶骇銆佽川妫�銆佸簱瀛樸�佸嚭搴撱�佽拷婧簲澶х淮搴︽牳蹇冩寚鏍囥��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+public class MesDashboardServiceImpl implements MesDashboardService {
+
+ /** 鎸囨爣缂栫爜 -> 鎸囨爣鍚嶇О */
+ private static final Map<String, String> INDICATOR_NAME_MAP = Map.of(
+ "purity", "绾害",
+ "cleanliness", "鍑�搴�",
+ "germination_rate", "鍙戣娊鐜�",
+ "moisture", "姘村垎");
+
+ @Resource
+ private MesDashboardMapper dashboardMapper;
+ @Resource
+ private MesTraceConsumerScanStatisticsService traceScanStatisticsService;
+ @Resource
+ private MesTraceCrossRegionService traceCrossRegionService;
+ @Resource
+ private MesWmStockWarningService stockWarningService;
+
+ @Override
+ public MesDashboardSummaryRespVO getSummary() {
+ MesDashboardSummaryRespVO summary = new MesDashboardSummaryRespVO();
+
+ // 1. 鐢熶骇缁村害
+ List<Map<String, Object>> batchStats = dashboardMapper.selectBatchCountGroupByStatus();
+ summary.setBatchTotal(getTotalCount(batchStats));
+ summary.setBatchDraft(getCountByStatus(batchStats, MesProBatchStatusEnum.DRAFT.getStatus()));
+ summary.setBatchProducing(getCountByStatus(batchStats, MesProBatchStatusEnum.PRODUCING.getStatus()));
+ summary.setBatchFinished(getCountByStatus(batchStats, MesProBatchStatusEnum.FINISHED.getStatus()));
+ summary.setBatchCanceled(getCountByStatus(batchStats, MesProBatchStatusEnum.CANCELED.getStatus()));
+
+ List<Map<String, Object>> bagCodeStats = dashboardMapper.selectBagCodeCountGroupByStatus();
+ summary.setBagCodeTotal(getTotalCount(bagCodeStats));
+ summary.setBagCodeGenerated(getCountByStatus(bagCodeStats, MesProBagCodeStatusEnum.GENERATED.getStatus()));
+ summary.setBagCodePrinted(getCountByStatus(bagCodeStats, MesProBagCodeStatusEnum.PRINTED.getStatus()));
+ summary.setBagCodeCanceled(getCountByStatus(bagCodeStats, MesProBagCodeStatusEnum.CANCELED.getStatus()));
+
+ summary.setPalletTotal(getLong(dashboardMapper.selectPalletTotal()));
+ summary.setTodayInboundBagCount(getLong(dashboardMapper.selectTodayInboundBagCount(LocalDateTimeUtils.getToday())));
+
+ // 2. 璐ㄦ缁村害
+ List<Map<String, Object>> qualityStats = dashboardMapper.selectQualityCountGroupByResult();
+ long qualityPending = getCountByResult(qualityStats, 0);
+ long qualityPass = getCountByResult(qualityStats, 1);
+ long qualityFail = getCountByResult(qualityStats, 2);
+ summary.setQualityTotal(qualityPending + qualityPass + qualityFail);
+ summary.setQualityPending(qualityPending);
+ summary.setQualityPass(qualityPass);
+ summary.setQualityFail(qualityFail);
+ summary.setQualityPassRate(calcPercent(qualityPass, qualityPass + qualityFail));
+
+ // 3. 搴撳瓨缁村害
+ List<Map<String, Object>> stockStateStats = dashboardMapper.selectStockGroupByState();
+ BigDecimal stockTemporary = getStockQuantityByState(stockStateStats, MesWmStockStateEnum.TEMP.getState());
+ BigDecimal stockQualified = getStockQuantityByState(stockStateStats, MesWmStockStateEnum.QUALIFIED.getState());
+ BigDecimal stockOutbound = getStockQuantityByState(stockStateStats, MesWmStockStateEnum.OUT.getState());
+ summary.setStockTemporary(stockTemporary);
+ summary.setStockQualified(stockQualified);
+ summary.setStockOutbound(stockOutbound);
+ summary.setStockTotal(stockTemporary.add(stockQualified).add(stockOutbound));
+ summary.setLowStockCount((long) stockWarningService.getCurrentWarnings().size());
+
+ // 4. 鍑哄簱缁村害
+ Map<String, Object> outboundSummary = dashboardMapper.selectOutboundSummary(LocalDateTimeUtils.getToday());
+ summary.setOutboundOrderTotal(getLong(outboundSummary == null ? null : outboundSummary.get("orderTotal")));
+ summary.setOutboundQuantityTotal(getDecimal(outboundSummary == null ? null : outboundSummary.get("quantityTotal")));
+ summary.setTodayOutboundQuantity(getDecimal(outboundSummary == null ? null : outboundSummary.get("todayQuantity")));
+
+ // 5. 杩芥函缁村害
+ MesTraceConsumerScanSummaryRespVO scanAll = traceScanStatisticsService.getSummary(null, null);
+ MesTraceConsumerScanSummaryRespVO scanToday = traceScanStatisticsService.getSummary(LocalDateTimeUtils.getToday(), null);
+ MesTraceCrossRegionSummaryRespVO crossRegion = traceCrossRegionService.getWarningSummary(null, null);
+ summary.setScanTotal(scanAll == null ? 0L : getLong(scanAll.getTotal()));
+ summary.setScanSuccessCount(scanAll == null ? 0L : getLong(scanAll.getSuccess()));
+ summary.setTodayScanCount(scanToday == null ? 0L : getLong(scanToday.getTotal()));
+ summary.setCrossRegionWarningTotal(crossRegion == null ? 0L : getLong(crossRegion.getTotal()));
+ summary.setCrossRegionPendingCount(crossRegion == null ? 0L : getLong(crossRegion.getPending()));
+
+ return summary;
+ }
+
+ @Override
+ public List<MesDashboardProductionTrendRespVO> getProductionTrend(Integer days) {
+ LocalDate endDate = LocalDate.now().plusDays(1);
+ LocalDate startDate = endDate.minusDays(days);
+ LocalDateTime beginTime = startDate.atStartOfDay();
+ LocalDateTime endTime = endDate.atStartOfDay();
+
+ Map<String, Map<String, Object>> batchMap = toDateMap(dashboardMapper.selectDailyBatchCount(beginTime, endTime));
+ Map<String, Map<String, Object>> bagCodeMap = toDateMap(dashboardMapper.selectDailyBagCodeCount(beginTime, endTime));
+ Map<String, Map<String, Object>> palletMap = toDateMap(dashboardMapper.selectDailyPalletCount(beginTime, endTime));
+ Map<String, Map<String, Object>> inboundMap = toDateMap(dashboardMapper.selectDailyInboundBagQuantity(beginTime, endTime));
+
+ DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ List<MesDashboardProductionTrendRespVO> result = new ArrayList<>();
+ for (LocalDate d = startDate; d.isBefore(endDate); d = d.plusDays(1)) {
+ String dateStr = d.format(fmt);
+ result.add(new MesDashboardProductionTrendRespVO(
+ dateStr,
+ getLongFromMap(batchMap.get(dateStr), "count"),
+ getLongFromMap(bagCodeMap.get(dateStr), "count"),
+ getLongFromMap(palletMap.get(dateStr), "count"),
+ getDecimalFromMap(inboundMap.get(dateStr), "quantity")));
+ }
+ return result;
+ }
+
+ @Override
+ public MesDashboardQualityRespVO getQualityStatistics() {
+ MesDashboardQualityRespVO vo = new MesDashboardQualityRespVO();
+
+ // 1. 妫�楠岀粨鏋滃垎甯�
+ List<MesDashboardNameCountRespVO> resultDistribution = new ArrayList<>();
+ for (Map<String, Object> stat : dashboardMapper.selectQualityCountGroupByResult()) {
+ int result = ((Number) stat.get("result")).intValue();
+ resultDistribution.add(new MesDashboardNameCountRespVO(qualityResultName(result), getLong(stat.get("count"))));
+ }
+ vo.setResultDistribution(resultDistribution);
+
+ // 2. 鍥涢」鎸囨爣鍧囧�间笌鍚堟牸鐜�
+ List<MesDashboardIndicatorAvgRespVO> indicatorList = new ArrayList<>();
+ for (Map<String, Object> stat : dashboardMapper.selectIndicatorStatistics()) {
+ MesDashboardIndicatorAvgRespVO item = new MesDashboardIndicatorAvgRespVO();
+ item.setIndicatorCode((String) stat.get("indicatorCode"));
+ item.setIndicatorName(indicatorName(item.getIndicatorCode()));
+ item.setCheckCount(getLong(stat.get("checkCount")));
+ item.setAverageValue(getDecimal(stat.get("averageValue")));
+ item.setPassRate(getDecimal(stat.get("passRate")));
+ indicatorList.add(item);
+ }
+ vo.setIndicatorList(indicatorList);
+ return vo;
+ }
+
+ @Override
+ public MesDashboardInventoryRespVO getInventoryStatistics() {
+ MesDashboardInventoryRespVO vo = new MesDashboardInventoryRespVO();
+ vo.setWarehouseDistribution(toNameCountList(dashboardMapper.selectStockGroupByWarehouse()));
+ vo.setItemDistribution(toNameCountList(dashboardMapper.selectStockGroupByItem()));
+ // 搴撳瓨鐘舵�佸垎甯�
+ List<MesDashboardNameCountRespVO> stateDistribution = new ArrayList<>();
+ for (Map<String, Object> stat : dashboardMapper.selectStockGroupByState()) {
+ int stockState = ((Number) stat.get("stockState")).intValue();
+ stateDistribution.add(new MesDashboardNameCountRespVO(stockStateName(stockState),
+ getDecimal(stat.get("quantity")).longValue()));
+ }
+ vo.setStateDistribution(stateDistribution);
+ return vo;
+ }
+
+ @Override
+ public MesDashboardOutboundRespVO getOutboundStatistics(Integer days) {
+ LocalDate endDate = LocalDate.now().plusDays(1);
+ LocalDate startDate = endDate.minusDays(days);
+ LocalDateTime beginTime = startDate.atStartOfDay();
+ LocalDateTime endTime = endDate.atStartOfDay();
+
+ MesDashboardOutboundRespVO vo = new MesDashboardOutboundRespVO();
+ // 鍑哄簱鎸夋棩瓒嬪娍锛堟棩琛ラ浂锛�
+ Map<String, Map<String, Object>> trendMap = toDateMap(dashboardMapper.selectOutboundDailyQuantity(beginTime, endTime));
+ DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ List<MesDashboardDailyCountRespVO> trend = new ArrayList<>();
+ for (LocalDate d = startDate; d.isBefore(endDate); d = d.plusDays(1)) {
+ String dateStr = d.format(fmt);
+ trend.add(new MesDashboardDailyCountRespVO(dateStr, getDecimalFromMap(trendMap.get(dateStr), "quantity")));
+ }
+ vo.setTrend(trend);
+ // 缁忛攢鍟嗗嚭搴撳垎甯�
+ vo.setDealerDistribution(toNameCountList(dashboardMapper.selectOutboundGroupByClient()));
+ return vo;
+ }
+
+ @Override
+ public MesDashboardTraceRespVO getTraceStatistics(Integer days) {
+ LocalDate endDate = LocalDate.now().plusDays(1);
+ LocalDate startDate = endDate.minusDays(days);
+ LocalDateTime beginTime = startDate.atStartOfDay();
+ LocalDateTime endTime = endDate.atStartOfDay();
+
+ MesDashboardTraceRespVO vo = new MesDashboardTraceRespVO();
+
+ // 1. 鎵爜鎸夋棩瓒嬪娍
+ List<MesTraceConsumerScanTrendRespVO> trend = traceScanStatisticsService.getTrend(beginTime, endTime);
+ Map<String, Long> trendMap = new LinkedHashMap<>();
+ if (trend != null) {
+ for (MesTraceConsumerScanTrendRespVO item : trend) {
+ trendMap.put(item.getDate(), getLong(item.getTotal()));
+ }
+ }
+ DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ List<MesDashboardDailyCountRespVO> scanTrend = new ArrayList<>();
+ for (LocalDate d = startDate; d.isBefore(endDate); d = d.plusDays(1)) {
+ String dateStr = d.format(fmt);
+ scanTrend.add(new MesDashboardDailyCountRespVO(dateStr, new BigDecimal(trendMap.getOrDefault(dateStr, 0L))));
+ }
+ vo.setScanTrend(scanTrend);
+
+ // 2. 鐪佷唤鍒嗗竷
+ vo.setProvinceDistribution(toDistribution(traceScanStatisticsService.getDistribution(
+ buildDistributionReqVO("province", beginTime, endTime))));
+ // 3. 娓犻亾鍒嗗竷
+ vo.setChannelDistribution(toDistribution(traceScanStatisticsService.getDistribution(
+ buildDistributionReqVO("channel", beginTime, endTime))));
+ // 4. 绐滆揣棰勮姹囨��
+ vo.setCrossRegionSummary(traceCrossRegionService.getWarningSummary(null, null));
+ return vo;
+ }
+
+ // ========== 宸ュ叿鏂规硶 ==========
+
+ private MesTraceConsumerScanDistributionReqVO buildDistributionReqVO(String groupBy, LocalDateTime beginTime, LocalDateTime endTime) {
+ MesTraceConsumerScanDistributionReqVO reqVO = new MesTraceConsumerScanDistributionReqVO();
+ reqVO.setGroupBy(groupBy);
+ reqVO.setStartTime(beginTime);
+ reqVO.setEndTime(endTime);
+ return reqVO;
+ }
+
+ private List<MesDashboardNameCountRespVO> toDistribution(List<MesTraceConsumerScanDistributionRespVO> distribution) {
+ List<MesDashboardNameCountRespVO> result = new ArrayList<>();
+ if (distribution == null) {
+ return result;
+ }
+ for (MesTraceConsumerScanDistributionRespVO item : distribution) {
+ result.add(new MesDashboardNameCountRespVO(item.getName(), getLong(item.getCount())));
+ }
+ return result;
+ }
+
+ private List<MesDashboardNameCountRespVO> toNameCountList(List<Map<String, Object>> stats) {
+ List<MesDashboardNameCountRespVO> result = new ArrayList<>();
+ if (stats == null) {
+ return result;
+ }
+ for (Map<String, Object> stat : stats) {
+ result.add(new MesDashboardNameCountRespVO((String) stat.get("name"), getDecimal(stat.get("quantity")).longValue()));
+ }
+ return result;
+ }
+
+ private Map<String, Map<String, Object>> toDateMap(List<Map<String, Object>> stats) {
+ Map<String, Map<String, Object>> map = new LinkedHashMap<>();
+ if (stats == null) {
+ return map;
+ }
+ for (Map<String, Object> stat : stats) {
+ map.put((String) stat.get("date"), stat);
+ }
+ return map;
+ }
+
+ private Long getCountByStatus(List<Map<String, Object>> stats, Integer status) {
+ if (stats == null) {
+ return 0L;
+ }
+ for (Map<String, Object> stat : stats) {
+ if (((Number) stat.get("status")).intValue() == status) {
+ return getLong(stat.get("count"));
+ }
+ }
+ return 0L;
+ }
+
+ private Long getCountByResult(List<Map<String, Object>> stats, Integer result) {
+ if (stats == null) {
+ return 0L;
+ }
+ for (Map<String, Object> stat : stats) {
+ if (((Number) stat.get("result")).intValue() == result) {
+ return getLong(stat.get("count"));
+ }
+ }
+ return 0L;
+ }
+
+ private BigDecimal getStockQuantityByState(List<Map<String, Object>> stats, Integer stockState) {
+ if (stats == null) {
+ return BigDecimal.ZERO;
+ }
+ for (Map<String, Object> stat : stats) {
+ if (((Number) stat.get("stockState")).intValue() == stockState) {
+ return getDecimal(stat.get("quantity"));
+ }
+ }
+ return BigDecimal.ZERO;
+ }
+
+ private Long getTotalCount(List<Map<String, Object>> stats) {
+ long total = 0L;
+ if (stats != null) {
+ for (Map<String, Object> stat : stats) {
+ total += getLong(stat.get("count"));
+ }
+ }
+ return total;
+ }
+
+ private Long getLongFromMap(Map<String, Object> map, String key) {
+ return map == null ? 0L : getLong(map.get(key));
+ }
+
+ private BigDecimal getDecimalFromMap(Map<String, Object> map, String key) {
+ return map == null ? BigDecimal.ZERO : getDecimal(map.get(key));
+ }
+
+ private Long getLong(Object obj) {
+ if (obj == null) {
+ return 0L;
+ }
+ if (obj instanceof Number) {
+ return ((Number) obj).longValue();
+ }
+ return Long.valueOf(obj.toString());
+ }
+
+ private BigDecimal getDecimal(Object obj) {
+ if (obj == null) {
+ return BigDecimal.ZERO;
+ }
+ if (obj instanceof BigDecimal) {
+ return (BigDecimal) obj;
+ }
+ if (obj instanceof Number) {
+ return BigDecimal.valueOf(((Number) obj).doubleValue());
+ }
+ return new BigDecimal(obj.toString());
+ }
+
+ private BigDecimal calcPercent(long pass, long total) {
+ if (total == 0) {
+ return BigDecimal.ZERO;
+ }
+ return BigDecimal.valueOf(pass)
+ .multiply(BigDecimal.valueOf(100))
+ .divide(BigDecimal.valueOf(total), 2, RoundingMode.HALF_UP);
+ }
+
+ private String indicatorName(String code) {
+ return code == null ? "鏈煡鎸囨爣" : INDICATOR_NAME_MAP.getOrDefault(code, code);
+ }
+
+ private String qualityResultName(int result) {
+ return switch (result) {
+ case 0 -> "澶勭悊涓�";
+ case 1 -> "鍚堟牸";
+ case 2 -> "涓嶅悎鏍�";
+ default -> "鏈煡";
+ };
+ }
+
+ private String stockStateName(int stockState) {
+ return switch (stockState) {
+ case 10 -> "鏆傚瓨搴�";
+ case 20 -> "鍚堟牸搴�";
+ case 30 -> "宸插嚭搴�";
+ default -> "鏈煡鐘舵��";
+ };
+ }
+
+}
diff --git a/yudao-module-mes/src/main/resources/mapper/dashboard/MesDashboardMapper.xml b/yudao-module-mes/src/main/resources/mapper/dashboard/MesDashboardMapper.xml
new file mode 100644
index 0000000..968cd45
--- /dev/null
+++ b/yudao-module-mes/src/main/resources/mapper/dashboard/MesDashboardMapper.xml
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.mes.dal.mysql.dashboard.MesDashboardMapper">
+
+ <!-- ========== 鐢熶骇缁村害 ========== -->
+
+ <!-- 鎸夌姸鎬佺粺璁℃壒娆℃暟閲� -->
+ <select id="selectBatchCountGroupByStatus" resultType="java.util.Map">
+ SELECT status, COUNT(*) AS count
+ FROM mes_pro_batch
+ WHERE deleted = 0
+ GROUP BY status
+ </select>
+
+ <!-- 鎸夌姸鎬佺粺璁¤鐮佹暟閲� -->
+ <select id="selectBagCodeCountGroupByStatus" resultType="java.util.Map">
+ SELECT status, COUNT(*) AS count
+ FROM mes_pro_bag_code
+ WHERE deleted = 0
+ GROUP BY status
+ </select>
+
+ <!-- 缁熻鎵樼洏鐮佹�绘暟 -->
+ <select id="selectPalletTotal" resultType="java.lang.Long">
+ SELECT COUNT(*)
+ FROM mes_pro_pallet
+ WHERE deleted = 0
+ </select>
+
+ <!-- 缁熻浠婃棩鎵爜鍏ュ簱琚嬫暟锛堟壂鐮佹垚鍔熶簨浠舵暟閲忎箣鍜岋級 -->
+ <select id="selectTodayInboundBagCount" resultType="java.lang.Long">
+ SELECT IFNULL(ROUND(SUM(quantity)), 0)
+ FROM mes_pro_scan_event
+ WHERE status = 'SUCCESS'
+ AND event_time >= #{beginTime}
+ </select>
+
+ <!-- 鎸夊ぉ鑱氬悎鏂板鎵规鏁� -->
+ <select id="selectDailyBatchCount" resultType="java.util.Map">
+ SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS date, COUNT(*) AS count
+ FROM mes_pro_batch
+ WHERE deleted = 0
+ AND create_time >= #{beginTime}
+ <if test="endTime != null">
+ AND create_time < #{endTime}
+ </if>
+ GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d')
+ ORDER BY date
+ </select>
+
+ <!-- 鎸夊ぉ鑱氬悎鏂板琚嬬爜鏁� -->
+ <select id="selectDailyBagCodeCount" resultType="java.util.Map">
+ SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS date, COUNT(*) AS count
+ FROM mes_pro_bag_code
+ WHERE deleted = 0
+ AND create_time >= #{beginTime}
+ <if test="endTime != null">
+ AND create_time < #{endTime}
+ </if>
+ GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d')
+ ORDER BY date
+ </select>
+
+ <!-- 鎸夊ぉ鑱氬悎鏂板鎵樼洏鐮佹暟 -->
+ <select id="selectDailyPalletCount" resultType="java.util.Map">
+ SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS date, COUNT(*) AS count
+ FROM mes_pro_pallet
+ WHERE deleted = 0
+ AND create_time >= #{beginTime}
+ <if test="endTime != null">
+ AND create_time < #{endTime}
+ </if>
+ GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d')
+ ORDER BY date
+ </select>
+
+ <!-- 鎸夊ぉ鑱氬悎鎵爜鍏ュ簱琚嬫暟锛堟壂鐮佹垚鍔熶簨浠舵暟閲忎箣鍜岋級 -->
+ <select id="selectDailyInboundBagQuantity" resultType="java.util.Map">
+ SELECT DATE_FORMAT(event_time, '%Y-%m-%d') AS date,
+ IFNULL(SUM(quantity), 0) AS quantity
+ FROM mes_pro_scan_event
+ WHERE status = 'SUCCESS'
+ AND event_time >= #{beginTime}
+ <if test="endTime != null">
+ AND event_time < #{endTime}
+ </if>
+ GROUP BY DATE_FORMAT(event_time, '%Y-%m-%d')
+ ORDER BY date
+ </select>
+
+ <!-- ========== 璐ㄦ缁村害 ========== -->
+
+ <!-- 鎸夋楠岀粨鏋滅粺璁℃暟閲忥紙0澶勭悊涓� 1鍚堟牸 2涓嶅悎鏍硷級 -->
+ <select id="selectQualityCountGroupByResult" resultType="java.util.Map">
+ SELECT result, COUNT(*) AS count
+ FROM mes_qc_seed_quality
+ WHERE deleted = 0
+ GROUP BY result
+ </select>
+
+ <!-- 鎸囨爣鍧囧�间笌鍚堟牸鐜囷紙鍊艰惤鍦ㄦ爣鍑嗗尯闂� [min_value, max_value] 鍐呰涓哄悎鏍硷級 -->
+ <select id="selectIndicatorStatistics" resultType="java.util.Map">
+ SELECT indicator_code,
+ COUNT(*) AS checkCount,
+ ROUND(AVG(value), 6) AS averageValue,
+ IFNULL(ROUND(SUM(CASE WHEN value >= min_value AND value <= max_value THEN 1 ELSE 0 END)
+ / COUNT(*) * 100, 2), 0) AS passRate
+ FROM mes_qc_seed_quality_indicator
+ WHERE deleted = 0
+ GROUP BY indicator_code
+ ORDER BY indicator_code
+ </select>
+
+ <!-- ========== 搴撳瓨缁村害 ========== -->
+
+ <!-- 鎸夊簱瀛樼姸鎬佹眹鎬绘暟閲忥紙10鏆傚瓨搴� 20鍚堟牸搴� 30宸插嚭搴擄級 -->
+ <select id="selectStockGroupByState" resultType="java.util.Map">
+ SELECT IFNULL(stock_state, -1) AS stockState,
+ IFNULL(SUM(quantity), 0) AS quantity
+ FROM mes_wm_material_stock
+ WHERE deleted = 0
+ GROUP BY stock_state
+ </select>
+
+ <!-- 鎸変粨搴撴眹鎬绘暟閲� -->
+ <select id="selectStockGroupByWarehouse" resultType="java.util.Map">
+ SELECT IFNULL(w.name, '鏈煡浠撳簱') AS name,
+ IFNULL(SUM(s.quantity), 0) AS quantity
+ FROM mes_wm_material_stock s
+ LEFT JOIN mes_wm_warehouse w ON w.id = s.warehouse_id AND w.deleted = 0
+ WHERE s.deleted = 0
+ GROUP BY s.warehouse_id
+ ORDER BY quantity DESC
+ </select>
+
+ <!-- 鎸夊搧绉嶆眹鎬绘暟閲� -->
+ <select id="selectStockGroupByItem" resultType="java.util.Map">
+ SELECT IFNULL(i.name, '鏈煡鍝佺') AS name,
+ IFNULL(SUM(s.quantity), 0) AS quantity
+ FROM mes_wm_material_stock s
+ LEFT JOIN mes_md_item i ON i.id = s.item_id AND i.deleted = 0
+ WHERE s.deleted = 0
+ GROUP BY s.item_id
+ ORDER BY quantity DESC
+ </select>
+
+ <!-- ========== 鍑哄簱缁村害 ========== -->
+
+ <!-- 鍑哄簱姹囨�伙紙鍗曟暟/鎬婚噺/浠婃棩鍑哄簱閲忥級 -->
+ <select id="selectOutboundSummary" resultType="java.util.Map">
+ SELECT COUNT(DISTINCT s.id) AS orderTotal,
+ IFNULL(SUM(l.quantity), 0) AS quantityTotal,
+ IFNULL(SUM(CASE WHEN s.sales_date >= #{beginTime} THEN l.quantity ELSE 0 END), 0) AS todayQuantity
+ FROM mes_wm_product_sales s
+ LEFT JOIN mes_wm_product_sales_line l ON l.sales_id = s.id AND l.deleted = 0
+ WHERE s.deleted = 0
+ </select>
+
+ <!-- 鎸夊ぉ鑱氬悎鍑哄簱閲� -->
+ <select id="selectOutboundDailyQuantity" resultType="java.util.Map">
+ SELECT DATE_FORMAT(s.sales_date, '%Y-%m-%d') AS date,
+ IFNULL(SUM(l.quantity), 0) AS quantity
+ FROM mes_wm_product_sales s
+ LEFT JOIN mes_wm_product_sales_line l ON l.sales_id = s.id AND l.deleted = 0
+ WHERE s.deleted = 0
+ AND s.sales_date >= #{beginTime}
+ <if test="endTime != null">
+ AND s.sales_date < #{endTime}
+ </if>
+ GROUP BY DATE_FORMAT(s.sales_date, '%Y-%m-%d')
+ ORDER BY date
+ </select>
+
+ <!-- 鎸夌粡閿�鍟嗘眹鎬诲嚭搴撻噺锛圱OP 20锛� -->
+ <select id="selectOutboundGroupByClient" resultType="java.util.Map">
+ SELECT COALESCE(NULLIF(c.name, ''), CONCAT('缁忛攢鍟�#', s.client_id), CONCAT('瀹㈡埛ID:', s.client_id)) AS name,
+ IFNULL(SUM(l.quantity), 0) AS quantity
+ FROM mes_wm_product_sales s
+ LEFT JOIN mes_wm_product_sales_line l ON l.sales_id = s.id AND l.deleted = 0
+ LEFT JOIN crm_customer c ON c.id = s.client_id AND c.deleted = 0
+ WHERE s.deleted = 0
+ GROUP BY s.client_id, c.name
+ ORDER BY quantity DESC
+ LIMIT 20
+ </select>
+
+</mapper>
--
Gitblit v1.9.3