feat(crm): 添加销售目标管理功能
- 新增销售目标相关字典类型和错误码定义
- 创建销售目标数据表和初始化SQL脚本
- 实现销售目标的增删改查和状态变更功能
- 添加达成分析功能和统计数据查询
- 集成前端销售计划和达成分析页面
- 完善销售目标相关的DTO和枚举类定义
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | - generic [active]: å·¥ä¸AIæºé å¹³å° |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | - 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]: æ°æ®æ¥è¡¨ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | - 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] |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | - 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] |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # è¥é管ç - éå®è®¡åï¼éå®ç®æ ï¼å端èè°æ¹æ¡ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - è¥é管ç â éå®è®¡åï¼èå 1065000ï¼ç»ä»¶ `crm/salesTarget/index`ï¼ |
| | | - è¥é管ç â è¾¾æåæï¼èå 1065100ï¼ç»ä»¶ `crm/salesTarget/analysis/index`ï¼ |
| | | |
| | | ## ä¸å¡æµç¨ä¸æ°æ®å¸¦å
¥ |
| | | |
| | | 1. **设å®ç®æ **ï¼ç®¡çåå¨ãéå®è®¡åã页æå¨æï¼å¹´/å£/æï¼+ 维度ï¼ä¸ªäºº/é¨é¨/产åï¼è®¾å®éå®ç®æ ãç®æ 对象ï¼ç¨æ·/é¨é¨/ç©æï¼ä»ç³»ç»ä¸æéæ©ï¼ç®æ ææ ï¼ååéé¢ã忬¾éé¢ãåæºéé¢ãæ°å¢å®¢æ·æ°ãæ°å¢ååæ°ï¼æå·¥å½å
¥ã |
| | | 2. **卿èªå¨æ¢ç®**ï¼åç«¯åªæäº¤ `periodType`ï¼1å¹´/2å£åº¦/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` | è¾¾æåæ | |
| | | |
| | | ### å建/æ´æ°è¯·æ±åæ°ï¼CrmSalesTargetSaveReqVOï¼ |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | id | Long | æ´æ°æ¶å¿
å¡« | ç®æ ç¼å· | |
| | | | name | String | æ¯ | ç®æ åç§° | |
| | | | periodType | Integer | æ¯ | å¨æç±»åï¼åå
¸ crm_sales_target_period_typeï¼1å¹´/2å£åº¦/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å·²ä½åºï¼ | |
| | | |
| | | ### è¾¾æåæè¯·æ±åæ°ï¼CrmSalesTargetAnalysisReqVOï¼ |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | periodType | Integer | æ¯ | å¨æç±»å | |
| | | | periodValue | String | æ¯ | å¨æå¼ | |
| | | | targetDimension | Integer | å¦ | ç®æ 维度ï¼ä¸ºç©ºæ¥å
¨é¨ | |
| | | | status | Integer | å¦ | ç¶æï¼ä¸ºç©ºæ¥å
¨é¨ | |
| | | |
| | | ### ååºå段ï¼CrmSalesTargetRespVOï¼ |
| | | |
| | | ç®æ åºç¡å段ï¼name/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ã | |
| | | | ç®æ 对象 | å表ã详æ
| æç»´åº¦å±ç¤ºç®æ ç¨æ·/é¨é¨/ç©æåç§°ï¼targetUserName/targetDeptName/targetItemNameï¼ | |
| | | | ç®æ ææ | å表ã详æ
ãåæ | ååéé¢/忬¾éé¢/åæºéé¢/æ°å¢å®¢æ·æ°/æ°å¢ååæ°ç®æ | |
| | | | å®é
è¾¾æ | å表ã详æ
ãåæ | ä¸ç®æ ææ ä¸ä¸å¯¹åºçå®é
å¼ | |
| | | | è¾¾æç | å表ã详æ
ãåæ | ç¾åæ¯ï¼ä¿ç两ä½å°æ°ï¼ç®æ 为 0 æç©ºæ¶è¾¾æç为空ï¼å±ç¤ºä¸º `-`ï¼ | |
| | | | ç¶æ | å表ã详æ
| åå
¸è½¬ææ¬ï¼å·²ä½åºéç½®ç°/ç¦æ¢ç¼è¾ | |
| | | | å建人 | å表ã详æ
| å建人æµç§° | |
| | | |
| | | ## ä¸å¡è§å说æ |
| | | |
| | | | åºæ¯ | è§å | |
| | | |------|------| |
| | | | è¾¾æçè®¡ç® | å®é
å¼ Ã· ç®æ å¼ Ã 100ï¼ä¿ç 2 ä½å°æ°ï¼ç®æ 为空æ â¤0 æ¶è¾¾æçè¿å null | |
| | | | 卿æ¢ç® | å端æ é计ç®èµ·æ¢æ¶é´ï¼åå°æ periodValue æ¢ç®ï¼å¹´ `2026`ãå£ `2026-Q1`ãæ `2026-07` | |
| | | | ç¶æå¤å® | åå°æå½åæ¶é´èªå¨å¤å® æªçæ/å·²çæ/å·²ç»æï¼å端æ°å¢ç¼è¾æ¶æ éæå¨éç¶æ | |
| | | | ä½åº/æ¢å¤ | å·²ä½åºçç®æ 被ç¼è¾åä»ä¿æä½åºç¶æï¼éå
ãæ¢å¤ãåç¼è¾çæ | |
| | | | ç»è®¡å£å¾ | ååéé¢/æ°éåªç»è®¡å®¡æ ¸éè¿ï¼audit_status=20ï¼çååï¼å款åªç»è®¡å®¡æ ¸éè¿ç忬¾ï¼åæºåªç»è®¡èµ¢åï¼end_status=1ï¼ï¼å®¢æ·æå建æ¶é´ç»è®¡ | |
| | | | 产å维度 | ä»
ç»è®¡ååéé¢ä¸ååæ°ï¼å款/åæº/å®¢æ·æ°ä¸º 0 | |
| | | | é¨é¨ç»´åº¦ | å«åé¨é¨ä¸ææç¨æ·çæ°æ® | |
| | | | æ°æ®æé | ç®æ 为管çé
ç½®ï¼ä¸æ¥å
¥ CRM æ°æ®æéï¼å表å±ç¤ºå
¨é¨ï¼å¯æéæåå»ºäººè¿æ»¤ï¼ | |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - è¾¾æçåæ®µå¯è½ä¸º nullï¼å端å±ç¤ºéå
åºä¸º `-`ï¼é¿å
æ¾ç¤º 0ã |
| | | - 卿弿 ¼å¼ä¸¥æ ¼ï¼å¹´ `YYYY`ãå£ `YYYY-Q1`~`YYYY-Q4`ãæ `YYYY-MM`ï¼åç«¯è¡¨åæ ¡éªéææ¤çº¦æã |
| | | - ç®æ 维度ä¸ç®æ å¯¹è±¡äºæ¥ï¼ç»´åº¦=个人填 userIdï¼é¨é¨å¡« deptIdï¼äº§åå¡« itemIdï¼å
¶ä½ç空ï¼å端æç»´åº¦åæ¢è¡¨å项ã |
| | | - è¾¾æåææ¥å£ç periodType + periodValue 为å¿
å¡«ï¼æªé卿æ¶ä¸åºå起请æ±ã |
| | | - è¾¾æç»è®¡æ¥è¯¢ä¸ºéæ¡ç®æ 计ç®ï¼çº¦ 5 æ¡å° SQLï¼ï¼ç®æ æ°é级å å~å ç¾æ¶æ§è½å¯æ¥åï¼è¥åç»ç®æ é级å¢å¤§éæ¹é 为èåç»è®¡ï¼éæåç¥ä¼å端ã |
| | | - ãéå®è®¡åã页æåºå»ºè®®æå建æ¶é´ååºï¼ä¸åå°è¿å顺åºä¸è´ï¼æ id ååºï¼ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ===================================================== |
| | | -- 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å£åº¦/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, 'å£åº¦', '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'); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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ãPostgreSQLãKingbaseãDB2ãH2 æ°æ®åºç主é®èªå¢ãå¦ææ¯ 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; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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); |
| | | } |
| | | |
| | | } |
| | |
| | | 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 éå®ç®æ ç¶æ |
| | | |
| | | } |
| | |
| | | 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, "卿å¼ã{}ãæ ¼å¼ä¸æ£ç¡®ï¼åºä¸º {}"); |
| | | ErrorCode CRM_SALES_TARGET_DIMENSION_TARGET_MISSING = new ErrorCode(1_020_017_002, "请填åç®æ {}ç¼å·"); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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, "å£åº¦"), |
| | | 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); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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, "å¹´/å£åº¦/æ"); |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®å½åæ¶é´ä¸å¨æèµ·æ¢ï¼èªå¨å¤å®ç®æ ç¶æ |
| | | */ |
| | | 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())); |
| | | } |
| | | |
| | | /** |
| | | * è§£æç®æ çç»è®¡ç¨æ·éåï¼ä¸ªäººç»´åº¦ä¸ºåç¨æ·ï¼é¨é¨ç»´åº¦ä¸ºé¨é¨ååé¨é¨ä¸ææç¨æ· |
| | | */ |
| | | 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); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?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> |