From 3411691fba52492dd417e4d7707c84f6250fff4f Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 12 八月 2026 16:18:50 +0800
Subject: [PATCH] feat(crm): 添加销售目标管理功能

---
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java                                              |    3 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/salesTarget/CrmSalesTargetDimensionEnum.java                        |   45 +
 .playwright-mcp/page-2026-08-12T08-14-30-909Z.yml                                                                                    |    1 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/CrmSalesTargetController.java                |  104 ++
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetRespVO.java                 |  130 +++
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java                                             |    4 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/salesTarget/CrmSalesTargetMapper.java                           |   38 +
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetPageReqVO.java              |   30 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/salesTarget/CrmSalesTargetAchievementMapper.java                |   62 +
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/salesTarget/CrmSalesTargetService.java                            |   81 ++
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/salesTarget/CrmSalesTargetDO.java                          |  107 ++
 yudao-module-crm/src/main/resources/mapper/salesTarget/CrmSalesTargetAchievementMapper.xml                                           |   84 ++
 .playwright-mcp/page-2026-08-12T08-14-46-176Z.yml                                                                                    |  194 +++++
 .playwright-mcp/page-2026-08-12T08-16-45-289Z.yml                                                                                    |  215 +++++
 docs/crm_sales_target_integration.md                                                                                                 |  141 +++
 docs/sql/crm_sales_target_init.sql                                                                                                   |   75 ++
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetChangeStatusReqVO.java      |   22 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/analysis/CrmSalesTargetAnalysisReqVO.java |   31 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/salesTarget/CrmSalesTargetServiceImpl.java                        |  368 ++++++++++
 .playwright-mcp/page-2026-08-12T08-15-00-361Z.yml                                                                                    |  215 +++++
 sales-target-analysis.png                                                                                                            |    0 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/salesTarget/CrmSalesTargetPeriodTypeEnum.java                       |   45 +
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetSaveReqVO.java              |   65 +
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/salesTarget/CrmSalesTargetStatusEnum.java                           |   46 +
 24 files changed, 2,106 insertions(+), 0 deletions(-)

diff --git a/.playwright-mcp/page-2026-08-12T08-14-30-909Z.yml b/.playwright-mcp/page-2026-08-12T08-14-30-909Z.yml
new file mode 100644
index 0000000..6adf37e
--- /dev/null
+++ b/.playwright-mcp/page-2026-08-12T08-14-30-909Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ヤ笟AI鏅洪�犲钩鍙�
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-08-12T08-14-46-176Z.yml b/.playwright-mcp/page-2026-08-12T08-14-46-176Z.yml
new file mode 100644
index 0000000..c0aa8b9
--- /dev/null
+++ b/.playwright-mcp/page-2026-08-12T08-14-46-176Z.yml
@@ -0,0 +1,194 @@
+- generic [ref=e7]:
+  - complementary [ref=e8]:
+    - generic [ref=e9]:
+      - generic [ref=e10] [cursor=pointer]
+      - link "宸ヤ笟AI鏅洪�犲钩鍙� 宸ヤ笟AI鏅洪�犲钩鍙�" [ref=e15] [cursor=pointer]:
+        - /url: javascript:void 0
+        - img "宸ヤ笟AI鏅洪�犲钩鍙�" [ref=e18]
+        - generic [ref=e19]: 宸ヤ笟AI鏅洪�犲钩鍙�
+      - menu [ref=e22]:
+        - menuitem "棣栭〉" [ref=e23] [cursor=pointer]
+        - listitem [ref=e30]:
+          - generic [ref=e31] [cursor=pointer]: 鍩虹鏁版嵁
+        - listitem [ref=e39]:
+          - generic [ref=e40] [cursor=pointer]: 閲囪喘绠$悊
+        - listitem [ref=e46]:
+          - generic [ref=e47] [cursor=pointer]: 钀ラ攢绠$悊
+          - list [ref=e577]:
+            - menuitem "寰呭姙浜嬮」" [ref=e578] [cursor=pointer]
+            - menuitem "绾跨储绠$悊" [ref=e583] [cursor=pointer]
+            - menuitem "鍟嗘満绠$悊" [ref=e588] [cursor=pointer]
+            - menuitem "閿�鍞姤浠�" [ref=e593] [cursor=pointer]
+            - menuitem "鍚堝悓绠$悊" [ref=e599] [cursor=pointer]
+            - menuitem "閿�鍞鍗�" [ref=e605] [cursor=pointer]
+            - menuitem "瀹㈡埛绠$悊" [ref=e610] [cursor=pointer]
+            - menuitem "鑱旂郴浜虹鐞�" [ref=e615] [cursor=pointer]
+            - menuitem "瀹㈡埛鍏捣" [ref=e620] [cursor=pointer]
+            - menuitem "寮�绁ㄧ鐞�" [ref=e625] [cursor=pointer]
+            - menuitem "鍥炴绠$悊" [ref=e630] [cursor=pointer]
+            - menuitem "鍥炴璁″垝" [ref=e637] [cursor=pointer]
+            - menuitem "閿�鍞鍒�" [ref=e642] [cursor=pointer]
+            - menuitem "杈炬垚鍒嗘瀽" [ref=e647] [cursor=pointer]
+            - listitem [ref=e653]:
+              - generic [ref=e654] [cursor=pointer]: 鏁版嵁缁熻
+            - listitem [ref=e660]:
+              - generic [ref=e661] [cursor=pointer]: 绯荤粺閰嶇疆
+        - listitem [ref=e53]:
+          - generic [ref=e54] [cursor=pointer]: 璁惧绠$悊
+        - listitem [ref=e61]:
+          - generic [ref=e62] [cursor=pointer]: 浠撳偍鐗╂祦
+        - listitem [ref=e70]:
+          - generic [ref=e71] [cursor=pointer]: 鐢熶骇绠℃帶
+        - listitem [ref=e78]:
+          - generic [ref=e79] [cursor=pointer]: 璐ㄩ噺绠$悊
+        - listitem [ref=e85]:
+          - generic [ref=e86] [cursor=pointer]: 鍐崇瓥BI澶у睆
+        - listitem [ref=e92]:
+          - generic [ref=e93] [cursor=pointer]: 鍗忓悓鍔炲叕
+        - listitem [ref=e99]:
+          - generic [ref=e100] [cursor=pointer]: 浜哄姏璧勬簮
+        - listitem [ref=e106]:
+          - generic [ref=e107] [cursor=pointer]: 鍞悗绠$悊
+        - listitem [ref=e113]:
+          - generic [ref=e114] [cursor=pointer]: 鍩虹璁炬柦
+        - listitem [ref=e120]:
+          - generic [ref=e121] [cursor=pointer]: 绯荤粺绠$悊
+        - listitem [ref=e127]:
+          - generic [ref=e128] [cursor=pointer]: IM 鍗虫椂閫氳
+      - generic [ref=e136] [cursor=pointer]
+  - generic [ref=e141]:
+    - generic [ref=e142]:
+      - banner [ref=e143]:
+        - button [ref=e144] [cursor=pointer]
+        - button [ref=e147] [cursor=pointer]
+        - navigation "breadcrumb" [ref=e152]:
+          - list [ref=e153]:
+            - listitem [ref=e154]:
+              - link "棣栭〉" [disabled] [ref=e155]
+        - generic [ref=e162]:
+          - generic [ref=e164] [cursor=pointer]:
+            - generic [ref=e168]: 鎼滅储
+            - generic [ref=e169]: Ctrl K
+          - button [ref=e171] [cursor=pointer]
+          - button "dark" [ref=e176] [cursor=pointer]
+          - button [ref=e185] [cursor=pointer]
+          - button [ref=e191] [cursor=pointer]
+          - button [ref=e194] [cursor=pointer]
+          - button [ref=e200] [cursor=pointer]:
+            - button [ref=e202]
+          - button "宸ヤ綔鍙�" [ref=e206] [cursor=pointer]
+          - button [ref=e210] [cursor=pointer]:
+            - img "瓒呯骇绠$悊鍛�" [ref=e215]
+      - generic [ref=e217]:
+        - generic [ref=e218]: 棣栭〉
+        - generic [ref=e239]:
+          - button [ref=e240] [cursor=pointer]
+          - generic [ref=e247] [cursor=pointer]
+          - generic [ref=e251] [cursor=pointer]
+    - main [ref=e258]:
+      - generic [ref=e261]:
+        - generic [ref=e262]:
+          - generic [ref=e263]:
+            - heading "涓嬪崍濂斤紝娆㈣繋鍥炴潵" [level=1] [ref=e264]
+            - paragraph [ref=e265]: 2026骞�8鏈�12鏃� 鏄熸湡涓�
+          - generic [ref=e266]: 绯荤粺杩愯涓�
+        - generic [ref=e271]:
+          - generic [ref=e280] [cursor=pointer]:
+            - generic [ref=e281]: 鐢熶骇璁㈠崟
+            - generic [ref=e282]:
+              - generic [ref=e283]: "25"
+              - generic [ref=e285]: 杩涜涓�
+            - generic [ref=e286]:
+              - text: 寰呮帓浜� 15
+              - generic [ref=e288]: "|"
+              - text: 宸插畬鎴� 5
+          - generic [ref=e300] [cursor=pointer]:
+            - generic [ref=e301]: 浠婃棩浜ч噺
+            - generic [ref=e302]:
+              - generic [ref=e303]: "0"
+              - generic [ref=e305]: 浠�
+            - generic [ref=e306]: 鏄ㄦ棩 0 浠�
+          - generic [ref=e317] [cursor=pointer]:
+            - generic [ref=e318]: 璐ㄩ噺鍚堟牸鐜�
+            - generic [ref=e319]:
+              - generic [ref=e320]: "0.0"
+              - generic [ref=e322]: "%"
+            - generic [ref=e323]: 鍚堟牸 0 | 涓嶈壇 0
+          - generic [ref=e335] [cursor=pointer]:
+            - generic [ref=e336]: 璁惧杩愯鐜�
+            - generic [ref=e337]:
+              - generic [ref=e338]: "33.3"
+              - generic [ref=e340]: "%"
+            - generic [ref=e341]:
+              - generic [ref=e342]: 鍋滄満 1
+              - generic [ref=e343]: "|"
+              - generic [ref=e344]: 缁存姢 1
+        - generic [ref=e347]:
+          - generic [ref=e357] [cursor=pointer]:
+            - generic [ref=e358]: 閲囪喘璁㈠崟
+            - generic [ref=e359]:
+              - generic [ref=e360]: "10"
+              - generic [ref=e362]: 绗�
+            - generic [ref=e363]: ERP 閲囪喘绠$悊
+          - generic [ref=e374] [cursor=pointer]:
+            - generic [ref=e375]: 閿�鍞鍗�
+            - generic [ref=e376]:
+              - generic [ref=e377]: "22"
+              - generic [ref=e379]: 绗�
+            - generic [ref=e380]: ERP 閿�鍞鐞�
+          - generic [ref=e392] [cursor=pointer]:
+            - generic [ref=e393]: 鐗╂枡搴撳瓨
+            - generic [ref=e394]:
+              - generic [ref=e395]: "15"
+              - generic [ref=e397]: 鏉�
+            - generic [ref=e398]: MES 搴撳瓨鍙拌处
+          - generic [ref=e410] [cursor=pointer]:
+            - generic [ref=e411]: 鎴戠殑寰呭姙
+            - generic [ref=e412]:
+              - generic [ref=e413]: "1"
+              - generic [ref=e415]: 椤�
+            - generic [ref=e416]: BPM 寰呭鎵�
+        - generic [ref=e419]:
+          - generic [ref=e420]:
+            - heading "AI 鏅鸿兘鍒嗘瀽" [level=3] [ref=e421]
+            - generic [ref=e422]: 25 涓伐鍗曞彲鍒嗘瀽
+          - generic [ref=e423]:
+            - generic [ref=e431] [cursor=pointer]:
+              - generic [ref=e432]: AI 鐗╂枡棰勬祴
+              - generic [ref=e433]: 棰勬祴鐗╂枡闇�姹備笌鐭己椋庨櫓
+            - generic [ref=e442] [cursor=pointer]:
+              - generic [ref=e443]: AI 鏃堕暱棰勬祴
+              - generic [ref=e444]: 棰勬祴鍚勫伐搴忕敓浜ц�楁椂
+            - generic [ref=e451] [cursor=pointer]:
+              - generic [ref=e452]: AI 椋庨櫓棰勬祴
+              - generic [ref=e453]: 璇嗗埆娼滃湪鐢熶骇椋庨櫓骞剁粰鍑哄缓璁�
+            - generic [ref=e463] [cursor=pointer]:
+              - generic [ref=e464]: AI 浜や粯璇勪及
+              - generic [ref=e465]: 璇勪及璁㈠崟浜や粯鍑嗘椂鐜囦笌寤舵湡鍥犵礌
+        - generic [ref=e468]:
+          - heading "鐢熶骇瓒嬪娍锛堣繎7澶╋級" [level=3] [ref=e471]
+          - generic [ref=e476]:
+            - generic [ref=e477]:
+              - generic [ref=e483] [cursor=pointer]:
+                - generic [ref=e484]: 瀹夌伅鍛煎彨
+                - generic [ref=e485]:
+                  - generic [ref=e486]: "0"
+                  - generic [ref=e488]: 鏈缃�
+              - generic [ref=e494] [cursor=pointer]:
+                - generic [ref=e495]: 缁翠慨宸ュ崟
+                - generic [ref=e496]:
+                  - generic [ref=e497]: "0"
+                  - generic [ref=e499]: 寰呭鐞�
+            - heading "宸ュ崟鐘舵�佸垎甯�" [level=4] [ref=e501]
+        - generic [ref=e509]:
+          - heading "蹇嵎鎿嶄綔" [level=3] [ref=e510]
+          - generic [ref=e511]:
+            - generic [ref=e512] [cursor=pointer]: 鐢熶骇宸ュ崟
+            - generic [ref=e519] [cursor=pointer]: 鐢熶骇鎶ュ伐
+            - generic [ref=e526] [cursor=pointer]: 璐ㄦ绠$悊
+            - generic [ref=e534] [cursor=pointer]: 璁惧鐐规
+            - generic [ref=e541] [cursor=pointer]: 鐢熶骇鎺掔▼
+            - generic [ref=e548] [cursor=pointer]: 瀹夌伅鍛煎彨
+            - generic [ref=e553] [cursor=pointer]: 鐗╂枡搴撳瓨
+            - generic [ref=e561] [cursor=pointer]: 宸ヨ壓璺嚎
+            - generic [ref=e569] [cursor=pointer]: 鏁版嵁鎶ヨ〃
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-08-12T08-15-00-361Z.yml b/.playwright-mcp/page-2026-08-12T08-15-00-361Z.yml
new file mode 100644
index 0000000..6bf9c8e
--- /dev/null
+++ b/.playwright-mcp/page-2026-08-12T08-15-00-361Z.yml
@@ -0,0 +1,215 @@
+- generic [ref=e7]:
+  - complementary [ref=e8]:
+    - generic [ref=e9]:
+      - generic [ref=e10] [cursor=pointer]
+      - link "宸ヤ笟AI鏅洪�犲钩鍙� 宸ヤ笟AI鏅洪�犲钩鍙�" [ref=e15] [cursor=pointer]:
+        - /url: javascript:void 0
+        - img "宸ヤ笟AI鏅洪�犲钩鍙�" [ref=e18]
+        - generic [ref=e19]: 宸ヤ笟AI鏅洪�犲钩鍙�
+      - menu [ref=e22]:
+        - menuitem "棣栭〉" [ref=e23] [cursor=pointer]
+        - listitem [ref=e30]:
+          - generic [ref=e31] [cursor=pointer]: 鍩虹鏁版嵁
+        - listitem [ref=e39]:
+          - generic [ref=e40] [cursor=pointer]: 閲囪喘绠$悊
+        - listitem [ref=e46]:
+          - generic [ref=e47] [cursor=pointer]: 钀ラ攢绠$悊
+          - list [ref=e577]:
+            - menuitem "寰呭姙浜嬮」" [ref=e578] [cursor=pointer]
+            - menuitem "绾跨储绠$悊" [ref=e583] [cursor=pointer]
+            - menuitem "鍟嗘満绠$悊" [ref=e588] [cursor=pointer]
+            - menuitem "閿�鍞姤浠�" [ref=e593] [cursor=pointer]
+            - menuitem "鍚堝悓绠$悊" [ref=e599] [cursor=pointer]
+            - menuitem "閿�鍞鍗�" [ref=e605] [cursor=pointer]
+            - menuitem "瀹㈡埛绠$悊" [ref=e610] [cursor=pointer]
+            - menuitem "鑱旂郴浜虹鐞�" [ref=e615] [cursor=pointer]
+            - menuitem "瀹㈡埛鍏捣" [ref=e620] [cursor=pointer]
+            - menuitem "寮�绁ㄧ鐞�" [ref=e625] [cursor=pointer]
+            - menuitem "鍥炴绠$悊" [ref=e630] [cursor=pointer]
+            - menuitem "鍥炴璁″垝" [ref=e637] [cursor=pointer]
+            - menuitem "閿�鍞鍒�" [ref=e642] [cursor=pointer]
+            - menuitem "杈炬垚鍒嗘瀽" [active] [ref=e647] [cursor=pointer]
+            - listitem [ref=e653]:
+              - generic [ref=e654] [cursor=pointer]: 鏁版嵁缁熻
+            - listitem [ref=e660]:
+              - generic [ref=e661] [cursor=pointer]: 绯荤粺閰嶇疆
+        - listitem [ref=e53]:
+          - generic [ref=e54] [cursor=pointer]: 璁惧绠$悊
+        - listitem [ref=e61]:
+          - generic [ref=e62] [cursor=pointer]: 浠撳偍鐗╂祦
+        - listitem [ref=e70]:
+          - generic [ref=e71] [cursor=pointer]: 鐢熶骇绠℃帶
+        - listitem [ref=e78]:
+          - generic [ref=e79] [cursor=pointer]: 璐ㄩ噺绠$悊
+        - listitem [ref=e85]:
+          - generic [ref=e86] [cursor=pointer]: 鍐崇瓥BI澶у睆
+        - listitem [ref=e92]:
+          - generic [ref=e93] [cursor=pointer]: 鍗忓悓鍔炲叕
+        - listitem [ref=e99]:
+          - generic [ref=e100] [cursor=pointer]: 浜哄姏璧勬簮
+        - listitem [ref=e106]:
+          - generic [ref=e107] [cursor=pointer]: 鍞悗绠$悊
+        - listitem [ref=e113]:
+          - generic [ref=e114] [cursor=pointer]: 鍩虹璁炬柦
+        - listitem [ref=e120]:
+          - generic [ref=e121] [cursor=pointer]: 绯荤粺绠$悊
+        - listitem [ref=e127]:
+          - generic [ref=e128] [cursor=pointer]: IM 鍗虫椂閫氳
+      - generic [ref=e136] [cursor=pointer]
+  - generic [ref=e141]:
+    - generic [ref=e142]:
+      - banner [ref=e143]:
+        - button [ref=e144] [cursor=pointer]
+        - button [ref=e147] [cursor=pointer]
+        - navigation "breadcrumb" [ref=e152]:
+          - list [ref=e153]:
+            - listitem [ref=e668]:
+              - link "钀ラ攢绠$悊" [ref=e669] [cursor=pointer]:
+                - /url: javascript:void 0
+              - listitem [ref=e673]
+            - listitem [ref=e676]:
+              - link "杈炬垚鍒嗘瀽" [disabled] [ref=e677]
+        - generic [ref=e162]:
+          - generic [ref=e164] [cursor=pointer]:
+            - generic [ref=e168]: 鎼滅储
+            - generic [ref=e169]: Ctrl K
+          - button [ref=e171] [cursor=pointer]
+          - button "dark" [ref=e176] [cursor=pointer]
+          - button [ref=e185] [cursor=pointer]
+          - button [ref=e191] [cursor=pointer]
+          - button [ref=e194] [cursor=pointer]
+          - button [ref=e200] [cursor=pointer]:
+            - button [ref=e202]
+          - button "宸ヤ綔鍙�" [ref=e206] [cursor=pointer]
+          - button [ref=e210] [cursor=pointer]:
+            - img "瓒呯骇绠$悊鍛�" [ref=e215]
+      - generic [ref=e217]:
+        - generic [ref=e222]:
+          - generic [ref=e223] [cursor=pointer]: 棣栭〉
+          - generic [ref=e684] [cursor=pointer]: 杈炬垚鍒嗘瀽
+        - generic [ref=e239]:
+          - button [ref=e240] [cursor=pointer]
+          - generic [ref=e247] [cursor=pointer]
+          - generic [ref=e251] [cursor=pointer]
+    - main [ref=e258]:
+      - generic [ref=e705]:
+        - generic [ref=e707]:
+          - generic [ref=e708]:
+            - generic [ref=e709]:
+              - generic [ref=e710]: "*"
+              - text: 鍛ㄦ湡绫诲瀷
+            - generic [ref=e716] [cursor=pointer]:
+              - combobox "* 鍛ㄦ湡绫诲瀷" [ref=e718]
+              - generic "骞村害" [ref=e719]
+          - generic [ref=e720]:
+            - generic [ref=e721]:
+              - generic [ref=e722]: "*"
+              - text: 鍛ㄦ湡鍊�
+            - textbox "* 鍛ㄦ湡鍊�" [ref=e727]:
+              - /placeholder: 濡� 2026 / 2026-Q1 / 2026-07
+              - text: "2026"
+          - generic [ref=e728]:
+            - generic [ref=e729]: 鐩爣缁村害
+            - generic [ref=e735] [cursor=pointer]:
+              - combobox "鐩爣缁村害" [ref=e737]
+              - generic: 鍏ㄩ儴
+          - generic [ref=e738]:
+            - generic [ref=e739]: 鐘舵��
+            - generic [ref=e745] [cursor=pointer]:
+              - combobox "鐘舵��" [ref=e747]
+              - generic: 鍏ㄩ儴
+          - generic [ref=e748]:
+            - button "閲� 缃�" [ref=e749] [cursor=pointer]
+            - button "鏌� 璇�" [ref=e751] [cursor=pointer]
+            - generic [ref=e753] [cursor=pointer]: 鏀惰捣
+        - generic [ref=e758]:
+          - tablist [ref=e759]:
+            - generic [ref=e761]:
+              - tab "鍚堝悓閲戦" [selected] [ref=e763] [cursor=pointer]
+              - tab "鍥炴閲戦" [ref=e765] [cursor=pointer]
+              - tab "鍟嗘満閲戦" [ref=e767] [cursor=pointer]
+              - tab "鏂板瀹㈡埛" [ref=e769] [cursor=pointer]
+              - tab "鏂板鍚堝悓" [ref=e771] [cursor=pointer]
+          - generic:
+            - generic:
+              - tabpanel "鍚堝悓閲戦"
+        - generic [ref=e784]:
+          - generic [ref=e785]:
+            - table [ref=e789]:
+              - rowgroup [ref=e809]:
+                - row [ref=e810]:
+                  - columnheader "鐩爣瀵硅薄" [ref=e811]
+                  - columnheader "鍚堝悓閲戦鐩爣" [ref=e816]
+                  - columnheader "瀹為檯鍚堝悓閲戦" [ref=e821]
+                  - columnheader "鍚堝悓杈炬垚鐜�(%)" [ref=e826]
+                  - columnheader "鍥炴閲戦鐩爣" [ref=e831]
+                  - columnheader "瀹為檯鍥炴閲戦" [ref=e836]
+                  - columnheader "鍥炴杈炬垚鐜�(%)" [ref=e841]
+                  - columnheader "鍟嗘満閲戦鐩爣" [ref=e846]
+                  - columnheader "瀹為檯鍟嗘満閲戦" [ref=e851]
+                  - columnheader "鍟嗘満杈炬垚鐜�(%)" [ref=e856]
+                  - columnheader "鏂板瀹㈡埛鐩爣" [ref=e861]
+                  - columnheader "瀹為檯鏂板瀹㈡埛" [ref=e866]
+                  - columnheader "瀹㈡埛杈炬垚鐜�(%)" [ref=e871]
+                  - columnheader "鏂板鍚堝悓鐩爣" [ref=e876]
+                  - columnheader "瀹為檯鏂板鍚堝悓" [ref=e881]
+                  - columnheader "鍚堝悓鏁拌揪鎴愮巼(%)" [ref=e886]
+            - table [ref=e894]:
+              - rowgroup [ref=e914]:
+                - row [ref=e915]:
+                  - cell "瓒呯骇绠$悊鍛�" [ref=e916]
+                  - cell "10000.00" [ref=e919]
+                  - cell "32377.00" [ref=e923]
+                  - cell "323.77%" [ref=e927]
+                  - cell "10000.00" [ref=e930]
+                  - cell "100.00" [ref=e934]
+                  - cell "1%" [ref=e938]
+                  - cell "10000.00" [ref=e941]
+                  - cell "0.00" [ref=e945]
+                  - cell "0%" [ref=e949]
+                  - cell "10000" [ref=e952]
+                  - cell "3" [ref=e956]
+                  - cell "0.03%" [ref=e960]
+                  - cell "10000" [ref=e963]
+                  - cell "5" [ref=e967]
+                  - cell "0.05%" [ref=e971]
+                - row [ref=e974]:
+                  - cell "瓒呯骇绠$悊鍛�" [ref=e975]
+                  - cell "500000.00" [ref=e978]
+                  - cell "32377.00" [ref=e982]
+                  - cell "6.48%" [ref=e986]
+                  - cell "300000.00" [ref=e989]
+                  - cell "100.00" [ref=e993]
+                  - cell "0.03%" [ref=e997]
+                  - cell "600000.00" [ref=e1000]
+                  - cell "0.00" [ref=e1004]
+                  - cell "0%" [ref=e1008]
+                  - cell "20" [ref=e1011]
+                  - cell "3" [ref=e1015]
+                  - cell "15%" [ref=e1019]
+                  - cell "30" [ref=e1022]
+                  - cell "5" [ref=e1026]
+                  - cell "16.67%" [ref=e1030]
+          - generic:
+            - generic [ref=e1033]:
+              - table [ref=e1036]:
+                - rowgroup [ref=e1056]:
+                  - row [ref=e1057]:
+                    - columnheader "鐩爣鍚嶇О" [ref=e1058]
+              - table [ref=e1065]:
+                - rowgroup [ref=e1068]:
+                  - row [ref=e1069]:
+                    - cell "閰掓硥" [ref=e1070]
+                  - row [ref=e1074]:
+                    - cell "2026骞村害閿�鍞洰鏍�-寮犱笁" [ref=e1075]
+            - generic [ref=e1079]:
+              - table [ref=e1082]:
+                - rowgroup [ref=e1102]:
+                  - row [ref=e1103]:
+                    - columnheader "鐘舵��" [ref=e1104]
+              - table [ref=e1111]:
+                - rowgroup [ref=e1114]:
+                  - row [ref=e1115]:
+                    - cell "宸茬敓鏁�" [ref=e1116]
+                  - row [ref=e1120]:
+                    - cell "宸茬敓鏁�" [ref=e1121]
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-08-12T08-16-45-289Z.yml b/.playwright-mcp/page-2026-08-12T08-16-45-289Z.yml
new file mode 100644
index 0000000..4b28c4d
--- /dev/null
+++ b/.playwright-mcp/page-2026-08-12T08-16-45-289Z.yml
@@ -0,0 +1,215 @@
+- generic [ref=e7]:
+  - complementary [ref=e8]:
+    - generic [ref=e9]:
+      - generic [ref=e10] [cursor=pointer]
+      - link "宸ヤ笟AI鏅洪�犲钩鍙� 宸ヤ笟AI鏅洪�犲钩鍙�" [ref=e15] [cursor=pointer]:
+        - /url: javascript:void 0
+        - img "宸ヤ笟AI鏅洪�犲钩鍙�" [ref=e18]
+        - generic [ref=e19]: 宸ヤ笟AI鏅洪�犲钩鍙�
+      - menu [ref=e22]:
+        - menuitem "棣栭〉" [ref=e23] [cursor=pointer]
+        - listitem [ref=e30]:
+          - generic [ref=e31] [cursor=pointer]: 鍩虹鏁版嵁
+        - listitem [ref=e39]:
+          - generic [ref=e40] [cursor=pointer]: 閲囪喘绠$悊
+        - listitem [ref=e46]:
+          - generic [ref=e47] [cursor=pointer]: 钀ラ攢绠$悊
+          - list [ref=e577]:
+            - menuitem "寰呭姙浜嬮」" [ref=e578] [cursor=pointer]
+            - menuitem "绾跨储绠$悊" [ref=e583] [cursor=pointer]
+            - menuitem "鍟嗘満绠$悊" [ref=e588] [cursor=pointer]
+            - menuitem "閿�鍞姤浠�" [ref=e593] [cursor=pointer]
+            - menuitem "鍚堝悓绠$悊" [ref=e599] [cursor=pointer]
+            - menuitem "閿�鍞鍗�" [ref=e605] [cursor=pointer]
+            - menuitem "瀹㈡埛绠$悊" [ref=e610] [cursor=pointer]
+            - menuitem "鑱旂郴浜虹鐞�" [ref=e615] [cursor=pointer]
+            - menuitem "瀹㈡埛鍏捣" [ref=e620] [cursor=pointer]
+            - menuitem "寮�绁ㄧ鐞�" [ref=e625] [cursor=pointer]
+            - menuitem "鍥炴绠$悊" [ref=e630] [cursor=pointer]
+            - menuitem "鍥炴璁″垝" [ref=e637] [cursor=pointer]
+            - menuitem "閿�鍞鍒�" [ref=e642] [cursor=pointer]
+            - menuitem "杈炬垚鍒嗘瀽" [ref=e647] [cursor=pointer]
+            - listitem [ref=e653]:
+              - generic [ref=e654] [cursor=pointer]: 鏁版嵁缁熻
+            - listitem [ref=e660]:
+              - generic [ref=e661] [cursor=pointer]: 绯荤粺閰嶇疆
+        - listitem [ref=e53]:
+          - generic [ref=e54] [cursor=pointer]: 璁惧绠$悊
+        - listitem [ref=e61]:
+          - generic [ref=e62] [cursor=pointer]: 浠撳偍鐗╂祦
+        - listitem [ref=e70]:
+          - generic [ref=e71] [cursor=pointer]: 鐢熶骇绠℃帶
+        - listitem [ref=e78]:
+          - generic [ref=e79] [cursor=pointer]: 璐ㄩ噺绠$悊
+        - listitem [ref=e85]:
+          - generic [ref=e86] [cursor=pointer]: 鍐崇瓥BI澶у睆
+        - listitem [ref=e92]:
+          - generic [ref=e93] [cursor=pointer]: 鍗忓悓鍔炲叕
+        - listitem [ref=e99]:
+          - generic [ref=e100] [cursor=pointer]: 浜哄姏璧勬簮
+        - listitem [ref=e106]:
+          - generic [ref=e107] [cursor=pointer]: 鍞悗绠$悊
+        - listitem [ref=e113]:
+          - generic [ref=e114] [cursor=pointer]: 鍩虹璁炬柦
+        - listitem [ref=e120]:
+          - generic [ref=e121] [cursor=pointer]: 绯荤粺绠$悊
+        - listitem [ref=e127]:
+          - generic [ref=e128] [cursor=pointer]: IM 鍗虫椂閫氳
+      - generic [ref=e136] [cursor=pointer]
+  - generic [ref=e141]:
+    - generic [ref=e142]:
+      - banner [ref=e143]:
+        - button [ref=e144] [cursor=pointer]
+        - button [ref=e147] [cursor=pointer]
+        - navigation "breadcrumb" [ref=e152]:
+          - list [ref=e153]:
+            - listitem [ref=e668]:
+              - link "钀ラ攢绠$悊" [ref=e669] [cursor=pointer]:
+                - /url: javascript:void 0
+              - listitem [ref=e673]
+            - listitem [ref=e676]:
+              - link "杈炬垚鍒嗘瀽" [disabled] [ref=e677]
+        - generic [ref=e162]:
+          - generic [ref=e164] [cursor=pointer]:
+            - generic [ref=e168]: 鎼滅储
+            - generic [ref=e169]: Ctrl K
+          - button [ref=e171] [cursor=pointer]
+          - button "dark" [ref=e176] [cursor=pointer]
+          - button [ref=e185] [cursor=pointer]
+          - button [ref=e191] [cursor=pointer]
+          - button [ref=e194] [cursor=pointer]
+          - button [ref=e200] [cursor=pointer]:
+            - button [ref=e202]
+          - button "宸ヤ綔鍙�" [ref=e206] [cursor=pointer]
+          - button [ref=e210] [cursor=pointer]:
+            - img "瓒呯骇绠$悊鍛�" [ref=e215]
+      - generic [ref=e217]:
+        - generic [ref=e222]:
+          - generic [ref=e223] [cursor=pointer]: 棣栭〉
+          - generic [ref=e684] [cursor=pointer]: 杈炬垚鍒嗘瀽
+        - generic [ref=e239]:
+          - button [ref=e240] [cursor=pointer]
+          - generic [ref=e247] [cursor=pointer]
+          - generic [ref=e251] [cursor=pointer]
+    - main [ref=e258]:
+      - generic [ref=e705]:
+        - generic [ref=e707]:
+          - generic [ref=e708]:
+            - generic [ref=e709]:
+              - generic [ref=e710]: "*"
+              - text: 鍛ㄦ湡绫诲瀷
+            - generic [ref=e716] [cursor=pointer]:
+              - combobox "* 鍛ㄦ湡绫诲瀷" [ref=e718]
+              - generic "骞村害" [ref=e719]
+          - generic [ref=e720]:
+            - generic [ref=e721]:
+              - generic [ref=e722]: "*"
+              - text: 鍛ㄦ湡鍊�
+            - textbox "* 鍛ㄦ湡鍊�" [ref=e727]:
+              - /placeholder: 濡� 2026 / 2026-Q1 / 2026-07
+              - text: "2026"
+          - generic [ref=e728]:
+            - generic [ref=e729]: 鐩爣缁村害
+            - generic [ref=e735] [cursor=pointer]:
+              - combobox "鐩爣缁村害" [ref=e737]
+              - generic: 鍏ㄩ儴
+          - generic [ref=e738]:
+            - generic [ref=e739]: 鐘舵��
+            - generic [ref=e745] [cursor=pointer]:
+              - combobox "鐘舵��" [ref=e747]
+              - generic: 鍏ㄩ儴
+          - generic [ref=e748]:
+            - button "閲� 缃�" [ref=e749] [cursor=pointer]
+            - button "鏌� 璇�" [ref=e751] [cursor=pointer]
+            - generic [ref=e753] [cursor=pointer]: 鏀惰捣
+        - generic [ref=e758]:
+          - tablist [ref=e759]:
+            - generic [ref=e761]:
+              - tab "鍚堝悓閲戦" [ref=e763] [cursor=pointer]
+              - tab "鍥炴閲戦" [active] [selected] [ref=e765] [cursor=pointer]
+              - tab "鍟嗘満閲戦" [ref=e767] [cursor=pointer]
+              - tab "鏂板瀹㈡埛" [ref=e769] [cursor=pointer]
+              - tab "鏂板鍚堝悓" [ref=e771] [cursor=pointer]
+          - generic:
+            - generic:
+              - tabpanel "鍥炴閲戦"
+        - generic [ref=e784]:
+          - generic [ref=e785]:
+            - table [ref=e789]:
+              - rowgroup [ref=e809]:
+                - row [ref=e810]:
+                  - columnheader "鐩爣瀵硅薄" [ref=e811]
+                  - columnheader "鍚堝悓閲戦鐩爣" [ref=e816]
+                  - columnheader "瀹為檯鍚堝悓閲戦" [ref=e821]
+                  - columnheader "鍚堝悓杈炬垚鐜�(%)" [ref=e826]
+                  - columnheader "鍥炴閲戦鐩爣" [ref=e831]
+                  - columnheader "瀹為檯鍥炴閲戦" [ref=e836]
+                  - columnheader "鍥炴杈炬垚鐜�(%)" [ref=e841]
+                  - columnheader "鍟嗘満閲戦鐩爣" [ref=e846]
+                  - columnheader "瀹為檯鍟嗘満閲戦" [ref=e851]
+                  - columnheader "鍟嗘満杈炬垚鐜�(%)" [ref=e856]
+                  - columnheader "鏂板瀹㈡埛鐩爣" [ref=e861]
+                  - columnheader "瀹為檯鏂板瀹㈡埛" [ref=e866]
+                  - columnheader "瀹㈡埛杈炬垚鐜�(%)" [ref=e871]
+                  - columnheader "鏂板鍚堝悓鐩爣" [ref=e876]
+                  - columnheader "瀹為檯鏂板鍚堝悓" [ref=e881]
+                  - columnheader "鍚堝悓鏁拌揪鎴愮巼(%)" [ref=e886]
+            - table [ref=e894]:
+              - rowgroup [ref=e914]:
+                - row [ref=e915]:
+                  - cell "瓒呯骇绠$悊鍛�" [ref=e916]
+                  - cell "10000.00" [ref=e919]
+                  - cell "32377.00" [ref=e923]
+                  - cell "323.77%" [ref=e927]
+                  - cell "10000.00" [ref=e930]
+                  - cell "100.00" [ref=e934]
+                  - cell "1%" [ref=e938]
+                  - cell "10000.00" [ref=e941]
+                  - cell "0.00" [ref=e945]
+                  - cell "0%" [ref=e949]
+                  - cell "10000" [ref=e952]
+                  - cell "3" [ref=e956]
+                  - cell "0.03%" [ref=e960]
+                  - cell "10000" [ref=e963]
+                  - cell "5" [ref=e967]
+                  - cell "0.05%" [ref=e971]
+                - row [ref=e974]:
+                  - cell "瓒呯骇绠$悊鍛�" [ref=e975]
+                  - cell "500000.00" [ref=e978]
+                  - cell "32377.00" [ref=e982]
+                  - cell "6.48%" [ref=e986]
+                  - cell "300000.00" [ref=e989]
+                  - cell "100.00" [ref=e993]
+                  - cell "0.03%" [ref=e997]
+                  - cell "600000.00" [ref=e1000]
+                  - cell "0.00" [ref=e1004]
+                  - cell "0%" [ref=e1008]
+                  - cell "20" [ref=e1011]
+                  - cell "3" [ref=e1015]
+                  - cell "15%" [ref=e1019]
+                  - cell "30" [ref=e1022]
+                  - cell "5" [ref=e1026]
+                  - cell "16.67%" [ref=e1030]
+          - generic:
+            - generic [ref=e1033]:
+              - table [ref=e1036]:
+                - rowgroup [ref=e1056]:
+                  - row [ref=e1057]:
+                    - columnheader "鐩爣鍚嶇О" [ref=e1058]
+              - table [ref=e1065]:
+                - rowgroup [ref=e1068]:
+                  - row [ref=e1069]:
+                    - cell "閰掓硥" [ref=e1070]
+                  - row [ref=e1074]:
+                    - cell "2026骞村害閿�鍞洰鏍�-寮犱笁" [ref=e1075]
+            - generic [ref=e1079]:
+              - table [ref=e1082]:
+                - rowgroup [ref=e1102]:
+                  - row [ref=e1103]:
+                    - columnheader "鐘舵��" [ref=e1104]
+              - table [ref=e1111]:
+                - rowgroup [ref=e1114]:
+                  - row [ref=e1115]:
+                    - cell "宸茬敓鏁�" [ref=e1116]
+                  - row [ref=e1120]:
+                    - cell "宸茬敓鏁�" [ref=e1121]
\ No newline at end of file
diff --git a/docs/crm_sales_target_integration.md b/docs/crm_sales_target_integration.md
new file mode 100644
index 0000000..62036da
--- /dev/null
+++ b/docs/crm_sales_target_integration.md
@@ -0,0 +1,141 @@
+# 钀ラ攢绠$悊 - 閿�鍞鍒掞紙閿�鍞洰鏍囷級鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 钀ラ攢绠$悊 鈫� 閿�鍞鍒掞紙鑿滃崟 1065000锛岀粍浠� `crm/salesTarget/index`锛�
+- 钀ラ攢绠$悊 鈫� 杈炬垚鍒嗘瀽锛堣彍鍗� 1065100锛岀粍浠� `crm/salesTarget/analysis/index`锛�
+
+## 涓氬姟娴佺▼涓庢暟鎹甫鍏�
+
+1. **璁惧畾鐩爣**锛氱鐞嗗憳鍦ㄣ�岄攢鍞鍒掋�嶉〉鎸夊懆鏈燂紙骞�/瀛�/鏈堬級+ 缁村害锛堜釜浜�/閮ㄩ棬/浜у搧锛夎瀹氶攢鍞洰鏍囥�傜洰鏍囧璞★紙鐢ㄦ埛/閮ㄩ棬/鐗╂枡锛変粠绯荤粺涓嬫媺閫夋嫨锛岀洰鏍囨寚鏍囷紙鍚堝悓閲戦銆佸洖娆鹃噾棰濄�佸晢鏈洪噾棰濄�佹柊澧炲鎴锋暟銆佹柊澧炲悎鍚屾暟锛夋墜宸ュ綍鍏ャ��
+2. **鍛ㄦ湡鑷姩鎹㈢畻**锛氬墠绔彧鎻愪氦 `periodType`锛�1骞�/2瀛e害/3鏈堬級涓� `periodValue`锛堝 `2026`銆乣2026-Q1`銆乣2026-07`锛夛紝鍚庡彴鑷姩鎹㈢畻鍛ㄦ湡璧锋鏃堕棿骞惰惤搴擄紱鍒楄〃/璇︽儏灞曠ず鎹㈢畻鍚庣殑璧锋鏃堕棿銆�
+3. **鐘舵�佽嚜鍔ㄥ垽瀹�**锛氬悗鍙颁繚瀛樻椂鎸夊綋鍓嶆椂闂翠笌鍛ㄦ湡鑷姩鍒ゅ畾鐘舵�侊紙鏈敓鏁�/宸茬敓鏁�/宸茬粨鏉燂級锛屽墠绔睍绀哄嵆鍙紱浠呫�屼綔搴�/鎭㈠銆嶇敱鐢ㄦ埛鎵嬪姩瑙﹀彂銆�
+4. **杈炬垚缁熻鑷姩甯﹀叆**锛氬垪琛ㄣ�佽鎯呫�佽揪鎴愬垎鏋愬潎杩斿洖瀹為檯杈炬垚锛堝悎鍚岄噾棰濄�佸洖娆鹃噾棰濄�佸晢鏈洪噾棰濄�佹柊澧炲鎴锋暟銆佹柊澧炲悎鍚屾暟锛変笌杈炬垚鐜囷紝鍓嶇鐩存帴灞曠ず锛屾棤闇�鍙﹁璋冪敤缁熻鎺ュ彛銆�
+5. **杈炬垚鍒嗘瀽**锛氱敤鎴峰湪銆岃揪鎴愬垎鏋愩�嶉〉閫夋嫨鍛ㄦ湡锛堝繀閫夛級+ 缁村害锛堝彲閫夛級+ 鐘舵�侊紙鍙�夛級锛屽悗鍙版寜鍛ㄦ湡杩囨护杩斿洖鐩爣鍒楄〃锛堝惈瀹為檯杈炬垚涓庤揪鎴愮巼锛夈��
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/crm/sales-target/create` | 鍒涘缓閿�鍞洰鏍� |
+| PUT | `/crm/sales-target/update` | 鏇存柊閿�鍞洰鏍� |
+| PUT | `/crm/sales-target/change-status` | 鍙樻洿鐩爣鐘舵�侊紙浣滃簾/鎭㈠/鐢熸晥锛� |
+| DELETE | `/crm/sales-target/delete?id=` | 鍒犻櫎閿�鍞洰鏍� |
+| GET | `/crm/sales-target/get?id=` | 鐩爣璇︽儏锛堝惈瀹為檯杈炬垚+杈炬垚鐜囷級 |
+| GET | `/crm/sales-target/page` | 鐩爣鍒嗛〉锛堝惈瀹為檯杈炬垚+杈炬垚鐜囷級 |
+| GET | `/crm/sales-target/export-excel` | 瀵煎嚭 Excel |
+| GET | `/crm/sales-target/get-achievement-analysis` | 杈炬垚鍒嗘瀽 |
+
+### 鍒涘缓/鏇存柊璇锋眰鍙傛暟锛圕rmSalesTargetSaveReqVO锛�
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏇存柊鏃跺繀濉� | 鐩爣缂栧彿 |
+| name | String | 鏄� | 鐩爣鍚嶇О |
+| periodType | Integer | 鏄� | 鍛ㄦ湡绫诲瀷锛堝瓧鍏� crm_sales_target_period_type锛�1骞�/2瀛e害/3鏈堬級 |
+| periodValue | String | 鏄� | 鍛ㄦ湡鍊硷紝濡� `2026` / `2026-Q1` / `2026-07` |
+| targetDimension | Integer | 鏄� | 鐩爣缁村害锛堝瓧鍏� crm_sales_target_dimension锛�1涓汉/2閮ㄩ棬/3浜у搧锛� |
+| targetUserId | Long | 缁村害=涓汉鏃跺繀濉� | 鐩爣鐢ㄦ埛缂栧彿 |
+| targetDeptId | Long | 缁村害=閮ㄩ棬鏃跺繀濉� | 鐩爣閮ㄩ棬缂栧彿 |
+| targetItemId | Long | 缁村害=浜у搧鏃跺繀濉� | 鐩爣鐗╂枡缂栧彿锛堝叧鑱� MDM锛� |
+| contractTarget | BigDecimal | 鍚� | 鍚堝悓閲戦鐩爣锛堝厓锛� |
+| receivableTarget | BigDecimal | 鍚� | 鍥炴閲戦鐩爣锛堝厓锛� |
+| businessTarget | BigDecimal | 鍚� | 鍟嗘満閲戦鐩爣锛堝厓锛� |
+| customerTarget | Integer | 鍚� | 鏂板瀹㈡埛鏁扮洰鏍� |
+| contractCountTarget | Integer | 鍚� | 鏂板鍚堝悓鏁扮洰鏍� |
+| remark | String | 鍚� | 澶囨敞 |
+
+### 鐘舵�佸彉鏇磋姹傚弬鏁帮紙CrmSalesTargetChangeStatusReqVO锛�
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 鐩爣缂栧彿 |
+| status | Integer | 鏄� | 鐩爣鐘舵�侊紙瀛楀吀 crm_sales_target_status锛�0鏈敓鏁�/1宸茬敓鏁�/2宸茬粨鏉�/3宸蹭綔搴燂級 |
+
+### 杈炬垚鍒嗘瀽璇锋眰鍙傛暟锛圕rmSalesTargetAnalysisReqVO锛�
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| periodType | Integer | 鏄� | 鍛ㄦ湡绫诲瀷 |
+| periodValue | String | 鏄� | 鍛ㄦ湡鍊� |
+| targetDimension | Integer | 鍚� | 鐩爣缁村害锛屼负绌烘煡鍏ㄩ儴 |
+| status | Integer | 鍚� | 鐘舵�侊紝涓虹┖鏌ュ叏閮� |
+
+### 鍝嶅簲瀛楁锛圕rmSalesTargetRespVO锛�
+
+鐩爣鍩虹瀛楁锛坣ame/periodType/periodValue/startTime/endTime/targetDimension/status/remark/createTime/creator锛変箣涓婏紝鏂板浠ヤ笅瀛楁锛�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| targetUserName | String | 鐩爣瀵硅薄鍚嶇О锛堢淮搴�=涓汉鏃朵负鏄电О锛� |
+| targetDeptName | String | 鐩爣閮ㄩ棬鍚嶇О锛堢淮搴�=閮ㄩ棬锛� |
+| targetItemName | String | 鐩爣鐗╂枡鍚嶇О锛堢淮搴�=浜у搧锛� |
+| actualContractPrice | BigDecimal | 瀹為檯鍚堝悓閲戦锛堝厓锛� |
+| actualReceivablePrice | BigDecimal | 瀹為檯鍥炴閲戦锛堝厓锛� |
+| actualBusinessPrice | BigDecimal | 瀹為檯鍟嗘満閲戦锛堝厓锛� |
+| actualCustomerCount | Long | 瀹為檯鏂板瀹㈡埛鏁� |
+| actualContractCount | Long | 瀹為檯鏂板鍚堝悓鏁� |
+| contractRate | BigDecimal | 鍚堝悓閲戦杈炬垚鐜囷紙%锛� |
+| receivableRate | BigDecimal | 鍥炴閲戦杈炬垚鐜囷紙%锛� |
+| businessRate | BigDecimal | 鍟嗘満閲戦杈炬垚鐜囷紙%锛� |
+| customerRate | BigDecimal | 鏂板瀹㈡埛鏁拌揪鎴愮巼锛�%锛� |
+| contractCountRate | BigDecimal | 鏂板鍚堝悓鏁拌揪鎴愮巼锛�%锛� |
+| creatorName | String | 鍒涘缓浜哄悕绉� |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+  "code": 0,
+  "data": {
+    "id": 1,
+    "name": "2026骞村害閿�鍞洰鏍�-寮犱笁",
+    "periodType": 1,
+    "periodValue": "2026",
+    "startTime": "2026-01-01 00:00:00",
+    "endTime": "2027-01-01 00:00:00",
+    "targetDimension": 1,
+    "targetUserId": 1,
+    "targetUserName": "寮犱笁",
+    "contractTarget": 500000.00,
+    "actualContractPrice": 32377.00,
+    "contractRate": 6.48,
+    "status": 1
+  }
+}
+```
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| 鐩爣鍚嶇О | 鍒楄〃銆佽鎯� | 涓绘爣棰� |
+| 鍛ㄦ湡 | 鍒楄〃銆佽鎯� | 鍛ㄦ湡绫诲瀷瀛楀吀杞枃鏈� + 鍛ㄦ湡鍊硷紝濡傘�屽勾搴� 2026銆� |
+| 鐩爣瀵硅薄 | 鍒楄〃銆佽鎯� | 鎸夌淮搴﹀睍绀虹洰鏍囩敤鎴�/閮ㄩ棬/鐗╂枡鍚嶇О锛坱argetUserName/targetDeptName/targetItemName锛� |
+| 鐩爣鎸囨爣 | 鍒楄〃銆佽鎯呫�佸垎鏋� | 鍚堝悓閲戦/鍥炴閲戦/鍟嗘満閲戦/鏂板瀹㈡埛鏁�/鏂板鍚堝悓鏁扮洰鏍� |
+| 瀹為檯杈炬垚 | 鍒楄〃銆佽鎯呫�佸垎鏋� | 涓庣洰鏍囨寚鏍囦竴涓�瀵瑰簲鐨勫疄闄呭�� |
+| 杈炬垚鐜� | 鍒楄〃銆佽鎯呫�佸垎鏋� | 鐧惧垎姣旓紝淇濈暀涓や綅灏忔暟锛涚洰鏍囦负 0 鎴栫┖鏃惰揪鎴愮巼涓虹┖锛堝睍绀轰负 `-`锛� |
+| 鐘舵�� | 鍒楄〃銆佽鎯� | 瀛楀吀杞枃鏈紝宸蹭綔搴熼渶缃伆/绂佹缂栬緫 |
+| 鍒涘缓浜� | 鍒楄〃銆佽鎯� | 鍒涘缓浜烘樀绉� |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 杈炬垚鐜囪绠� | 瀹為檯鍊� 梅 鐩爣鍊� 脳 100锛屼繚鐣� 2 浣嶅皬鏁帮紱鐩爣涓虹┖鎴� 鈮�0 鏃惰揪鎴愮巼杩斿洖 null |
+| 鍛ㄦ湡鎹㈢畻 | 鍓嶇鏃犻渶璁$畻璧锋鏃堕棿锛屽悗鍙版寜 periodValue 鎹㈢畻锛涘勾 `2026`銆佸 `2026-Q1`銆佹湀 `2026-07` |
+| 鐘舵�佸垽瀹� | 鍚庡彴鎸夊綋鍓嶆椂闂磋嚜鍔ㄥ垽瀹� 鏈敓鏁�/宸茬敓鏁�/宸茬粨鏉燂紱鍓嶇鏂板缂栬緫鏃舵棤闇�鎵嬪姩閫夌姸鎬� |
+| 浣滃簾/鎭㈠ | 宸蹭綔搴熺殑鐩爣琚紪杈戝悗浠嶄繚鎸佷綔搴熺姸鎬侊紝闇�鍏堛�屾仮澶嶃�嶅啀缂栬緫鐢熸晥 |
+| 缁熻鍙e緞 | 鍚堝悓閲戦/鏁伴噺鍙粺璁″鏍搁�氳繃锛坅udit_status=20锛夌殑鍚堝悓锛涘洖娆惧彧缁熻瀹℃牳閫氳繃鐨勫洖娆撅紱鍟嗘満鍙粺璁¤耽鍗曪紙end_status=1锛夛紱瀹㈡埛鎸夊垱寤烘椂闂寸粺璁� |
+| 浜у搧缁村害 | 浠呯粺璁″悎鍚岄噾棰濅笌鍚堝悓鏁帮紝鍥炴/鍟嗘満/瀹㈡埛鏁颁负 0 |
+| 閮ㄩ棬缁村害 | 鍚瓙閮ㄩ棬涓嬫墍鏈夌敤鎴风殑鏁版嵁 |
+| 鏁版嵁鏉冮檺 | 鐩爣涓虹鐞嗛厤缃紝涓嶆帴鍏� CRM 鏁版嵁鏉冮檺锛屽垪琛ㄥ睍绀哄叏閮紙鍙寜闇�鎸夊垱寤轰汉杩囨护锛� |
+
+## 娉ㄦ剰浜嬮」
+
+- 杈炬垚鐜囧瓧娈靛彲鑳戒负 null锛屽墠绔睍绀洪渶鍏滃簳涓� `-`锛岄伩鍏嶆樉绀� 0銆�
+- 鍛ㄦ湡鍊兼牸寮忎弗鏍硷細骞� `YYYY`銆佸 `YYYY-Q1`~`YYYY-Q4`銆佹湀 `YYYY-MM`锛屽墠绔〃鍗曟牎楠岄渶鎸夋绾︽潫銆�
+- 鐩爣缁村害涓庣洰鏍囧璞′簰鏂ワ細缁村害=涓汉濉� userId锛岄儴闂ㄥ~ deptId锛屼骇鍝佸~ itemId锛屽叾浣欑暀绌猴紱鍓嶇鎸夌淮搴﹀垏鎹㈣〃鍗曢」銆�
+- 杈炬垚鍒嗘瀽鎺ュ彛鐨� periodType + periodValue 涓哄繀濉紝鏈�夊懆鏈熸椂涓嶅簲鍙戣捣璇锋眰銆�
+- 杈炬垚缁熻鏌ヨ涓洪�愭潯鐩爣璁$畻锛堢害 5 鏉″皬 SQL锛夛紝鐩爣鏁伴噺绾у嚑鍗亊鍑犵櫨鏃舵�ц兘鍙帴鍙楋紱鑻ュ悗缁洰鏍囬噺绾у澶ч渶鏀归�犱负鑱氬悎缁熻锛岄渶鎻愬墠鐭ヤ細鍚庣銆�
+- 銆岄攢鍞鍒掋�嶉〉鎺掑簭寤鸿鎸夊垱寤烘椂闂村�掑簭锛屼笌鍚庡彴杩斿洖椤哄簭涓�鑷达紙鎸� id 鍊掑簭锛夈��
diff --git a/docs/sql/crm_sales_target_init.sql b/docs/sql/crm_sales_target_init.sql
new file mode 100644
index 0000000..6a90913
--- /dev/null
+++ b/docs/sql/crm_sales_target_init.sql
@@ -0,0 +1,75 @@
+-- =====================================================
+-- CRM 閿�鍞洰鏍囷紙閿�鍞鍒掞級鍔熻兘鍒濆鍖栬剼鏈�
+-- 1. 寤鸿〃 crm_sales_target
+-- 2. 鏁版嵁瀛楀吀锛堝懆鏈熺被鍨� / 鐩爣缁村害 / 鐘舵�侊級
+-- 3. 鑿滃崟涓庢寜閽潈闄愶紙钀ラ攢绠$悊 -> 閿�鍞鍒� / 杈炬垚鍒嗘瀽锛�
+-- =====================================================
+
+-- 1. 寤鸿〃
+CREATE TABLE IF NOT EXISTS `crm_sales_target` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `name` varchar(128) NOT NULL COMMENT '鐩爣鍚嶇О',
+  `period_type` int NOT NULL COMMENT '鍛ㄦ湡绫诲瀷(瀛楀吀 crm_sales_target_period_type: 1骞�/2瀛e害/3鏈�)',
+  `period_value` varchar(32) NOT NULL COMMENT '鍛ㄦ湡鍊硷紝濡� 2026 / 2026-Q1 / 2026-07',
+  `start_time` datetime NOT NULL COMMENT '鍛ㄦ湡寮�濮嬫椂闂�(淇濆瓨鏃剁敱鍛ㄦ湡璁$畻鍐欏叆)',
+  `end_time` datetime NOT NULL COMMENT '鍛ㄦ湡缁撴潫鏃堕棿(淇濆瓨鏃剁敱鍛ㄦ湡璁$畻鍐欏叆)',
+  `target_dimension` int NOT NULL COMMENT '鐩爣缁村害(瀛楀吀 crm_sales_target_dimension: 1涓汉/2閮ㄩ棬/3浜у搧)',
+  `target_user_id` bigint DEFAULT NULL COMMENT '鐩爣鐢ㄦ埛缂栧彿(缁村害=涓汉)',
+  `target_dept_id` bigint DEFAULT NULL COMMENT '鐩爣閮ㄩ棬缂栧彿(缁村害=閮ㄩ棬)',
+  `target_item_id` bigint DEFAULT NULL COMMENT '鐩爣鐗╂枡缂栧彿(缁村害=浜у搧锛屽叧鑱� MDM)',
+  `contract_target` decimal(18,2) DEFAULT NULL COMMENT '鍚堝悓閲戦鐩爣(鍏�)',
+  `receivable_target` decimal(18,2) DEFAULT NULL COMMENT '鍥炴閲戦鐩爣(鍏�)',
+  `business_target` decimal(18,2) DEFAULT NULL COMMENT '鍟嗘満閲戦鐩爣(鍏�)',
+  `customer_target` int DEFAULT NULL COMMENT '鏂板瀹㈡埛鏁扮洰鏍�',
+  `contract_count_target` int DEFAULT NULL COMMENT '鏂板鍚堝悓鏁扮洰鏍�',
+  `status` int NOT NULL DEFAULT 0 COMMENT '鐘舵��(瀛楀吀 crm_sales_target_status: 0鏈敓鏁�/1宸茬敓鏁�/2宸茬粨鏉�/3宸蹭綔搴�)',
+  `remark` varchar(255) DEFAULT NULL COMMENT '澶囨敞',
+  `creator` varchar(64) DEFAULT NULL COMMENT '鍒涘缓鑰�',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+  `updater` varchar(64) DEFAULT NULL COMMENT '鏇存柊鑰�',
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏈�鍚庢洿鏂版椂闂�',
+  `deleted` tinyint NOT NULL DEFAULT 0 COMMENT '鏄惁鍒犻櫎',
+  PRIMARY KEY (`id`),
+  KEY `idx_period` (`period_type`,`period_value`),
+  KEY `idx_dimension` (`target_dimension`,`target_user_id`,`target_dept_id`,`target_item_id`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CRM 閿�鍞洰鏍�';
+
+-- 2. 鏁版嵁瀛楀吀绫诲瀷
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`)
+VALUES (1065300, '閿�鍞洰鏍囧懆鏈熺被鍨�', 'crm_sales_target_period_type', 0, '閿�鍞洰鏍囩殑璁″垝鍛ㄦ湡绫诲瀷', '1', NOW(), '1', NOW(), b'0', NULL),
+       (1065301, '閿�鍞洰鏍囩淮搴�', 'crm_sales_target_dimension', 0, '閿�鍞洰鏍囨寜浠�涔堢淮搴﹁瀹�', '1', NOW(), '1', NOW(), b'0', NULL),
+       (1065302, '閿�鍞洰鏍囩姸鎬�', 'crm_sales_target_status', 0, '閿�鍞洰鏍囩殑鐢熷懡鍛ㄦ湡鐘舵��', '1', NOW(), '1', NOW(), b'0', NULL);
+
+-- 2.1 鏁版嵁瀛楀吀鏁版嵁
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES
+  -- 鍛ㄦ湡绫诲瀷
+  (1065310, 1, '骞村害', '1', 'crm_sales_target_period_type', 0, 'primary', '', '', '1', NOW(), '1', NOW(), b'0'),
+  (1065311, 2, '瀛e害', '2', 'crm_sales_target_period_type', 0, 'success', '', '', '1', NOW(), '1', NOW(), b'0'),
+  (1065312, 3, '鏈堝害', '3', 'crm_sales_target_period_type', 0, 'warning', '', '', '1', NOW(), '1', NOW(), b'0'),
+  -- 鐩爣缁村害
+  (1065320, 1, '涓汉', '1', 'crm_sales_target_dimension', 0, 'primary', '', '', '1', NOW(), '1', NOW(), b'0'),
+  (1065321, 2, '閮ㄩ棬', '2', 'crm_sales_target_dimension', 0, 'success', '', '', '1', NOW(), '1', NOW(), b'0'),
+  (1065322, 3, '浜у搧', '3', 'crm_sales_target_dimension', 0, 'warning', '', '', '1', NOW(), '1', NOW(), b'0'),
+  -- 鐘舵��
+  (1065330, 0, '鏈敓鏁�', '0', 'crm_sales_target_status', 0, 'info', '', '', '1', NOW(), '1', NOW(), b'0'),
+  (1065331, 1, '宸茬敓鏁�', '1', 'crm_sales_target_status', 0, 'success', '', '', '1', NOW(), '1', NOW(), b'0'),
+  (1065332, 2, '宸茬粨鏉�', '2', 'crm_sales_target_status', 0, 'warning', '', '', '1', NOW(), '1', NOW(), b'0'),
+  (1065333, 3, '宸蹭綔搴�', '3', 'crm_sales_target_status', 0, 'danger', '', '', '1', NOW(), '1', NOW(), b'0');
+
+-- 3. 鑿滃崟锛氳惀閿�绠$悊(id=2617) -> 閿�鍞鍒� / 杈炬垚鍒嗘瀽
+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`)
+VALUES
+  (1065000, '閿�鍞鍒�', '', 2, 62, 2617, 'salesTarget', 'ep:data-line', 'crm/salesTarget/index', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+  (1065100, '杈炬垚鍒嗘瀽', '', 2, 63, 2617, 'salesTarget/analysis', 'ep:pie-chart', 'crm/salesTarget/analysis/index', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 3.1 閿�鍞鍒掓寜閽潈闄�
+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`)
+VALUES
+  (1065001, '閿�鍞鍒掓煡璇�', 'crm:sales-target:query', 3, 1, 1065000, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+  (1065002, '閿�鍞鍒掑垱寤�', 'crm:sales-target:create', 3, 2, 1065000, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+  (1065003, '閿�鍞鍒掓洿鏂�', 'crm:sales-target:update', 3, 3, 1065000, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+  (1065004, '閿�鍞鍒掑垹闄�', 'crm:sales-target:delete', 3, 4, 1065000, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+  (1065005, '閿�鍞鍒掑鍑�', 'crm:sales-target:export', 3, 5, 1065000, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+  (1065101, '杈炬垚鍒嗘瀽鏌ヨ', 'crm:sales-target:query', 3, 1, 1065100, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
diff --git a/sales-target-analysis.png b/sales-target-analysis.png
new file mode 100644
index 0000000..41c6439
--- /dev/null
+++ b/sales-target-analysis.png
Binary files differ
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/CrmSalesTargetController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/CrmSalesTargetController.java
new file mode 100644
index 0000000..34d1ec7
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/CrmSalesTargetController.java
@@ -0,0 +1,104 @@
+package cn.iocoder.yudao.module.crm.controller.admin.salesTarget;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.CrmSalesTargetChangeStatusReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.CrmSalesTargetPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.CrmSalesTargetRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.CrmSalesTargetSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.analysis.CrmSalesTargetAnalysisReqVO;
+import cn.iocoder.yudao.module.crm.service.salesTarget.CrmSalesTargetService;
+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.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+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;
+import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
+
+@Tag(name = "绠$悊鍚庡彴 - CRM 閿�鍞洰鏍�")
+@RestController
+@RequestMapping("/crm/sales-target")
+@Validated
+public class CrmSalesTargetController {
+
+    @Resource
+    private CrmSalesTargetService salesTargetService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓閿�鍞洰鏍�")
+    @PreAuthorize("@ss.hasPermission('crm:sales-target:create')")
+    public CommonResult<Long> createSalesTarget(@Valid @RequestBody CrmSalesTargetSaveReqVO createReqVO) {
+        return success(salesTargetService.createSalesTarget(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊閿�鍞洰鏍�")
+    @PreAuthorize("@ss.hasPermission('crm:sales-target:update')")
+    public CommonResult<Boolean> updateSalesTarget(@Valid @RequestBody CrmSalesTargetSaveReqVO updateReqVO) {
+        salesTargetService.updateSalesTarget(updateReqVO);
+        return success(true);
+    }
+
+    @PutMapping("/change-status")
+    @Operation(summary = "鍙樻洿閿�鍞洰鏍囩姸鎬�", description = "浣滃簾/鎭㈠/鐢熸晥绛�")
+    @PreAuthorize("@ss.hasPermission('crm:sales-target:update')")
+    public CommonResult<Boolean> changeSalesTargetStatus(@Valid @RequestBody CrmSalesTargetChangeStatusReqVO reqVO) {
+        salesTargetService.changeSalesTargetStatus(reqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎閿�鍞洰鏍�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('crm:sales-target:delete')")
+    public CommonResult<Boolean> deleteSalesTarget(@RequestParam("id") Long id) {
+        salesTargetService.deleteSalesTarget(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱閿�鍞洰鏍�", description = "鍚疄闄呰揪鎴愪笌杈炬垚鐜�")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('crm:sales-target:query')")
+    public CommonResult<CrmSalesTargetRespVO> getSalesTarget(@RequestParam("id") Long id) {
+        return success(salesTargetService.getSalesTarget(id));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱閿�鍞洰鏍囧垎椤�", description = "鍚疄闄呰揪鎴愪笌杈炬垚鐜�")
+    @PreAuthorize("@ss.hasPermission('crm:sales-target:query')")
+    public CommonResult<PageResult<CrmSalesTargetRespVO>> getSalesTargetPage(@Valid CrmSalesTargetPageReqVO pageReqVO) {
+        return success(salesTargetService.getSalesTargetPage(pageReqVO));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "瀵煎嚭閿�鍞洰鏍� Excel")
+    @PreAuthorize("@ss.hasPermission('crm:sales-target:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportSalesTargetExcel(@Valid CrmSalesTargetPageReqVO exportReqVO,
+                                       HttpServletResponse response) throws IOException {
+        exportReqVO.setPageSize(PAGE_SIZE_NONE);
+        List<CrmSalesTargetRespVO> list = salesTargetService.getSalesTargetList(exportReqVO);
+        ExcelUtils.write(response, "閿�鍞洰鏍�.xls", "鏁版嵁", CrmSalesTargetRespVO.class, list);
+    }
+
+    @GetMapping("/get-achievement-analysis")
+    @Operation(summary = "鑾峰緱閿�鍞洰鏍囪揪鎴愬垎鏋�", description = "鎸夊懆鏈� + 缁村害杩囨护鐩爣锛岃繑鍥炵洰鏍� vs 瀹為檯 + 杈炬垚鐜�")
+    @PreAuthorize("@ss.hasPermission('crm:sales-target:query')")
+    public CommonResult<List<CrmSalesTargetRespVO>> getSalesTargetAnalysis(@Valid CrmSalesTargetAnalysisReqVO reqVO) {
+        return success(salesTargetService.getSalesTargetAnalysis(reqVO));
+    }
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetChangeStatusReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetChangeStatusReqVO.java
new file mode 100644
index 0000000..6530cf2
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetChangeStatusReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.crm.enums.salesTarget.CrmSalesTargetStatusEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 閿�鍞洰鏍囩姸鎬佸彉鏇� Request VO")
+@Data
+public class CrmSalesTargetChangeStatusReqVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "缂栧彿涓嶈兘涓虹┖")
+    private Long id;
+
+    @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+    @NotNull(message = "鐘舵�佷笉鑳戒负绌�")
+    @InEnum(CrmSalesTargetStatusEnum.class)
+    private Integer status;
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetPageReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetPageReqVO.java
new file mode 100644
index 0000000..96bcb58
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetPageReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 閿�鍞洰鏍囧垎椤� Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CrmSalesTargetPageReqVO extends PageParam {
+
+    @Schema(description = "鐩爣鍚嶇О", example = "2026 涓婂崐骞撮攢鍞洰鏍�")
+    private String name;
+
+    @Schema(description = "鍛ㄦ湡绫诲瀷", example = "1")
+    private Integer periodType;
+
+    @Schema(description = "鍛ㄦ湡鍊硷紝濡� 2026 / 2026-Q1 / 2026-07", example = "2026-Q1")
+    private String periodValue;
+
+    @Schema(description = "鐩爣缁村害", example = "1")
+    private Integer targetDimension;
+
+    @Schema(description = "鐘舵��", example = "1")
+    private Integer status;
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetRespVO.java
new file mode 100644
index 0000000..a42f655
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetRespVO.java
@@ -0,0 +1,130 @@
+package cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 閿�鍞洰鏍� Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class CrmSalesTargetRespVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "鐩爣鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026 涓婂崐骞撮攢鍞洰鏍�")
+    @ExcelProperty("鐩爣鍚嶇О")
+    private String name;
+
+    @Schema(description = "鍛ㄦ湡绫诲瀷", example = "1")
+    @ExcelProperty(value = "鍛ㄦ湡绫诲瀷", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.CRM_SALES_TARGET_PERIOD_TYPE)
+    private Integer periodType;
+
+    @Schema(description = "鍛ㄦ湡鍊硷紝濡� 2026 / 2026-Q1 / 2026-07", example = "2026-Q1")
+    @ExcelProperty("鍛ㄦ湡鍊�")
+    private String periodValue;
+
+    @Schema(description = "鍛ㄦ湡寮�濮嬫椂闂�", example = "2026-01-01 00:00:00")
+    private LocalDateTime startTime;
+
+    @Schema(description = "鍛ㄦ湡缁撴潫鏃堕棿", example = "2026-03-31 23:59:59")
+    private LocalDateTime endTime;
+
+    @Schema(description = "鐩爣缁村害", example = "1")
+    @ExcelProperty(value = "鐩爣缁村害", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.CRM_SALES_TARGET_DIMENSION)
+    private Integer targetDimension;
+
+    @Schema(description = "鐩爣鐢ㄦ埛缂栧彿锛堢淮搴�=涓汉锛�", example = "1")
+    private Long targetUserId;
+    @Schema(description = "鐩爣鐢ㄦ埛鍚嶇О", example = "寮犱笁")
+    @ExcelProperty("鐩爣瀵硅薄")
+    private String targetUserName;
+
+    @Schema(description = "鐩爣閮ㄩ棬缂栧彿锛堢淮搴�=閮ㄩ棬锛�", example = "100")
+    private Long targetDeptId;
+    @Schema(description = "鐩爣閮ㄩ棬鍚嶇О", example = "閿�鍞儴")
+    private String targetDeptName;
+
+    @Schema(description = "鐩爣鐗╂枡缂栧彿锛堢淮搴�=浜у搧锛�", example = "1")
+    private Long targetItemId;
+    @Schema(description = "鐩爣鐗╂枡鍚嶇О", example = "铻轰笣")
+    private String targetItemName;
+
+    @Schema(description = "鍚堝悓閲戦鐩爣锛堝厓锛�", example = "1000000")
+    @ExcelProperty("鍚堝悓閲戦鐩爣")
+    private BigDecimal contractTarget;
+    @Schema(description = "鍥炴閲戦鐩爣锛堝厓锛�", example = "800000")
+    @ExcelProperty("鍥炴閲戦鐩爣")
+    private BigDecimal receivableTarget;
+    @Schema(description = "鍟嗘満閲戦鐩爣锛堝厓锛�", example = "1500000")
+    @ExcelProperty("鍟嗘満閲戦鐩爣")
+    private BigDecimal businessTarget;
+    @Schema(description = "鏂板瀹㈡埛鏁扮洰鏍�", example = "50")
+    @ExcelProperty("鏂板瀹㈡埛鏁扮洰鏍�")
+    private Integer customerTarget;
+    @Schema(description = "鏂板鍚堝悓鏁扮洰鏍�", example = "30")
+    @ExcelProperty("鏂板鍚堝悓鏁扮洰鏍�")
+    private Integer contractCountTarget;
+
+    @Schema(description = "瀹為檯鍚堝悓閲戦锛堝厓锛�", example = "500000")
+    @ExcelProperty("瀹為檯鍚堝悓閲戦")
+    private BigDecimal actualContractPrice;
+    @Schema(description = "瀹為檯鍥炴閲戦锛堝厓锛�", example = "300000")
+    @ExcelProperty("瀹為檯鍥炴閲戦")
+    private BigDecimal actualReceivablePrice;
+    @Schema(description = "瀹為檯鍟嗘満閲戦锛堝厓锛�", example = "800000")
+    @ExcelProperty("瀹為檯鍟嗘満閲戦")
+    private BigDecimal actualBusinessPrice;
+    @Schema(description = "瀹為檯鏂板瀹㈡埛鏁�", example = "20")
+    @ExcelProperty("瀹為檯鏂板瀹㈡埛鏁�")
+    private Long actualCustomerCount;
+    @Schema(description = "瀹為檯鏂板鍚堝悓鏁�", example = "15")
+    @ExcelProperty("瀹為檯鏂板鍚堝悓鏁�")
+    private Long actualContractCount;
+
+    @Schema(description = "鍚堝悓杈炬垚鐜囷紙鐧惧垎姣旓級", example = "50.0")
+    @ExcelProperty("鍚堝悓杈炬垚鐜�(%)")
+    private BigDecimal contractRate;
+    @Schema(description = "鍥炴杈炬垚鐜囷紙鐧惧垎姣旓級", example = "37.5")
+    @ExcelProperty("鍥炴杈炬垚鐜�(%)")
+    private BigDecimal receivableRate;
+    @Schema(description = "鍟嗘満杈炬垚鐜囷紙鐧惧垎姣旓級", example = "53.3")
+    @ExcelProperty("鍟嗘満杈炬垚鐜�(%)")
+    private BigDecimal businessRate;
+    @Schema(description = "鏂板瀹㈡埛鏁拌揪鎴愮巼锛堢櫨鍒嗘瘮锛�", example = "40.0")
+    @ExcelProperty("鏂板瀹㈡埛鏁拌揪鎴愮巼(%)")
+    private BigDecimal customerRate;
+    @Schema(description = "鏂板鍚堝悓鏁拌揪鎴愮巼锛堢櫨鍒嗘瘮锛�", example = "50.0")
+    @ExcelProperty("鏂板鍚堝悓鏁拌揪鎴愮巼(%)")
+    private BigDecimal contractCountRate;
+
+    @Schema(description = "鐘舵��", example = "1")
+    @ExcelProperty(value = "鐘舵��", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.CRM_SALES_TARGET_STATUS)
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+    @Schema(description = "鍒涘缓浜�", example = "1")
+    private String creator;
+    @Schema(description = "鍒涘缓浜哄悕绉�", example = "瓒呯骇绠$悊鍛�")
+    @ExcelProperty("鍒涘缓浜�")
+    private String creatorName;
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetSaveReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetSaveReqVO.java
new file mode 100644
index 0000000..a1b452b
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/CrmSalesTargetSaveReqVO.java
@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.crm.enums.salesTarget.CrmSalesTargetDimensionEnum;
+import cn.iocoder.yudao.module.crm.enums.salesTarget.CrmSalesTargetPeriodTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 閿�鍞洰鏍囨柊澧�/淇敼 Request VO")
+@Data
+public class CrmSalesTargetSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "鐩爣鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026 涓婂崐骞撮攢鍞洰鏍�")
+    @NotEmpty(message = "鐩爣鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "鍛ㄦ湡绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍛ㄦ湡绫诲瀷涓嶈兘涓虹┖")
+    @InEnum(CrmSalesTargetPeriodTypeEnum.class)
+    private Integer periodType;
+
+    @Schema(description = "鍛ㄦ湡鍊硷紝濡� 2026 / 2026-Q1 / 2026-07", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-Q1")
+    @NotEmpty(message = "鍛ㄦ湡鍊间笉鑳戒负绌�")
+    private String periodValue;
+
+    @Schema(description = "鐩爣缁村害", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鐩爣缁村害涓嶈兘涓虹┖")
+    @InEnum(CrmSalesTargetDimensionEnum.class)
+    private Integer targetDimension;
+
+    @Schema(description = "鐩爣鐢ㄦ埛缂栧彿锛堢淮搴�=涓汉锛�", example = "1")
+    private Long targetUserId;
+
+    @Schema(description = "鐩爣閮ㄩ棬缂栧彿锛堢淮搴�=閮ㄩ棬锛�", example = "100")
+    private Long targetDeptId;
+
+    @Schema(description = "鐩爣鐗╂枡缂栧彿锛堢淮搴�=浜у搧锛屽叧鑱� MDM锛�", example = "1")
+    private Long targetItemId;
+
+    @Schema(description = "鍚堝悓閲戦鐩爣锛堝厓锛�", example = "1000000")
+    private BigDecimal contractTarget;
+
+    @Schema(description = "鍥炴閲戦鐩爣锛堝厓锛�", example = "800000")
+    private BigDecimal receivableTarget;
+
+    @Schema(description = "鍟嗘満閲戦鐩爣锛堝厓锛�", example = "1500000")
+    private BigDecimal businessTarget;
+
+    @Schema(description = "鏂板瀹㈡埛鏁扮洰鏍�", example = "50")
+    private Integer customerTarget;
+
+    @Schema(description = "鏂板鍚堝悓鏁扮洰鏍�", example = "30")
+    private Integer contractCountTarget;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/analysis/CrmSalesTargetAnalysisReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/analysis/CrmSalesTargetAnalysisReqVO.java
new file mode 100644
index 0000000..fce1a5d
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/salesTarget/vo/analysis/CrmSalesTargetAnalysisReqVO.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.analysis;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.crm.enums.salesTarget.CrmSalesTargetDimensionEnum;
+import cn.iocoder.yudao.module.crm.enums.salesTarget.CrmSalesTargetPeriodTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - CRM 閿�鍞洰鏍囪揪鎴愬垎鏋� Request VO")
+@Data
+public class CrmSalesTargetAnalysisReqVO {
+
+    @Schema(description = "鍛ㄦ湡绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍛ㄦ湡绫诲瀷涓嶈兘涓虹┖")
+    @InEnum(CrmSalesTargetPeriodTypeEnum.class)
+    private Integer periodType;
+
+    @Schema(description = "鍛ㄦ湡鍊硷紝濡� 2026 / 2026-Q1 / 2026-07", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
+    @NotEmpty(message = "鍛ㄦ湡鍊间笉鑳戒负绌�")
+    private String periodValue;
+
+    @Schema(description = "鐘舵�侊紝涓虹┖鍒欏叏閮�", example = "1")
+    private Integer status;
+
+    @Schema(description = "鐩爣缁村害锛屼负绌哄垯鍏ㄩ儴", example = "1")
+    @InEnum(CrmSalesTargetDimensionEnum.class)
+    private Integer targetDimension;
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/salesTarget/CrmSalesTargetDO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/salesTarget/CrmSalesTargetDO.java
new file mode 100644
index 0000000..5349b75
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/salesTarget/CrmSalesTargetDO.java
@@ -0,0 +1,107 @@
+package cn.iocoder.yudao.module.crm.dal.dataobject.salesTarget;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
+import cn.iocoder.yudao.module.crm.enums.salesTarget.CrmSalesTargetDimensionEnum;
+import cn.iocoder.yudao.module.crm.enums.salesTarget.CrmSalesTargetPeriodTypeEnum;
+import cn.iocoder.yudao.module.crm.enums.salesTarget.CrmSalesTargetStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * CRM 閿�鍞洰鏍� DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("crm_sales_target")
+@KeySequence("crm_sales_target_seq") // 鐢ㄤ簬 Oracle銆丳ostgreSQL銆並ingbase銆丏B2銆丠2 鏁版嵁搴撶殑涓婚敭鑷銆傚鏋滄槸 MySQL 绛夋暟鎹簱锛屽彲涓嶅啓銆�
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CrmSalesTargetDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 鐩爣鍚嶇О
+     */
+    private String name;
+    /**
+     * 鍛ㄦ湡绫诲瀷
+     *
+     * 瀛楀吀 {@link DictTypeConstants#CRM_SALES_TARGET_PERIOD_TYPE} / 鏋氫妇 {@link CrmSalesTargetPeriodTypeEnum}
+     */
+    private Integer periodType;
+    /**
+     * 鍛ㄦ湡鍊硷紝濡� 2026 / 2026-Q1 / 2026-07
+     */
+    private String periodValue;
+    /**
+     * 鍛ㄦ湡寮�濮嬫椂闂达紙淇濆瓨鏃剁敱鍛ㄦ湡璁$畻鍐欏叆锛�
+     */
+    private LocalDateTime startTime;
+    /**
+     * 鍛ㄦ湡缁撴潫鏃堕棿锛堜繚瀛樻椂鐢卞懆鏈熻绠楀啓鍏ワ級
+     */
+    private LocalDateTime endTime;
+    /**
+     * 鐩爣缁村害
+     *
+     * 瀛楀吀 {@link DictTypeConstants#CRM_SALES_TARGET_DIMENSION} / 鏋氫妇 {@link CrmSalesTargetDimensionEnum}
+     */
+    private Integer targetDimension;
+    /**
+     * 鐩爣鐢ㄦ埛缂栧彿锛堢淮搴�=涓汉锛�
+     */
+    private Long targetUserId;
+    /**
+     * 鐩爣閮ㄩ棬缂栧彿锛堢淮搴�=閮ㄩ棬锛�
+     */
+    private Long targetDeptId;
+    /**
+     * 鐩爣鐗╂枡缂栧彿锛堢淮搴�=浜у搧锛屽叧鑱� MDM锛�
+     */
+    private Long targetItemId;
+    /**
+     * 鍚堝悓閲戦鐩爣锛堝厓锛�
+     */
+    private BigDecimal contractTarget;
+    /**
+     * 鍥炴閲戦鐩爣锛堝厓锛�
+     */
+    private BigDecimal receivableTarget;
+    /**
+     * 鍟嗘満閲戦鐩爣锛堝厓锛�
+     */
+    private BigDecimal businessTarget;
+    /**
+     * 鏂板瀹㈡埛鏁扮洰鏍�
+     */
+    private Integer customerTarget;
+    /**
+     * 鏂板鍚堝悓鏁扮洰鏍�
+     */
+    private Integer contractCountTarget;
+    /**
+     * 鐘舵��
+     *
+     * 瀛楀吀 {@link DictTypeConstants#CRM_SALES_TARGET_STATUS} / 鏋氫妇 {@link CrmSalesTargetStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/salesTarget/CrmSalesTargetAchievementMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/salesTarget/CrmSalesTargetAchievementMapper.java
new file mode 100644
index 0000000..b988284
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/salesTarget/CrmSalesTargetAchievementMapper.java
@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.crm.dal.mysql.salesTarget;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Collection;
+
+/**
+ * 閿�鍞洰鏍囪揪鎴愮粺璁� Mapper
+ *
+ * 缁熻瀹為檯杈炬垚閲戦/鏁伴噺锛屼粠 CRM 鐜版湁涓氬姟琛ㄦ寜鍛ㄦ湡鑼冨洿 + 褰掑睘浜猴紙鎴栫墿鏂欙級鑱氬悎銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface CrmSalesTargetAchievementMapper {
+
+    /**
+     * 鍚堝悓閲戦锛堜釜浜�/閮ㄩ棬缁村害锛�
+     */
+    BigDecimal selectContractPriceByUserIds(@Param("userIds") Collection<Long> userIds,
+                                            @Param("start") LocalDateTime start, @Param("end") LocalDateTime end);
+
+    /**
+     * 鍚堝悓鏁伴噺锛堜釜浜�/閮ㄩ棬缁村害锛�
+     */
+    Long selectContractCountByUserIds(@Param("userIds") Collection<Long> userIds,
+                                      @Param("start") LocalDateTime start, @Param("end") LocalDateTime end);
+
+    /**
+     * 鍥炴閲戦锛堜釜浜�/閮ㄩ棬缁村害锛�
+     */
+    BigDecimal selectReceivablePriceByUserIds(@Param("userIds") Collection<Long> userIds,
+                                              @Param("start") LocalDateTime start, @Param("end") LocalDateTime end);
+
+    /**
+     * 鍟嗘満閲戦锛堜釜浜�/閮ㄩ棬缁村害锛岃耽鍗曪級
+     */
+    BigDecimal selectBusinessPriceByUserIds(@Param("userIds") Collection<Long> userIds,
+                                            @Param("start") LocalDateTime start, @Param("end") LocalDateTime end);
+
+    /**
+     * 鏂板瀹㈡埛鏁帮紙涓汉/閮ㄩ棬缁村害锛�
+     */
+    Long selectCustomerCountByUserIds(@Param("userIds") Collection<Long> userIds,
+                                      @Param("start") LocalDateTime start, @Param("end") LocalDateTime end);
+
+    /**
+     * 鍚堝悓閲戦锛堜骇鍝佺淮搴︼紝鎸夊悎鍚屼骇鍝佹槑缁嗗綊灞炵墿鏂欙級
+     */
+    BigDecimal selectContractPriceByItemId(@Param("itemId") Long itemId,
+                                           @Param("start") LocalDateTime start, @Param("end") LocalDateTime end);
+
+    /**
+     * 鍚堝悓鏁伴噺锛堜骇鍝佺淮搴︼紝鎸夊悎鍚屼骇鍝佹槑缁嗗綊灞炵墿鏂欙級
+     */
+    Long selectContractCountByItemId(@Param("itemId") Long itemId,
+                                     @Param("start") LocalDateTime start, @Param("end") LocalDateTime end);
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/salesTarget/CrmSalesTargetMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/salesTarget/CrmSalesTargetMapper.java
new file mode 100644
index 0000000..485d920
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/salesTarget/CrmSalesTargetMapper.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.crm.dal.mysql.salesTarget;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.CrmSalesTargetPageReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.salesTarget.CrmSalesTargetDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 閿�鍞洰鏍� Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface CrmSalesTargetMapper extends BaseMapperX<CrmSalesTargetDO> {
+
+    default PageResult<CrmSalesTargetDO> selectPage(CrmSalesTargetPageReqVO reqVO) {
+        return selectPage(reqVO, buildQuery(reqVO));
+    }
+
+    default List<CrmSalesTargetDO> selectList(CrmSalesTargetPageReqVO reqVO) {
+        return selectList(buildQuery(reqVO));
+    }
+
+    private LambdaQueryWrapperX<CrmSalesTargetDO> buildQuery(CrmSalesTargetPageReqVO reqVO) {
+        return new LambdaQueryWrapperX<CrmSalesTargetDO>()
+                .likeIfPresent(CrmSalesTargetDO::getName, reqVO.getName())
+                .eqIfPresent(CrmSalesTargetDO::getPeriodType, reqVO.getPeriodType())
+                .eqIfPresent(CrmSalesTargetDO::getPeriodValue, reqVO.getPeriodValue())
+                .eqIfPresent(CrmSalesTargetDO::getTargetDimension, reqVO.getTargetDimension())
+                .eqIfPresent(CrmSalesTargetDO::getStatus, reqVO.getStatus())
+                .orderByDesc(CrmSalesTargetDO::getId);
+    }
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java
index b38795f..1dbd1b9 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java
@@ -16,5 +16,8 @@
     String CRM_FOLLOW_UP_TYPE = "crm_follow_up_type"; // CRM 璺熻繘鏂瑰紡
     String CRM_RECEIVABLE_RETURN_TYPE = "crm_receivable_return_type"; // CRM 鍥炴鏂瑰紡
     String CRM_INVOICE_TYPE = "crm_invoice_type"; // CRM 鍙戠エ绫诲瀷
+    String CRM_SALES_TARGET_PERIOD_TYPE = "crm_sales_target_period_type"; // CRM 閿�鍞洰鏍囧懆鏈熺被鍨�
+    String CRM_SALES_TARGET_DIMENSION = "crm_sales_target_dimension"; // CRM 閿�鍞洰鏍囩淮搴�
+    String CRM_SALES_TARGET_STATUS = "crm_sales_target_status"; // CRM 閿�鍞洰鏍囩姸鎬�
 
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
index 59f92fa..ffcdf9c 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -136,4 +136,8 @@
     ErrorCode RECEIVABLE_CREATE_FAIL_INVOICE_CONTRACT_MISMATCH = new ErrorCode(1_020_016_011, "鍒涘缓鍥炴澶辫触锛屽師鍥狅細鍏宠仈鐨勫紑绁ㄤ笌鎵�閫夊悎鍚屼笉鍖归厤");
     ErrorCode RECEIVABLE_CREATE_FAIL_INVOICE_PRICE_EXCEEDS = new ErrorCode(1_020_016_012, "鍒涘缓鍥炴澶辫触锛屽師鍥狅細鍥炴閲戦瓒呭嚭璇ュ紑绁ㄥ墿浣欏彲鍥炴閲戦锛岀洰鍓嶅彲鍥炴锛歿} 鍏�");
 
+    ErrorCode CRM_SALES_TARGET_NOT_EXISTS = new ErrorCode(1_020_017_000, "閿�鍞洰鏍囦笉瀛樺湪");
+    ErrorCode CRM_SALES_TARGET_PERIOD_VALUE_INVALID = new ErrorCode(1_020_017_001, "鍛ㄦ湡鍊笺�恵}銆戞牸寮忎笉姝g‘锛屽簲涓� {}");
+    ErrorCode CRM_SALES_TARGET_DIMENSION_TARGET_MISSING = new ErrorCode(1_020_017_002, "璇峰~鍐欑洰鏍噞}缂栧彿");
+
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/salesTarget/CrmSalesTargetDimensionEnum.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/salesTarget/CrmSalesTargetDimensionEnum.java
new file mode 100644
index 0000000..4f03060
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/salesTarget/CrmSalesTargetDimensionEnum.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.crm.enums.salesTarget;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 閿�鍞洰鏍囩殑鐩爣缁村害鏋氫妇
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@RequiredArgsConstructor
+@Getter
+public enum CrmSalesTargetDimensionEnum implements ArrayValuable<Integer> {
+
+    USER(1, "涓汉"),
+    DEPT(2, "閮ㄩ棬"),
+    ITEM(3, "浜у搧");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(CrmSalesTargetDimensionEnum::getType).toArray(Integer[]::new);
+
+    /**
+     * 缁村害绫诲瀷
+     */
+    private final Integer type;
+    /**
+     * 缁村害鍚嶇О
+     */
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+    public static CrmSalesTargetDimensionEnum fromType(Integer type) {
+        return Arrays.stream(values())
+                .filter(value -> value.getType().equals(type))
+                .findFirst()
+                .orElse(null);
+    }
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/salesTarget/CrmSalesTargetPeriodTypeEnum.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/salesTarget/CrmSalesTargetPeriodTypeEnum.java
new file mode 100644
index 0000000..96678f4
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/salesTarget/CrmSalesTargetPeriodTypeEnum.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.crm.enums.salesTarget;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 閿�鍞洰鏍囩殑鍛ㄦ湡绫诲瀷鏋氫妇
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@RequiredArgsConstructor
+@Getter
+public enum CrmSalesTargetPeriodTypeEnum implements ArrayValuable<Integer> {
+
+    YEAR(1, "骞村害"),
+    QUARTER(2, "瀛e害"),
+    MONTH(3, "鏈堝害");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(CrmSalesTargetPeriodTypeEnum::getType).toArray(Integer[]::new);
+
+    /**
+     * 鍛ㄦ湡绫诲瀷
+     */
+    private final Integer type;
+    /**
+     * 鍛ㄦ湡鍚嶇О
+     */
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+    public static CrmSalesTargetPeriodTypeEnum fromType(Integer type) {
+        return Arrays.stream(values())
+                .filter(value -> value.getType().equals(type))
+                .findFirst()
+                .orElse(null);
+    }
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/salesTarget/CrmSalesTargetStatusEnum.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/salesTarget/CrmSalesTargetStatusEnum.java
new file mode 100644
index 0000000..c184f6b
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/salesTarget/CrmSalesTargetStatusEnum.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.crm.enums.salesTarget;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 閿�鍞洰鏍囩殑鐘舵�佹灇涓�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@RequiredArgsConstructor
+@Getter
+public enum CrmSalesTargetStatusEnum implements ArrayValuable<Integer> {
+
+    NOT_ACTIVE(0, "鏈敓鏁�"),
+    ACTIVE(1, "宸茬敓鏁�"),
+    END(2, "宸茬粨鏉�"),
+    INVALID(3, "宸蹭綔搴�");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(CrmSalesTargetStatusEnum::getStatus).toArray(Integer[]::new);
+
+    /**
+     * 鐘舵��
+     */
+    private final Integer status;
+    /**
+     * 鐘舵�佸悕绉�
+     */
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+    public static CrmSalesTargetStatusEnum fromStatus(Integer status) {
+        return Arrays.stream(values())
+                .filter(value -> value.getStatus().equals(status))
+                .findFirst()
+                .orElse(null);
+    }
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/salesTarget/CrmSalesTargetService.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/salesTarget/CrmSalesTargetService.java
new file mode 100644
index 0000000..f7f3064
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/salesTarget/CrmSalesTargetService.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.crm.service.salesTarget;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.CrmSalesTargetChangeStatusReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.CrmSalesTargetPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.CrmSalesTargetRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.CrmSalesTargetSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.analysis.CrmSalesTargetAnalysisReqVO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
+/**
+ * CRM 閿�鍞洰鏍� Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface CrmSalesTargetService {
+
+    /**
+     * 鍒涘缓閿�鍞洰鏍�
+     *
+     * @param createReqVO 鍒涘缓淇℃伅
+     * @return 缂栧彿
+     */
+    Long createSalesTarget(@Valid CrmSalesTargetSaveReqVO createReqVO);
+
+    /**
+     * 鏇存柊閿�鍞洰鏍�
+     *
+     * @param updateReqVO 鏇存柊淇℃伅
+     */
+    void updateSalesTarget(@Valid CrmSalesTargetSaveReqVO updateReqVO);
+
+    /**
+     * 鍙樻洿閿�鍞洰鏍囩姸鎬侊紙浣滃簾/鎭㈠绛夛級
+     *
+     * @param reqVO 鐘舵�佸彉鏇翠俊鎭�
+     */
+    void changeSalesTargetStatus(@Valid CrmSalesTargetChangeStatusReqVO reqVO);
+
+    /**
+     * 鍒犻櫎閿�鍞洰鏍�
+     *
+     * @param id 缂栧彿
+     */
+    void deleteSalesTarget(Long id);
+
+    /**
+     * 鑾峰緱閿�鍞洰鏍囷紙鍚疄闄呰揪鎴愶級
+     *
+     * @param id 缂栧彿
+     * @return 閿�鍞洰鏍�
+     */
+    CrmSalesTargetRespVO getSalesTarget(Long id);
+
+    /**
+     * 鑾峰緱閿�鍞洰鏍囧垎椤碉紙鍚疄闄呰揪鎴愶級
+     *
+     * @param pageReqVO 鍒嗛〉鏌ヨ
+     * @return 閿�鍞洰鏍囧垎椤�
+     */
+    PageResult<CrmSalesTargetRespVO> getSalesTargetPage(CrmSalesTargetPageReqVO pageReqVO);
+
+    /**
+     * 鑾峰緱閿�鍞洰鏍囧垪琛紙鍏ㄩ噺锛岀敤浜� Excel 瀵煎嚭锛�
+     *
+     * @param reqVO 鏌ヨ鏉′欢
+     * @return 閿�鍞洰鏍囧垪琛�
+     */
+    List<CrmSalesTargetRespVO> getSalesTargetList(CrmSalesTargetPageReqVO reqVO);
+
+    /**
+     * 鑾峰緱閿�鍞洰鏍囪揪鎴愬垎鏋愬垪琛紙鎸夊懆鏈�+缁村害杩囨护锛屽惈瀹為檯杈炬垚涓庤揪鎴愮巼锛�
+     *
+     * @param reqVO 鍒嗘瀽鏌ヨ
+     * @return 杈炬垚鍒嗘瀽鍒楄〃
+     */
+    List<CrmSalesTargetRespVO> getSalesTargetAnalysis(CrmSalesTargetAnalysisReqVO reqVO);
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/salesTarget/CrmSalesTargetServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/salesTarget/CrmSalesTargetServiceImpl.java
new file mode 100644
index 0000000..9784da9
--- /dev/null
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/salesTarget/CrmSalesTargetServiceImpl.java
@@ -0,0 +1,368 @@
+package cn.iocoder.yudao.module.crm.service.salesTarget;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.CrmSalesTargetChangeStatusReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.CrmSalesTargetPageReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.CrmSalesTargetRespVO;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.CrmSalesTargetSaveReqVO;
+import cn.iocoder.yudao.module.crm.controller.admin.salesTarget.vo.analysis.CrmSalesTargetAnalysisReqVO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.salesTarget.CrmSalesTargetDO;
+import cn.iocoder.yudao.module.crm.dal.mysql.salesTarget.CrmSalesTargetAchievementMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.salesTarget.CrmSalesTargetMapper;
+import cn.iocoder.yudao.module.crm.enums.salesTarget.CrmSalesTargetDimensionEnum;
+import cn.iocoder.yudao.module.crm.enums.salesTarget.CrmSalesTargetPeriodTypeEnum;
+import cn.iocoder.yudao.module.crm.enums.salesTarget.CrmSalesTargetStatusEnum;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
+
+/**
+ * 閿�鍞洰鏍� Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class CrmSalesTargetServiceImpl implements CrmSalesTargetService {
+
+    private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
+
+    private static final Pattern YEAR_PATTERN = Pattern.compile("^\\d{4}$");
+    private static final Pattern QUARTER_PATTERN = Pattern.compile("^(\\d{4})-Q([1-4])$");
+    private static final Pattern MONTH_PATTERN = Pattern.compile("^(\\d{4})-(0[1-9]|1[0-2])$");
+    private static final DateTimeFormatter MONTH_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM");
+
+    @Resource
+    private CrmSalesTargetMapper salesTargetMapper;
+    @Resource
+    private CrmSalesTargetAchievementMapper achievementMapper;
+
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private DeptApi deptApi;
+    @Resource
+    private MdmItemApi mdmItemApi;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createSalesTarget(CrmSalesTargetSaveReqVO createReqVO) {
+        // 1. 鏍¢獙鐩爣缁村害瀵硅薄
+        validateTargetDimension(createReqVO);
+        // 2. 璁$畻鍛ㄦ湡璧锋锛屽苟鎻掑叆
+        CrmSalesTargetDO salesTarget = BeanUtils.toBean(createReqVO, CrmSalesTargetDO.class);
+        LocalDateTime[] period = calculatePeriod(createReqVO.getPeriodType(), createReqVO.getPeriodValue());
+        salesTarget.setStartTime(period[0]).setEndTime(period[1])
+                .setStatus(calcStatusByPeriod(period[0], period[1]));
+        salesTargetMapper.insert(salesTarget);
+        return salesTarget.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateSalesTarget(CrmSalesTargetSaveReqVO updateReqVO) {
+        // 1. 鏍¢獙瀛樺湪
+        CrmSalesTargetDO salesTarget = validateSalesTargetExists(updateReqVO.getId());
+        // 2. 鏍¢獙鐩爣缁村害瀵硅薄
+        validateTargetDimension(updateReqVO);
+        // 3. 鏇存柊
+        CrmSalesTargetDO updateDO = BeanUtils.toBean(updateReqVO, CrmSalesTargetDO.class);
+        LocalDateTime[] period = calculatePeriod(updateReqVO.getPeriodType(), updateReqVO.getPeriodValue());
+        updateDO.setStartTime(period[0]).setEndTime(period[1]);
+        // 鎵嬪姩浣滃簾鐨勭姸鎬佷笉鍥犵紪杈戣�岃鐩�
+        updateDO.setStatus(Objects.equals(salesTarget.getStatus(), CrmSalesTargetStatusEnum.INVALID.getStatus())
+                ? CrmSalesTargetStatusEnum.INVALID.getStatus() : calcStatusByPeriod(period[0], period[1]));
+        salesTargetMapper.updateById(updateDO);
+    }
+
+    @Override
+    public void changeSalesTargetStatus(CrmSalesTargetChangeStatusReqVO reqVO) {
+        // 1. 鏍¢獙瀛樺湪
+        validateSalesTargetExists(reqVO.getId());
+        // 2. 鏇存柊鐘舵��
+        salesTargetMapper.updateById(new CrmSalesTargetDO().setId(reqVO.getId()).setStatus(reqVO.getStatus()));
+    }
+
+    @Override
+    public void deleteSalesTarget(Long id) {
+        // 1. 鏍¢獙瀛樺湪
+        validateSalesTargetExists(id);
+        // 2. 鍒犻櫎
+        salesTargetMapper.deleteById(id);
+    }
+
+    @Override
+    public CrmSalesTargetRespVO getSalesTarget(Long id) {
+        CrmSalesTargetDO salesTarget = validateSalesTargetExists(id);
+        return convertListToRespVO(Collections.singletonList(salesTarget)).get(0);
+    }
+
+    @Override
+    public PageResult<CrmSalesTargetRespVO> getSalesTargetPage(CrmSalesTargetPageReqVO pageReqVO) {
+        PageResult<CrmSalesTargetDO> pageResult = salesTargetMapper.selectPage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return PageResult.empty();
+        }
+        return new PageResult<>(convertListToRespVO(pageResult.getList()), pageResult.getTotal());
+    }
+
+    @Override
+    public List<CrmSalesTargetRespVO> getSalesTargetList(CrmSalesTargetPageReqVO reqVO) {
+        List<CrmSalesTargetDO> list = salesTargetMapper.selectList(reqVO);
+        return convertListToRespVO(list);
+    }
+
+    @Override
+    public List<CrmSalesTargetRespVO> getSalesTargetAnalysis(CrmSalesTargetAnalysisReqVO reqVO) {
+        // 鎸夊懆鏈� + 缁村害杩囨护鏌ヨ鍏ㄩ儴鐩爣锛堝惈宸茬粨鏉燂級锛岀敤浜庤揪鎴愬垎鏋愮湅鏉�
+        List<CrmSalesTargetDO> list = salesTargetMapper.selectList(new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<CrmSalesTargetDO>()
+                .eqIfPresent(CrmSalesTargetDO::getStatus, reqVO.getStatus())
+                .eqIfPresent(CrmSalesTargetDO::getTargetDimension, reqVO.getTargetDimension())
+                .eq(CrmSalesTargetDO::getPeriodType, reqVO.getPeriodType())
+                .eq(CrmSalesTargetDO::getPeriodValue, reqVO.getPeriodValue())
+                .orderByDesc(CrmSalesTargetDO::getId));
+        return convertListToRespVO(list);
+    }
+
+    // ========== 绉佹湁鏂规硶 ==========
+
+    private CrmSalesTargetDO validateSalesTargetExists(Long id) {
+        CrmSalesTargetDO salesTarget = salesTargetMapper.selectById(id);
+        if (salesTarget == null) {
+            throw exception(CRM_SALES_TARGET_NOT_EXISTS);
+        }
+        return salesTarget;
+    }
+
+    private void validateTargetDimension(CrmSalesTargetSaveReqVO reqVO) {
+        if (Objects.equals(reqVO.getTargetDimension(), CrmSalesTargetDimensionEnum.USER.getType())
+                && reqVO.getTargetUserId() == null) {
+            throw exception(CRM_SALES_TARGET_DIMENSION_TARGET_MISSING, "鐢ㄦ埛");
+        }
+        if (Objects.equals(reqVO.getTargetDimension(), CrmSalesTargetDimensionEnum.DEPT.getType())
+                && reqVO.getTargetDeptId() == null) {
+            throw exception(CRM_SALES_TARGET_DIMENSION_TARGET_MISSING, "閮ㄩ棬");
+        }
+        if (Objects.equals(reqVO.getTargetDimension(), CrmSalesTargetDimensionEnum.ITEM.getType())
+                && reqVO.getTargetItemId() == null) {
+            throw exception(CRM_SALES_TARGET_DIMENSION_TARGET_MISSING, "鐗╂枡");
+        }
+    }
+
+    /**
+     * 鏍规嵁鍛ㄦ湡绫诲瀷 + 鍛ㄦ湡鍊艰绠楀懆鏈熻捣姝㈡椂闂达紙鍗婂紑鍖洪棿 [start, end)锛�
+     */
+    private LocalDateTime[] calculatePeriod(Integer periodType, String periodValue) {
+        if (Objects.equals(periodType, CrmSalesTargetPeriodTypeEnum.YEAR.getType())) {
+            if (!YEAR_PATTERN.matcher(periodValue).matches()) {
+                throw exception(CRM_SALES_TARGET_PERIOD_VALUE_INVALID, periodValue, "2026");
+            }
+            int year = Integer.parseInt(periodValue);
+            return new LocalDateTime[]{LocalDateTime.of(year, 1, 1, 0, 0), LocalDateTime.of(year + 1, 1, 1, 0, 0)};
+        }
+        if (Objects.equals(periodType, CrmSalesTargetPeriodTypeEnum.QUARTER.getType())) {
+            Matcher matcher = QUARTER_PATTERN.matcher(periodValue);
+            if (!matcher.matches()) {
+                throw exception(CRM_SALES_TARGET_PERIOD_VALUE_INVALID, periodValue, "2026-Q1");
+            }
+            int year = Integer.parseInt(matcher.group(1));
+            int startMonth = (Integer.parseInt(matcher.group(2)) - 1) * 3 + 1;
+            return new LocalDateTime[]{
+                    YearMonth.of(year, startMonth).atDay(1).atStartOfDay(),
+                    YearMonth.of(year, startMonth).plusMonths(3).atDay(1).atStartOfDay()};
+        }
+        if (Objects.equals(periodType, CrmSalesTargetPeriodTypeEnum.MONTH.getType())) {
+            if (!MONTH_PATTERN.matcher(periodValue).matches()) {
+                throw exception(CRM_SALES_TARGET_PERIOD_VALUE_INVALID, periodValue, "2026-07");
+            }
+            YearMonth yearMonth = YearMonth.parse(periodValue, MONTH_FORMAT);
+            return new LocalDateTime[]{
+                    yearMonth.atDay(1).atStartOfDay(),
+                    yearMonth.plusMonths(1).atDay(1).atStartOfDay()};
+        }
+        throw exception(CRM_SALES_TARGET_PERIOD_VALUE_INVALID, periodValue, "骞�/瀛e害/鏈�");
+    }
+
+    /**
+     * 鏍规嵁褰撳墠鏃堕棿涓庡懆鏈熻捣姝紝鑷姩鍒ゅ畾鐩爣鐘舵��
+     */
+    private Integer calcStatusByPeriod(LocalDateTime startTime, LocalDateTime endTime) {
+        LocalDateTime now = LocalDateTime.now();
+        if (now.isBefore(startTime)) {
+            return CrmSalesTargetStatusEnum.NOT_ACTIVE.getStatus();
+        }
+        if (now.isAfter(endTime)) {
+            return CrmSalesTargetStatusEnum.END.getStatus();
+        }
+        return CrmSalesTargetStatusEnum.ACTIVE.getStatus();
+    }
+
+    /**
+     * 灏� DO 鍒楄〃杞崲涓� RespVO 鍒楄〃锛屽~鍏呭璞″悕绉般�佸垱寤轰汉鍚嶇О涓庡疄闄呰揪鎴愭暟鎹�
+     */
+    private List<CrmSalesTargetRespVO> convertListToRespVO(List<CrmSalesTargetDO> targetList) {
+        if (CollUtil.isEmpty(targetList)) {
+            return Collections.emptyList();
+        }
+        // 1. 鏀堕泦鍏宠仈 ID锛屾壒閲忔煡璇㈠悕绉�
+        Set<Long> userIds = new HashSet<>();
+        Set<Long> deptIds = new HashSet<>();
+        Set<Long> itemIds = new HashSet<>();
+        for (CrmSalesTargetDO target : targetList) {
+            if (Objects.equals(target.getTargetDimension(), CrmSalesTargetDimensionEnum.USER.getType())) {
+                userIds.add(target.getTargetUserId());
+            } else if (Objects.equals(target.getTargetDimension(), CrmSalesTargetDimensionEnum.DEPT.getType())) {
+                deptIds.add(target.getTargetDeptId());
+            } else if (Objects.equals(target.getTargetDimension(), CrmSalesTargetDimensionEnum.ITEM.getType())) {
+                itemIds.add(target.getTargetItemId());
+            }
+            if (StrUtil.isNumeric(target.getCreator())) { // 鍒涘缓浜猴紙鐢ㄦ埛缂栧彿瀛楃涓诧級
+                userIds.add(Long.valueOf(target.getCreator()));
+            }
+        }
+        Map<Long, AdminUserRespDTO> userMap = CollUtil.isNotEmpty(userIds) ? adminUserApi.getUserMap(userIds) : Collections.emptyMap();
+        Map<Long, DeptRespDTO> deptMap = CollUtil.isNotEmpty(deptIds) ? deptApi.getDeptMap(deptIds) : Collections.emptyMap();
+        Map<Long, MdmItemRespDTO> itemMap = CollUtil.isNotEmpty(itemIds) ? mdmItemApi.getItemMap(itemIds) : Collections.emptyMap();
+
+        // 2. 杞崲骞跺~鍏�
+        return convertList(targetList, target -> {
+            CrmSalesTargetRespVO respVO = BeanUtils.toBean(target, CrmSalesTargetRespVO.class);
+            // 鐩爣瀵硅薄鍚嶇О
+            if (Objects.equals(target.getTargetDimension(), CrmSalesTargetDimensionEnum.USER.getType())) {
+                AdminUserRespDTO user = userMap.get(target.getTargetUserId());
+                if (user != null) {
+                    respVO.setTargetUserName(user.getNickname());
+                }
+            } else if (Objects.equals(target.getTargetDimension(), CrmSalesTargetDimensionEnum.DEPT.getType())) {
+                DeptRespDTO dept = deptMap.get(target.getTargetDeptId());
+                if (dept != null) {
+                    respVO.setTargetDeptName(dept.getName());
+                }
+            } else if (Objects.equals(target.getTargetDimension(), CrmSalesTargetDimensionEnum.ITEM.getType())) {
+                MdmItemRespDTO item = itemMap.get(target.getTargetItemId());
+                if (item != null) {
+                    respVO.setTargetItemName(item.getName());
+                }
+            }
+            // 鍒涘缓浜哄悕绉�
+            if (StrUtil.isNumeric(target.getCreator())) {
+                AdminUserRespDTO creator = userMap.get(Long.valueOf(target.getCreator()));
+                if (creator != null) {
+                    respVO.setCreatorName(creator.getNickname());
+                }
+            }
+            // 瀹為檯杈炬垚涓庤揪鎴愮巼
+            fillAchievement(target, respVO);
+            return respVO;
+        });
+    }
+
+    /**
+     * 璁$畻瀹為檯杈炬垚閲戦/鏁伴噺涓庤揪鎴愮巼
+     */
+    private void fillAchievement(CrmSalesTargetDO target, CrmSalesTargetRespVO respVO) {
+        if (Objects.equals(target.getTargetDimension(), CrmSalesTargetDimensionEnum.ITEM.getType())) {
+            // 浜у搧缁村害锛氫粎鍚堝悓閲戦涓庡悎鍚屾暟鍙寜鐗╂枡缁熻
+            BigDecimal contractPrice = achievementMapper.selectContractPriceByItemId(target.getTargetItemId(),
+                    target.getStartTime(), target.getEndTime());
+            Long contractCount = achievementMapper.selectContractCountByItemId(target.getTargetItemId(),
+                    target.getStartTime(), target.getEndTime());
+            respVO.setActualContractPrice(contractPrice);
+            respVO.setActualContractCount(contractCount);
+            respVO.setActualReceivablePrice(BigDecimal.ZERO);
+            respVO.setActualBusinessPrice(BigDecimal.ZERO);
+            respVO.setActualCustomerCount(0L);
+        } else {
+            Set<Long> userIds = resolveTargetUserIds(target);
+            if (CollUtil.isEmpty(userIds)) { // 閮ㄩ棬涓嬫棤鐢ㄦ埛
+                respVO.setActualContractPrice(BigDecimal.ZERO);
+                respVO.setActualContractCount(0L);
+                respVO.setActualReceivablePrice(BigDecimal.ZERO);
+                respVO.setActualBusinessPrice(BigDecimal.ZERO);
+                respVO.setActualCustomerCount(0L);
+            } else {
+                respVO.setActualContractPrice(achievementMapper.selectContractPriceByUserIds(userIds,
+                        target.getStartTime(), target.getEndTime()));
+                respVO.setActualContractCount(achievementMapper.selectContractCountByUserIds(userIds,
+                        target.getStartTime(), target.getEndTime()));
+                respVO.setActualReceivablePrice(achievementMapper.selectReceivablePriceByUserIds(userIds,
+                        target.getStartTime(), target.getEndTime()));
+                respVO.setActualBusinessPrice(achievementMapper.selectBusinessPriceByUserIds(userIds,
+                        target.getStartTime(), target.getEndTime()));
+                respVO.setActualCustomerCount(achievementMapper.selectCustomerCountByUserIds(userIds,
+                        target.getStartTime(), target.getEndTime()));
+            }
+        }
+        // 杈炬垚鐜� = 瀹為檯 / 鐩爣 * 100
+        respVO.setContractRate(calcRate(respVO.getActualContractPrice(), target.getContractTarget()));
+        respVO.setReceivableRate(calcRate(respVO.getActualReceivablePrice(), target.getReceivableTarget()));
+        respVO.setBusinessRate(calcRate(respVO.getActualBusinessPrice(), target.getBusinessTarget()));
+        respVO.setCustomerRate(calcRate(respVO.getActualCustomerCount(), target.getCustomerTarget()));
+        respVO.setContractCountRate(calcRate(respVO.getActualContractCount(), target.getContractCountTarget()));
+    }
+
+    /**
+     * 瑙f瀽鐩爣鐨勭粺璁$敤鎴烽泦鍚堬細涓汉缁村害涓哄崟鐢ㄦ埛锛岄儴闂ㄧ淮搴︿负閮ㄩ棬鍙婂瓙閮ㄩ棬涓嬫墍鏈夌敤鎴�
+     */
+    private Set<Long> resolveTargetUserIds(CrmSalesTargetDO target) {
+        if (Objects.equals(target.getTargetDimension(), CrmSalesTargetDimensionEnum.USER.getType())) {
+            return target.getTargetUserId() == null ? Collections.emptySet() : Collections.singleton(target.getTargetUserId());
+        }
+        // 閮ㄩ棬缁村害锛氬惈瀛愰儴闂�
+        List<Long> deptIds = new ArrayList<>();
+        deptIds.add(target.getTargetDeptId());
+        List<DeptRespDTO> childDepts = deptApi.getChildDeptList(target.getTargetDeptId());
+        if (CollUtil.isNotEmpty(childDepts)) {
+            deptIds.addAll(convertList(childDepts, DeptRespDTO::getId));
+        }
+        return convertSet(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
+    }
+
+    private BigDecimal calcRate(BigDecimal actual, BigDecimal target) {
+        if (target == null || target.compareTo(BigDecimal.ZERO) <= 0) {
+            return null;
+        }
+        return (actual == null ? BigDecimal.ZERO : actual)
+                .multiply(HUNDRED).divide(target, 2, RoundingMode.HALF_UP);
+    }
+
+    private BigDecimal calcRate(Long actual, Integer target) {
+        if (target == null || target <= 0) {
+            return null;
+        }
+        return BigDecimal.valueOf(actual == null ? 0 : actual)
+                .multiply(HUNDRED).divide(BigDecimal.valueOf(target), 2, RoundingMode.HALF_UP);
+    }
+
+}
diff --git a/yudao-module-crm/src/main/resources/mapper/salesTarget/CrmSalesTargetAchievementMapper.xml b/yudao-module-crm/src/main/resources/mapper/salesTarget/CrmSalesTargetAchievementMapper.xml
new file mode 100644
index 0000000..d2033e4
--- /dev/null
+++ b/yudao-module-crm/src/main/resources/mapper/salesTarget/CrmSalesTargetAchievementMapper.xml
@@ -0,0 +1,84 @@
+<?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.crm.dal.mysql.salesTarget.CrmSalesTargetAchievementMapper">
+
+    <!-- 鍚堝悓閲戦锛堜釜浜�/閮ㄩ棬缁村害锛� -->
+    <select id="selectContractPriceByUserIds" resultType="java.math.BigDecimal">
+        SELECT IFNULL(SUM(total_price), 0) FROM crm_contract
+        WHERE deleted = 0
+        AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
+        AND order_date BETWEEN #{start} AND #{end}
+        AND owner_user_id IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+    </select>
+
+    <!-- 鍚堝悓鏁伴噺锛堜釜浜�/閮ㄩ棬缁村害锛� -->
+    <select id="selectContractCountByUserIds" resultType="java.lang.Long">
+        SELECT COUNT(1) FROM crm_contract
+        WHERE deleted = 0
+        AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
+        AND order_date BETWEEN #{start} AND #{end}
+        AND owner_user_id IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+    </select>
+
+    <!-- 鍥炴閲戦锛堜釜浜�/閮ㄩ棬缁村害锛� -->
+    <select id="selectReceivablePriceByUserIds" resultType="java.math.BigDecimal">
+        SELECT IFNULL(SUM(price), 0) FROM crm_receivable
+        WHERE deleted = 0
+        AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
+        AND return_time BETWEEN #{start} AND #{end}
+        AND owner_user_id IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+    </select>
+
+    <!-- 鍟嗘満閲戦锛堜釜浜�/閮ㄩ棬缁村害锛屼粎璧㈠崟锛� -->
+    <select id="selectBusinessPriceByUserIds" resultType="java.math.BigDecimal">
+        SELECT IFNULL(SUM(total_price), 0) FROM crm_business
+        WHERE deleted = 0
+        AND end_status = ${@cn.iocoder.yudao.module.crm.enums.business.CrmBusinessEndStatusEnum@WIN.status}
+        AND deal_time BETWEEN #{start} AND #{end}
+        AND owner_user_id IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+    </select>
+
+    <!-- 鏂板瀹㈡埛鏁帮紙涓汉/閮ㄩ棬缁村害锛� -->
+    <select id="selectCustomerCountByUserIds" resultType="java.lang.Long">
+        SELECT COUNT(1) FROM crm_customer
+        WHERE deleted = 0
+        AND create_time BETWEEN #{start} AND #{end}
+        AND owner_user_id IN
+            <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+                #{userId}
+            </foreach>
+    </select>
+
+    <!-- 鍚堝悓閲戦锛堜骇鍝佺淮搴︼紝鎸夊悎鍚屼骇鍝佹槑缁嗗綊灞炵墿鏂欙級 -->
+    <select id="selectContractPriceByItemId" resultType="java.math.BigDecimal">
+        SELECT IFNULL(SUM(cp.total_price), 0) FROM crm_contract c
+        INNER JOIN crm_contract_product cp ON cp.contract_id = c.id
+        WHERE c.deleted = 0 AND cp.deleted = 0
+        AND c.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
+        AND cp.item_id = #{itemId}
+        AND c.order_date BETWEEN #{start} AND #{end}
+    </select>
+
+    <!-- 鍚堝悓鏁伴噺锛堜骇鍝佺淮搴︼紝鎸夊悎鍚屼骇鍝佹槑缁嗗綊灞炵墿鏂欙級 -->
+    <select id="selectContractCountByItemId" resultType="java.lang.Long">
+        SELECT COUNT(DISTINCT c.id) FROM crm_contract c
+        INNER JOIN crm_contract_product cp ON cp.contract_id = c.id
+        WHERE c.deleted = 0 AND cp.deleted = 0
+        AND c.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
+        AND cp.item_id = #{itemId}
+        AND c.order_date BETWEEN #{start} AND #{end}
+    </select>
+
+</mapper>

--
Gitblit v1.9.3