From f71a3b7dc0edaa6f15d27f94bbf2418703224080 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 17 Feb 2024 22:38:38 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=202.0.1=20=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E5=87=86=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .image/common/erp-feature.png | Bin 0 -> 47016 bytes README.md | 8 + sql/mysql/ruoyi-vue-pro.sql | 216 ++++++++++++++++-- .../util/collection/CollectionUtils.java | 19 +- .../common/util/number/MoneyUtils.java | 58 +++++ .../common/util/number/NumberUtils.java | 20 ++ .../file/core/client/FileClient.java | 16 +- .../client/s3/FilePresignedUrlRespDTO.java | 29 +++ .../file/core/client/s3/S3FileClient.java | 14 ++ .../mybatis/core/query/QueryWrapperX.java | 2 +- .../banner/core/BannerApplicationRunner.java | 2 + .../core/handler/GlobalExceptionHandler.java | 8 +- .../gateway/util/BannerApplicationRunner.java | 2 + .../controller/admin/file/FileController.java | 31 ++- .../admin/file/vo/file/FileCreateReqVO.java | 33 +++ .../file/vo/file/FilePresignedUrlRespVO.java | 29 +++ .../infra/service/file/FileService.java | 26 ++- .../infra/service/file/FileServiceImpl.java | 22 +- .../main/resources/codegen/vue3/api/api.ts.vm | 7 +- .../resources/codegen/vue3/views/form.vue.vm | 2 +- .../resources/codegen/vue3/views/index.vue.vm | 3 +- .../dal/dataobject/coupon/CouponDO.java | 3 + .../dataobject/coupon/CouponTemplateDO.java | 4 + .../system/enums/ErrorCodeConstants.java | 2 +- .../auth/vo/AuthPermissionInfoRespVO.java | 3 + .../admin/notice/vo/NoticeSaveReqVO.java | 3 +- .../system/convert/auth/AuthConvert.java | 3 +- 27 files changed, 503 insertions(+), 62 deletions(-) create mode 100644 .image/common/erp-feature.png create mode 100644 yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/FilePresignedUrlRespDTO.java create mode 100644 yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileCreateReqVO.java create mode 100644 yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java diff --git a/.image/common/erp-feature.png b/.image/common/erp-feature.png new file mode 100644 index 0000000000000000000000000000000000000000..d30b30eed9d4e072c61ebf3bb63809b2b5593078 GIT binary patch literal 47016 zcmeFZXHb+)7dEJ(0wN%wNE#5yIp-uQ3_0g0IfD!Wk|ijThA25GLmmOi86<}xNS3U? z5QZ=khav8b&-?B^-?u-uws!Z&R((~}T|>|5?$do@pT6#i&`<*t-lMvA5n7XL-MUIK5EYcD2M~?$5MG&CV*Bc<_47VuR;Bx$VSd*vgvqhK2y2#3Vg$bDx~4LrtD@k$PbPYj#mK~@ z8AtH}zv-F}ch6|cu0+WXihisLKrgH7zV*U1e3q#q1a~4VQeP8XuWefXG3+sqS!xu; z2SqDVGP0HsU{6+#{|;dDhOa{r^ooKxKz9As?FiWQMpxGmmG(j5rTx#yKy!cnHGlYpf4MXTniJixMMtJ~{ak4TnweLw zwSYPE`q|?NG^_Ny2UJ8w^!gdS1vIBdU(c2L`mb6{UpG@;&&B)u;s1w>xWj4k85fh2 z>hlv4UbmZc@yh<2iS8Ik(*CP*#vIP4Y<6v9=J zVx3y2BmTYKdQE7;@RXiqULuXe+&Sr*P(o4?Ck8E(1lKR`uUKKR=^a)utZ39 zKt#t@I(Nzb2|g9)|2|UjcWyr8fq^imH?`GfkEQTbo1ZiT=5%Wr~ z#-2mx@7Sz;^;uh(v+d|&LrL9`r(agJEg1ynD(7e7H}-B z@#=S|cHwaIv7hfT<*SoCeM%wQ5M+(*z*SvjX4~u0^Qm_;LBBrR$UMj@kMw7-8PH8Y z32iB@6qM$@D0Jnw{)v|+9#Ft(*!W!7Wsx11NWQ;YIfYSLda>EK&9KV0dC`c%QLjpDxw=blAly0a#cziTb6=mQxzue=|j`9|1z_G#VnvUB@Y zofkUd<4<~GI%yq~ra)6Kx!vR(l;B}N(PhZXCM0NKcfI@MAw@k(#L!Wr&04 z>E*QBPiyXDU@l)QWyL_zU{;-m>UihJ(e_ZyHOm~6`nQTPCcpiL%)k9K+3x!v#PSr( zhQBVwF;3Y9%oh=|mSSt5MFwihLrR_2h}IMJXHKl*1&uV{i7c7EPbuDEvMth{%-~0z(S)J(7+eZGhT3CLq?Kbvjbwg~d|+*ZvI66SASTHvdi;@UDO z7YIXWwuW@)KhH05Q|U$r{nX!1tR)@(Tn zZb$JPu*w?Mvt9(1590k#Ochb?*f?Bx`+uCvoO>$gz=VB6qSz`tan9$UWc0E(b7C>H zty|qwY8rGg|Fxz;R9CEHNg(LUGD?U+K1Sg2SqwM0VV^wVsd-AERpcjD__5zFjignc zZLIXKa5_LoZA&OO=swLgEfzS5UQJMHu{L_Ova3GiFL4u}8s zb^EpbqhHtvxgc-+lmnv^fe z({IyALnaDo6jh{_nK0QTUq%k5sA%b13PN1FV)wf;86 zTBXU2!>IMOQsgv|X`UVWlK<@n9;Jvy!eFXc?}6JDf2{A)vd!>A#(z0k#2hmim*pH2 zgT%u~cd&a!t~4+rFUb#j^Vl2=lbckVpn*%{B`$uHg4XrR(@@j=)gbnw9Wjr`Nii9< zr_JiIGQjq^49y;CeSVFvYgQ@`I`UR^I^L1_ycqrn%`bjH&Y7g^G^Kp_o58UkZCvNd z5v&j^s*=M84j5q`GHRJ><{T zCaQ!2<1JwGHgpBfzQ_8oX%2=0(4WOy-FdE|%#o3wO8VROAMNkL-prsGg^!c5r8lem zFw#eh(}rF2I)-iTRaRSU|I!yGPGldn9=jH+BotiGhmG2kqR4N*g+s=ou01}G^fT#7 zZ8%%PE-*tR*!5P1gt-MJzjIz!uMB@1*W^p*rQ%~guX)BJnkf3O#%+5wGpzwB4kP6u z$oj&O%q-X1ou3Yi`TtWCY*{_tDi1j>fLQwz@48JCDH7DQye!_||6|eo+q66vq1ml8 z$ppceb+_W+u1*wJq1;jzfAxI*$y2r?OGF!4+ox~zZWT))KQ>q%1QPe&`JZRF-aqkU zb;Za_g?RtQO2+OyHX)p&%Ko5vNIWCZNgpSHAxf={q!`uZJ`THgh9aweQH$p5)vk zXAOSYYp&%-Tu|UMaG;=bXyM`hW7Yo0kKc}MBqVc_9;VRKV>)Nw`~Z#EF?Miy1`bc# z-(;!B@Sj{EET1M#p;h?DwShY3TBTeYC^+k`sxG$fsm4*IeR$*mURq@-#3t8t6fQxeXW(qaGQYN8WFGG zV!L0xDQ(jp2b-%y7mXnFKc{;8I1gx^{trs1EGB;ZzWL*2lsvhjPyO^ciG!LW0b-9WMs6m*oDi9!72R>1tk6-nTp1+Tpl*Ya9eVMiP8B(LpcTF35hQBMh=8+}+4TxCmX7 z4$+Yaw=Z36B_J8+QhbnuhD+2VD2NVBPb!Vi$(1SDSTT-rm~K zA|rF_k39O(X0gd%XTJm7UM%GO|01UZ*7W~8xrNb4`5mhOhlU{@3kv3#;D?&m33kdq zW;X}{vinzo_?^5C_XB1Aik9j6X+mil_xd^JWY6j-)gpD8AW#I@NTs+RGvAl4GkxM~ z!RYs}f;W!tZtvNqR^zs%0O=VU?=9u)REg$qZvKf+z4QfIL~S29*(Cb@nQKQQ+e2|P zwR(9~4E`#dlNGI0$dljJ#2h~>a5F8wwxe%l;h$vPP*Fq{{U7N%5H~mWgbBv3gY@Oa z83es3Mfw!F{V}Fvp%FQg1_7@tLpIsy^0{yI)Z!>uySaAiB* zBSkkm`{{o8^(n4-*pJ`DdQa9wipvbvB_$5ZU8#P?xe3BQF#bCNxcGOk%yp|e2T|k~f%G1c}C6Ql;NQ*}b35H1u_M(q`p5L3AFR(Z8%g@iW zmOe#?Og{$3E4yZZtael`BJ6`ele%I%oi{7hq~Y~Ki93O%gV6Q(|q-y;4 z|G&1}<8a-(lb0Uex1cDzmc#W>|Euf&i`%66 zpz7D_pfsP)1oY6)E4vk1fxTMKXJ+kNiV_uHCUDq;=UR4)D2}ypRp!taV)rar z!Ci_P(2wJvX-J;;ZOMw=3uFb$Mr%UVdUsMdFlrA-`R{_3Hl^4uj{b1Zc&$97r)Ppp z-<&zz5tMrgdk+!ZC#9!ff4xFas+$jA-dKb~Ru5l1e?AabY{v#pqX2lZN`m3yyRTV( z=$}w&eLc$oPUOktdn}+*z_Pb*+MwYWxh#)(1bmIJn2`P^sY~hT5#+3_A=QFk>Ojx* z3ylrHq3nI+M|Uk&aP&B!fSD8nBrDY#P=1jl*pm;CE>WNm@K;~euBiY`O``^81-yDD za8kb}q+q$G;!1)I5+YeY0*b8$oawI#qsgwR$OkmnK0qFJT@wPD3=lH(Tpyp+2#~BE z|Ljn7;`O|M)~{Ss0s5ZQ@P+0MA3&)158+35fXi#{2Ie)OA=eD%{X^I#dQAmbla1^D z?PX-Bq`+2_tbO9oO|n^}Jf%9lC*pUh5v3=AuG%IeMx0s%c+?A z>OWRD(rmrgf;8&?&r}TNApwM10EMRITdvkuxIlOOzug&@Ujj2aXh8iH-|qE&1yQDd z?jwl?n8pYH&rF9xRtfYf2y8>1f=3cbIj`bghXI`228JSp(cJV9qyO^mFK2y9ykx*W zD@4|vGjzo7q>%R&c#J=C$JggXhFLSmyW4$w7`#yD^PT0(`eK$7#Is+RXAScFc}Kw` zND%UQcW1bMDL}}7re?0Ab^AD{!Jds6oS>aBcp7hkA4wzSv-e{^_6cCxc=Ta$B1pLb zkE%dx1J-L}ziz1mDr@xrd}h>w0xj3gFM%ET&-X9?_pe$1+vh=of|yd)y7%f{)LWnI zbDab&GXuE5q}H@fZK3P^_}Svv)iOPn#K~B!&lp+hb}RBI z>~;n?!W)^UcSV}ht*F#Ns2KQ0(~Qx?&ya=Az)W4?n@J2Ut+OKjE5Al zOPCy~>0zfDEAIgTpOQ)43B!Xe#WlbPZ%A_>`xz9?YSqjRX_-ghg8KBi?wuhR!Qj+)$`3CocLf?!mhH z%FAK32QwI0-|W>EqJZ-$SX_BO5x)O%S&jduE=wZmYTrfT*ww^Cwz|T6>xiOVGoR9afI7acBq_Jk4EzIP^TnakNq!9&R4HUQ?}_UW%Wjy#NLW!SP>)(t1^mI8dw z`5zT0gG3S6#C1=jGlh6iTwY!;u|PBblAo`#<*rFW;$!u`((q|cq*rtrudLVZlEsXx zE%H=Xt4fcd;ec^UOE_Sd(_yk8)J6iIYvWT&DB|UZq`gm1B?J|fe2x{)TDpvnS7LE` ziTc9W5?e;xy3H|cZlfrDF^Kr=*|}?nDneS=Wr=l#@a$s?9c(iH7{a|)4b-|UUCk35 zg;0p8zS3m=^SkVNB}~0MG&M=_KS*-kswdE8lh34&u*-R6f_U(GXbq1K8V|R}&8M7= zoMve8np?fwZYJ;C^a(VctwrZfZn7m;`lUaZDqT`ZE;Y4aWB7vOx!;qRp%fYY+`3m^ zg1WEgr^00D6|>Z_$gb%pvw$;1H? zHfrx7);M3Kd^W|SZr^zx!wBSsh8VlG6e82rjD$~l8`F>SUVnik@R&RFf2ykYk1vh0 z#tP>6pU4tnJJFQ`ZkOv;JW+a_nrNxO%hV(`+OeM#2p?x|h8?G8MBP&YL0BqP4YkBRrz^%Le*mBF8Vjjpaq+eV$ ztjm7g8GifDEe6FA+P3Y|%H44l#8jKoj<{;`h-P_@TINQ;<;}=rcR!0}adK$LD#u&Q+o3Dg)I_CJ|Iyjy=*&X?; ziBo#+$9} z3ybC4-JB}cgnwZckTI^swVXO{*q>YSf0|gJeo*WkcX$Uuj*w`(&{^X)`lbD0N#U@c zRXJ|A z%q-Lv7&b70c@T}{V)7_}6v~lkdCtSKp<2M9_O)?bF4Q<}~t|m9>=$Q{xxKMk?EpwAj zu{ko9*sI_?!U;vO#%^{Q@fTNC3k3poF^#lrHD;rTI_^HYr3;f&LRxmGn$uA6FyE71 zv~*%OJh^0g1M=n=<$&c>miD|MyleI6+wW{&&7$ZeR?SfYMr2|ea&M*})He#Fmd&S{ zwLLX~G91qAZuTTq$rM)&#Hf*bLNZL9CSp7x1A`7)YH)Z__(`A`gj_c^rPQ#j714oI z){)v?2#Pyy8Sve*4=x`lqUArhB4*rSlxkQG>aw>pjDMl}^67b!d3siX+30S|nNpca zcd5PSy`#y>3#P4FXN_gc+ebHZtrlIGosw^*PxSVeg~soN3?G`%F4bV5a)L()(gFBsC{Hc`h}dNuTqE`_5PU7&}bmmU=xMmz`|SI`b+* zSHJDnI%?K$yQx)cqKmk~1-Nox5fs9B*Y?H6L;QBS@fj3SBkdLoKcfO9#asG6zl<{i9jE6sUsD)hF>Xyw_KW$BVzZ384 zS!_B*)r`0)XZgy&L;rlLCvc=*-55dJXlrNdIeiG+*?puNQPyLs6~ja&rsK~*5%E0K zo8@i4sSlm?Mio#AuT+ai z>pg#UN<_nXCPX5#U!_Nf5^;8&A^H}}R%Kfx-r}MjmRbMW}fjEzuoTmIstlG~nWV0ogn$wr_F1$3*(9FHV*Iz=vL?561FgI3`D8}U z6=&giZ{wm+WU|OUvA|{ZvdTCa9V<-Eg4Yu?^4TK0mSns@6C9g$G%@PnVXFyUyfM+A z&zHeDW@U}Z>VzC@y7W70>$$3PD2`q}R+v>clPs8Jdl#QhUMgXKwXh7h36DjS$=_D< zUWyXKsIy?2&7*!gV=vdEcm^6}4*#eX@5yMfz@@!6Dtjj&lLReNp}hv%c-R3k8Pjn; z0i9P5vw>MSNg+4_1MK)Mv5pCI6&dhCXDv_F<9JVFk-pqV$~L3>`fqkd^|M%(_euz} zi~B`$&cE2=sHzOWR|4KgP9huidZTlKM>9#8S|{_GGDi+avjfgJNTQ@YHONv-QT`s& zmsus9HQ<~;rP;!vYs$ZJSxWTf_9|XWeJcBnN;{sf7iUWF-Z%;2vQ69;wLowT z?IP(6|L@5I9E22sP^!5|ZKU|d8FjYIXOIfLwCH!Lx|Q#Z4!yQVnC_sfKTRV#JP$r} z>bn`OWLbu5Do!Z&k)VWL&MQD^D3*DwXUfc%!tQ#Wul6tcidc6kY|6Y%^);zi@NxKA z|Gw@%Oku$fqaO=XqdCO3dO^a-oF+P#yE%Lda+|-x=z@|sHe%ozK6@vXa$XOo_5A(} zXpzTvy^U`X;wo1a6Z-C!H4&^*&3B|#>$D_y=I6UA#bwg!BYT8WpU#sn2VZ`FBifA1 zknSsmgP1OxK&g66+FIUIcHMaf?PZ;NP@##Ma$@$Y@uC`u$ujeT==|bPpZmxbSOV26 z-ztSA!440eV$=Lp1eA?wq|=D&CzsD$(-fTEv<2&78%;TB4NL>T2wB`Cdd9b)xdGp1G z{tfq)NvpmDcNs6p!+{kMWZ#Vs6MRC$oRZJ*HM6D?DxJ)qiQVFH?J_uaVLsJK68}OY z45{{>>)FYe`1X`&@IXkhy`T95I0pS9zcLX9cvHAi#Nq@g6Fauq?q^?oNujwEt$>oM z`ExlHjuSr5ote2N841APNL03PY!G^*RFefLz5=33cU769ME<52-M4GidhjuHx`>Q+p{vV2ZXGEMmCsk&{9%_d?5{apN<%2t$%!z`3R?!3L-Uk9cvZMP0^I^?^LM zCa#F<(gqHdfQll&n~eXg$5Y?^4tHJf>xHa_T`bPHjs{Q66_JD(m9%YiNjR5Lojyyo zFQ~iv>noA6T_m*p24#Dsrvw7)+Nm)1rC@5>btn;a^tzFz20cF{VYBN!8+^gA^UFS4 zsj$jw$tx$|4SG^vb^Uw)6SZ3xdl^gqoH-X#Idz@)rt58n5|*?uw}Qp%L>%idRXeLi zUJJ}6M8jsh>Mem?KPyl^2YPK9(6J$q800ER&g)H}^t}3Tk9opf+$P*4Sixw(q}I~Z z#az`Jq}F8d+l`Z(=vt;(;p&W3D!0HEqScK#bYPsjHZvAEt+v1k34;zF(zG=lRf+{t zuYT-B|9S`A_c~kQ?7(?&ARP2^0l)cUjEoq+I=pj(SbQ{Q95rkrmVddZPdFAjx_@kR z(J5#heb2&ZzL|-DGlxJJ{qrf>zFc6s>6taw&rJ0@;&dB#?t-?>_#%C?*KHvF=AmSM zt+=VtDytZ~3x{LrT(e%TdG?c#R-C(to|I3NT7rJRB@up{kT=r|;QDWbQ&-C2!(*o@ zP-%4g10PT+UyoIv=21GK4zf4S=gFai_K7NAY8L%^ArXZ3BGAri64Jpqti&d z#}8q&xkO&5Sz{@Ab++Sv2Tw}k0uUQMKLSdrQqlQQvxD$Vj#@Y{cURVGv5mwO`Z_ma z6H>qN7$*NV+Gi8iqaG(@B;lB;WhdEZmA=-5w*ACbRcL%%8Y=#NQMDTWfIL8A)qZcE zDf!ecJY5U3|Bgtka}K-$ew(kpSi^Q*e?Nw9bWoAB6EiO=7M@1|3jCRH{W zE0p47;S7-ImA@D~6M>jWOhMBGN0o(k-a7hUVrA|X)&8h=>6j>%K6^)lMCfZ#UCqz( zd}_Pu*MkP0R9rfN+BDanfRk4+&Va`u+_lkhne&X={&kvrK%9;ExPn=oe=+aHU^vw) zc`T6-mfwcMb&O;H`sExHr{c!0igTsO za3PZBO+Hz^Qe6bE7Eu^o$Ku`>HDu1$QO#59^kL-yG_AqqWwvx>n=n3&Nj%DuzWxRU zoCP&P53c_RaXU04s9GPv48#+u;qkV;-5bc4o+z4pM!1wwXY~_=j*4n46Z)1h(QF-^ zoW7Myw5AB-mdlaMORitKgoNFI=@o@LDoT@h)HvrN=Y5OkLzdQd zSCfT%yShmb-&9?%=vR3g{hsQu0LbT>c@A*!R$c-uB}+=Pm$>A#!lka&a#`(21S+6n zB@p6sXmE&AK2zxtl~428qeQ28pkzqga--;09Oau^zb-_-@J@q4{)KE+lKbzA3o3}q z21*Fwx>ARn@(IRLw35zYrx=~f#c^RGY9Xy1YLNQdOYN+lB-on}aj~s2_NeW5FwBU; z8LV?+1wgGl2`=y}o2{3RA^)AnSNb-2>Nr{<-{6E zOTr!Fy!?8TRjuJ@R{C38mbCrL1bfNTw@kbr7O9~iVV{=w*Lhqxz^S@nL~>uBIVCaB z;37W33sB$b0&{eglE3EFsKTlbdVZ7ouxhp3?X~|(gzNsn-{K|yQSOTrTGYJ9R#~ai z{9d4k=|np=#Ic;tRG));xlwIf;@4gixI7L7947Am3Ho68W-w0j?_^PI=JT%2YXsu z&T#dNKuZjqvdO`d0)?J}$KGPNV{mvgw&{3dsON}Kp^I2hNUWnqp4e4sAnC&tKd6Q^ z^df?_Y#~k5vxKTqzMO?tJzH9}pX4@8i@fdYSG9>Sln_dBDHtc<84oe{s-PRLU#?^f z&vymVg7EaV9HQOLFNpYt`#)9TPNS&Dy2SK$Cv}^if=*NCvTh~|kqIyXSZSpdt{%KI z_rCBnO}-oyCE{2Hdg52o`-x*ZK`Xy4)o$PUT#Ci!o5on3YlFx zFAwT3rWJ7-zH>3xj_+P8>ko7nxn5xLz+iXhWpt`Y2 zU%mKI&fwXT;?PM^v4(FMBa3Xq8 zb!ZW>fpdF3XxcTG0Ux9W7x|iush;LCs?{k?vc6mHgIO5innTMReU&T(oO;UOk>>ii z`azx{^oLbRRI-l|NH1Kxa8mg|S^@Q0ZMPL<6Q4zm?+#(UOd#$XJu3tX#(~Mx)1p5V zwX_nr&O|~;j)|rJv@G(KzM25F?4~w;)ov-U3#LjMnV5YR;8>*=Z;8xb`YbDb)*rWm zQ5IX?lX&~e$h5OBgM5aqflt07HCyZ$EZCOlZBhw2Ffl3XSGMd?x%x&FdmN;2 zX?4a{V5}J37J&*f(_!4QH8~;MZgFtr$A(joL>radnCwa<*((!CN8DY*@h?D_TF0rP z+4fEVw;~-E0#n}AzYt~D($^jzLIu;j^5AR<(_&j_SVe|?x zFBUI@cEn&veiV?eWz?xnQ&kuetmWTx&2+f*@6!}k-5B!JwEJDjgktR2^}ZPm3ER3A zeN?VkRcrLZe9(@%ATbpqJ0?GnNCDrzQFHQKV9BM<>nG1yz*nad<%#8NPO?I^3TNEl zf=P=c_kKji?c9KcZ=RtqX``z(stxXJ&X*S2G)do4N`a*rwFm5u6F={($X?!dk3Jx!hhq=}CdU4EI`i>US9!AhB5GSO zGWt`7y4lIcAAKvB2~UN!>5$akU}TZzxE?B*0w2iPA z^kANMP7TV-Z~s+h!S`6VO8$qtTbZ%Xm8q6Il-dbP6vy!OtzBTt@S#|hjud##Igp2U zAf5~g0C{jw&sXNWOFIszAYql&{^01tYWo8?)v)}X9^qJ;Aq`~~VfzU!=}XZR@fpEI zb@@WmvtdVj_rWfFlvr}&MS(Sw(ES;=Kd&fdY!bY3jrBgn(sH@Xdg-v-=)u6;yC1S< z-YC4AOexZ;?`C3wMogEENjp`JsJ2zwa1^W$!Wjpg})p= zutB`sa+VQ!<&it=dDWBsX=Pn;zzDf2uu)3Ldbs+HJs7zI5A=RI1<0`uz7K`cv>;btre@L%H+B*<&g!zj)x``^4yK`vS_e>&TG##V6Ew{ z&TQk~C%iwV`beze5%8pK<$%1ZWS)T4!~?aVDzBjS``b+9n|lN5IllwlNHeZ))VdAj zdir+2A`@|p$^3&E80wjz!%0X^Bh7CxE}!NgJ`qGm}ckhDP;X^deSe2j)w-ggsev@LU~w1}0rC zn9oio-y3ard?Vu&V!cSj>lij#_zWHreD6_ETy2|IrL#H#B^%g5Ie;x>iwbs^3GYkBNwp*C6@ap3@+9@Nf&2xp_=-L>d!?WZ zXX4QMLXX;38f#2jBN}=w+x2g2zV>%997ffa5txIqH|N5taKwF9F!Spl>Qk4P+!r!Y z=eP5`I20oqm}AZ7>$b-gVDel#cQ$_HH+3yyOhmt0B{6{JJ?kwJ$xFt-v_JFZGnG=X zX6beNCNHil=ssYHlQM&?&6##cmdJ|v&}J8*^`4my_e$VsJrPJqX=j8kRoW&A=Hge2 zaJyP3^CatBytrFLM$}A%;HGLHBJ?}lbeeJ99%6F9#SNgTX8I9A;Ppy;p;Wn8jck;% z6u4MZNZDwtyrtkN~S<0-izbEi^Ls8;=wn5J$tux zG+VvJiqPXp;h2pmZrJ^T+x9X7%{dn%vdvhIoBTUjxr%E!k)7l!Id46GKQro+=+_2Us zKw5lYnolJS31f;0kEZyY|N3`jEhK5}KjB^Qne%J(*Vb@1<5dQVafQad*xe)~mSS6uB0o)ykR?T z&L8R~bOhoBl;5OcH|CkT6uAkWV2HK#c7qRU{wY_l+q~V@8GB9l&&6h<(~$cVX`{tP zrL{J)A1q*CU)c<2mcJIA1QU@C^avAJ=yK|Hd(&veo@MwBSo~g&)ITu$G2USPQ*N zUKwiBV51}jzaFEhI&so2c0x5~oL&Bgc6_uH-xk9Z0=Ow{83Aq=xI25J6X+Q}zzO(= zaA!wXdYxFl1aI+(dSLUaw|;b2f@01$B38KOh6d9`z+TPG;g9j9MBl-wTWEKzKhcBc z9~_T4m#Rst>qfuf?XX|8y)DwQJ5%1aBa|l3>oQ#bvDd3~Aw$rmL?u;RPS_R0Bmc{B zUhar;ZHs&8Rra|_Muu17kGQx)1KO6ioB}-Maphh{kIin>U)qlkEjW6Ln~1tChJz@? z8sDybwed4*x3?I%ya=duiJ4~?Fw0)ufkq}(6gXrPBy%NulC);smJDnQ_`J}$M?DsL zMw9j)Kw0AHoF6dyr1yy9-}3x(quMH?#!6teX+yg;>mb#2rp4R5hBq7=u;IM)phA|b zw%YN69q~|#;pf%ho#T|Z6z!bQ&|V^gS_#4d%^R;RBOYx}Bo)kg%c*7Q67b(>fT#+U zY~(9#wP(jg-;m1e?^n!zPgO>9)JIzHDSILR2Nswa(MI;1wynOfz)flS#R%$+LF?K4 z?UAuXZ`g#Q?5!=v{+F*!dW5>zX0NyV^9~&eeYHWmvNV`2ztjO~ZkT zaF?WBW>9LM_~sKyNbQckP*8OWXcY=GKkumj^gX=l1r00G-Y3|811=~|b zm~1#{+1%UoRR<0!&jegwQt@WbgkHDhCtrKrrW9HJFHuAP0Tn%X&wJVz!+mLj} zUu#3pDL8kiHXLqmZAS;2gBv>`CqdvpT8`lCG3HmR&hh>EPYbHt8pKrrR*IdHF z61tQKx#sZjna1@p6N(Y)hP8=xWCQW4l>dOtfESHK3ID;bdCQ7bvN13|1Tb@Cz)B0R zv2OrUjhhuLY!eoC3m|&>A6Oj#a+|+{>???l(|86DU4z%IVRVE8@lBs;W|Bxq@BJ5h z2f#ytaUhSA7&7>uKfehO{e!jp3(wmPfmmzHE3g7Yf3bZ3Y-^vX2Gn@~5C0A@*?$mq z*D%utg8#+z|Jv!h$bZy>Kcop?1i2RKL`vqZIBYJ#&^t`~=>m)akZE{g{^GOyz6{3R za(zJP;hJj)xwMh^)f(0ozJIi(*T4KonQIQ0kd7NS7WB=TB~AI;s{KhSALO{Ca8&xS z$81xJ2WAn18goB7*)NTfMmP8$guK~YRHl`T)gIAzG2yCuJXo`BYDdA9$K!z$zOk7)f6ibWY=&S>u`WA+YYvA5sI>YX?z#a$J>WGWqn04g zF~~Eg@O7?AH}mU=r$6Ol3YhkDW!t{d#f=4i3#DI;LF?{c#j|m5xfEjqHousjaW4uV z12Jv0q-JStnxfq}awO^*<{moE*8+aR;y`L*IJLVY2_Bg_;jKs4Wh2m1KH(nv%Icvvi8>iSkvWTnytTs$mS1201Kug&5PwOQ4 zoh~JnDN*8!-pd{C*wq!2lFO~L=eV*{K@TU?ZgCq+;{tFeUHKTg!V`Y5<~8IJ(=hzW zn9`#|c?^90GSm$b^!#$?ppVDuf_K%xz!99w#;z<=2OT9%nGeMRNVIWs8e?2SYFn(N zKsc1{hnnr2xwTCG+!%ffMG5&|66WAh9{$QmB$8)wxe8FYdKT50qvT=XmB2bLr=gFB(Il z(|H&7jp|+iAwrplg@;C4>i6kE@~o^7s!JAqaHhVJr?yZ~2>{xTXmF1LL+=*%y6=hT zSb6U3+D!gTX5GuuDKh9(9x{q)Icvv9h10FY=~C;#7qv4@HqJGwTF>WmOEK8@TiHG3 znR>qw$#C`=AwxuBU6}9v#UPm`$M1XfwZ{vCSIh}Mw_jW%dbo1})4>-3aEE=!OR`n< zYO+xVyWCGS7J^m#n40{nUtbn6uO`;CcZ5o3YcaQqu?l2?E5h$W#TP?yc)QwMFLEYt zP8+a*q114-z@zo}aIa#_eQGhSfbwM~_5pL5K>#_n!T9~AY)~uu4;)9l-#>&645WSu zvC;!fO*LNkUF?2$AoKjtK)LqIHgVgTiW4ri4;%0%`G^NY9dKKMrWS@Iz1f}8cDgE2 zp89&nES{+i?GfY01Ll#LY8?S!x4O8WKsi^icV1fOC8jKC>hg%upNfh37Awh-=`~co zbXyap(-Z61bXBK`vnOCN5AuUKxU~P^h7J-BrhccDorxr#B$;0WdzNDjfMBE{06x&L zSf{w4`l2S0n5;le6J(jMq<=gx+F=ugTwinf^aF`7*XmJC%R44ffPVjM(U~u|WzIDh zAqBiR__TWfar$urTvlbn!Rc3X&;dI0?we;KcqqE2mMN?%W!B)D|B&mr^x@d7{)!KB z`BOI0HI5N(<+53+JAo|>+~)eDONs52$nnW3UuYiQpK%3Hwc5#MKUVPYM@#)s+i6Dc zIng^c+Y4@Sj@7Qx(V8QkVkM@9#Z$bl+A+FqvM=)F;O08&u|#TmRp(MU>Nx%+>&zQ{%o2|wjt}?azF6TyZU;~0z{)IN3huuHSMPjxf~;|th{p}u1iz!X z7^{c^qRBY+ac4(sT4brOu2BU)zS!r~>u$#MH**9r2AH zgBf*s%?ESp`JcTiXRFXRumuy|V|L$PVb#d;=o#b(M3($nq)zY0oyMfYF~6{Y8YR)_ zmg?!(?(BVhT!7V|uKoQsE}f=Z`lF`HJZ%Di#_yJ$*ye}J~k;XJys=NRe zs(zVlEFVn?r$qyGcgQN|)4A%aVdG{E&jh`(Jikqp4eFv(K6Y$a9rtZ)(?;C+uTUDu z`8?dpJ<-<1TJK%)k!$F+)LpAT)Tp)ONkUyR8_5 z{q>uH6->_zL*gAojehpr996Ec$66L^*F{*56Gi&$`>%BRU7a;-%QUKI-3D&4RUqqZ z^c;eQqiNz!1>y?Sb)`W*Ts>ox2&p4TYd|NZ0`v#0cv{xPBLt_mB6|&?p8$Di2^3U+ z6sTrM7}wZM204GOaj={G7(+7XgPv*1X+Asq>WF$!Rt~(AGTmaK)AB5sA7xDvJM`Ig zxr$jehq(=#6RQipQ7~04A#3^Vc=Xy1nAT zy(G!={$8@~cwvuv+EW;FbRt&yaq%%|Cm-IdQX*^d2@IO8GI7?6Ch z+Jdh_Tl=xLX#@}OiwM=;-BJ37A!<~a`nkZ{Ldxkhsu@z99Xz1M5~%k?#wlS2!m~8y zVs%B*tRaMEvSgvpZyqG=SWP8M1=Q!gsl_kZz?6x95*Y|hetp~u`E&ATv(pieHkKGP zSMK{Krd()XG})A-q@qKJy6C&iq{DImh>ABeHCMmYYI;}6*{IgR4eR_%9lCfCK%ORP zm7X&2$w{&0!%*``cnqy7utStw;J724^gplCXY-1+Co`@4M#(#C*zzmFfVf? z8L#o@IUgu+S7jV;+05(ZJ4zibws(dj%0W$iZZ$n~&E^jjpy_p|lg7BC0#Qrv0QruX zNB$$<*BbKW$3Px9AxJow0ugOxQ0xTI1ZjFkuq6LsxmTBPOQjrLiCA!at!4SGX697ndW^m@Y3|d(iVq_y!>SV zIP^!lNAU0*tL&SD6L2b7@J-g0SuM*_`G9<<`bw?VKUr=2!U>g!QOZPzh>9CL!-pNe zjdt)nnFo?5#}Oc)wiGy$-;m#9QO{x47Q{y_HpPkueaG|nQaxRoMzPb1IL;a+7TwWm z?(&{>>+zsXAQv-gc1&y3n{CcA=)W(MW}p(bhyZ5Lw#PL7+X93vBa zB);EMXr!^Q_n!_{m^833-;wavW+5IjzFf|_Wkb^lW;khttfuLCPbhEI?$~^Pr2;>Y zT&f9rjAZSyuBU0a-LhHVgBLr>Yngths|$~uSo42l?>~c@4&VMkRKzaDh6n~w0gc>e7Ge*)ysR?khwv=@@tori6AJ%qZ~?t1js?5(Dx zGyXUhb)T&i*50`+{b`5z5(R%z6W8*N)Eg+jdTxLJ}Re3BEu6v)I_jXaAnW!qB8^+0K8|~TDQhy z)^~yCXW~8M(EEWNvvECa>MJsVj}2Ut+@u4e3iKadtY^#7K+6X}vIrl!uW6UxW>*Sj zOwi4rDsAzvgeBd4lzZ)4*4`ra17vq)d>T11>Erg#T>|})KImRI`kVxU9u_If#l2^GXV4zkH{A47rOh@EdXn@wp8> zm=sC&;LavZ_EBl5xk36j{S9-6%9nNvXcNEvJ%^*_4T*4U$=#5jhCVs-7HylMR?jFI zD>wU|z_e_8Jwt2g2vJ{Kl*4g+Fa?&s4A~S9EIgBjhQX{21E>qW%Z}$T zC)$hErkho~{gKpro9_qhvx&U})56m>@3Kd!?jl8DG>)Z$R>9Grd(fG`Qt`xMsyOkp zx3B1RE~9FmJ*VaLjg?;^DG=y4aOqX%&be-wY(ekN(JKGfytG}R!0ea$Zhu4M3^sa9 zg5FQKD8u6jF>w9*1`(vc-rAFy!4I`8+u;1XnAx{&QBek2cA0eIjuvO=cMKC6f8wD zLP8EH=^tN@=Dyn29*~4&B1wTHqCIpJ83#L$+Ium+A`3eUnuNbZ=?(RI>N)H3)v-S% z=b73)c_dK}<}2-N4<<&fIv^VJqD~7D6W;U7TT^xBHE|tZTeUviZX!MNuSnD#RuQ2+ z$gvL~A44p|{nr)Gli-glO~$p0o?Y6zVwiUlac^gtZV^u^li{8H7^|7PWJ_ecr%0K_ zn`F>zm68uu!(HRYB+!+KJb$7)R{Xfu@j~NJaHrOdU&)Xks5ujLLysx4d{?V&C+pFB zMJnwS(w`n%Cx4J)Txa?vw?X0yf|J%)%0MKDCfz%?3DE!EyV@M#rLcjY$cv844pygq zHd*@W>o=>|ZIpg>4y)QhTUhMw819`;(tpkBPIEAMtx2aw&m))x{)|b_H)<^lt+Z_w z%|i=+Lt2a1=b4$69;cNk@J-fV&tbbyJ=L)Aa4Ci_Nq7D0w>N5puIb*qGc>C2`eM~w z_0=~zU0#AotFs@=S5G;Z!ZQ>JRqaEKbC(BdkzH4%H?XBuylK-jjoFPlwT@rsyVX>0 ze-Bb!UHD4!9>Q-mrkW>9885hZEq1{IccQ;?p^+@gRGQIpiXu?(8gneHaG|Xv!t-_+ z8Ao~!&Hkn@H`?*jL+61UmARI`X`UCS19%b8rYCn+B2`+}=C6olFM9Y7*XATH#Z~wY ztYe>lgaP2-$L-4lUsP)L68ZNxA)7TjORRH`kx_IlvW7VVYlAED`pBKVzB)C{!Dh>e zOFQ9;19bYR!Uo@ck@w)5VsCFU-U+drDDNeRmENW-ZAajbbH(p39oM3P` zDgIroEV9w?Q8>o?GW6xBzG5oIKztT`jv>`lXL9N5?SY@r#kI47)Z##InxO6sSs5ASlg`>vE)2VjccgN*mny!Af@58zwE{L zP|!zN@a0*9N*Qjhpqb;(XVuqH^C+>3Mf9EWr)oEXFEHpJDI4Zo5c)ZR&6$0C=S`30-Q~`D3r9Zu!b(7;KlKHM;NB!-?fFq1n)UWyZk|`x$=&1KOGIc> z&^t;xk?-kSoZ83R#FS$L_{#v~{k4e-wM1+1XPylh3n!31^X58QO)2Gq?+dr*@1no< z3;1-{p-q7)b-toG*WXc%@y6XyjuTfOveRK1EvAmr=eGBHU88gxLI1V&nJc$8s-l&D zxrzCqP&9w^?u{9%VqwP+$=W>j39J=Er@|7gX4d^i@XtURJup37Buq{P%{16VFb<5f z4X&^)H##W@vIGb%(ih5U ze>h63RSUrw6mhL7@c0Ktr6dElUq4ItI4KGAWlgMvq2)zk7y|k-vFVCf+it-hl7%NL z^Do-BiM3hZV!2>2EWL(&0JnD8oAmA^)-*Yvf_w%IwKcN-xv4iOm(}qf>$ev-Jed9f zZ1YN@V5Y=}=dG0UORIz7D;|ou^Z~lfGxq*S2CCBoT^iwo!N8wqVz|QxK|W9(`xZML zePDYpX6=0P)DUeNPZ70tEw3*qR;Url;`-L+lYli#;SDYh?rdVxfwAkGR1eN{dLx90 zQx%=XFZZC(5^y|xIQGN)_ys}IJm|LWi79dFRz#a|j=AHwNFh%h(Blj7)JHsaeN5U6 zTUM;Ec`F}i4a$;FnA{CB^A4hE)4{tWa*WGbfPkbQnsoAAm^g{nCXGD0{taGVq$`Jk z(KIqhC9y?O_v@63$)z>!b#mX#)*0@Y0RMWaPZhMFA zgH6HNlRDy-r=v4cYFvVCdRU3=@yokEfN<2Z)F=pFvRM&(`+eb98crEkUVq8%(A#cG zDUZj0I%q0AC7db;w>D8PX(!>0;3A<&{ucFv@hDhkme8XVU*Q`+W=~0{9#d_~#kdEM zDQU%f8~T<4H+=obi7w}77dK+!a$Tnqf}m^tRryY@MJOBC{1?~G4L1xQWflFk)@&yE zFIMA$mgB3o_!}J1M@(q&Z9mht{pC`GKMnY$clX)@MSu$er1}I@OQAjgHUy$i^(72Vlog{@kWW4;7}C;tA2Om`=Jh`t=Dkj#`!n1b-Z;IF`CSbH9Ft9ZCy-eH=Yf zVuc%b;-Jul#uZjX+ANj||9QZ+G0HKAanPZ2xSOB>OSqIVcr)WexQ|kf6XpL9O$q-) z>HYsZ5Ba~sj&+~8uJbrfXm##ApXl}G1r<~% zu=eV7$Sk#yLE~yn>TD+J+KVS;x)M9H*T{Z#gA9ue-eR+U9U3vCkES(ryo;DHQ|l<* z^6;MUv#!jeLLuf><@&Tc#5&8@tS2gN`ZDOH+5n_IFgHhyl0AJP$yw0*waTkK!;aga z>dlay0p1I>C&DETpJZudj&bCJkH9ycO|*T+<5D-iKa5)Yp5?+Jh3-u*2p+kyS-YLC z;Uny_#tH%^wnpg@e?rJ0!V^fY8(jaY{$_MW#e52-QwKm5Ki8gStZon2&qrWSl9Y>& zOA6<^2|;(bRF&jTamXyaeHI#C%byB1_2-JP!17XaadKC(^1Y&ahXkU$iMhr@?T_FX1#UqPpVoL zPpNMa(WI?=Ii%M4K2!jO7DP@DFGef%Wje@1-B#bf8y%zC-hx$~xrH|bZXi#yJ-l|> zC5uJa`VPGl?i}r-ZN#T9veX0OX-B*j*@f817*kAj=+}r^qqch;^HK_M4?ta6R$czv z9~1Y5`fqlrsYnmy5Ga#EfHCMf+et+_r_bP+VX5^2Zf3q;FZeE}Asd6|W?Jr4VT(C( zzENgZSG&~nYxIP>oQ%KHq*pnOcG_C=kv(Iq=wyk!c)^? zkJ{5V@%Gq~%r3*~o^5f!2+5-;=p?>lZWm0~*sw|-cjhM=ul)JAXAk7X--0DDX{ncj z&ASrw%8JBHS5|4FyzVWJr1?j18!FmaxQ5HG}H`Hx!rQ=v$sx={f1vX{L283 zmS-C;HYHrSc%AfoV8fLQ6R$|lhdzVGBskrI;u4%t@))Q;Gbi+0>3pTkd*k@v>mCNAtlZ2^Dw;VC(GMlbs*9_cKwnLGAA@;L#vzU{xNfkc{6z{ z1GXzRP3!p?GeukN*FrcKWr9DCdp!)n6=mrj4gP>B50>f(K05~4W@lVeYYt5u^$_B% zo90xJl;lVSLG3ZGhW}=drlrZ-*j#p6j9Ej(56g!Bq)L7JqnKn(ZtcSWjhdI@MzLVGZqK$JjET zse$X{BFp`_klfL0F%8Ce3AZmYY8`IU zPYBz~H>`{jEmgCmti5DLcXIdW@9~{3Ns%L&Zt77?e<~EgedtNH8IUxg+ z05URy1$IPpCD`9&TP8}}k68mzuuy=-+&_wwp<}&?T9-gGGI;I8+KiN|0DIb_DyQfx zo0fZzwsqNSms~Qq#1_Vma{w2wH#b-B`FQTJqQJOt^%EGTskrrkrs7>}m5ff6m6&$f z!~H`yu(L`KHCKNwz<5u2re3D;>U zTc8ifpGFmEKP0Cu53$j>LMh%}cj(U%24DPQd4cRce+wCFU-jE~^xL|smkV6u#dvqg z9Yk|)SCvy@b%tiHvzsA2(Pq3Z0Ou;XBAW$I1uKaaffsbzLR$a|2cCoqp((-0{cs zhh!kes88V4y6Epbfkuouc9esCIFHAyE7o|h@ne{Tp{&zQqxl#I%(F7wj2+hc5hC@K z5Ur?ZDsL9}jtXk5>2_+Yrw@_yCf z-*iu4LwEn1i?SB#BWw<6^9B{&ejMkt0A3~Vpf{C;u3>$Os8J^=;eG|3qn#ODdszV) z3e>fB3=;8g^Ji)p@DwnSIoEh@gGA>ns!9XjyO=rW7nfa!v&MnjHcdOgrLW4G`z#*; zS6SQ~>N1NLr4-vL7%x^*(j^}u@%7X8F)@cUhHr&DA;HlVN;WN8z_`Bb1!XQVO(Wmr$L`|;Ft~7?g4_V$s|NPB%Z~vqs;_A#^ zUEeSn1q_H{p6%uX4NqUXmwAa;ztuU5&0_rgV02|)a76g6*-Uxj)M9N~b7Zzgj(zzc za#=jmQA59!xa+Xz-?sUpPcfQYs7ygG^7efA zVXe8~E$2Y5qCZr52M~{EHxmn@nVr1x6Lca$pDZG7%~}lALY2@tud|{`@?J0-)^R6# znNlVXyIUDX<*sH0lndC3=t_j+E-gEidRqsZZ2ly-!{sOOQ)l*}8vIR`Bi&aCkA_c-UU~Uqhi%`=*g+anLE^*L zKdRRew=%@d38?Pv?p?xS=jqC%d=fT2$Hx*IW*1B^@gk1T*0_9k<%0p;6>rf;jH|Ot zG-#fDr$A?2gq? zb5uf4muEeevYy@?i5QWESLH`!Vnn`hAhxOK<*62S^9{r-r}`0BGv8LzXW7^kS5 zP=Bib*r}|hjhae9h?d`__A0a8fz%oWta|L-X6o+iKN_6;*w_-cx)P4cYy7$EOQR|2 z8EsSCYU$NiPmLc3^0I#!MZB(%IM#KfQ{Vfh^d%6Cy|A_xb>USmVt=tBtpC2HGLsT9 z!zMk+YI0CraBW+!>YE7pEB(LH_wSBr4HER$^_UgBIubKJX@BnWni_X?3+B*8H}Snh zxV1;b@`_%6YSJEO2oBuURagAzFaa7W3>FQ)eXqe{E826;-eBSpA<=eN?AUH_pN`hT znS`mPc8QuQv0*NKm(Z!Ie!fG%W}Im-oBzs_{?GTEUwx}lK&F0-HXRB$e=ssbe6e!H zRjAq#7Z$5yO^X5w zhEUWmJMx(`_aWNgiD0q_bVK;?y%}ikp(+R*k$dSih3Nm zwI9iP^EOs^e%9f!*|8`|T}xf=PxH#K3lrRkPXCehqJ<8HN)|DDOuZpdjAiZLO0V{u zJNO=H%~#26_+pT~FuF^f1F;=HXI2-6E-)xvp;OhUYz|dKqA-;ns6}oAlcq{thaB&b zS()fPdSS;v4&9RN&l9(o2@CAZ2Qde96LTBAT(Z!qE5Kcz_i zws9fyLqK@q^c6(kU)+QO_P;m}v-8wEItrB8SHD~+-A(ky}`C1jK z+w&)?fLnYlmHhdBT9B0DzOVx8U*07&MX4$S6fwdYUjqMd=I+SV>#Lc5?yvNHIEQqd z=eHN?5o21j%C_n@XX^LCw*K}=yXyHOI@BtXQ89+6U9)QYLpZM?Kg5#srSCqddflaH#@)16LVZ7?s@+uKRmq08QS`@fEQiOO-A>%F zUhUKQB}zdwE_AP&+9(zFMm7&VPr=bRD}#)4zSCBflhMW2;<0ta%dR(lyq*r#)F{Fj zd8p`dSC8kgS&ysX>*9wL*Nm=lY5MvFZF;tpeu3ZlGlGwS+UGBqfs{FP$)iLONYUAi7Fw6!y_vsv2^i8Q01nP?rR4PK^DF zONkdYC}5xSTFf_%Cl1K&{mp6W{ZvcG8u8!!kuc8!?JKP5&h z#1dlf*D)tMOYq`H{dHD`(#0fz(C%p@vTQf4Ktgg-KlUQcPkFgdDTm5-vFeLEb(}CP z(}hndpjC^1FKXTBc{_dWb-}F6{cDY`NZ0MHCY>D zZ+q$kq{KT%iyufjUo{u1HcoUZU9VjU;UkAte{!g+O(RR>H3190-cn0-Y%U8-3li#l zGyVp8hFd4(MOAYsYHhgXw;q1umFE7yO_Ii7*vfTRp={0is@+p`ea4X#A7t6RKR?{V zVE|l!kDBwtg*`_oU>>LW=P$~~Cov!|d^K1aP`{5bdaU0|m>0y_Lka@8Y)8sROD{Vv zKC#L_!talYdmlHd^JdKZ5}jh`S?C;o;*Msp0Gf)M&D3wTTWl%3n0qjoYr6fdZRHFH zl(72&q>h~vw9>wnQ-_%&)Kn%rzpm!DdKs_#rV@Luc}q}PY3b)agnSmd%T=9Pq48(N zu{qlrq=)hvPpiyD_+;%aWjSK&7 zUbb1Ak0p7U-NDIXR^?@`8T$VDebu(O_wkokCWI0<%i@7w-?(|4~Lfm z*M?k=fl9I4aYESq*D-x{sb`9Go+)5D9sjP$tA`eA$q&$RyOs=(B>Gt(;W& zU7LP6y!}zEknK0^spFIWhNrpg&0OUxe-`4~i3`=9hd-e{m+JHD7R8e{v=d1UGYp)* zE>yn~y5>OkgkGB50F5%~qYv;;7O#X}mOg|$9J_() zyL+~eo-^G*-3Yn@`y%j(@zwn%m9dxqKITHx&7U`Cuk`wXw@3=f>0W_7M3IC(7kbI} z=--=f*j|BI$}9M%u=S3 zixo8G2#|L@%J+$t>|X>_t>L&(*f> zA~i*^Xvf2!&g)~Ho&u8$f|g;su_&Qd`^WkO(aTvZX-&Oa_*24yZaa%@9;u5vwvKcm z9*^tV3-bSPx?wLW6Jg+Wxvk$Ytn1{!_P)ui>S=wnBnt&}eEMXXRN1R^6oIvE2r+Wp zvH3WP0w@Dm^B~5CmHrXJ!^WXUwu>$zth*Mqsp;kTC@D(<#5gVX9!rgC< zG(;XGj20ix*eO?m{xR1n>5X|oLe#8riwC`bn@kqG)N9%$7V27_;o0>`wwTvDBSVi< zt61i-LM-iTmu{=u8UP!x^SSS4a6ELNB#zv7et-G2n$esqIV>$F`l9GwZ~cb*RA;#< zQG-U%{aeP7q7*n|R}rEo%RfA0#4~CS{d2?Cl}8uAWh|FozZhxb!_MUH7#;HRbEjNZ zLAG2Z+pbAp;PN6gK$An!+vY$|8l)#Hxc4vGny&x3m_&T-pC`T4>eJ3iU63qzNP3Af za$<fG0;Y~x6s8a+{-z}ribzClH@$|XeK26 z18*_av$v zE~C zHz$nM*x+{NM~2{mXw}(Fh0&E_t7a=@_wdKI%yxsS%2nPt>%?4}GJf5%Ws@aW%VR%I zW@A55rPw%rdldBY$^N8`YS#gUCWBW*XX$m|GusM)dF1>xde z6i|T7<3{5Ds}-q!t4<-dJbJh$>{6msjV-VdKO>;DsGRXhwq`z!VL~)K?>>zx9h@YY zbn84{_XIJhtN$khC2oI#jOLkKhmOzFU>}dSO})AMPNd@gd#^zeTIT|cAOPc&>T)Vw zjlYje(<-_a1~vA^n@efedVUo>AnMm`GCl#+)G)mlv!vg|;sxCl7h~Xli14&Ye4uP7 z44A2*NH%pm&`G5v;#N!Rj(D|#EvK_`Qe;a6wtrCloittfTa~g11+&Vvocr9l>zXbt z>RWW?{65El_d2|^$tu-~)cvq-{un zBY8(vp5N>lHgrU`P+@N#kpK;lOTx;>;3A%AOP&1n=lC$y{;1xEB|Ib74n6ObCcsqY zU`_PSo`ILXZGV@wi+zka;bB*>e43+H5u0LDMN4v6p5%4O&0Fy7^HbV+&Ef)hvVk!U zQ=Vs(5=C+d^y7S3!13%3=(kR^D8(&Zg^gY6A#9mzh+#v~A)^{JxrC&05@%g}!XdPT z8WWy9xY8M=uWNF!I*O7G($!rHT*sVrRbiX&s%px8Vx+T7{UC*pMjiB2TXr7FinAU_ zxtrJ9`sX{&j=8r-7er7k4R;RfBiq}G=DIRNmo=^oJ1v6_R?%Wu<$+EcnoA72EZe>A zk${HR0hkA)S2le7K~g%VXM4u5qPva`WaU|~)=;iKqmuPi9^W&RZ^~+>u+23)vNph7 z$StVvA|uG~%*qtdFLdhmh4R8wSRi6YC;1T-w2V6Y*Da0zI)9vRC!|@v_dbfoo>xjA zJ51Bes5_Q8%~wlJ0A2qYRmVQ@r{i7+E3c>0{Q$wR{Mduk@qOUfTh!MzZSOFB{KV&bq{F zo$_^y@|xWh{KiE;(&k7-rH#Y+N}^XNY=tsu`((Ral?+JwJAg6O(rl>YKF{ zHjykCGRGuL>rHa+tE-!P`|U|vTAl9sBhBpmt9NtXSqsHM?NKyiCL-@HvSr}f4itnZ zmIHU=Xi+=-2n7PTK4gHVqf5OIxc2S=041a^E$1rBa2o(C``oQLb8o*Z@Fpco6zpxV z`fFN3oAi$;TxTeb**71%a!5FEruIfj_~lKX8g zJ5f5kT#+k+n~c`E##hZ)TzW)yCyFV@w&$n%<>`9z)LL-5xCc_{&(ST z(`Vz)sNeRATFtS3acFY9+w&HBiRK9T5CJL|I&pVU9>#N+%4tRy)DQ(sZO2`o?Y=-q zud6xrPD};s?t5-sSX;}VE{rS)mN?*W7NU&=V7<|2#|0~ya+`~(4&eeMcR7Q2v5XM( ziO(}Cl!q4itF=96Ks0akHGN!ipn=^_Euh}`qiC5%S$K|6*X%lknlu^ILK?@;X0+ic4q8D#5@7BINRX+w;Wqd2BWDZj@cQ1>2fjS+&MvG*;g;#1e!_xdd;^~ z`vhW{qQd>C%xYM+PnW#a;P_Hp$h>!Meq5%c{Ny#QhdvXjte851Cy$kob=my@KuL7i zQ_mR|BsZHJGScABt&g9<`nJh@g%4CKnlJbJ6vpDVrbQ2C^C$M#(`goF)1CZ)cx0Z{ zmBL)7B;3`M|#^lJQQdS3jn;bC)XXUQeL zM?kA0v;31Ud4X-6L(3UAjyKy;d9wavX{5`Q$`rt%96c#V$#q$QxR|A0vN?xWY7 z!vQ~l>W3dAYAwIi^VfOEU0ApLW1YC$h8lob{?TKyHvOn>l%3dN>`uPn;@EGXcF=Bc zNSp(tOv z;CxoAZfeRC+o$%+@8PLAoR^fJQ~q8bq69Ev?t1aygP!osn5(x<+E=K;hOcQZun$GV zQ-(*E4-(t$1`#zBE7QLtXm}I7=nPM1535A$sZ}*QKBp*{E&O`DGqFsK+UXRTGbG4- z4U05g$)&;En0s4o6-LqO6n&90M@^WUY5B0m3<VQ2l9iN*2oOFW*0OB6A zDw3PPeryVILwQ_#Jxg?Qk$=<1yHj!UE!Oe8Ct9`KVm=rBQ@+wBm?`X%<@8I{7JI5T zXm@p*d3=@*3iCg$o|BJ0PrQFXy>?bZ9qUxD{uH<{Y&Z3m&1j+{O*PfRomUidT$!ff z>#*~8D{Mdf$nmt@meue_RAezsmwZrFqdJ*uO$5Q)3BkW+LjT$S2~TC84WbupRPY>r z$m&6Pfrtbvi~Z8x30PaX9b&zs!xCIgDkcDsA1%eT)s3Y9EbEWdm!ZjIinuxgvt zo>1ei=&b;~$|%oZCMv+i?wv4hnaQfqO{HqaO(e@VhjNiyZQ$W=Y(I@1R)s(VUnH}f%y(+odOf?u z$$r67zkh8@?Qwin^w_z?gYCW8!Lk-z!v&c$_)^`**$sJtwBCg0Kt$Vo&sdnp@yLyNst}c-I?05CMNv&K1 zg#z-#awcB~{edPbpWwNB^rpmxF_y<#`W)T9!$etM;ZS{tI$l1_XW&^*@1;V(mC>is zp-NlPVd_IEu7=uXWwO9oXypN$%o!}R>u-lR`H0g^#nwKJA(muZJcHT{qNnO5HA{OP z;)EX`Z?-o-hAclLXQ<{G?t(fcaH%3zCsdrIinTi_7Uu=bw>)V;{w0lT?Yl|2_V7db z(PHcPJ7u&5drlKT6qa&VHBCnhs2RTuWDQR^6Y)LogT)UxW=@_q&SnDk&SYT30V56u zUR4;v%*{OAq!-kuPBqzAPt3agA8Z-F$p?ITA!vVG2p$B%*WJQd_yh`l6C(nqdy#)! z2QgeH#{fa?d^*xzzlHI-a|PD>FkyD7rTNwJSV~2T(=ff+-&FK;G2-%aPnK?jv66vj z-91<)6lUk^M-l!(G8}7M0H}HKuSv+mo%>AaLvm;Eb>9Fs81gzjC~SrZTLH&vYO^0= zD|Nz8w*c_UcMJ5a7Fn0~?&Kt$R#Y*_xPzV5X!ls3 zcUDI8{dMtlcC3|9--ew&T%P)MU%*BcAtGdQh?pJ4iVps+)T?!a+T4S7_hq_!kRiQn z9adg8zn(h!HZIIwVh-^3Gph9XvUP9bk$8;9&1t%}KHC3cd=_h~78kXg*My(nFxyjW{sHyKt63LesZJ*kVwEQB<$N^0jI?AKow|K$o zfUE7halquP47Ju3@4$G2_TuK=$<~UlBjMw);qbhqPrK5;#(Y=m;<9e9wZ?t@)s7I| znxf*J=i1l$S=C|6%{z_Ol)yW!hcz}!yG`BCVo#WD+Z(yI!k%~VgS_E`q@Tz_N=uMn z_~*;ybo4#Emi^TZ!WOdHf{84t^7Qz<9KRIUX&pC%XGnk7&EDAucEXf$*vR$byqXG+ zDTXs)<?oS&p1yZ&zV zmeXo^DKe$AAP4|sm@2ob|#z3(k+M;J>LJJ zot7V=9!jK}vZnt}4zHb$y+(Zt`=n@J7K%9LP=6GyG3{mH7I*wQ=VHlfA#Dezj_cDLq2+8v+Juijpuq=vk2BCJ zr^}KX)60961Pqpem89d)$NlXeyQX&j9^|mG*H`MsYvNsq#-|6MeV{z znVQoleHo|EmrV!T>CuNEB1(rl96t}zf#z2dJJbDT{UesPyBJcj505WVJj>x+_7u%K z)y^aKv=D^w*Iz`Rr#zVq5e8@RPMx(eixQ96x8HS7?7e94P~OdtJ&m_7r&9UcAnq%N0Y1x^|bt8dGGa zH(MAQuq8@)P;E~lCfw4j=Q{L<;RhwBUqGud^?$3#g>E~It zWmM*z3Hbdk%d2PlTX=8BP`|TL%c3(jxhlu< z$hz9F*eZ6}Z1`}DL+7sf`s5G6?gWO49zFpU#dtHnfF;F0F26UfK{ZY^RDUkN=%Y`c zG5&{ezG)i0V2FwARsAwX z>C>=!eW}6Y;dW+?kUo#9Hohm$AB(2+N~H5zG#=2}p{a zUh+bgBX>aBged;^p5F~Y{mr*AyQfq1W?*xt2uhQs!De*%0&B%;+0hks3(%N>B-hrF@}Pi$@b$u+{8m|1pa)}(!dIqx=*~Te`cE?P{JCGZ{*!J!_g?G&LbRT{A^)EvNpkMp*>?CpNW*i_IsTI_ym0Od z^M4YB=gt*<`cI1RfByM@bKU>HIP9y#mENb{$QZ<0Y&J$J^aa*OW+3sx)=>#UJ_Ce= z2ApB!9$ihxnfC*=Ps8t$0D&Uf)aY$ix@d+D8JzaG^+e3Y)i&o|jsp0K!=Ub$8fx>5 z;98ravh1z)75zI*hkYi2yM>i&EkL%-CDXg1E=t&~{*omz?aw_xG_p~8=eBYOr-BRy zM_dhl%J@qrkPLui213NB$1dGpLwpkm6kDjod+t2~Oiu_vT#y}xEvpv^Ci`(zNsi~9 zkDVzER)>39jLu|4dm!7zw4vt*rs&KjP@Q+WAeJZnZXtU&S9-w)TjW1I9m$*%jRdHw zR!3CMEUV^lZecnWeL7#B>cj0nC{#CJJ9V5O` zP{Y)%C19PlR)A!Z4(hJ;C%8o-y3p|2ohP>Z9Iy*fx^~Qfcr*k|r5OIyhZ#Vo)wr1P zFVBp@(^|d3Z^mJwTlqs3Vr>}M7lh$;dbbd-XAlL0+m~MaMbJXl~rdf5z zQO1&XygAw;?ep2Z0gEjjOgsJFi1AJaaT(Hi(j>u9*WFV0WTBi;?Fs{4%c?cmt+*h*TvyR0Mhtk-3QUhhsJqOZk%XALWr7G57woS9xhE7ji z%tW*R9nuCU4!je{Y+we4q$`_^htO{8s!prpl6C^I_@x&J^*7l&+D|%&fEv$u1DLJL zX~=f;fmELGUA+PuU;m&85UJ}F+VMIV4C zJm$Dz{5__QjzwZcO%eFd6bhud=cY0p*p;isi}~5Rp+!oQW)$en`KgNCRo(`I=edUX z;D6FO@c7Kgv-rka8~ULiFLubJ2fEMC)z1rzuV6RK!j(@oMr@{mnI$QOTw(2pLp>%u zIb5`0&4^!`T|J-CxJB8y<19%FWF(d)ZP1;(nX(GW)u|X$J9$J40;H_C(Fc2!viF-m zrECJv#m?rTh~tb_?HT==4s&d+OC&)hi?IO=W#LmwI5kQh1(9N2_OGccU!7t*Q_t68 zQO6?8J|}%iM4y}FFaQrNW@(lKKm1M9&*x_fR_^l`qeuBXS*ER|SHH`lZBw3nbkrlN+}zugCU3HEz^^o#F&Y3KFNLLT z*SL}mSY5}h187OX++sYRwKYIFb>*~zV93+NY16208nI}fgFmu_1Vs^nc>1-oXJ^&Sg0W)_UoPs2CP29nJx zqs9E9yBz|^M$aadJAhnIMN5JVG;-NsKssXF2lgF^%GW37!PwdOD3HZ zICbh?iOVFb$wg-^Ld{B=RWb^YaX(T=^PH;1VHLhmoW;54ac4K`Dq?3@qOB{o2W}FL z?kW_n!2xg4qhfr6omx7%G;+)L6YteCvc0>Oa&p*V9Rvb~dBOHN0Dzn7?K@Q5X(bc4 z=9g#ET1U4SgSk28nu z(<5toWpwfK?+Z;T^FXN8my~j!GT$#^^xF16PuB>*ysxt_U1X%o-Um(;RA%N43g{KDn@e9(CRm?H6Awo<~b&U)g#bCNFwvj+l^7&1f zj#rb)J)q1TD|e08t8(hKF|0=P>TL~OTGX}o;_y181fEB9^XFSiEHUiF(PFBdv><5_ zhvU|=+NTtt@N-*<z4H}o`Z+*&X% zoK{1O(p&W0gSE4=YEO7a?k7c1Kink$e{1hMqncW{ZG&DLDA+wz3m_ei^p1!a0Tt;W zU3!%&z1X5hAV_ZkR6sBj7zFkh z$==y}mAU4eOO%+*=e^UT%WshHgx3R_MJ5p2ZRK|AgO@6_&GjTyZs0Qc6zJK z{CcfceBJ>rB=PM<2dgJ;rxgJ_DbL>>B0R=07B$C^HN=p4g=cC?e6F~TYdiE-n3S4y5%pFu8 z4jeHOJ6%?@8!x$(s@P)oGyLcemlQ^$W5J3Lp^$rzSkJJ1Q-Kgs`>WOETVD8*H;xY| zG8i^p8curDy=~lOC|KZ0pyzf4W@{p|%p(MZn|{>PJUXkH*)-17^P+wm;!~}5p=|Q* zH9x4oyK)QtFaw^}N+fnz$)VNiscLSso%wpEzq(z10rM}ufc4^jJj?kF=3b+S zgD!uh2o#)UPsZ9V$?oMmXmP(x)!H5zh1=$yx7B0j56o>?ZjWDUbTR?sv3_as-qT4H zZ~L1(o^!G%$4WRUYi_s6_{}ve1~AS=Xr(s0Cx(tV9Ip$tPz zzxRNY+PUMc#;hjvEN`WNjf!ioT)j1gxzyLM%FrBm$0teg^4j)43xy^|myQQD=wuSypqcBwo9YVjZz_M_kwDXR{sJ_>4YOP;8@#2Wp9pjbWht1}# zO3qaq7A?{#XcDfC+{&G!PD$Ma>C)tTox+@?aEE9epR*(*$eXA`-x) zaK7=KMo`F_rEq&w#^we$n_FSxg-%CtxWUtza&$fqPwGrF81od+N;^k%T%qoCNv+mM zWm7b0cW?h{a})R9GW0Yt;BNnR-m6@>_Uy@0$DE}0w{%lGF*Ug*zTSMy=!1-YgBD)x ztVM*{C{*+<=B37kC8R=RP?Q0{M4yUPN+r#_2h&{7%cWSbFvditcn#>asbopm53iu2P9)wI2Ifv}i_{p)L`8DgT#NqQUy^N&4X#d6YoIz3)*cI#`TD_?^QuZYxL|Q+S4-{! z8)!pRlO-RJ{UUZROT&18p}?FF`^nTt&TH;+H-(2Of&cF7@M8Oc&pGDcQ;TPLYjazn zYxG_uMX7SuPZLNvD9&SC0GLJ3bjf5To>7-{?vx38#K1ZEE#sX~Z}O5k4KPD>mQ4T9 zQ*6@f&#U+&Z_GfixpI72{*MrGv%#XYLhmbUVxJr|^=aC7>l}Vj_uslXcZ=}kCEl~v zh|r(Pd!cR?ca5gJ%|nd@_W-^_{&bt{JDIT9|E$44+WZ+&?YET0i)@oAz10&|-~W)K z9vYlOuJ8jb(*ad8g)eZEcEOh_=hsev zFBzGy+w2zmHOFp|W1h0k?Q^%USw9nFbD($@D(=6Pdi2b-ey`6@z)VZ77VYA1rC<5u z)J#p$e!55n=qkt(Dy>WJh&kb<#390uoAd5j0->gr5hX_0YpC7=He?!cxO7EcoClEE~h7a%IEeM49Q_g=K_=?kc>ZNa^L7M_1}6Es$FoXU^Mg$1r}&s?It@&@2lQS;;VI&3(9lWtX$Gr{DpA?` zh1_%FlSn>`Tka%Js8A2V*YQh$`%CN2({1Rg&FY|G<|Yq?HG=!~{zko#)Fy0Ti#ig( zhAhXD4LY0899z?9=fN@~;Ca2mXvzyrm%7G6&wFq4nS!WIGY_=HZi>bWBXMJ6&&CkC z8@Y}ipz3253$6%Hmsb-WsJH;8rcsYno@=yGeH5zC?UgnA5Nqmxxh>cEVAUh;MPxyP zdZTPnB5PGCMRwD56Fn0LCH)^l_q<<~4mDnY;jMS$wX(Qa`$4spi(f1mm`&>dYLNv zDrK|D1?WPj$4|o$UR_k-Sdw*;D#YbUsZc=6XrJZ;MV4?W%?2bDtmhkBaYyb*V~6TE z^nHNEDdRqu=-gA%Qc>kvUP%nRjJUeY$p()pG(QA?@>@)`{|!KPgv7`#QA>Z>SJr==aohT&FdkMaSwZd@DK-`E_BFO z!Y&IMRhp>Be;8@rZN2EyJ!ifkAKa51_s-zGUoK;s74$e!g6U%k$ND4>#tyB|*TTec z**e#ZFmP9YxWuilPtRNa%2)CNNUs%Bt%{pBa%$I{1-c6#qB?J56!mDMm=YfpF$V(SJ|V+$`Td}462jPc>6Svgs+d ziXl_?Au^9aP!25wi0QT*{w8?k*bI(51IcL(f~Iu$nrjn#J+tv-%<|zwY5HJ=V{#zj zrp8Wd_dlqP(}Mm%xiPlDHh!ebtpofflFIELi97c<9znK~<#*YEAsQw-8Tg(@NhvlP z(s5(w@07tjU?2GnG9X?(JPx^{3Wzf_Dius*V-ybgGy(T$-=qo0F~K?PCMkX^xYO-) z$V_Ol>t+e2-2aB+7H>lT&MM(nx8g*?d2Tb7cx6#}17j;3O_Y0STy&jgbpjhE zqQIN9F+bBuyWCZ$VXa$U)S}+a?&1-6W99#NWB;{n_9UGBqwM}Jf~j5E(i$q;gk9GC z?u5~!Z-eRbR)po>a#!EABy)q?=&Jl6{(@^!Ak}IS+wZEA?|db6yL>Y6YP(68C4f8OmdJVG1?+RXn+=4C;-Qd}D|w9n%Ih#F`X4TP;#h_!O-7g@ z!j=b8NB5J8EH)!&5uW6ySgsg+)#xKgXhti#f5LE~Ir9iux@fM? zZ=2Zm&Gv2!q>;$0UAuu?z`++yZ=L=6>{MFaKEz<)Ufh6Ten=osF3=7P!Me&eR=`}x zaTk|^qu?p^W>Xzm&s%|cOY`)hxhH1}#u}^rMH-s+NuudoM1io)XN*UIdqe+BE}KkP zeG%>myc=+2^;0ivS1X4hpygPWKl}F$O-ozUwRbr4itF4)Wv(SaAP2rE@9pHNAD{ZF zIChrh4AvS3xzy}~!hl9Pgu|)5Itc#I z)+vzbyP$sg_11U#3#u>Ur8ZqX&$alZdCE-@0_TJ82sE?B(F6{#jAoy8{j0hPVsFdz zSjV>*5X$m6w0@}E0w1wveEz)%6mgXXF!y?=s8h{?QmHimu+!^rD^$#JG+QhiPm^>A ze4r$Rzz1!JX{DzY(OiM^0DWwH($Ylz`d2Yg-^rUY?mO>$^jTiakYUP%{Gk8fm!FV# zzyF=jkF#h)mc_~EpSh`&QVe031w!;Hi&8-9;#V|CB?ZD>UEX{IY8>kpia z&o^@LcS|n){YwUK(|s14z=B}{W_5MFvpj(tiT(onsUG60t#yIg8L9unLd6IScJ+pMyFD>Op#)MiFzPkZ8uhSDZs!aJfK>N>J^1&-Nf;TPzPCZ}BvB zoG;X@Hh^f;*-+Dk)>B!A_Xsr=HUT&V`=4%8QuZ%;I6z`p?AX`f3?@#*W*d(o`?LQ{ z)&MHJH-Tz)yg#lz&8iU`62J;>LdcAZA^vL;YrU`uR{B>F*hOiSqfNO$rKDXQ?1{^- z)ZPI4#*NGUlX#*Yzt-EgJ%c47*@3SVvrX`d8CBmOjUM9C`CdV5YMSrXC{?H21_9zl z%@98>n% z$7Xlh`{*JoA5vby#8@Jq_+``fs)GCk5<7yk+>0-dgLKx;JeaWv3*~W=dF(IL&WJ z?5Wd5X`T(FA)_46R@~l8CH%8`u8V;f_pLTHtjlah=SSeT?B$siT_wIM!X{qya zefQFQDOmE*KE!d;fApFsXdXnDptts3rD2K*n@j0fxy|l5(zCgau=OT_#Mx4}CO-8D8-C0?g$7KKb$%VJf%PHR5NRrs-9xri^zCR|^^<7CuVZ_a_{(^=WhkRP=6*Bxd<=>a zEZG;f`t@u6J=@g*01PbBXtSMR509A{EgeOhiCb!Rl@lPRP#~49|Jf>EC&C79W@5h< zV2~v`Z2vj>n|5iin3zd{#b;4b?=C@zyA{KK_t&5m6a>IRyx8s?fDFVP%rk~-s0`>P z)l(pT(1zWQR7;=CDt>H|uVX}lJ~eq!sVYX*vCCo1uR<0wu^$?hVVUv09vB(iO*{uM zZn0u^t)X?w%uQp1y`0>^<# z-Qv4Bdz6SriNW=_t^_}7K=_zp0q$`$MLP2}JBm5MU0GCVI;!>C$$KSpyiSt&VwJMA zuCHfjcXpsJEY=25w@`icrTRIifGt4&Ze=1V=V7|x}8$-lRKrd>XA?xPs4UD-}jO38fv*NmLpbd~Rl zmF=Jdb-O^vpm1{ym+W<>Y^~}w-TnthJST`6R1q^Q*K(z+@1Rw1!S)q}cesR3AtSt? z{K|5F#ZYMw8@s&Gxw#Z+?uvDy@zfe|Kn~04de;!QvzKs_o_=Btv$rrHL>kFOJY+M7&KmywW-|NKJtgg4yxjPmNz2?83f3q>1GrUqd*vyz#~nd#k!87+`K6t?k2D+vWJxR2cWT~OcGtsUF7hnaZ17A+^zyi zG=d;AdN?$i{|dT*aNojkb;<&-3hPoCos7YDXZiJ9ByEMBA==&s>bIr>wD zL+|=3qjb{4I+V6e*AhiaLr$QMvg>)tZ&+*Gey>o$LluM9%w}dAj)T;%RF##o)$hv} zabmVDYU$PDU{UqwXhVVXX8gVDpUS^q>Mcyszkd4m~SaZBg6 z@~gH{+3v*)?GeL+3rwfCG4DI1?^P|%&aZ`U2{e~%=DK&ZE+^q?RXQVWLmrxLYC$>LJNr-sLJ8K)S=VtBB#@pdqZZZ{oR}BPL%m{2z@nXr{?D!o85u!I;XJ6 z{Xbm(Kq6wjgu^)zll-(hml=t`Xu>OXQ8Yq2o%T;_Haa)OD{xi~())e|-e;q`tIdYQ zu+r~7K=4+-Yi{_mu7&3pMc*mr5RyHDK!oBt&x$@<%5c`RW|P2ki2UPkxN0~5*pipw z8S%GtczE_95N;Fb5;i^zhfV&aQQoc}fv+d65eNiP*YjPG+e0`qBI8}xDOH~RaO7o{ zWF}jK8^*&kyiR9WkxBSl(p?^(CNtsqo%}BR#FqxGRenkIeU~1kgD2s5Nq)ceIW%mh zDd~=NIEqZBo=fFIAg=NulbN2r>ndQrQ6$WaELsyS_r1SBe+nrp;|`zNw`?v$==n9P ziR=E{_YAshM&Da4aE%%^7MH-=SeKw~9Z>qUlD?cC&Z!p7r6$`gisph}`2Ik`tLWJO cXRrFw$Fdb8-A<~|J{Cev_0IKzYZj0H9r)ODb^rhX literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 56c8ecc0c..e97d48896 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,8 @@ * 数据报表 * 商城系统 * 微信公众号 +* ERP 系统 +* CRM 系统 > 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。 > @@ -230,6 +232,12 @@ | 🚀 | 会员分组 | 对会员进行分组,用于用户画像、内容推送等运营手段 | | 🚀 | 积分签到 | 回馈给签到、消费等行为的积分,会员可订单抵现、积分兑换等途径消耗 | +### ERP 系统 + +![功能图](/.image/common/erp-feature.png) + +演示地址: + ## 🐨 技术栈 ### 微服务 diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql index 0a01aa9dc..3306b7c72 100644 --- a/sql/mysql/ruoyi-vue-pro.sql +++ b/sql/mysql/ruoyi-vue-pro.sql @@ -11,7 +11,7 @@ Target Server Version : 80200 (8.2.0) File Encoding : 65001 - Date: 26/01/2024 20:11:43 + Date: 17/02/2024 21:00:07 */ SET NAMES utf8mb4; @@ -385,7 +385,7 @@ CREATE TABLE `infra_api_error_log` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 15093 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志'; +) ENGINE = InnoDB AUTO_INCREMENT = 15324 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志'; -- ---------------------------- -- Records of infra_api_error_log @@ -423,7 +423,7 @@ CREATE TABLE `infra_codegen_column` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2015 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义'; +) ENGINE = InnoDB AUTO_INCREMENT = 2248 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表字段定义'; -- ---------------------------- -- Records of infra_codegen_column @@ -461,7 +461,7 @@ CREATE TABLE `infra_codegen_table` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 156 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义'; +) ENGINE = InnoDB AUTO_INCREMENT = 171 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成表定义'; -- ---------------------------- -- Records of infra_codegen_table @@ -690,7 +690,7 @@ CREATE TABLE `infra_file` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1242 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; +) ENGINE = InnoDB AUTO_INCREMENT = 1261 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; -- ---------------------------- -- Records of infra_file @@ -879,7 +879,7 @@ CREATE TABLE `system_dict_data` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1486 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表'; +) ENGINE = InnoDB AUTO_INCREMENT = 1508 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表'; -- ---------------------------- -- Records of system_dict_data @@ -1236,6 +1236,28 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st 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 (1483, 3, '转账成功', '20', 'pay_transfer_status', 0, 'success', '', '', '1', '2023-10-28 16:23:50', '1', '2023-10-28 16:23:50', b'0'); 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 (1484, 2, '转账进行中', '10', 'pay_transfer_status', 0, 'info', '', '', '1', '2023-10-28 16:23:12', '1', '2023-10-28 16:23:12', b'0'); 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 (1485, 1, '等待转账', '0', 'pay_transfer_status', 0, 'default', '', '', '1', '2023-10-28 16:21:43', '1', '2023-10-28 16:23:22', b'0'); +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 (1486, 10, '其它入库', '10', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-05 18:07:25', '1', '2024-02-05 18:07:43', b'0'); +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 (1487, 11, '其它入库(作废)', '11', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-05 18:08:07', '1', '2024-02-05 19:20:16', b'0'); +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 (1488, 20, '其它出库', '20', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-05 18:08:51', '1', '2024-02-05 18:08:51', b'0'); +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 (1489, 21, '其它出库(作废)', '21', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-05 18:09:00', '1', '2024-02-05 19:20:10', b'0'); +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 (1490, 10, '未审核', '10', 'erp_audit_status', 0, 'default', '', '', '1', '2024-02-06 00:00:21', '1', '2024-02-06 00:00:21', b'0'); +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 (1491, 20, '已审核', '20', 'erp_audit_status', 0, 'success', '', '', '1', '2024-02-06 00:00:35', '1', '2024-02-06 00:00:35', b'0'); +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 (1492, 30, '调拨入库', '30', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-07 20:34:19', '1', '2024-02-07 12:36:31', b'0'); +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 (1493, 31, '调拨入库(作废)', '31', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-07 20:34:29', '1', '2024-02-07 20:37:11', b'0'); +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 (1494, 32, '调拨出库', '32', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-07 20:34:38', '1', '2024-02-07 12:36:33', b'0'); +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 (1495, 33, '调拨出库(作废)', '33', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-07 20:34:49', '1', '2024-02-07 20:37:06', b'0'); +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 (1496, 40, '盘盈入库', '40', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-08 08:53:00', '1', '2024-02-08 08:53:09', b'0'); +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 (1497, 41, '盘盈入库(作废)', '41', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-08 08:53:39', '1', '2024-02-16 19:40:54', b'0'); +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 (1498, 42, '盘亏出库', '42', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-08 08:54:16', '1', '2024-02-08 08:54:16', b'0'); +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 (1499, 43, '盘亏出库(作废)', '43', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-08 08:54:31', '1', '2024-02-16 19:40:46', b'0'); +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 (1500, 50, '销售出库', '50', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-11 21:47:25', '1', '2024-02-11 21:50:40', b'0'); +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 (1501, 51, '销售出库(作废)', '51', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-11 21:47:37', '1', '2024-02-11 21:51:12', b'0'); +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 (1502, 60, '销售退货入库', '60', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-12 06:51:05', '1', '2024-02-12 06:51:05', b'0'); +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 (1503, 61, '销售退货入库(作废)', '61', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-12 06:51:18', '1', '2024-02-12 06:51:18', b'0'); +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 (1504, 70, '采购入库', '70', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-16 13:10:02', '1', '2024-02-16 13:10:02', b'0'); +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 (1505, 71, '采购入库(作废)', '71', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-16 13:10:10', '1', '2024-02-16 19:40:40', b'0'); +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 (1506, 80, '采购退货出库', '80', 'erp_stock_record_biz_type', 0, '', '', '', '1', '2024-02-16 13:10:17', '1', '2024-02-16 13:10:17', b'0'); +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 (1507, 81, '采购退货出库(作废)', '81', 'erp_stock_record_biz_type', 0, 'danger', '', '', '1', '2024-02-16 13:10:26', '1', '2024-02-16 19:40:33', b'0'); COMMIT; -- ---------------------------- @@ -1256,7 +1278,7 @@ CREATE TABLE `system_dict_type` ( `deleted_time` datetime NULL DEFAULT NULL COMMENT '删除时间', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `dict_type`(`type` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 611 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表'; +) ENGINE = InnoDB AUTO_INCREMENT = 613 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表'; -- ---------------------------- -- Records of system_dict_type @@ -1343,6 +1365,8 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (608, 'CRM 跟进方式', 'crm_follow_up_type', 0, '', '1', '2024-01-15 20:48:05', '1', '2024-01-15 20:48:05', b'0', '1970-01-01 00:00:00'); INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (609, '支付转账类型', 'pay_transfer_type', 0, '', '1', '2023-10-28 16:27:18', '1', '2023-10-28 16:27:18', b'0', '1970-01-01 00:00:00'); INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (610, '转账订单状态', 'pay_transfer_status', 0, '', '1', '2023-10-28 16:18:32', '1', '2023-10-28 16:18:32', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (611, 'ERP 库存明细的业务类型', 'erp_stock_record_biz_type', 0, 'ERP 库存明细的业务类型', '1', '2024-02-05 18:07:02', '1', '2024-02-05 18:07:02', b'0', '1970-01-01 00:00:00'); +INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (612, 'ERP 审批状态', 'erp_audit_status', 0, '', '1', '2024-02-06 00:00:07', '1', '2024-02-06 00:00:07', b'0', '1970-01-01 00:00:00'); COMMIT; -- ---------------------------- @@ -1391,7 +1415,7 @@ CREATE TABLE `system_login_log` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2861 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录'; +) ENGINE = InnoDB AUTO_INCREMENT = 2906 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录'; -- ---------------------------- -- Records of system_login_log @@ -1521,7 +1545,7 @@ CREATE TABLE `system_menu` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2560 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表'; +) ENGINE = InnoDB AUTO_INCREMENT = 2702 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '菜单权限表'; -- ---------------------------- -- Records of system_menu @@ -2013,44 +2037,44 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i 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 (2388, '商城首页', '', 2, 1, 2362, 'home', 'ep:home-filled', 'mall/home/index', 'MallHome', 0, b'1', b'1', b'1', '', '2023-10-16 12:10:33', '', '2023-10-16 12:10:33', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2389, '核销订单', '', 2, 2, 2166, 'pick-up-order', 'ep:list', 'mall/trade/delivery/pickUpOrder/index', 'PickUpOrder', 0, b'1', b'1', b'1', '', '2023-10-19 16:09:51', '', '2023-10-19 16:09:51', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2390, '优惠活动', '', 1, 99, 2030, 'youhui', 'ep:aim', '', '', 0, b'1', b'1', b'1', '1', '2023-10-21 19:23:49', '1', '2023-10-21 19:23:49', b'0'); -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2391, '客户管理', '', 2, 0, 2397, 'customer', 'fa:address-book-o', 'crm/customer/index', 'CrmCustomer', 0, b'1', b'1', b'1', '', '2023-10-29 09:04:21', '1', '2024-01-15 21:27:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2391, '客户管理', '', 2, 10, 2397, 'customer', 'fa:address-book-o', 'crm/customer/index', 'CrmCustomer', 0, b'1', b'1', b'1', '', '2023-10-29 09:04:21', '1', '2024-02-17 17:13:32', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2392, '客户查询', 'crm:customer:query', 3, 1, 2391, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2393, '客户创建', 'crm:customer:create', 3, 2, 2391, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2394, '客户更新', 'crm:customer:update', 3, 3, 2391, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2395, '客户删除', 'crm:customer:delete', 3, 4, 2391, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2396, '客户导出', 'crm:customer:export', 3, 5, 2391, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 09:04:21', '', '2023-10-29 09:04:21', b'0'); -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2397, '客户管理系统', '', 1, 200, 0, '/crm', 'ep:avatar', '', '', 0, b'1', b'1', b'1', '1', '2023-10-29 17:08:30', '1', '2023-10-29 17:08:30', b'0'); -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2398, '合同管理', '', 2, 1, 2397, 'contract', 'ep:notebook', 'crm/contract/index', 'CrmContract', 0, b'1', b'1', b'1', '', '2023-10-29 10:50:41', '1', '2023-10-29 18:55:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2397, 'CRM 系统', '', 1, 200, 0, '/crm', 'ep:avatar', '', '', 0, b'1', b'1', b'1', '1', '2023-10-29 17:08:30', '1', '2024-02-04 15:37:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2398, '合同管理', '', 2, 50, 2397, 'contract', 'ep:notebook', 'crm/contract/index', 'CrmContract', 0, b'1', b'1', b'1', '', '2023-10-29 10:50:41', '1', '2024-02-17 17:15:09', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2399, '合同查询', 'crm:contract:query', 3, 1, 2398, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2400, '合同创建', 'crm:contract:create', 3, 2, 2398, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2401, '合同更新', 'crm:contract:update', 3, 3, 2398, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2402, '合同删除', 'crm:contract:delete', 3, 4, 2398, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2403, '合同导出', 'crm:contract:export', 3, 5, 2398, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 10:50:41', '', '2023-10-29 10:50:41', b'0'); -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2404, '线索管理', '', 2, 0, 2397, 'clue', 'fa:pagelines', 'crm/clue/index', 'CrmClue', 0, b'1', b'1', b'1', '', '2023-10-29 11:06:29', '1', '2023-10-29 19:08:35', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2404, '线索管理', '', 2, 8, 2397, 'clue', 'fa:pagelines', 'crm/clue/index', 'CrmClue', 0, b'1', b'1', b'1', '', '2023-10-29 11:06:29', '1', '2024-02-17 17:15:41', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2405, '线索查询', 'crm:clue:query', 3, 1, 2404, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2406, '线索创建', 'crm:clue:create', 3, 2, 2404, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2407, '线索更新', 'crm:clue:update', 3, 3, 2404, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2408, '线索删除', 'crm:clue:delete', 3, 4, 2404, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2409, '线索导出', 'crm:clue:export', 3, 5, 2404, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:06:29', '', '2023-10-29 11:06:29', b'0'); -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2410, '商机管理', '', 2, 0, 2397, 'business', 'fa:bus', 'crm/business/index', 'CrmBusiness', 0, b'1', b'1', b'1', '', '2023-10-29 11:12:35', '1', '2023-10-29 19:13:01', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2410, '商机管理', '', 2, 40, 2397, 'business', 'fa:bus', 'crm/business/index', 'CrmBusiness', 0, b'1', b'1', b'1', '', '2023-10-29 11:12:35', '1', '2024-02-17 17:14:55', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2411, '商机查询', 'crm:business:query', 3, 1, 2410, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2412, '商机创建', 'crm:business:create', 3, 2, 2410, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2413, '商机更新', 'crm:business:update', 3, 3, 2410, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2414, '商机删除', 'crm:business:delete', 3, 4, 2410, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2415, '商机导出', 'crm:business:export', 3, 5, 2410, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:12:35', '', '2023-10-29 11:12:35', b'0'); -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2416, '联系人管理', '', 2, 0, 2397, 'contact', 'fa:address-book-o', 'crm/contact/index', 'CrmContact', 0, b'1', b'1', b'1', '', '2023-10-29 11:14:56', '1', '2023-12-01 19:39:50', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2416, '联系人管理', '', 2, 20, 2397, 'contact', 'fa:address-book-o', 'crm/contact/index', 'CrmContact', 0, b'1', b'1', b'1', '', '2023-10-29 11:14:56', '1', '2024-02-17 17:13:49', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2417, '联系人查询', 'crm:contact:query', 3, 1, 2416, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2418, '联系人创建', 'crm:contact:create', 3, 2, 2416, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2419, '联系人更新', 'crm:contact:update', 3, 3, 2416, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2420, '联系人删除', 'crm:contact:delete', 3, 4, 2416, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2421, '联系人导出', 'crm:contact:export', 3, 5, 2416, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:14:56', '', '2023-10-29 11:14:56', b'0'); -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2422, '回款管理', '', 2, 0, 2397, 'receivable', 'ep:money', 'crm/receivable/index', 'CrmReceivable', 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '1', '2023-10-29 19:18:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2422, '回款管理', '', 2, 60, 2397, 'receivable', 'ep:money', 'crm/receivable/index', 'CrmReceivable', 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '1', '2024-02-17 17:16:18', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2423, '回款管理查询', 'crm:receivable:query', 3, 1, 2422, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2424, '回款管理创建', 'crm:receivable:create', 3, 2, 2422, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2425, '回款管理更新', 'crm:receivable:update', 3, 3, 2422, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2426, '回款管理删除', 'crm:receivable:delete', 3, 4, 2422, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2427, '回款管理导出', 'crm:receivable:export', 3, 5, 2422, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2428, '回款计划', '', 2, 0, 2397, 'receivable-plan', 'fa:money', 'crm/receivable/plan/index', 'CrmReceivablePlan', 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '1', '2023-12-01 19:55:48', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2428, '回款计划', '', 2, 61, 2397, 'receivable-plan', 'fa:money', 'crm/receivable/plan/index', 'CrmReceivablePlan', 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '1', '2024-02-17 17:16:11', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2429, '回款计划查询', 'crm:receivable-plan:query', 3, 1, 2428, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2430, '回款计划创建', 'crm:receivable-plan:create', 3, 2, 2428, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2431, '回款计划更新', 'crm:receivable-plan:update', 3, 3, 2428, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-10-29 11:18:09', '', '2023-10-29 11:18:09', b'0'); @@ -2103,7 +2127,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i 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 (2521, '客户限制配置更新', 'crm:customer-limit-config:update', 3, 3, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2522, '客户限制配置删除', 'crm:customer-limit-config:delete', 3, 4, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2523, '客户限制配置导出', 'crm:customer-limit-config:export', 3, 5, 2518, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-11-18 13:33:53', '', '2023-11-18 13:33:53', b'0'); -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2524, '系统配置', '', 1, 99, 2397, 'config', 'ep:connection', '', '', 0, b'1', b'1', b'1', '1', '2023-11-18 21:58:00', '1', '2023-11-18 21:58:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2524, '系统配置', '', 1, 999, 2397, 'config', 'ep:connection', '', '', 0, b'1', b'1', b'1', '1', '2023-11-18 21:58:00', '1', '2024-02-17 17:14:34', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2525, 'WebSocket 测试', '', 2, 7, 2, 'websocket', 'ep:connection', 'infra/webSocket/index', 'InfraWebSocket', 0, b'1', b'1', b'1', '1', '2023-11-23 19:41:55', '1', '2023-11-24 19:22:30', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2526, '产品管理', '', 2, 10, 2397, 'product', 'fa:product-hunt', 'crm/product/index', 'CrmProduct', 0, b'1', b'1', b'1', '1', '2023-12-05 22:45:26', '1', '2023-12-05 22:46:45', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2527, '产品查询', 'crm:product:query', 3, 1, 2526, '', '', '', '', 0, b'1', b'1', b'1', '1', '2023-12-05 22:47:16', '1', '2023-12-05 22:47:16', b'0'); @@ -2125,7 +2149,7 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i 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 (2543, '关联商机', 'crm:contact:create-business', 3, 10, 2416, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-01-02 17:28:25', '1', '2024-01-02 17:28:25', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2544, '取关商机', 'crm:contact:delete-business', 3, 11, 2416, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-01-02 17:28:43', '1', '2024-01-02 17:28:51', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2545, '商品统计', '', 2, 3, 2358, 'product', 'fa:product-hunt', 'statistics/product/index', 'ProductStatistics', 0, b'1', b'1', b'1', '', '2023-12-15 18:54:28', '1', '2024-01-17 23:11:54', b'0'); -INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2546, '客户公海', '', 2, -1, 2397, 'customer/pool', 'fa-solid:swimming-pool', 'crm/customer/pool/index', 'CrmCustomerPool', 0, b'1', b'1', b'1', '1', '2024-01-15 21:29:34', '1', '2023-12-29 19:47:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2546, '客户公海', '', 2, 30, 2397, 'customer/pool', 'fa-solid:swimming-pool', 'crm/customer/pool/index', 'CrmCustomerPool', 0, b'1', b'1', b'1', '1', '2024-01-15 21:29:34', '1', '2024-02-17 17:14:18', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2547, '订单查询', 'trade:order:query', 3, 1, 2076, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-01-16 08:52:00', '1', '2024-01-16 08:52:00', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2548, '订单更新', 'trade:order:update', 3, 2, 2076, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-01-16 08:52:21', '1', '2024-01-16 08:52:21', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2549, '支付&退款案例', '', 2, 1, 2161, 'order', 'fa:paypal', 'pay/demo/order/index', '', 0, b'1', b'1', b'1', '1', '2024-01-18 23:45:00', '1', '2024-01-18 23:47:21', b'0'); @@ -2139,6 +2163,148 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i 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 (2557, '钱包余额', '', 2, 1, 2551, 'wallet-balance', 'fa:leaf', 'pay/wallet/balance/index', 'WalletBalance', 0, b'1', b'1', b'1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2558, '钱包余额查询', 'pay:wallet:query', 3, 1, 2557, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', b'0'); INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2559, '转账订单', '', 2, 3, 1117, 'transfer', 'ep:credit-card', 'pay/transfer/index', 'PayTransfer', 0, b'1', b'1', b'1', '', '2023-12-29 02:32:54', '', '2023-12-29 02:32:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2560, '商业智能', '', 1, 200, 2397, 'bi', 'ep:data-line', '', '', 0, b'1', b'1', b'1', '1', '2024-01-26 22:50:35', '1', '2024-02-17 17:14:39', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2561, '排行榜', '', 2, 1, 2560, 'ranking', 'fa:area-chart', 'crm/bi/rank/index', 'BiRanking', 0, b'1', b'1', b'1', '1', '2024-01-26 22:52:09', '1', '2024-02-03 01:10:43', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2562, '客户导入', 'crm:customer:import', 3, 6, 2391, '', '', '', '', 0, b'1', b'1', b'1', '1', '2024-02-01 13:09:00', '1', '2024-02-01 13:09:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2563, 'ERP 系统', '', 1, 300, 0, '/erp', 'fa-solid:store', '', '', 0, b'1', b'1', b'1', '1', '2024-02-04 15:37:25', '1', '2024-02-04 15:37:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2564, '产品管理', '', 1, 40, 2563, 'product', 'fa:product-hunt', '', '', 0, b'1', b'1', b'1', '1', '2024-02-04 15:38:43', '1', '2024-02-04 15:38:43', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2565, '产品信息', '', 2, 0, 2564, 'product', 'fa-solid:apple-alt', 'erp/product/product/index', 'ErpProduct', 0, b'1', b'1', b'1', '', '2024-02-04 07:52:15', '1', '2024-02-05 14:42:11', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2566, '产品查询', 'erp:product:query', 3, 1, 2565, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-02-04 07:52:15', '1', '2024-02-04 17:21:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2567, '产品创建', 'erp:product:create', 3, 2, 2565, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-02-04 07:52:15', '1', '2024-02-04 17:22:12', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2568, '产品更新', 'erp:product:update', 3, 3, 2565, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-02-04 07:52:15', '1', '2024-02-04 17:22:16', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2569, '产品删除', 'erp:product:delete', 3, 4, 2565, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-02-04 07:52:15', '1', '2024-02-04 17:22:22', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2570, '产品导出', 'erp:product:export', 3, 5, 2565, '', '', '', '', 0, b'1', b'1', b'1', '', '2024-02-04 07:52:15', '1', '2024-02-04 17:22:26', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2571, '产品分类', '', 2, 1, 2564, 'product-category', 'fa:certificate', 'erp/product/category/index', 'ErpProductCategory', 0, b'1', b'1', b'1', '', '2024-02-04 09:21:04', '1', '2024-02-04 17:24:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2572, '分类查询', 'erp:product-category:query', 3, 1, 2571, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2573, '分类创建', 'erp:product-category:create', 3, 2, 2571, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2574, '分类更新', 'erp:product-category:update', 3, 3, 2571, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2575, '分类删除', 'erp:product-category:delete', 3, 4, 2571, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2576, '分类导出', 'erp:product-category:export', 3, 5, 2571, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 09:21:04', '', '2024-02-04 09:21:04', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2577, '产品单位', '', 2, 2, 2564, 'unit', 'ep:opportunity', 'erp/product/unit/index', 'ErpProductUnit', 0, b'1', b'1', b'1', '', '2024-02-04 11:54:08', '1', '2024-02-04 19:54:37', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2578, '单位查询', 'erp:product-unit:query', 3, 1, 2577, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2579, '单位创建', 'erp:product-unit:create', 3, 2, 2577, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2580, '单位更新', 'erp:product-unit:update', 3, 3, 2577, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2581, '单位删除', 'erp:product-unit:delete', 3, 4, 2577, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2582, '单位导出', 'erp:product-unit:export', 3, 5, 2577, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 11:54:08', '', '2024-02-04 11:54:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2583, '库存管理', '', 1, 30, 2563, 'stock', 'fa:window-restore', '', '', 0, b'1', b'1', b'1', '1', '2024-02-05 00:29:37', '1', '2024-02-05 00:29:37', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2584, '仓库信息', '', 2, 0, 2583, 'warehouse', 'ep:house', 'erp/stock/warehouse/index', 'ErpWarehouse', 0, b'1', b'1', b'1', '', '2024-02-04 17:12:09', '1', '2024-02-05 01:12:53', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2585, '仓库查询', 'erp:warehouse:query', 3, 1, 2584, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2586, '仓库创建', 'erp:warehouse:create', 3, 2, 2584, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2587, '仓库更新', 'erp:warehouse:update', 3, 3, 2584, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2588, '仓库删除', 'erp:warehouse:delete', 3, 4, 2584, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2589, '仓库导出', 'erp:warehouse:export', 3, 5, 2584, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-04 17:12:09', '', '2024-02-04 17:12:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2590, '产品库存', '', 2, 1, 2583, 'stock', 'ep:coffee', 'erp/stock/stock/index', 'ErpStock', 0, b'1', b'1', b'1', '', '2024-02-05 06:40:50', '1', '2024-02-05 14:42:44', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2591, '库存查询', 'erp:stock:query', 3, 1, 2590, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 06:40:50', '', '2024-02-05 06:40:50', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2592, '库存导出', 'erp:stock:export', 3, 5, 2590, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 06:40:50', '', '2024-02-05 06:40:50', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2593, '出入库明细', '', 2, 2, 2583, 'record', 'fa-solid:blog', 'erp/stock/record/index', 'ErpStockRecord', 0, b'1', b'1', b'1', '', '2024-02-05 10:27:21', '1', '2024-02-06 17:26:11', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2594, '库存明细查询', 'erp:stock-record:query', 3, 1, 2593, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 10:27:21', '', '2024-02-05 10:27:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2595, '库存明细导出', 'erp:stock-record:export', 3, 5, 2593, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 10:27:21', '', '2024-02-05 10:27:21', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2596, '其它入库', '', 2, 3, 2583, 'in', 'ep:zoom-in', 'erp/stock/in/index', 'ErpStockIn', 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-07 19:06:51', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2597, '其它入库单查询', 'erp:stock-in:query', 3, 1, 2596, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2598, '其它入库单创建', 'erp:stock-in:create', 3, 2, 2596, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2599, '其它入库单更新', 'erp:stock-in:update', 3, 3, 2596, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2600, '其它入库单删除', 'erp:stock-in:delete', 3, 4, 2596, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2601, '其它入库单导出', 'erp:stock-in:export', 3, 5, 2596, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2602, '采购管理', '', 1, 10, 2563, 'purchase', 'fa:buysellads', '', '', 0, b'1', b'1', b'1', '1', '2024-02-06 16:01:01', '1', '2024-02-06 16:01:23', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2603, '供应商信息', '', 2, 4, 2602, 'supplier', 'fa:superpowers', 'erp/purchase/supplier/index', 'ErpSupplier', 0, b'1', b'1', b'1', '', '2024-02-06 08:21:55', '1', '2024-02-06 16:22:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2604, '供应商查询', 'erp:supplier:query', 3, 1, 2603, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2605, '供应商创建', 'erp:supplier:create', 3, 2, 2603, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2606, '供应商更新', 'erp:supplier:update', 3, 3, 2603, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2607, '供应商删除', 'erp:supplier:delete', 3, 4, 2603, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2608, '供应商导出', 'erp:supplier:export', 3, 5, 2603, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-06 08:21:55', '', '2024-02-06 08:21:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2609, '其它入库单审批', 'erp:stock-in:update-status', 3, 6, 2596, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-05 16:08:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2610, '其它出库', '', 2, 4, 2583, 'out', 'ep:zoom-out', 'erp/stock/out/index', 'ErpStockOut', 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-07 19:06:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2611, '其它出库单查询', 'erp:stock-out:query', 3, 1, 2610, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 06:43:39', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2612, '其它出库单创建', 'erp:stock-out:create', 3, 2, 2610, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 06:43:42', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2613, '其它出库单更新', 'erp:stock-out:update', 3, 3, 2610, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 06:43:44', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2614, '其它出库单删除', 'erp:stock-out:delete', 3, 4, 2610, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 06:43:56', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2615, '其它出库单导出', 'erp:stock-out:export', 3, 5, 2610, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 06:43:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2616, '其它出库单审批', 'erp:stock-out:update-status', 3, 6, 2610, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 06:43:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2617, '销售管理', '', 1, 20, 2563, 'sale', 'fa:sellsy', '', '', 0, b'1', b'1', b'1', '1', '2024-02-07 15:12:32', '1', '2024-02-07 15:12:32', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2618, '客户信息', '', 2, 4, 2617, 'customer', 'ep:avatar', 'erp/sale/customer/index', 'ErpCustomer', 0, b'1', b'1', b'1', '', '2024-02-07 07:21:45', '1', '2024-02-07 15:22:25', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2619, '客户查询', 'erp:customer:query', 3, 1, 2618, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2620, '客户创建', 'erp:customer:create', 3, 2, 2618, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2621, '客户更新', 'erp:customer:update', 3, 3, 2618, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2622, '客户删除', 'erp:customer:delete', 3, 4, 2618, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2623, '客户导出', 'erp:customer:export', 3, 5, 2618, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-07 07:21:45', '', '2024-02-07 07:21:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2624, '库存调拨', '', 2, 5, 2583, 'move', 'ep:folder-remove', 'erp/stock/move/index', 'ErpStockMove', 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-16 18:53:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2625, '库存调度单查询', 'erp:stock-move:query', 3, 1, 2624, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2626, '库存调度单创建', 'erp:stock-move:create', 3, 2, 2624, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2627, '库存调度单更新', 'erp:stock-move:update', 3, 3, 2624, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2628, '库存调度单删除', 'erp:stock-move:delete', 3, 4, 2624, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2629, '库存调度单导出', 'erp:stock-move:export', 3, 5, 2624, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2630, '库存调度单审批', 'erp:stock-move:update-status', 3, 6, 2624, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2631, '库存盘点', '', 2, 6, 2583, 'check', 'ep:circle-check-filled', 'erp/stock/check/index', 'ErpStockCheck', 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-08 08:31:09', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2632, '库存盘点单查询', 'erp:stock-check:query', 3, 1, 2631, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2633, '库存盘点单创建', 'erp:stock-check:create', 3, 2, 2631, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2634, '库存盘点单更新', 'erp:stock-check:update', 3, 3, 2631, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2635, '库存盘点单删除', 'erp:stock-check:delete', 3, 4, 2631, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2636, '库存盘点单导出', 'erp:stock-check:export', 3, 5, 2631, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2637, '库存盘点单审批', 'erp:stock-check:update-status', 3, 6, 2631, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2638, '销售订单', '', 2, 1, 2617, 'order', 'fa:first-order', 'erp/sale/order/index', 'ErpSaleOrder', 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-10 21:59:20', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2639, '销售订单查询', 'erp:sale-order:query', 3, 1, 2638, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2640, '销售订单创建', 'erp:sale-order:create', 3, 2, 2638, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2641, '销售订单更新', 'erp:sale-order:update', 3, 3, 2638, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2642, '销售订单删除', 'erp:sale-order:delete', 3, 4, 2638, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2643, '销售订单导出', 'erp:sale-order:export', 3, 5, 2638, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2644, '销售订单审批', 'erp:sale-order:update-status', 3, 6, 2638, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2645, '财务管理', '', 1, 50, 2563, 'finance', 'ep:money', '', '', 0, b'1', b'1', b'1', '1', '2024-02-10 08:05:58', '1', '2024-02-10 08:06:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2646, '结算账户', '', 2, 10, 2645, 'account', 'fa:universal-access', 'erp/finance/account/index', 'ErpAccount', 0, b'1', b'1', b'1', '', '2024-02-10 00:15:07', '1', '2024-02-14 08:24:31', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2647, '结算账户查询', 'erp:account:query', 3, 1, 2646, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2648, '结算账户创建', 'erp:account:create', 3, 2, 2646, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2649, '结算账户更新', 'erp:account:update', 3, 3, 2646, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2650, '结算账户删除', 'erp:account:delete', 3, 4, 2646, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2651, '结算账户导出', 'erp:account:export', 3, 5, 2646, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-10 00:15:07', '', '2024-02-10 00:15:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2652, '销售出库', '', 2, 2, 2617, 'out', 'ep:sold-out', 'erp/sale/out/index', 'ErpSaleOut', 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-10 22:02:07', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2653, '销售出库查询', 'erp:sale-out:query', 3, 1, 2652, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2654, '销售出库创建', 'erp:sale-out:create', 3, 2, 2652, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2655, '销售出库更新', 'erp:sale-out:update', 3, 3, 2652, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2656, '销售出库删除', 'erp:sale-out:delete', 3, 4, 2652, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2657, '销售出库导出', 'erp:sale-out:export', 3, 5, 2652, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2658, '销售出库审批', 'erp:sale-out:update-status', 3, 6, 2652, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2659, '销售退货', '', 2, 3, 2617, 'return', 'fa-solid:bone', 'erp/sale/return/index', 'ErpSaleReturn', 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-12 06:12:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2660, '销售退货查询', 'erp:sale-return:query', 3, 1, 2659, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2661, '销售退货创建', 'erp:sale-return:create', 3, 2, 2659, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:52', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2662, '销售退货更新', 'erp:sale-return:update', 3, 3, 2659, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:55', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2663, '销售退货删除', 'erp:sale-return:delete', 3, 4, 2659, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:57', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2664, '销售退货导出', 'erp:sale-return:export', 3, 5, 2659, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:12:59', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2665, '销售退货审批', 'erp:sale-return:update-status', 3, 6, 2659, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-07 11:13:03', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2666, '采购订单', '', 2, 1, 2602, 'order', 'fa-solid:border-all', 'erp/purchase/order/index', 'ErpPurchaseOrder', 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-12 08:51:49', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2667, '采购订单查询', 'erp:purchase-order:query', 3, 1, 2666, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2668, '采购订单创建', 'erp:purchase-order:create', 3, 2, 2666, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2669, '采购订单更新', 'erp:purchase-order:update', 3, 3, 2666, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2670, '采购订单删除', 'erp:purchase-order:delete', 3, 4, 2666, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2671, '采购订单导出', 'erp:purchase-order:export', 3, 5, 2666, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2672, '采购订单审批', 'erp:purchase-order:update-status', 3, 6, 2666, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2673, '采购入库', '', 2, 2, 2602, 'in', 'fa-solid:gopuram', 'erp/purchase/in/index', 'ErpPurchaseIn', 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-12 11:19:27', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2674, '采购入库查询', 'erp:purchase-in:query', 3, 1, 2673, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2675, '采购入库创建', 'erp:purchase-in:create', 3, 2, 2673, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2676, '采购入库更新', 'erp:purchase-in:update', 3, 3, 2673, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2677, '采购入库删除', 'erp:purchase-in:delete', 3, 4, 2673, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2678, '采购入库导出', 'erp:purchase-in:export', 3, 5, 2673, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2679, '采购入库审批', 'erp:purchase-in:update-status', 3, 6, 2673, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2680, '采购退货', '', 2, 3, 2602, 'return', 'ep:minus', 'erp/purchase/return/index', 'ErpPurchaseReturn', 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-12 20:51:02', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2681, '采购退货查询', 'erp:purchase-return:query', 3, 1, 2680, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2682, '采购退货创建', 'erp:purchase-return:create', 3, 2, 2680, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2683, '采购退货更新', 'erp:purchase-return:update', 3, 3, 2680, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2684, '采购退货删除', 'erp:purchase-return:delete', 3, 4, 2680, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2685, '采购退货导出', 'erp:purchase-return:export', 3, 5, 2680, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2686, '采购退货审批', 'erp:purchase-return:update-status', 3, 6, 2680, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2687, '付款单', '', 2, 1, 2645, 'payment', 'ep:caret-right', 'erp/finance/payment/index', 'ErpFinancePayment', 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-14 08:24:23', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2688, '付款单查询', 'erp:finance-payment:query', 3, 1, 2687, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2689, '付款单创建', 'erp:finance-payment:create', 3, 2, 2687, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2690, '付款单更新', 'erp:finance-payment:update', 3, 3, 2687, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2691, '付款单删除', 'erp:finance-payment:delete', 3, 4, 2687, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2692, '付款单导出', 'erp:finance-payment:export', 3, 5, 2687, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2693, '付款单审批', 'erp:finance-payment:update-status', 3, 6, 2687, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2694, '收款单', '', 2, 2, 2645, 'receipt', 'ep:expand', 'erp/finance/receipt/index', 'ErpFinanceReceipt', 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '1', '2024-02-15 19:35:45', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2695, '收款单查询', 'erp:finance-receipt:query', 3, 1, 2694, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:17', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2696, '收款单创建', 'erp:finance-receipt:create', 3, 2, 2694, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:54', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2697, '收款单更新', 'erp:finance-receipt:update', 3, 3, 2694, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:44:58', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2698, '收款单删除', 'erp:finance-receipt:delete', 3, 4, 2694, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:00', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2699, '收款单导出', 'erp:finance-receipt:export', 3, 5, 2694, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:05', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2700, '收款单审批', 'erp:finance-receipt:update-status', 3, 6, 2694, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2024-02-05 16:08:56', '', '2024-02-12 00:45:08', b'0'); +INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2701, '待办事项', '', 2, 0, 2397, 'backlog', 'fa-solid:tasks', 'crm/backlog/index', 'CrmBacklog', 0, b'1', b'1', b'1', '1', '2024-02-17 17:17:11', '1', '2024-02-17 17:17:11', b'0'); COMMIT; -- ---------------------------- @@ -2259,7 +2425,7 @@ CREATE TABLE `system_oauth2_access_token` ( PRIMARY KEY (`id`) USING BTREE, INDEX `idx_access_token`(`access_token` ASC) USING BTREE, INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 4335 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌'; +) ENGINE = InnoDB AUTO_INCREMENT = 4781 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌'; -- ---------------------------- -- Records of system_oauth2_access_token @@ -2381,7 +2547,7 @@ CREATE TABLE `system_oauth2_refresh_token` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1309 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌'; +) ENGINE = InnoDB AUTO_INCREMENT = 1346 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌'; -- ---------------------------- -- Records of system_oauth2_refresh_token @@ -2421,7 +2587,7 @@ CREATE TABLE `system_operate_log` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 9706 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录'; +) ENGINE = InnoDB AUTO_INCREMENT = 10187 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录'; -- ---------------------------- -- Records of system_operate_log @@ -2454,7 +2620,7 @@ CREATE TABLE `system_operate_log_v2` ( `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 8888 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本'; +) ENGINE = InnoDB AUTO_INCREMENT = 8892 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录 V2 版本'; -- ---------------------------- -- Records of system_operate_log_v2 @@ -4517,7 +4683,7 @@ CREATE TABLE `system_sms_log` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 586 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志'; +) ENGINE = InnoDB AUTO_INCREMENT = 588 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志'; -- ---------------------------- -- Records of system_sms_log @@ -4826,7 +4992,7 @@ CREATE TABLE `system_users` ( -- Records of system_users -- ---------------------------- BEGIN; -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '18818260277', 2, 'http://127.0.0.1:48080/admin-api/infra/file/4/get/37e56010ecbee472cdd821ac4b608e151e62a74d9633f15d085aee026eedeb60.png', 0, '0:0:0:0:0:0:0:1', '2024-01-22 21:01:28', 'admin', '2021-01-05 17:03:47', NULL, '2024-01-22 21:01:28', b'0', 1); +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '18818260277', 2, 'http://127.0.0.1:48080/admin-api/infra/file/4/get/37e56010ecbee472cdd821ac4b608e151e62a74d9633f15d085aee026eedeb60.png', 0, '0:0:0:0:0:0:0:1', '2024-02-17 17:10:34', 'admin', '2021-01-05 17:03:47', NULL, '2024-02-17 17:10:34', b'0', 1); INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 1, '127.0.0.1', '2022-07-09 23:03:33', '', '2021-01-07 09:07:17', NULL, '2022-07-09 23:03:33', b'0', 1); INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$10$YMpimV4T6BtDhIaA8jSW.u8UTGBeGhc/qwXP4oxoMr4mOw9.qttt6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '127.0.0.1', '2022-07-08 01:26:27', '', '2021-01-13 23:50:35', NULL, '2022-07-08 01:26:27', b'0', 1); INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$10$GP8zvqHB//TekuzYZSBYAuBQJiNq1.fxQVDYJ.uBCOnWCtDVKE4H6', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2023-09-24 18:21:19', '', '2021-01-21 02:13:53', NULL, '2023-09-24 18:21:19', b'0', 1); diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java index 35ebc8ff3..8ffd21ccc 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java @@ -288,11 +288,16 @@ public class CollectionUtils { public static > V getSumValue(List from, Function valueFunc, BinaryOperator accumulator) { + return getSumValue(from, valueFunc, accumulator, null); + } + + public static > V getSumValue(Collection from, Function valueFunc, + BinaryOperator accumulator, V defaultValue) { if (CollUtil.isEmpty(from)) { - return null; + return defaultValue; } - assert from.size() > 0; // 断言,避免告警 - return from.stream().map(valueFunc).reduce(accumulator).get(); + assert !from.isEmpty(); // 断言,避免告警 + return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue); } public static void addIfNotNull(Collection coll, T item) { @@ -302,8 +307,12 @@ public class CollectionUtils { coll.add(item); } - public static Collection singleton(T deptId) { - return deptId == null ? Collections.emptyList() : Collections.singleton(deptId); + public static Collection singleton(T obj) { + return obj == null ? Collections.emptyList() : Collections.singleton(obj); + } + + public static List newArrayList(List> list) { + return list.stream().flatMap(Collection::stream).collect(Collectors.toList()); } } diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/MoneyUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/MoneyUtils.java index e0b739920..90888d7d3 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/MoneyUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/MoneyUtils.java @@ -13,6 +13,16 @@ import java.math.RoundingMode; */ public class MoneyUtils { + /** + * 金额的小数位数 + */ + private static final int PRICE_SCALE = 2; + + /** + * 百分比对应的 BigDecimal 对象 + */ + public static final BigDecimal PERCENT_100 = BigDecimal.valueOf(100); + /** * 计算百分比金额,四舍五入 * @@ -35,6 +45,22 @@ public class MoneyUtils { return calculateRatePrice(price, rate, 0, RoundingMode.FLOOR).intValue(); } + /** + * 计算百分比金额 + * + * @param price 金额(单位分) + * @param count 数量 + * @param percent 折扣(单位分),列如 60.2%,则传入 6020 + * @return 商品总价 + */ + public static Integer calculator(Integer price, Integer count, Integer percent) { + price = price * count; + if (percent == null) { + return price; + } + return MoneyUtils.calculateRatePriceFloor(price, (double) (percent / 100)); + } + /** * 计算百分比金额 * @@ -70,4 +96,36 @@ public class MoneyUtils { return new Money(0, fen).toString(); } + /** + * 金额相乘,默认进行四舍五入 + * + * 位数:{@link #PRICE_SCALE} + * + * @param price 金额 + * @param count 数量 + * @return 金额相乘结果 + */ + public static BigDecimal priceMultiply(BigDecimal price, BigDecimal count) { + if (price == null || count == null) { + return null; + } + return price.multiply(count).setScale(PRICE_SCALE, RoundingMode.HALF_UP); + } + + /** + * 金额相乘(百分比),默认进行四舍五入 + * + * 位数:{@link #PRICE_SCALE} + * + * @param price 金额 + * @param percent 百分比 + * @return 金额相乘结果 + */ + public static BigDecimal priceMultiplyPercent(BigDecimal price, BigDecimal percent) { + if (price == null || percent == null) { + return null; + } + return price.multiply(percent).divide(PERCENT_100, PRICE_SCALE, RoundingMode.HALF_UP); + } + } diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/NumberUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/NumberUtils.java index 55ab367a3..ea131e86e 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/NumberUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/NumberUtils.java @@ -1,7 +1,10 @@ package cn.iocoder.yudao.framework.common.util.number; +import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; +import java.math.BigDecimal; + /** * 数字的工具类,补全 {@link cn.hutool.core.util.NumberUtil} 的功能 * @@ -37,4 +40,21 @@ public class NumberUtils { return distance; } + /** + * 提供精确的乘法运算 + * + * 和 hutool {@link NumberUtil#mul(BigDecimal...)} 的差别是,如果存在 null,则返回 null + * + * @param values 多个被乘值 + * @return 积 + */ + public static BigDecimal mul(BigDecimal... values) { + for (BigDecimal value : values) { + if (value == null) { + return null; + } + } + return NumberUtil.mul(values); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java index fb576d508..2944ca72c 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.framework.file.core.client; +import cn.iocoder.yudao.framework.file.core.client.s3.FilePresignedUrlRespDTO; + /** * 文件客户端 * @@ -18,11 +20,11 @@ public interface FileClient { * 上传文件 * * @param content 文件流 - * @param path 相对路径 + * @param path 相对路径 * @return 完整路径,即 HTTP 访问地址 * @throws Exception 上传文件时,抛出 Exception 异常 */ - String upload(byte[] content, String path, String type) throws Exception; + String upload(byte[] content, String path, String type) throws Exception; /** * 删除文件 @@ -40,4 +42,14 @@ public interface FileClient { */ byte[] getContent(String path) throws Exception; + /** + * 获得文件预签名地址 + * + * @param path 相对路径 + * @return 文件预签名地址 + */ + default FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception { + throw new UnsupportedOperationException("不支持的操作"); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/FilePresignedUrlRespDTO.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/FilePresignedUrlRespDTO.java new file mode 100644 index 000000000..6048494ed --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/FilePresignedUrlRespDTO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.framework.file.core.client.s3; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 文件预签名地址 Response DTO + * + * @author owen + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class FilePresignedUrlRespDTO { + + /** + * 文件上传 URL(用于上传) + * + * 例如说: + */ + private String uploadUrl; + + /** + * 文件 URL(用于读取、下载等) + */ + private String url; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClient.java index 49238f8f9..e7b470bad 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClient.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClient.java @@ -5,8 +5,10 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient; import io.minio.*; +import io.minio.http.Method; import java.io.ByteArrayInputStream; +import java.util.concurrent.TimeUnit; import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN; import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_TENCENT; @@ -117,4 +119,16 @@ public class S3FileClient extends AbstractFileClient { return IoUtil.readBytes(response); } + @Override + public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception { + String uploadUrl = client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() + .method(Method.PUT) + .bucket(config.getBucket()) + .object(path) + .expiry(10, TimeUnit.MINUTES) // 过期时间(秒数)取值范围:1 秒 ~ 7 天 + .build() + ); + return new FilePresignedUrlRespDTO(uploadUrl, config.getDomain() + "/" + path); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java index 9661b7363..eec4172f1 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java @@ -151,7 +151,7 @@ public class QueryWrapperX extends QueryWrapper { switch (SqlConstants.DB_TYPE) { case ORACLE: case ORACLE_12C: - super.eq("ROWNUM", n); + super.le("ROWNUM", n); break; case SQL_SERVER: case SQL_SERVER2005: diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java index bc821bec2..dd5b96121 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/banner/core/BannerApplicationRunner.java @@ -37,6 +37,8 @@ public class BannerApplicationRunner implements ApplicationRunner { System.out.println("[微信公众号 yudao-module-mp 教程][参考 https://cloud.iocoder.cn/mp/build/ 开启]"); // 商城系统 System.out.println("[商城系统 yudao-module-mall 教程][参考 https://cloud.iocoder.cn/mall/build/ 开启]"); + // ERP 系统 + System.out.println("[ERP 系统 yudao-module-erp - 已禁用][参考 https://doc.iocoder.cn/erp/build/ 开启]"); // 支付平台 System.out.println("[支付系统 yudao-module-pay - 已禁用][参考 https://doc.iocoder.cn/pay/build/ 开启]"); }); diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java index beeb82d38..460bad1fb 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java @@ -314,7 +314,13 @@ public class GlobalExceptionHandler { return CommonResult.error(NOT_IMPLEMENTED.getCode(), "[商城系统 yudao-module-mall - 已禁用][参考 https://doc.iocoder.cn/mall/build/ 开启]"); } - // 5. 支付平台 + // 5. ERP 系统 + if (message.contains("erp_")) { + log.error("[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]"); + return CommonResult.error(NOT_IMPLEMENTED.getCode(), + "[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://doc.iocoder.cn/erp/build/ 开启]"); + } + // 6. 支付平台 if (message.contains("pay_")) { log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]"); return CommonResult.error(NOT_IMPLEMENTED.getCode(), diff --git a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/BannerApplicationRunner.java b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/BannerApplicationRunner.java index 52b4dc7e9..765092cb7 100644 --- a/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/BannerApplicationRunner.java +++ b/yudao-gateway/src/main/java/cn/iocoder/yudao/gateway/util/BannerApplicationRunner.java @@ -39,6 +39,8 @@ public class BannerApplicationRunner implements ApplicationRunner { System.out.println("[微信公众号 yudao-module-mp 教程][参考 https://cloud.iocoder.cn/mp/build/ 开启]"); // 商城系统 System.out.println("[商城系统 yudao-module-mall 教程][参考 https://cloud.iocoder.cn/mall/build/ 开启]"); + // ERP 系统 + System.out.println("[ERP 系统 yudao-module-erp - 已禁用][参考 https://doc.iocoder.cn/erp/build/ 开启]"); // 支付平台 System.out.println("[支付系统 yudao-module-pay - 已禁用][参考 https://doc.iocoder.cn/pay/build/ 开启]"); }); diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index 3d3cd7ed6..7e1dea2e8 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -8,14 +8,17 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileUploadReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.service.file.FileService; 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.annotation.security.PermitAll; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; @@ -23,12 +26,6 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import jakarta.annotation.Resource; -import jakarta.annotation.security.PermitAll; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; - import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - 文件存储") @@ -42,7 +39,7 @@ public class FileController { private FileService fileService; @PostMapping("/upload") - @Operation(summary = "上传文件") + @Operation(summary = "上传文件", description = "模式一:后端上传文件") @OperateLog(logArgs = false) // 上传文件,没有记录操作日志的必要 public CommonResult uploadFile(FileUploadReqVO uploadReqVO) throws Exception { MultipartFile file = uploadReqVO.getFile(); @@ -50,6 +47,18 @@ public class FileController { return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream()))); } + @GetMapping("/presigned-url") + @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") + public CommonResult getFilePresignedUrl(@RequestParam("path") String path) throws Exception { + return success(fileService.getFilePresignedUrl(path)); + } + + @PostMapping("/create") + @Operation(summary = "创建文件", description = "模式二:前端上传文件:配合 presigned-url 接口,记录上传了上传的文件") + public CommonResult createFile(@Valid @RequestBody FileCreateReqVO createReqVO) { + return success(fileService.createFile(createReqVO)); + } + @DeleteMapping("/delete") @Operation(summary = "删除文件") @Parameter(name = "id", description = "编号", required = true) @@ -62,7 +71,7 @@ public class FileController { @GetMapping("/{configId}/get/**") @PermitAll @Operation(summary = "下载文件") - @Parameter(name = "configId", description = "配置编号", required = true) + @Parameter(name = "configId", description = "配置编号", required = true) public void getFileContent(HttpServletRequest request, HttpServletResponse response, @PathVariable("configId") Long configId) throws Exception { diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileCreateReqVO.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileCreateReqVO.java new file mode 100644 index 000000000..5daa3972e --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileCreateReqVO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 文件创建 Request VO") +@Data +public class FileCreateReqVO { + + @NotNull(message = "文件配置编号不能为空") + @Schema(description = "文件配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11") + private Long configId; + + @NotNull(message = "文件路径不能为空") + @Schema(description = "文件路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao.jpg") + private String path; + + @NotNull(message = "原文件名不能为空") + @Schema(description = "原文件名", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao.jpg") + private String name; + + @NotNull(message = "文件 URL不能为空") + @Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg") + private String url; + + @Schema(description = "文件 MIME 类型", example = "application/octet-stream") + private String type; + + @Schema(description = "文件大小", example = "2048", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer size; + +} diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java new file mode 100644 index 000000000..926133ebc --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "管理后台 - 文件预签名地址 Response VO") +@Data +public class FilePresignedUrlRespVO { + + @Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11") + private Long configId; + + @Schema(description = "文件上传 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://s3.cn-south-1.qiniucs.com/ruoyi-vue-pro/758d3a5387507358c7236de4c8f96de1c7f5097ff6a7722b34772fb7b76b140f.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS%2F20240217%2Fcn-south-1%2Fs3%2Faws4_request&X-Amz-Date=20240217T123222Z&X-Amz-Expires=600&X-Amz-SignedHeaders=host&X-Amz-Signature=a29f33770ab79bf523ccd4034d0752ac545f3c2a3b17baa1eb4e280cfdccfda5") + private String uploadUrl; + + /** + * 为什么要返回 url 字段? + * + * 前端上传完文件后,需要使用该 URL 进行访问 + */ + @Schema(description = "文件访问 URL", requiredMode = Schema.RequiredMode.REQUIRED, + example = "https://test.yudao.iocoder.cn/758d3a5387507358c7236de4c8f96de1c7f5097ff6a7722b34772fb7b76b140f.png") + private String url; + +} diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java index 24baf4218..3ca9a2419 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java @@ -1,7 +1,9 @@ package cn.iocoder.yudao.module.infra.service.file; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; /** @@ -22,13 +24,21 @@ public interface FileService { /** * 保存文件,并返回文件的访问路径 * - * @param name 文件名称 - * @param path 文件路径 + * @param name 文件名称 + * @param path 文件路径 * @param content 文件内容 * @return 文件路径 */ String createFile(String name, String path, byte[] content); + /** + * 创建文件 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createFile(FileCreateReqVO createReqVO); + /** * 删除文件 * @@ -40,9 +50,17 @@ public interface FileService { * 获得文件内容 * * @param configId 配置编号 - * @param path 文件路径 + * @param path 文件路径 * @return 文件内容 */ byte[] getFileContent(Long configId, String path) throws Exception; + /** + * 生成文件预签名地址信息 + * + * @param path 文件路径 + * @return 预签名地址信息 + */ + FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception; + } diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java index 82ba6d4ff..f7c4b0b8e 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java @@ -4,16 +4,19 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.io.FileUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.file.core.client.FileClient; +import cn.iocoder.yudao.framework.file.core.client.s3.FilePresignedUrlRespDTO; import cn.iocoder.yudao.framework.file.core.utils.FileTypeUtils; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; +import jakarta.annotation.Resource; import lombok.SneakyThrows; import org.springframework.stereotype.Service; -import jakarta.annotation.Resource; - import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS; @@ -66,6 +69,13 @@ public class FileServiceImpl implements FileService { return url; } + @Override + public Long createFile(FileCreateReqVO createReqVO) { + FileDO file = BeanUtils.toBean(createReqVO, FileDO.class); + fileMapper.insert(file); + return file.getId(); + } + @Override public void deleteFile(Long id) throws Exception { // 校验存在 @@ -95,4 +105,12 @@ public class FileServiceImpl implements FileService { return client.getContent(path); } + @Override + public FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception { + FileClient fileClient = fileConfigService.getMasterFileClient(); + FilePresignedUrlRespDTO presignedObjectUrl = fileClient.getPresignedObjectUrl(path); + return BeanUtils.toBean(presignedObjectUrl, FilePresignedUrlRespVO.class, + object -> object.setConfigId(fileClient.getId())); + } + } diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/api/api.ts.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/api/api.ts.vm index 7c2ab277b..c3044fb87 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/api/api.ts.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/api/api.ts.vm @@ -5,13 +5,12 @@ import request from '@/config/axios' export interface ${simpleClassName}VO { #foreach ($column in $columns) #if ($column.createOperation || $column.updateOperation) - // ${column.columnComment} #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal") - ${column.javaField}: number + ${column.javaField}: number // ${column.columnComment} #elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime") - ${column.javaField}: Date + ${column.javaField}: Date // ${column.columnComment} #else - ${column.javaField}: ${column.javaType.toLowerCase()} + ${column.javaField}: ${column.javaType.toLowerCase()} // ${column.columnComment} #end #end #end diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm index 14a72d264..8e3596b4f 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm @@ -244,7 +244,7 @@ const submitForm = async () => { // 提交请求 formLoading.value = true try { - const data = formData.value as unknown as ${simpleClassName}Api.${simpleClassName}VO + const data = formData.value as unknown as ${simpleClassName}VO ## 特殊:主子表专属逻辑 #if ( $table.templateType == 10 || $table.templateType == 12 ) #if ( $subTables && $subTables.size() > 0 ) diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm index a76dbaa82..361d379fa 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm +++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm @@ -259,8 +259,7 @@ const loading = ref(true) // 列表的加载中 const list = ref<${simpleClassName}VO[]>([]) // 列表的数据 ## 特殊:树表专属逻辑(树不需要分页接口) #if ( $table.templateType != 2 ) -// 列表的总页数 -const total = ref(0) +const total = ref(0) // 列表的总页数 #end const queryParams = reactive({ ## 特殊:树表专属逻辑(树不需要分页接口) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponDO.java index b98615093..31cef2e78 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponDO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponDO.java @@ -48,8 +48,11 @@ public class CouponDO extends BaseDO { * * 枚举 {@link CouponStatusEnum} */ + // TODO 芋艿:已作废? private Integer status; + // TODO 芋艿:发放 adminid? + // ========== 基本信息 END ========== // ========== 领取情况 BEGIN ========== diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponTemplateDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponTemplateDO.java index 6cab9c58c..ad4ebab9b 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponTemplateDO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/coupon/CouponTemplateDO.java @@ -45,6 +45,7 @@ public class CouponTemplateDO extends BaseDO { * * 枚举 {@link CommonStatusEnum} */ + // TODO 芋艿:要不要改成 3 个状态?? private Integer status; // ========== 基本信息 END ========== @@ -159,4 +160,7 @@ public class CouponTemplateDO extends BaseDO { private Integer useCount; // ========== 统计信息 END ========== + // TODO 芋艿:领取开始时间、领取结束时间 + + // TODO 芋艿:要不要加描述 } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index ef5c3a624..2f41f67e9 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -98,7 +98,7 @@ public interface ErrorCodeConstants { ErrorCode SMS_CODE_USED = new ErrorCode(1_002_014_002, "验证码已使用"); ErrorCode SMS_CODE_NOT_CORRECT = new ErrorCode(1_002_014_003, "验证码不正确"); ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1_002_014_004, "超过每日短信发送数量"); - ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1_002_014_005, "短信发送过于频率"); + ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1_002_014_005, "短信发送过于频繁"); ErrorCode SMS_CODE_IS_EXISTS = new ErrorCode(1_002_014_006, "手机号已被使用"); ErrorCode SMS_CODE_IS_UNUSED = new ErrorCode(1_002_014_007, "验证码未被使用"); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java index 54aaa4dc7..c26acb8ca 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java @@ -44,6 +44,9 @@ public class AuthPermissionInfoRespVO { @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.jpg") private String avatar; + @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long deptId; + } @Schema(description = "管理后台 - 登录用户的菜单信息 Response VO") diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/notice/vo/NoticeSaveReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/notice/vo/NoticeSaveReqVO.java index d3d3e61ce..78794ed71 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/notice/vo/NoticeSaveReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/notice/vo/NoticeSaveReqVO.java @@ -11,8 +11,7 @@ import jakarta.validation.constraints.Size; @Data public class NoticeSaveReqVO { - @Schema(description = "岗位公告编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "岗位公告编号不能为空") + @Schema(description = "岗位公告编号", example = "1024") private Long id; @Schema(description = "公告标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "小博主") diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java index 8343547d9..18463e7f6 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.system.convert.auth; import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; @@ -29,7 +30,7 @@ public interface AuthConvert { default AuthPermissionInfoRespVO convert(AdminUserDO user, List roleList, List menuList) { return AuthPermissionInfoRespVO.builder() - .user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build()) + .user(BeanUtils.toBean(user, AuthPermissionInfoRespVO.UserVO.class)) .roles(convertSet(roleList, RoleDO::getCode)) // 权限标识信息 .permissions(convertSet(menuList, MenuDO::getPermission)) From 5f53986d5e959f4972921685cfb0c68342c29557 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 17 Feb 2024 22:56:28 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=A8=20=E5=A2=9E=E5=8A=A0=20ERP=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 3 +- yudao-dependencies/pom.xml | 2 +- .../src/main/resources/application.yaml | 12 +- yudao-module-erp/pom.xml | 24 ++ yudao-module-erp/yudao-module-erp-api/pom.xml | 33 ++ .../yudao/module/erp/api/package-info.java | 4 + .../yudao/module/erp/enums/ApiConstants.java | 23 ++ .../module/erp/enums/DictTypeConstants.java | 13 + .../module/erp/enums/ErpAuditStatus.java | 39 +++ .../module/erp/enums/ErrorCodeConstants.java | 168 ++++++++++ .../module/erp/enums/LogRecordConstants.java | 12 + .../erp/enums/common/ErpBizTypeEnum.java | 43 +++ .../stock/ErpStockRecordBizTypeEnum.java | 63 ++++ yudao-module-erp/yudao-module-erp-biz/pom.xml | 132 ++++++++ .../module/erp/ErpServerApplication.java | 30 ++ .../admin/finance/ErpAccountController.java | 116 +++++++ .../finance/ErpFinancePaymentController.java | 153 +++++++++ .../finance/ErpFinanceReceiptController.java | 153 +++++++++ .../vo/account/ErpAccountPageReqVO.java | 24 ++ .../finance/vo/account/ErpAccountRespVO.java | 50 +++ .../vo/account/ErpAccountSaveReqVO.java | 36 ++ .../payment/ErpFinancePaymentPageReqVO.java | 48 +++ .../vo/payment/ErpFinancePaymentRespVO.java | 97 ++++++ .../payment/ErpFinancePaymentSaveReqVO.java | 74 ++++ .../receipt/ErpFinanceReceiptPageReqVO.java | 48 +++ .../vo/receipt/ErpFinanceReceiptRespVO.java | 97 ++++++ .../receipt/ErpFinanceReceiptSaveReqVO.java | 74 ++++ .../product/ErpProductCategoryController.java | 101 ++++++ .../admin/product/ErpProductController.java | 105 ++++++ .../product/ErpProductUnitController.java | 102 ++++++ .../category/ErpProductCategoryListReqVO.java | 16 + .../vo/category/ErpProductCategoryRespVO.java | 47 +++ .../category/ErpProductCategorySaveReqVO.java | 35 ++ .../vo/product/ErpProductPageReqVO.java | 27 ++ .../product/vo/product/ErpProductRespVO.java | 76 +++++ .../product/vo/product/ProductSaveReqVO.java | 58 ++++ .../vo/unit/ErpProductUnitPageReqVO.java | 21 ++ .../product/vo/unit/ErpProductUnitRespVO.java | 34 ++ .../vo/unit/ErpProductUnitSaveReqVO.java | 26 ++ .../purchase/ErpPurchaseInController.java | 165 +++++++++ .../purchase/ErpPurchaseOrderController.java | 164 +++++++++ .../purchase/ErpPurchaseReturnController.java | 165 +++++++++ .../admin/purchase/ErpSupplierController.java | 102 ++++++ .../vo/in/ErpPurchaseInPageReqVO.java | 61 ++++ .../purchase/vo/in/ErpPurchaseInRespVO.java | 145 ++++++++ .../vo/in/ErpPurchaseInSaveReqVO.java | 81 +++++ .../vo/order/ErpPurchaseOrderPageReqVO.java | 80 +++++ .../vo/order/ErpPurchaseOrderRespVO.java | 152 +++++++++ .../vo/order/ErpPurchaseOrderSaveReqVO.java | 73 ++++ .../returns/ErpPurchaseReturnPageReqVO.java | 61 ++++ .../vo/returns/ErpPurchaseReturnRespVO.java | 145 ++++++++ .../returns/ErpPurchaseReturnSaveReqVO.java | 81 +++++ .../vo/supplier/ErpSupplierPageReqVO.java | 24 ++ .../vo/supplier/ErpSupplierRespVO.java | 84 +++++ .../vo/supplier/ErpSupplierSaveReqVO.java | 71 ++++ .../admin/sale/ErpCustomerController.java | 102 ++++++ .../admin/sale/ErpSaleOrderController.java | 165 +++++++++ .../admin/sale/ErpSaleOutController.java | 165 +++++++++ .../admin/sale/ErpSaleReturnController.java | 165 +++++++++ .../vo/customer/ErpCustomerPageReqVO.java | 28 ++ .../sale/vo/customer/ErpCustomerRespVO.java | 84 +++++ .../vo/customer/ErpCustomerSaveReqVO.java | 61 ++++ .../sale/vo/order/ErpSaleOrderPageReqVO.java | 80 +++++ .../sale/vo/order/ErpSaleOrderRespVO.java | 155 +++++++++ .../sale/vo/order/ErpSaleOrderSaveReqVO.java | 76 +++++ .../sale/vo/out/ErpSaleOutPageReqVO.java | 61 ++++ .../admin/sale/vo/out/ErpSaleOutRespVO.java | 148 ++++++++ .../sale/vo/out/ErpSaleOutSaveReqVO.java | 84 +++++ .../vo/returns/ErpSaleReturnPageReqVO.java | 61 ++++ .../sale/vo/returns/ErpSaleReturnRespVO.java | 148 ++++++++ .../vo/returns/ErpSaleReturnSaveReqVO.java | 84 +++++ .../admin/stock/ErpStockCheckController.java | 149 +++++++++ .../admin/stock/ErpStockController.java | 112 +++++++ .../admin/stock/ErpStockInController.java | 165 +++++++++ .../admin/stock/ErpStockMoveController.java | 160 +++++++++ .../admin/stock/ErpStockOutController.java | 165 +++++++++ .../admin/stock/ErpStockRecordController.java | 105 ++++++ .../admin/stock/ErpWarehouseController.java | 116 +++++++ .../vo/check/ErpStockCheckPageReqVO.java | 45 +++ .../stock/vo/check/ErpStockCheckRespVO.java | 111 ++++++ .../vo/check/ErpStockCheckSaveReqVO.java | 69 ++++ .../stock/vo/in/ErpStockInPageReqVO.java | 48 +++ .../admin/stock/vo/in/ErpStockInRespVO.java | 110 ++++++ .../stock/vo/in/ErpStockInSaveReqVO.java | 64 ++++ .../stock/vo/move/ErpStockMovePageReqVO.java | 45 +++ .../stock/vo/move/ErpStockMoveRespVO.java | 107 ++++++ .../stock/vo/move/ErpStockMoveSaveReqVO.java | 77 +++++ .../stock/vo/out/ErpStockOutPageReqVO.java | 48 +++ .../admin/stock/vo/out/ErpStockOutRespVO.java | 110 ++++++ .../stock/vo/out/ErpStockOutSaveReqVO.java | 64 ++++ .../vo/record/ErpStockRecordPageReqVO.java | 36 ++ .../stock/vo/record/ErpStockRecordRespVO.java | 87 +++++ .../stock/vo/stock/ErpStockPageReqVO.java | 21 ++ .../admin/stock/vo/stock/ErpStockRespVO.java | 49 +++ .../vo/warehouse/ErpWarehousePageReqVO.java | 24 ++ .../vo/warehouse/ErpWarehouseRespVO.java | 64 ++++ .../vo/warehouse/ErpWarehouseSaveReqVO.java | 47 +++ .../module/erp/controller/package-info.java | 6 + .../dal/dataobject/finance/ErpAccountDO.java | 56 ++++ .../finance/ErpFinancePaymentDO.java | 86 +++++ .../finance/ErpFinancePaymentItemDO.java | 75 +++++ .../finance/ErpFinanceReceiptDO.java | 86 +++++ .../finance/ErpFinanceReceiptItemDO.java | 75 +++++ .../product/ErpProductCategoryDO.java | 54 +++ .../dal/dataobject/product/ErpProductDO.java | 86 +++++ .../dataobject/product/ErpProductUnitDO.java | 39 +++ .../dataobject/purchase/ErpPurchaseInDO.java | 122 +++++++ .../purchase/ErpPurchaseInItemDO.java | 95 ++++++ .../purchase/ErpPurchaseOrderDO.java | 115 +++++++ .../purchase/ErpPurchaseOrderItemDO.java | 93 ++++++ .../purchase/ErpPurchaseReturnDO.java | 122 +++++++ .../purchase/ErpPurchaseReturnItemDO.java | 95 ++++++ .../dataobject/purchase/ErpSupplierDO.java | 90 +++++ .../dal/dataobject/sale/ErpCustomerDO.java | 90 +++++ .../dal/dataobject/sale/ErpSaleOrderDO.java | 121 +++++++ .../dataobject/sale/ErpSaleOrderItemDO.java | 93 ++++++ .../erp/dal/dataobject/sale/ErpSaleOutDO.java | 128 +++++++ .../dal/dataobject/sale/ErpSaleOutItemDO.java | 96 ++++++ .../dal/dataobject/sale/ErpSaleReturnDO.java | 128 +++++++ .../dataobject/sale/ErpSaleReturnItemDO.java | 95 ++++++ .../dal/dataobject/stock/ErpStockCheckDO.java | 63 ++++ .../dataobject/stock/ErpStockCheckItemDO.java | 83 +++++ .../erp/dal/dataobject/stock/ErpStockDO.java | 49 +++ .../dal/dataobject/stock/ErpStockInDO.java | 70 ++++ .../dataobject/stock/ErpStockInItemDO.java | 73 ++++ .../dal/dataobject/stock/ErpStockMoveDO.java | 63 ++++ .../dataobject/stock/ErpStockMoveItemDO.java | 79 +++++ .../dal/dataobject/stock/ErpStockOutDO.java | 69 ++++ .../dataobject/stock/ErpStockOutItemDO.java | 73 ++++ .../dataobject/stock/ErpStockRecordDO.java | 82 +++++ .../dal/dataobject/stock/ErpWarehouseDO.java | 70 ++++ .../dal/mysql/finance/ErpAccountMapper.java | 36 ++ .../finance/ErpFinancePaymentItemMapper.java | 44 +++ .../finance/ErpFinancePaymentMapper.java | 48 +++ .../finance/ErpFinanceReceiptItemMapper.java | 44 +++ .../finance/ErpFinanceReceiptMapper.java | 48 +++ .../product/ErpProductCategoryMapper.java | 34 ++ .../dal/mysql/product/ErpProductMapper.java | 40 +++ .../mysql/product/ErpProductUnitMapper.java | 35 ++ .../purchase/ErpPurchaseInItemMapper.java | 56 ++++ .../mysql/purchase/ErpPurchaseInMapper.java | 70 ++++ .../purchase/ErpPurchaseOrderItemMapper.java | 30 ++ .../purchase/ErpPurchaseOrderMapper.java | 75 +++++ .../purchase/ErpPurchaseReturnItemMapper.java | 56 ++++ .../purchase/ErpPurchaseReturnMapper.java | 70 ++++ .../dal/mysql/purchase/ErpSupplierMapper.java | 32 ++ .../erp/dal/mysql/sale/ErpCustomerMapper.java | 32 ++ .../mysql/sale/ErpSaleOrderItemMapper.java | 30 ++ .../dal/mysql/sale/ErpSaleOrderMapper.java | 76 +++++ .../dal/mysql/sale/ErpSaleOutItemMapper.java | 56 ++++ .../erp/dal/mysql/sale/ErpSaleOutMapper.java | 70 ++++ .../mysql/sale/ErpSaleReturnItemMapper.java | 56 ++++ .../dal/mysql/sale/ErpSaleReturnMapper.java | 71 ++++ .../mysql/stock/ErpStockCheckItemMapper.java | 30 ++ .../dal/mysql/stock/ErpStockCheckMapper.java | 46 +++ .../dal/mysql/stock/ErpStockInItemMapper.java | 30 ++ .../erp/dal/mysql/stock/ErpStockInMapper.java | 47 +++ .../erp/dal/mysql/stock/ErpStockMapper.java | 64 ++++ .../mysql/stock/ErpStockMoveItemMapper.java | 30 ++ .../dal/mysql/stock/ErpStockMoveMapper.java | 46 +++ .../mysql/stock/ErpStockOutItemMapper.java | 30 ++ .../dal/mysql/stock/ErpStockOutMapper.java | 47 +++ .../dal/mysql/stock/ErpStockRecordMapper.java | 28 ++ .../dal/mysql/stock/ErpWarehouseMapper.java | 35 ++ .../erp/dal/redis/RedisKeyConstants.java | 18 + .../erp/dal/redis/no/ErpNoRedisDAO.java | 96 ++++++ .../module/erp/framework/package-info.java | 6 + .../rpc/config/RpcConfiguration.java | 10 + .../erp/framework/rpc/package-info.java | 4 + .../config/SecurityConfiguration.java | 37 ++ .../framework/security/core/package-info.java | 4 + .../web/config/ErpWebConfiguration.java | 24 ++ .../erp/framework/web/package-info.java | 4 + .../yudao/module/erp/package-info.java | 10 + .../service/finance/ErpAccountService.java | 102 ++++++ .../finance/ErpAccountServiceImpl.java | 112 +++++++ .../finance/ErpFinancePaymentService.java | 84 +++++ .../finance/ErpFinancePaymentServiceImpl.java | 273 +++++++++++++++ .../finance/ErpFinanceReceiptService.java | 84 +++++ .../finance/ErpFinanceReceiptServiceImpl.java | 273 +++++++++++++++ .../product/ErpProductCategoryService.java | 77 +++++ .../ErpProductCategoryServiceImpl.java | 149 +++++++++ .../service/product/ErpProductService.java | 111 ++++++ .../product/ErpProductServiceImpl.java | 147 ++++++++ .../product/ErpProductUnitService.java | 86 +++++ .../product/ErpProductUnitServiceImpl.java | 111 ++++++ .../purchase/ErpPurchaseInService.java | 101 ++++++ .../purchase/ErpPurchaseInServiceImpl.java | 308 +++++++++++++++++ .../purchase/ErpPurchaseOrderService.java | 110 ++++++ .../purchase/ErpPurchaseOrderServiceImpl.java | 295 ++++++++++++++++ .../purchase/ErpPurchaseReturnService.java | 101 ++++++ .../ErpPurchaseReturnServiceImpl.java | 304 +++++++++++++++++ .../service/purchase/ErpSupplierService.java | 94 ++++++ .../purchase/ErpSupplierServiceImpl.java | 94 ++++++ .../erp/service/sale/ErpCustomerService.java | 94 ++++++ .../service/sale/ErpCustomerServiceImpl.java | 97 ++++++ .../erp/service/sale/ErpSaleOrderService.java | 110 ++++++ .../service/sale/ErpSaleOrderServiceImpl.java | 307 +++++++++++++++++ .../erp/service/sale/ErpSaleOutService.java | 102 ++++++ .../service/sale/ErpSaleOutServiceImpl.java | 316 ++++++++++++++++++ .../service/sale/ErpSaleReturnService.java | 101 ++++++ .../sale/ErpSaleReturnServiceImpl.java | 316 ++++++++++++++++++ .../service/stock/ErpStockCheckService.java | 84 +++++ .../stock/ErpStockCheckServiceImpl.java | 232 +++++++++++++ .../erp/service/stock/ErpStockInService.java | 84 +++++ .../service/stock/ErpStockInServiceImpl.java | 228 +++++++++++++ .../service/stock/ErpStockMoveService.java | 84 +++++ .../stock/ErpStockMoveServiceImpl.java | 229 +++++++++++++ .../erp/service/stock/ErpStockOutService.java | 84 +++++ .../service/stock/ErpStockOutServiceImpl.java | 228 +++++++++++++ .../service/stock/ErpStockRecordService.java | 39 +++ .../stock/ErpStockRecordServiceImpl.java | 53 +++ .../erp/service/stock/ErpStockService.java | 61 ++++ .../service/stock/ErpStockServiceImpl.java | 89 +++++ .../service/stock/ErpWarehouseService.java | 102 ++++++ .../stock/ErpWarehouseServiceImpl.java | 126 +++++++ .../stock/bo/ErpStockRecordCreateReqBO.java | 59 ++++ .../src/main/resources/application-dev.yaml | 104 ++++++ .../src/main/resources/application-local.yaml | 130 +++++++ .../src/main/resources/application.yaml | 105 ++++++ .../src/main/resources/bootstrap-local.yaml | 23 ++ .../src/main/resources/bootstrap.yaml | 14 + .../src/main/resources/logback-spring.xml | 76 +++++ .../mapper/finance/ErpAccountMapper.xml | 12 + .../mapper/product/ErpProductUnitMapper.xml | 12 + .../mapper/sale/ErpCustomerMapper.xml | 12 + .../mapper/stock/ErpWarehouseMapper.xml | 12 + .../mapper/supplier/ErpSupplierMapper.xml | 12 + .../src/main/resources/application-dev.yaml | 11 +- .../src/main/resources/application-local.yaml | 11 +- .../application-integration-test.yaml | 9 +- .../module/system/api/user/AdminUserApi.java | 16 +- .../application-integration-test.yaml | 9 +- 233 files changed, 19094 insertions(+), 28 deletions(-) create mode 100644 yudao-module-erp/pom.xml create mode 100644 yudao-module-erp/yudao-module-erp-api/pom.xml create mode 100644 yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/package-info.java create mode 100644 yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ApiConstants.java create mode 100644 yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/DictTypeConstants.java create mode 100644 yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java create mode 100644 yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java create mode 100644 yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/LogRecordConstants.java create mode 100644 yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/common/ErpBizTypeEnum.java create mode 100644 yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/stock/ErpStockRecordBizTypeEnum.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/pom.xml create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/ErpServerApplication.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpAccountController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinancePaymentController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinanceReceiptController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/account/ErpAccountPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/account/ErpAccountRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/account/ErpAccountSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductCategoryController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductUnitController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/category/ErpProductCategoryListReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/category/ErpProductCategoryRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/category/ErpProductCategorySaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ProductSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/unit/ErpProductUnitPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/unit/ErpProductUnitRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/unit/ErpProductUnitSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseReturnController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpSupplierController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/supplier/ErpSupplierPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/supplier/ErpSupplierRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/supplier/ErpSupplierSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpCustomerController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOutController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleReturnController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/customer/ErpCustomerPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/customer/ErpCustomerRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/customer/ErpCustomerSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/returns/ErpSaleReturnPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/returns/ErpSaleReturnRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/returns/ErpSaleReturnSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockCheckController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockInController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockMoveController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockOutController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockRecordController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpWarehouseController.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/check/ErpStockCheckPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/check/ErpStockCheckRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/check/ErpStockCheckSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMovePageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/out/ErpStockOutPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/out/ErpStockOutRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/out/ErpStockOutSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/record/ErpStockRecordPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/record/ErpStockRecordRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockPageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/warehouse/ErpWarehousePageReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/warehouse/ErpWarehouseRespVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/warehouse/ErpWarehouseSaveReqVO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/package-info.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpAccountDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinancePaymentDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinancePaymentItemDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinanceReceiptDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinanceReceiptItemDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/product/ErpProductCategoryDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/product/ErpProductDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/product/ErpProductUnitDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInItemDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseReturnDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseReturnItemDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpSupplierDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpCustomerDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderItemDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutItemDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleReturnDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleReturnItemDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockCheckDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockCheckItemDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockInDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockInItemDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveItemDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockOutDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockOutItemDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockRecordDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpWarehouseDO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpAccountMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinancePaymentItemMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinancePaymentMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinanceReceiptItemMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinanceReceiptMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductCategoryMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductUnitMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInItemMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderItemMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseReturnItemMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseReturnMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpSupplierMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpCustomerMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderItemMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutItemMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleReturnItemMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleReturnMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockCheckItemMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockCheckMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockInItemMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockInMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMoveItemMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMoveMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockOutItemMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockOutMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockRecordMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpWarehouseMapper.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/RedisKeyConstants.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/package-info.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/rpc/config/RpcConfiguration.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/rpc/package-info.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/security/config/SecurityConfiguration.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/security/core/package-info.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/config/ErpWebConfiguration.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/package-info.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/package-info.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpAccountService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpAccountServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpCustomerService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpCustomerServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpWarehouseService.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpWarehouseServiceImpl.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/bo/ErpStockRecordCreateReqBO.java create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/resources/application-dev.yaml create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/resources/application-local.yaml create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/resources/application.yaml create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/resources/bootstrap-local.yaml create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/resources/bootstrap.yaml create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/resources/logback-spring.xml create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/finance/ErpAccountMapper.xml create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/product/ErpProductUnitMapper.xml create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/sale/ErpCustomerMapper.xml create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/stock/ErpWarehouseMapper.xml create mode 100644 yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/supplier/ErpSupplierMapper.xml diff --git a/pom.xml b/pom.xml index 7533454bb..3f234c29d 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ yudao-module-report yudao-module-mp yudao-module-mall + yudao-module-erp ${project.artifactId} @@ -27,7 +28,7 @@ https://github.com/YunaiV/ruoyi-vue-pro - 2.0.0-snapshot + 2.0.1-snapshot 21 ${java.version} diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 938ef8312..dfd82713c 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -14,7 +14,7 @@ https://github.com/YunaiV/ruoyi-vue-pro - 2.0.0-snapshot + 2.0.1-snapshot 1.5.0 3.2.1 diff --git a/yudao-gateway/src/main/resources/application.yaml b/yudao-gateway/src/main/resources/application.yaml index 5992bc27b..87d9a4a9b 100644 --- a/yudao-gateway/src/main/resources/application.yaml +++ b/yudao-gateway/src/main/resources/application.yaml @@ -138,12 +138,13 @@ spring: - Path=/admin-api/statistics/** filters: - RewritePath=/admin-api/statistics/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs - - id: statistics-app-api # 路由的编号 - uri: grayLb://statistics-server + ## erp-server 服务 + - id: erp-admin-api # 路由的编号 + uri: grayLb://erp-server predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 - - Path=/app-api/statistics/** + - Path=/admin-api/erp/** filters: - - RewritePath=/app-api/statistics/v3/api-docs, /v3/api-docs + - RewritePath=/admin-api/erp/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs x-forwarded: prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀 @@ -182,6 +183,9 @@ knife4j: - name: statistics-server service-name: statistics-server url: /admin-api/statistics/v3/api-docs + - name: erp-server + service-name: erp-server + url: /admin-api/erp/v3/api-docs --- #################### 芋道相关配置 #################### diff --git a/yudao-module-erp/pom.xml b/yudao-module-erp/pom.xml new file mode 100644 index 000000000..bed9d7d51 --- /dev/null +++ b/yudao-module-erp/pom.xml @@ -0,0 +1,24 @@ + + + + cn.iocoder.cloud + yudao + ${revision} + + + yudao-module-erp-api + yudao-module-erp-biz + + 4.0.0 + yudao-module-erp + pom + + ${project.artifactId} + + erp 包下,企业资源管理(Enterprise Resource Planning)。 + 例如说:采购、销售、库存、财务、产品等等 + + + \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-api/pom.xml b/yudao-module-erp/yudao-module-erp-api/pom.xml new file mode 100644 index 000000000..ef5394f10 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-api/pom.xml @@ -0,0 +1,33 @@ + + + + cn.iocoder.cloud + yudao-module-erp + ${revision} + + 4.0.0 + yudao-module-erp-api + jar + + ${project.artifactId} + + erp 模块 API,暴露给其它模块调用 + + + + + cn.iocoder.cloud + yudao-common + + + + + org.springframework.boot + spring-boot-starter-validation + true + + + + \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/package-info.java b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/package-info.java new file mode 100644 index 000000000..540f18f0e --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/package-info.java @@ -0,0 +1,4 @@ +/** + * erp API 包,定义暴露给其它模块的 API + */ +package cn.iocoder.yudao.module.erp.api; diff --git a/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ApiConstants.java b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ApiConstants.java new file mode 100644 index 000000000..1241dbc91 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ApiConstants.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.erp.enums; + +import cn.iocoder.yudao.framework.common.enums.RpcConstants; + +/** + * API 相关的枚举 + * + * @author 芋道源码 + */ +public class ApiConstants { + + /** + * 服务名 + * + * 注意,需要保证和 spring.application.name 保持一致 + */ + public static final String NAME = "erp-server"; + + public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/erp"; + + public static final String VERSION = "1.0.0"; + +} diff --git a/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/DictTypeConstants.java b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/DictTypeConstants.java new file mode 100644 index 000000000..36d4df852 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/DictTypeConstants.java @@ -0,0 +1,13 @@ +package cn.iocoder.yudao.module.erp.enums; + +/** + * ERP 字典类型的枚举类 + * + * @author 芋道源码 + */ +public interface DictTypeConstants { + + String AUDIT_STATUS = "erp_audit_status"; // 审核状态 + String STOCK_RECORD_BIZ_TYPE = "erp_stock_record_biz_type"; // 库存明细的业务类型 + +} diff --git a/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java new file mode 100644 index 000000000..a10147a70 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.erp.enums; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * ERP 审核状态枚举 + * + * TODO 芋艿:目前只有待审批、已审批两个状态,未来接入工作流后,会丰富下:待提交(草稿)=》已提交(待审核)=》审核通过、审核不通过;另外,工作流需要支持“反审核”,把工作流退回到原点; + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ErpAuditStatus implements IntArrayValuable { + + PROCESS(10, "未审核"), // 审核中 + APPROVE(20, "已审核"); // 审核通过 + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ErpAuditStatus::getStatus).toArray(); + + /** + * 状态 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java new file mode 100644 index 000000000..65f64c2f5 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java @@ -0,0 +1,168 @@ +package cn.iocoder.yudao.module.erp.enums; + +import cn.iocoder.yudao.framework.common.exception.ErrorCode; + +/** + * ERP 错误码枚举类 + *

+ * erp 系统,使用 1-030-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== ERP 供应商(1-030-100-000) ========== + ErrorCode SUPPLIER_NOT_EXISTS = new ErrorCode(1_030_100_000, "供应商不存在"); + ErrorCode SUPPLIER_NOT_ENABLE = new ErrorCode(1_030_100_000, "供应商({})未启用"); + + // ========== ERP 采购订单(1-030-101-000) ========== + ErrorCode PURCHASE_ORDER_NOT_EXISTS = new ErrorCode(1_030_101_000, "采购订单不存在"); + ErrorCode PURCHASE_ORDER_DELETE_FAIL_APPROVE = new ErrorCode(1_030_101_001, "采购订单({})已审核,无法删除"); + ErrorCode PURCHASE_ORDER_PROCESS_FAIL = new ErrorCode(1_030_101_002, "反审核失败,只有已审核的采购订单才能反审核"); + ErrorCode PURCHASE_ORDER_APPROVE_FAIL = new ErrorCode(1_030_101_003, "审核失败,只有未审核的采购订单才能审核"); + ErrorCode PURCHASE_ORDER_NO_EXISTS = new ErrorCode(1_030_101_004, "生成采购单号失败,请重新提交"); + ErrorCode PURCHASE_ORDER_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_101_005, "采购订单({})已审核,无法修改"); + ErrorCode PURCHASE_ORDER_NOT_APPROVE = new ErrorCode(1_030_101_006, "采购订单未审核,无法操作"); + ErrorCode PURCHASE_ORDER_ITEM_IN_FAIL_PRODUCT_EXCEED = new ErrorCode(1_030_101_007, "采购订单项({})超过最大允许入库数量({})"); + ErrorCode PURCHASE_ORDER_PROCESS_FAIL_EXISTS_IN = new ErrorCode(1_030_101_008, "反审核失败,已存在对应的采购入库单"); +ErrorCode PURCHASE_ORDER_ITEM_RETURN_FAIL_IN_EXCEED = new ErrorCode(1_030_101_009, "采购订单项({})超过最大允许退货数量({})"); + ErrorCode PURCHASE_ORDER_PROCESS_FAIL_EXISTS_RETURN = new ErrorCode(1_030_101_010, "反审核失败,已存在对应的采购退货单"); + + // ========== ERP 采购入库(1-030-102-000) ========== + ErrorCode PURCHASE_IN_NOT_EXISTS = new ErrorCode(1_030_102_000, "采购入库单不存在"); + ErrorCode PURCHASE_IN_DELETE_FAIL_APPROVE = new ErrorCode(1_030_102_001, "采购入库单({})已审核,无法删除"); + ErrorCode PURCHASE_IN_PROCESS_FAIL = new ErrorCode(1_030_102_002, "反审核失败,只有已审核的入库单才能反审核"); + ErrorCode PURCHASE_IN_APPROVE_FAIL = new ErrorCode(1_030_102_003, "审核失败,只有未审核的入库单才能审核"); + ErrorCode PURCHASE_IN_NO_EXISTS = new ErrorCode(1_030_102_004, "生成入库单失败,请重新提交"); + ErrorCode PURCHASE_IN_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_102_005, "采购入库单({})已审核,无法修改"); + ErrorCode PURCHASE_IN_NOT_APPROVE = new ErrorCode(1_030_102_006, "采购入库单未审核,无法操作"); + ErrorCode PURCHASE_IN_FAIL_PAYMENT_PRICE_EXCEED = new ErrorCode(1_030_102_007, "付款金额({})超过采购入库单总金额({})"); + ErrorCode PURCHASE_IN_PROCESS_FAIL_EXISTS_PAYMENT = new ErrorCode(1_030_102_008, "反审核失败,已存在对应的付款单"); + + // ========== ERP 采购退货(1-030-103-000) ========== + ErrorCode PURCHASE_RETURN_NOT_EXISTS = new ErrorCode(1_030_103_000, "采购退货单不存在"); + ErrorCode PURCHASE_RETURN_DELETE_FAIL_APPROVE = new ErrorCode(1_030_103_001, "采购退货单({})已审核,无法删除"); + ErrorCode PURCHASE_RETURN_PROCESS_FAIL = new ErrorCode(1_030_103_002, "反审核失败,只有已审核的退货单才能反审核"); + ErrorCode PURCHASE_RETURN_APPROVE_FAIL = new ErrorCode(1_030_103_003, "审核失败,只有未审核的退货单才能审核"); + ErrorCode PURCHASE_RETURN_NO_EXISTS = new ErrorCode(1_030_103_004, "生成退货单失败,请重新提交"); + ErrorCode PURCHASE_RETURN_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_103_005, "采购退货单({})已审核,无法修改"); + ErrorCode PURCHASE_RETURN_NOT_APPROVE = new ErrorCode(1_030_103_006, "采购退货单未审核,无法操作"); + ErrorCode PURCHASE_RETURN_FAIL_REFUND_PRICE_EXCEED = new ErrorCode(1_030_103_007, "退款金额({})超过采购退货单总金额({})"); + ErrorCode PURCHASE_RETURN_PROCESS_FAIL_EXISTS_REFUND = new ErrorCode(1_030_103_008, "反审核失败,已存在对应的退款单"); + + // ========== ERP 客户(1-030-200-000)========== + ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_020_200_000, "客户不存在"); + ErrorCode CUSTOMER_NOT_ENABLE = new ErrorCode(1_020_200_001, "客户({})未启用"); + + // ========== ERP 销售订单(1-030-201-000) ========== + ErrorCode SALE_ORDER_NOT_EXISTS = new ErrorCode(1_020_201_000, "销售订单不存在"); + ErrorCode SALE_ORDER_DELETE_FAIL_APPROVE = new ErrorCode(1_020_201_001, "销售订单({})已审核,无法删除"); + ErrorCode SALE_ORDER_PROCESS_FAIL = new ErrorCode(1_020_201_002, "反审核失败,只有已审核的销售订单才能反审核"); + ErrorCode SALE_ORDER_APPROVE_FAIL = new ErrorCode(1_020_201_003, "审核失败,只有未审核的销售订单才能审核"); + ErrorCode SALE_ORDER_NO_EXISTS = new ErrorCode(1_020_201_004, "生成销售单号失败,请重新提交"); + ErrorCode SALE_ORDER_UPDATE_FAIL_APPROVE = new ErrorCode(1_020_201_005, "销售订单({})已审核,无法修改"); + ErrorCode SALE_ORDER_NOT_APPROVE = new ErrorCode(1_020_201_006, "销售订单未审核,无法操作"); + ErrorCode SALE_ORDER_ITEM_OUT_FAIL_PRODUCT_EXCEED = new ErrorCode(1_020_201_007, "销售订单项({})超过最大允许出库数量({})"); + ErrorCode SALE_ORDER_PROCESS_FAIL_EXISTS_OUT = new ErrorCode(1_020_201_008, "反审核失败,已存在对应的销售出库单"); + ErrorCode SALE_ORDER_ITEM_RETURN_FAIL_OUT_EXCEED = new ErrorCode(1_020_201_009, "销售订单项({})超过最大允许退货数量({})"); + ErrorCode SALE_ORDER_PROCESS_FAIL_EXISTS_RETURN = new ErrorCode(1_020_201_010, "反审核失败,已存在对应的销售退货单"); + + // ========== ERP 销售出库(1-030-202-000) ========== + ErrorCode SALE_OUT_NOT_EXISTS = new ErrorCode(1_020_202_000, "销售出库单不存在"); + ErrorCode SALE_OUT_DELETE_FAIL_APPROVE = new ErrorCode(1_020_202_001, "销售出库单({})已审核,无法删除"); + ErrorCode SALE_OUT_PROCESS_FAIL = new ErrorCode(1_020_202_002, "反审核失败,只有已审核的出库单才能反审核"); + ErrorCode SALE_OUT_APPROVE_FAIL = new ErrorCode(1_020_202_003, "审核失败,只有未审核的出库单才能审核"); + ErrorCode SALE_OUT_NO_EXISTS = new ErrorCode(1_020_202_004, "生成出库单失败,请重新提交"); + ErrorCode SALE_OUT_UPDATE_FAIL_APPROVE = new ErrorCode(1_020_202_005, "销售出库单({})已审核,无法修改"); + ErrorCode SALE_OUT_NOT_APPROVE = new ErrorCode(1_020_202_006, "销售出库单未审核,无法操作"); + ErrorCode SALE_OUT_FAIL_RECEIPT_PRICE_EXCEED = new ErrorCode(1_020_202_007, "收款金额({})超过销售出库单总金额({})"); + ErrorCode SALE_OUT_PROCESS_FAIL_EXISTS_RECEIPT = new ErrorCode(1_020_202_008, "反审核失败,已存在对应的收款单"); + + // ========== ERP 销售退货(1-030-203-000) ========== + ErrorCode SALE_RETURN_NOT_EXISTS = new ErrorCode(1_020_203_000, "销售退货单不存在"); + ErrorCode SALE_RETURN_DELETE_FAIL_APPROVE = new ErrorCode(1_020_203_001, "销售退货单({})已审核,无法删除"); + ErrorCode SALE_RETURN_PROCESS_FAIL = new ErrorCode(1_020_203_002, "反审核失败,只有已审核的退货单才能反审核"); + ErrorCode SALE_RETURN_APPROVE_FAIL = new ErrorCode(1_020_203_003, "审核失败,只有未审核的退货单才能审核"); + ErrorCode SALE_RETURN_NO_EXISTS = new ErrorCode(1_020_203_004, "生成退货单失败,请重新提交"); + ErrorCode SALE_RETURN_UPDATE_FAIL_APPROVE = new ErrorCode(1_020_203_005, "销售退货单({})已审核,无法修改"); + ErrorCode SALE_RETURN_NOT_APPROVE = new ErrorCode(1_020_203_006, "销售退货单未审核,无法操作"); + ErrorCode SALE_RETURN_FAIL_REFUND_PRICE_EXCEED = new ErrorCode(1_020_203_007, "退款金额({})超过销售退货单总金额({})"); + ErrorCode SALE_RETURN_PROCESS_FAIL_EXISTS_REFUND = new ErrorCode(1_020_203_008, "反审核失败,已存在对应的退款单"); + + // ========== ERP 仓库 1-030-400-000 ========== + ErrorCode WAREHOUSE_NOT_EXISTS = new ErrorCode(1_030_400_000, "仓库不存在"); + ErrorCode WAREHOUSE_NOT_ENABLE = new ErrorCode(1_030_400_001, "仓库({})未启用"); + + // ========== ERP 其它入库单 1-030-401-000 ========== + ErrorCode STOCK_IN_NOT_EXISTS = new ErrorCode(1_030_401_000, "其它入库单不存在"); + ErrorCode STOCK_IN_DELETE_FAIL_APPROVE = new ErrorCode(1_030_401_001, "其它入库单({})已审核,无法删除"); + ErrorCode STOCK_IN_PROCESS_FAIL = new ErrorCode(1_030_401_002, "反审核失败,只有已审核的入库单才能反审核"); + ErrorCode STOCK_IN_APPROVE_FAIL = new ErrorCode(1_030_401_003, "审核失败,只有未审核的入库单才能审核"); + ErrorCode STOCK_IN_NO_EXISTS = new ErrorCode(1_030_401_004, "生成入库单失败,请重新提交"); + ErrorCode STOCK_IN_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_401_005, "其它入库单({})已审核,无法修改"); + + // ========== ERP 其它出库单 1-030-402-000 ========== + ErrorCode STOCK_OUT_NOT_EXISTS = new ErrorCode(1_030_402_000, "其它出库单不存在"); + ErrorCode STOCK_OUT_DELETE_FAIL_APPROVE = new ErrorCode(1_030_402_001, "其它出库单({})已审核,无法删除"); + ErrorCode STOCK_OUT_PROCESS_FAIL = new ErrorCode(1_030_402_002, "反审核失败,只有已审核的出库单才能反审核"); + ErrorCode STOCK_OUT_APPROVE_FAIL = new ErrorCode(1_030_402_003, "审核失败,只有未审核的出库单才能审核"); + ErrorCode STOCK_OUT_NO_EXISTS = new ErrorCode(1_030_402_004, "生成出库单失败,请重新提交"); + ErrorCode STOCK_OUT_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_402_005, "其它出库单({})已审核,无法修改"); + + // ========== ERP 库存调拨单 1-030-403-000 ========== + ErrorCode STOCK_MOVE_NOT_EXISTS = new ErrorCode(1_030_402_000, "库存调拨单不存在"); + ErrorCode STOCK_MOVE_DELETE_FAIL_APPROVE = new ErrorCode(1_030_402_001, "库存调拨单({})已审核,无法删除"); + ErrorCode STOCK_MOVE_PROCESS_FAIL = new ErrorCode(1_030_402_002, "反审核失败,只有已审核的调拨单才能反审核"); + ErrorCode STOCK_MOVE_APPROVE_FAIL = new ErrorCode(1_030_402_003, "审核失败,只有未审核的调拨单才能审核"); + ErrorCode STOCK_MOVE_NO_EXISTS = new ErrorCode(1_030_402_004, "生成调拨号失败,请重新提交"); + ErrorCode STOCK_MOVE_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_402_005, "库存调拨单({})已审核,无法修改"); + + // ========== ERP 库存盘点单 1-030-403-000 ========== + ErrorCode STOCK_CHECK_NOT_EXISTS = new ErrorCode(1_030_403_000, "库存盘点单不存在"); + ErrorCode STOCK_CHECK_DELETE_FAIL_APPROVE = new ErrorCode(1_030_403_001, "库存盘点单({})已审核,无法删除"); + ErrorCode STOCK_CHECK_PROCESS_FAIL = new ErrorCode(1_030_403_002, "反审核失败,只有已审核的盘点单才能反审核"); + ErrorCode STOCK_CHECK_APPROVE_FAIL = new ErrorCode(1_030_403_003, "审核失败,只有未审核的盘点单才能审核"); + ErrorCode STOCK_CHECK_NO_EXISTS = new ErrorCode(1_030_403_004, "生成盘点号失败,请重新提交"); + ErrorCode STOCK_CHECK_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_403_005, "库存盘点单({})已审核,无法修改"); + + // ========== ERP 产品库存 1-030-404-000 ========== + ErrorCode STOCK_COUNT_NEGATIVE = new ErrorCode(1_030_404_000, "操作失败,产品({})所在仓库({})的库存:{},小于变更数量:{}"); + ErrorCode STOCK_COUNT_NEGATIVE2 = new ErrorCode(1_030_404_001, "操作失败,产品({})所在仓库({})的库存不足"); + + // ========== ERP 产品 1-030-500-000 ========== + ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_030_500_000, "产品不存在"); + ErrorCode PRODUCT_NOT_ENABLE = new ErrorCode(1_030_500_001, "产品({})未启用"); + + // ========== ERP 产品分类 1-030-501-000 ========== + ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_501_000, "产品分类不存在"); + ErrorCode PRODUCT_CATEGORY_EXITS_CHILDREN = new ErrorCode(1_030_501_001, "存在存在子产品分类,无法删除"); + ErrorCode PRODUCT_CATEGORY_PARENT_NOT_EXITS = new ErrorCode(1_030_501_002,"父级产品分类不存在"); + ErrorCode PRODUCT_CATEGORY_PARENT_ERROR = new ErrorCode(1_030_501_003, "不能设置自己为父产品分类"); + ErrorCode PRODUCT_CATEGORY_NAME_DUPLICATE = new ErrorCode(1_030_501_004, "已经存在该分类名称的产品分类"); + ErrorCode PRODUCT_CATEGORY_PARENT_IS_CHILD = new ErrorCode(1_030_501_005, "不能设置自己的子分类为父分类"); + ErrorCode PRODUCT_CATEGORY_EXITS_PRODUCT = new ErrorCode(1_030_502_002, "存在产品使用该分类,无法删除"); + + // ========== ERP 产品单位 1-030-502-000 ========== + ErrorCode PRODUCT_UNIT_NOT_EXISTS = new ErrorCode(1_030_502_000, "产品单位不存在"); + ErrorCode PRODUCT_UNIT_NAME_DUPLICATE = new ErrorCode(1_030_502_001, "已存在该名字的产品单位"); + ErrorCode PRODUCT_UNIT_EXITS_PRODUCT = new ErrorCode(1_030_502_002, "存在产品使用该单位,无法删除"); + + // ========== ERP 结算账户 1-030-600-000 ========== + ErrorCode ACCOUNT_NOT_EXISTS = new ErrorCode(1_030_600_000, "结算账户不存在"); + ErrorCode ACCOUNT_NOT_ENABLE = new ErrorCode(1_030_600_001, "结算账户({})未启用"); + + // ========== ERP 付款单 1-030-601-000 ========== + ErrorCode FINANCE_PAYMENT_NOT_EXISTS = new ErrorCode(1_030_601_000, "付款单不存在"); + ErrorCode FINANCE_PAYMENT_DELETE_FAIL_APPROVE = new ErrorCode(1_030_601_001, "付款单({})已审核,无法删除"); + ErrorCode FINANCE_PAYMENT_PROCESS_FAIL = new ErrorCode(1_030_601_002, "反审核失败,只有已审核的付款单才能反审核"); + ErrorCode FINANCE_PAYMENT_APPROVE_FAIL = new ErrorCode(1_030_601_003, "审核失败,只有未审核的付款单才能审核"); + ErrorCode FINANCE_PAYMENT_NO_EXISTS = new ErrorCode(1_030_601_004, "生成付款单号失败,请重新提交"); + ErrorCode FINANCE_PAYMENT_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_601_005, "付款单({})已审核,无法修改"); + + // ========== ERP 收款单 1-030-602-000 ========== + ErrorCode FINANCE_RECEIPT_NOT_EXISTS = new ErrorCode(1_030_602_000, "收款单不存在"); + ErrorCode FINANCE_RECEIPT_DELETE_FAIL_APPROVE = new ErrorCode(1_030_602_001, "收款单({})已审核,无法删除"); + ErrorCode FINANCE_RECEIPT_PROCESS_FAIL = new ErrorCode(1_030_602_002, "反审核失败,只有已审核的收款单才能反审核"); + ErrorCode FINANCE_RECEIPT_APPROVE_FAIL = new ErrorCode(1_030_602_003, "审核失败,只有未审核的收款单才能审核"); + ErrorCode FINANCE_RECEIPT_NO_EXISTS = new ErrorCode(1_030_602_004, "生成收款单号失败,请重新提交"); + ErrorCode FINANCE_RECEIPT_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_602_005, "收款单({})已审核,无法修改"); + +} diff --git a/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/LogRecordConstants.java b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/LogRecordConstants.java new file mode 100644 index 000000000..73b72c0a9 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/LogRecordConstants.java @@ -0,0 +1,12 @@ +package cn.iocoder.yudao.module.erp.enums; + +/** + * ERP 操作日志枚举 + * 目的:统一管理,也减少 Service 里各种“复杂”字符串 + * + * @author 芋道源码 + */ +public interface LogRecordConstants { + + +} diff --git a/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/common/ErpBizTypeEnum.java b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/common/ErpBizTypeEnum.java new file mode 100644 index 000000000..bba2b309b --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/common/ErpBizTypeEnum.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.erp.enums.common; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * ERP 业务类型枚举 + * + * @author HUIHUI + */ +@RequiredArgsConstructor +@Getter +public enum ErpBizTypeEnum implements IntArrayValuable { + + PURCHASE_ORDER(10, "采购订单"), + PURCHASE_IN(11, "采购入库"), + PURCHASE_RETURN(12, "采购退货"), + + SALE_ORDER(20, "销售订单"), + SALE_OUT(21, "销售订单"), + SALE_RETURN(22, "销售退货"), + ; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ErpBizTypeEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + /** + * 名称 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/stock/ErpStockRecordBizTypeEnum.java b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/stock/ErpStockRecordBizTypeEnum.java new file mode 100644 index 000000000..559bf4ccf --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/stock/ErpStockRecordBizTypeEnum.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.module.erp.enums.stock; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * ERP 库存明细 - 业务类型枚举 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum ErpStockRecordBizTypeEnum implements IntArrayValuable { + + OTHER_IN(10, "其它入库"), + OTHER_IN_CANCEL(11, "其它入库(作废)"), + + OTHER_OUT(20, "其它出库"), + OTHER_OUT_CANCEL(21, "其它出库(作废)"), + + MOVE_IN(30, "调拨入库"), + MOVE_IN_CANCEL(31, "调拨入库(作废)"), + MOVE_OUT(32, "调拨出库"), + MOVE_OUT_CANCEL(33, "调拨出库(作废)"), + + CHECK_MORE_IN(40, "盘盈入库"), + CHECK_MORE_IN_CANCEL(41, "盘盈入库(作废)"), + CHECK_LESS_OUT(42, "盘亏出库"), + CHECK_LESS_OUT_CANCEL(43, "盘亏出库(作废)"), + + SALE_OUT(50, "销售出库"), + SALE_OUT_CANCEL(51, "销售出库(作废)"), + + SALE_RETURN(60, "销售退货入库"), + SALE_RETURN_CANCEL(61, "销售退货入库(作废)"), + + PURCHASE_IN(70, "采购入库"), + PURCHASE_IN_CANCEL(71, "采购入库(作废)"), + + PURCHASE_RETURN(80, "采购退货出库"), + PURCHASE_RETURN_CANCEL(81, "采购退货出库(作废)"), + ; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ErpStockRecordBizTypeEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + /** + * 名字 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/pom.xml b/yudao-module-erp/yudao-module-erp-biz/pom.xml new file mode 100644 index 000000000..a5b427006 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/pom.xml @@ -0,0 +1,132 @@ + + + + cn.iocoder.cloud + yudao-module-erp + ${revision} + + 4.0.0 + yudao-module-erp-biz + + ${project.artifactId} + + erp 包下,企业资源管理(Enterprise Resource Planning)。 + 例如说:采购、销售、库存、财务、产品等等 + + + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + + cn.iocoder.cloud + yudao-spring-boot-starter-env + + + + + cn.iocoder.cloud + yudao-module-system-api + ${revision} + + + cn.iocoder.cloud + yudao-module-erp-api + ${revision} + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-operatelog + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-tenant + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-security + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-mybatis + + + + cn.iocoder.cloud + yudao-spring-boot-starter-redis + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-rpc + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-excel + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-dict + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-monitor + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-test + + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + + \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/ErpServerApplication.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/ErpServerApplication.java new file mode 100644 index 000000000..07a3cd6e1 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/ErpServerApplication.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.erp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 项目的启动类 + *

+ * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * + * @author 芋道源码 + */ +@SpringBootApplication +public class ErpServerApplication { + + public static void main(String[] args) { + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + + SpringApplication.run(ErpServerApplication.class, args); + + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + } + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpAccountController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpAccountController.java new file mode 100644 index 000000000..4c8c98058 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpAccountController.java @@ -0,0 +1,116 @@ +package cn.iocoder.yudao.module.erp.controller.admin.finance; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account.ErpAccountPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account.ErpAccountRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account.ErpAccountSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO; +import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +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.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 结算账户") +@RestController +@RequestMapping("/erp/account") +@Validated +public class ErpAccountController { + + @Resource + private ErpAccountService accountService; + + @PostMapping("/create") + @Operation(summary = "创建结算账户") + @PreAuthorize("@ss.hasPermission('erp:account:create')") + public CommonResult createAccount(@Valid @RequestBody ErpAccountSaveReqVO createReqVO) { + return success(accountService.createAccount(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新结算账户") + @PreAuthorize("@ss.hasPermission('erp:account:update')") + public CommonResult updateAccount(@Valid @RequestBody ErpAccountSaveReqVO updateReqVO) { + accountService.updateAccount(updateReqVO); + return success(true); + } + + @PutMapping("/update-default-status") + @Operation(summary = "更新结算账户默认状态") + @Parameters({ + @Parameter(name = "id", description = "编号", required = true), + @Parameter(name = "status", description = "状态", required = true) + }) + public CommonResult updateAccountDefaultStatus(@RequestParam("id") Long id, + @RequestParam("defaultStatus") Boolean defaultStatus) { + accountService.updateAccountDefaultStatus(id, defaultStatus); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除结算账户") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('erp:account:delete')") + public CommonResult deleteAccount(@RequestParam("id") Long id) { + accountService.deleteAccount(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得结算账户") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:account:query')") + public CommonResult getAccount(@RequestParam("id") Long id) { + ErpAccountDO account = accountService.getAccount(id); + return success(BeanUtils.toBean(account, ErpAccountRespVO.class)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得结算账户精简列表", description = "只包含被开启的结算账户,主要用于前端的下拉选项") + public CommonResult> getWarehouseSimpleList() { + List list = accountService.getAccountListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(convertList(list, account -> new ErpAccountRespVO().setId(account.getId()) + .setName(account.getName()).setDefaultStatus(account.getDefaultStatus()))); + } + + @GetMapping("/page") + @Operation(summary = "获得结算账户分页") + @PreAuthorize("@ss.hasPermission('erp:account:query')") + public CommonResult> getAccountPage(@Valid ErpAccountPageReqVO pageReqVO) { + PageResult pageResult = accountService.getAccountPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ErpAccountRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出结算账户 Excel") + @PreAuthorize("@ss.hasPermission('erp:account:export')") + @OperateLog(type = EXPORT) + public void exportAccountExcel(@Valid ErpAccountPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = accountService.getAccountPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "结算账户.xls", "数据", ErpAccountRespVO.class, + BeanUtils.toBean(list, ErpAccountRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinancePaymentController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinancePaymentController.java new file mode 100644 index 000000000..b1f028a28 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinancePaymentController.java @@ -0,0 +1,153 @@ +package cn.iocoder.yudao.module.erp.controller.admin.finance; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentItemDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO; +import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService; +import cn.iocoder.yudao.module.erp.service.finance.ErpFinancePaymentService; +import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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 java.util.Map; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 付款单") +@RestController +@RequestMapping("/erp/finance-payment") +@Validated +public class ErpFinancePaymentController { + + @Resource + private ErpFinancePaymentService financePaymentService; + @Resource + private ErpSupplierService supplierService; + @Resource + private ErpAccountService accountService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建付款单") + @PreAuthorize("@ss.hasPermission('erp:finance-payment:create')") + public CommonResult createFinancePayment(@Valid @RequestBody ErpFinancePaymentSaveReqVO createReqVO) { + return success(financePaymentService.createFinancePayment(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新付款单") + @PreAuthorize("@ss.hasPermission('erp:finance-payment:update')") + public CommonResult updateFinancePayment(@Valid @RequestBody ErpFinancePaymentSaveReqVO updateReqVO) { + financePaymentService.updateFinancePayment(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新付款单的状态") + @PreAuthorize("@ss.hasPermission('erp:finance-payment:update-status')") + public CommonResult updateFinancePaymentStatus(@RequestParam("id") Long id, + @RequestParam("status") Integer status) { + financePaymentService.updateFinancePaymentStatus(id, status); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除付款单") + @Parameter(name = "ids", description = "编号数组", required = true) + @PreAuthorize("@ss.hasPermission('erp:finance-payment:delete')") + public CommonResult deleteFinancePayment(@RequestParam("ids") List ids) { + financePaymentService.deleteFinancePayment(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得付款单") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:finance-payment:query')") + public CommonResult getFinancePayment(@RequestParam("id") Long id) { + ErpFinancePaymentDO payment = financePaymentService.getFinancePayment(id); + if (payment == null) { + return success(null); + } + List paymentItemList = financePaymentService.getFinancePaymentItemListByPaymentId(id); + return success(BeanUtils.toBean(payment, ErpFinancePaymentRespVO.class, financePaymentVO -> + financePaymentVO.setItems(BeanUtils.toBean(paymentItemList, ErpFinancePaymentRespVO.Item.class)))); + } + + @GetMapping("/page") + @Operation(summary = "获得付款单分页") + @PreAuthorize("@ss.hasPermission('erp:finance-payment:query')") + public CommonResult> getFinancePaymentPage(@Valid ErpFinancePaymentPageReqVO pageReqVO) { + PageResult pageResult = financePaymentService.getFinancePaymentPage(pageReqVO); + return success(buildFinancePaymentVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出付款单 Excel") + @PreAuthorize("@ss.hasPermission('erp:finance-payment:export')") + @OperateLog(type = EXPORT) + public void exportFinancePaymentExcel(@Valid ErpFinancePaymentPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildFinancePaymentVOPageResult(financePaymentService.getFinancePaymentPage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "付款单.xls", "数据", ErpFinancePaymentRespVO.class, list); + } + + private PageResult buildFinancePaymentVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + // 1.1 付款项 + List paymentItemList = financePaymentService.getFinancePaymentItemListByPaymentIds( + convertSet(pageResult.getList(), ErpFinancePaymentDO::getId)); + Map> financePaymentItemMap = convertMultiMap(paymentItemList, ErpFinancePaymentItemDO::getPaymentId); + // 1.2 供应商信息 + Map supplierMap = supplierService.getSupplierMap( + convertSet(pageResult.getList(), ErpFinancePaymentDO::getSupplierId)); + // 1.3 结算账户信息 + Map accountMap = accountService.getAccountMap( + convertSet(pageResult.getList(), ErpFinancePaymentDO::getAccountId)); + // 1.4 管理员信息 + Map userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(), + contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getFinanceUserId()))); + // 2. 开始拼接 + return BeanUtils.toBean(pageResult, ErpFinancePaymentRespVO.class, payment -> { + payment.setItems(BeanUtils.toBean(financePaymentItemMap.get(payment.getId()), ErpFinancePaymentRespVO.Item.class)); + MapUtils.findAndThen(supplierMap, payment.getSupplierId(), supplier -> payment.setSupplierName(supplier.getName())); + MapUtils.findAndThen(accountMap, payment.getAccountId(), account -> payment.setAccountName(account.getName())); + MapUtils.findAndThen(userMap, Long.parseLong(payment.getCreator()), user -> payment.setCreatorName(user.getNickname())); + MapUtils.findAndThen(userMap, payment.getFinanceUserId(), user -> payment.setFinanceUserName(user.getNickname())); + }); + } + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinanceReceiptController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinanceReceiptController.java new file mode 100644 index 000000000..d36ab3990 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinanceReceiptController.java @@ -0,0 +1,153 @@ +package cn.iocoder.yudao.module.erp.controller.admin.finance; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt.ErpFinanceReceiptPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt.ErpFinanceReceiptRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt.ErpFinanceReceiptSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptItemDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpCustomerDO; +import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService; +import cn.iocoder.yudao.module.erp.service.finance.ErpFinanceReceiptService; +import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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 java.util.Map; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 收款单") +@RestController +@RequestMapping("/erp/finance-receipt") +@Validated +public class ErpFinanceReceiptController { + + @Resource + private ErpFinanceReceiptService financeReceiptService; + @Resource + private ErpCustomerService customerService; + @Resource + private ErpAccountService accountService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建收款单") + @PreAuthorize("@ss.hasPermission('erp:finance-receipt:create')") + public CommonResult createFinanceReceipt(@Valid @RequestBody ErpFinanceReceiptSaveReqVO createReqVO) { + return success(financeReceiptService.createFinanceReceipt(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新收款单") + @PreAuthorize("@ss.hasPermission('erp:finance-receipt:update')") + public CommonResult updateFinanceReceipt(@Valid @RequestBody ErpFinanceReceiptSaveReqVO updateReqVO) { + financeReceiptService.updateFinanceReceipt(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新收款单的状态") + @PreAuthorize("@ss.hasPermission('erp:finance-receipt:update-status')") + public CommonResult updateFinanceReceiptStatus(@RequestParam("id") Long id, + @RequestParam("status") Integer status) { + financeReceiptService.updateFinanceReceiptStatus(id, status); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除收款单") + @Parameter(name = "ids", description = "编号数组", required = true) + @PreAuthorize("@ss.hasPermission('erp:finance-receipt:delete')") + public CommonResult deleteFinanceReceipt(@RequestParam("ids") List ids) { + financeReceiptService.deleteFinanceReceipt(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得收款单") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:finance-receipt:query')") + public CommonResult getFinanceReceipt(@RequestParam("id") Long id) { + ErpFinanceReceiptDO receipt = financeReceiptService.getFinanceReceipt(id); + if (receipt == null) { + return success(null); + } + List receiptItemList = financeReceiptService.getFinanceReceiptItemListByReceiptId(id); + return success(BeanUtils.toBean(receipt, ErpFinanceReceiptRespVO.class, financeReceiptVO -> + financeReceiptVO.setItems(BeanUtils.toBean(receiptItemList, ErpFinanceReceiptRespVO.Item.class)))); + } + + @GetMapping("/page") + @Operation(summary = "获得收款单分页") + @PreAuthorize("@ss.hasPermission('erp:finance-receipt:query')") + public CommonResult> getFinanceReceiptPage(@Valid ErpFinanceReceiptPageReqVO pageReqVO) { + PageResult pageResult = financeReceiptService.getFinanceReceiptPage(pageReqVO); + return success(buildFinanceReceiptVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出收款单 Excel") + @PreAuthorize("@ss.hasPermission('erp:finance-receipt:export')") + @OperateLog(type = EXPORT) + public void exportFinanceReceiptExcel(@Valid ErpFinanceReceiptPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildFinanceReceiptVOPageResult(financeReceiptService.getFinanceReceiptPage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "收款单.xls", "数据", ErpFinanceReceiptRespVO.class, list); + } + + private PageResult buildFinanceReceiptVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + // 1.1 收款项 + List receiptItemList = financeReceiptService.getFinanceReceiptItemListByReceiptIds( + convertSet(pageResult.getList(), ErpFinanceReceiptDO::getId)); + Map> financeReceiptItemMap = convertMultiMap(receiptItemList, ErpFinanceReceiptItemDO::getReceiptId); + // 1.2 客户信息 + Map customerMap = customerService.getCustomerMap( + convertSet(pageResult.getList(), ErpFinanceReceiptDO::getCustomerId)); + // 1.3 结算账户信息 + Map accountMap = accountService.getAccountMap( + convertSet(pageResult.getList(), ErpFinanceReceiptDO::getAccountId)); + // 1.4 管理员信息 + Map userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(), + contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getFinanceUserId()))); + // 2. 开始拼接 + return BeanUtils.toBean(pageResult, ErpFinanceReceiptRespVO.class, receipt -> { + receipt.setItems(BeanUtils.toBean(financeReceiptItemMap.get(receipt.getId()), ErpFinanceReceiptRespVO.Item.class)); + MapUtils.findAndThen(customerMap, receipt.getCustomerId(), customer -> receipt.setCustomerName(customer.getName())); + MapUtils.findAndThen(accountMap, receipt.getAccountId(), account -> receipt.setAccountName(account.getName())); + MapUtils.findAndThen(userMap, Long.parseLong(receipt.getCreator()), user -> receipt.setCreatorName(user.getNickname())); + MapUtils.findAndThen(userMap, receipt.getFinanceUserId(), user -> receipt.setFinanceUserName(user.getNickname())); + }); + } + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/account/ErpAccountPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/account/ErpAccountPageReqVO.java new file mode 100644 index 000000000..3e1fa72f4 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/account/ErpAccountPageReqVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account; + +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 = "管理后台 - ERP 结算账户分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpAccountPageReqVO extends PageParam { + + @Schema(description = "账户编码", example = "A88") + private String no; + + @Schema(description = "账户名称", example = "张三") + private String name; + + @Schema(description = "备注", example = "随便") + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/account/ErpAccountRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/account/ErpAccountRespVO.java new file mode 100644 index 000000000..a1c2e954d --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/account/ErpAccountRespVO.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.module.system.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - ERP 结算账户 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpAccountRespVO { + + @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28684") + @ExcelProperty("结算账户编号") + private Long id; + + @Schema(description = "账户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + @ExcelProperty("账户名称") + private String name; + + @Schema(description = "账户编码", example = "A88") + @ExcelProperty("账户编码") + private String no; + + @Schema(description = "备注", example = "随便") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("开启状态") + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("排序") + private Integer sort; + + @Schema(description = "是否默认", example = "1") + @ExcelProperty("是否默认") + private Boolean defaultStatus; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/account/ErpAccountSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/account/ErpAccountSaveReqVO.java new file mode 100644 index 000000000..6f3556530 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/account/ErpAccountSaveReqVO.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - ERP 结算账户新增/修改 Request VO") +@Data +public class ErpAccountSaveReqVO { + + @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28684") + private Long id; + + @Schema(description = "账户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + @NotEmpty(message = "账户名称不能为空") + private String name; + + @Schema(description = "账户编码", example = "A88") + private String no; + + @Schema(description = "备注", example = "随便") + private String remark; + + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "开启状态不能为空") + @InEnum(value = CommonStatusEnum.class) + private Integer status; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "排序不能为空") + private Integer sort; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentPageReqVO.java new file mode 100644 index 000000000..b5618cadc --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentPageReqVO.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment; + +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; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 付款单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpFinancePaymentPageReqVO extends PageParam { + + @Schema(description = "付款单编号", example = "XS001") + private String no; + + @Schema(description = "付款时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] paymentTime; + + @Schema(description = "供应商编号", example = "1724") + private Long supplierId; + + @Schema(description = "创建者", example = "666") + private String creator; + + @Schema(description = "财务人员编号", example = "888") + private String financeUserId; + + @Schema(description = "结算账户编号", example = "31189") + private Long accountId; + + @Schema(description = "付款状态", example = "2") + private Integer status; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "业务编号", example = "123") + private String bizNo; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentRespVO.java new file mode 100644 index 000000000..43820a7d2 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentRespVO.java @@ -0,0 +1,97 @@ +package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 付款单 Response VO") +@Data +public class ErpFinancePaymentRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23752") + private Long id; + + @Schema(description = "付款单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "FKD888") + private String no; + + @Schema(description = "付款状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @Schema(description = "付款时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime paymentTime; + + @Schema(description = "财务人员编号", example = "19690") + private Long financeUserId; + @Schema(description = "财务人员名称", example = "张三") + private String financeUserName; + + @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29399") + private Long supplierId; + @Schema(description = "供应商名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小番茄公司") + private String supplierName; + + @Schema(description = "付款账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28989") + private Long accountId; + @Schema(description = "付款账户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + private String accountName; + + @Schema(description = "合计价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "13832") + private BigDecimal totalPrice; + + @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "11600") + private BigDecimal discountPrice; + + @Schema(description = "实际价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + private BigDecimal paymentPrice; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "创建人", example = "芋道") + private String creator; + @Schema(description = "创建人名称", example = "芋道") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "付款项列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Data + public static class Item { + + @Schema(description = "付款项编号", example = "11756") + private Long id; + + @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer bizType; + + @Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + private Long bizId; + + @Schema(description = "业务单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + private String bizNo; + + @Schema(description = "应付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + private BigDecimal totalPrice; + + @Schema(description = "已付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + private BigDecimal paidPrice; + + @Schema(description = "本次付款,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + @NotNull(message = "本次付款不能为空") + private BigDecimal paymentPrice; + + @Schema(description = "备注", example = "随便") + private String remark; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentSaveReqVO.java new file mode 100644 index 000000000..e50577b25 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentSaveReqVO.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 付款单新增/修改 Request VO") +@Data +public class ErpFinancePaymentSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23752") + private Long id; + + @Schema(description = "付款时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "付款时间不能为空") + private LocalDateTime paymentTime; + + @Schema(description = "财务人员编号", example = "19690") + private Long financeUserId; + + @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29399") + @NotNull(message = "供应商编号不能为空") + private Long supplierId; + + @Schema(description = "付款账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28989") + @NotNull(message = "付款账户编号不能为空") + private Long accountId; + + @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "11600") + @NotNull(message = "优惠金额不能为空") + private BigDecimal discountPrice; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "付款项列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "付款项列表不能为空") + @Valid + private List items; + + @Data + public static class Item { + + @Schema(description = "付款项编号", example = "11756") + private Long id; + + @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "业务类型不能为空") + private Integer bizType; + + @Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + @NotNull(message = "业务编号不能为空") + private Long bizId; + + @Schema(description = "已付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + @NotNull(message = "已付金额不能为空") + private BigDecimal paidPrice; + + @Schema(description = "本次付款,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + @NotNull(message = "本次付款不能为空") + private BigDecimal paymentPrice; + + @Schema(description = "备注", example = "随便") + private String remark; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptPageReqVO.java new file mode 100644 index 000000000..d3e938c66 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptPageReqVO.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt; + +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; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 收款单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpFinanceReceiptPageReqVO extends PageParam { + + @Schema(description = "收款单编号", example = "XS001") + private String no; + + @Schema(description = "收款时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] receiptTime; + + @Schema(description = "客户编号", example = "1724") + private Long customerId; + + @Schema(description = "创建者", example = "666") + private String creator; + + @Schema(description = "财务人员编号", example = "888") + private String financeUserId; + + @Schema(description = "收款账户编号", example = "31189") + private Long accountId; + + @Schema(description = "收款状态", example = "2") + private Integer status; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "业务编号", example = "123") + private String bizNo; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptRespVO.java new file mode 100644 index 000000000..5c7133fe6 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptRespVO.java @@ -0,0 +1,97 @@ +package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 收款单 Response VO") +@Data +public class ErpFinanceReceiptRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23752") + private Long id; + + @Schema(description = "收款单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "FKD888") + private String no; + + @Schema(description = "收款状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @Schema(description = "收款时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime receiptTime; + + @Schema(description = "财务人员编号", example = "19690") + private Long financeUserId; + @Schema(description = "财务人员名称", example = "张三") + private String financeUserName; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29399") + private Long customerId; + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小番茄公司") + private String customerName; + + @Schema(description = "收款账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28989") + private Long accountId; + @Schema(description = "收款账户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + private String accountName; + + @Schema(description = "合计价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "13832") + private BigDecimal totalPrice; + + @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "11600") + private BigDecimal discountPrice; + + @Schema(description = "实际价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + private BigDecimal receiptPrice; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "创建人", example = "芋道") + private String creator; + @Schema(description = "创建人名称", example = "芋道") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "收款项列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Data + public static class Item { + + @Schema(description = "收款项编号", example = "11756") + private Long id; + + @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer bizType; + + @Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + private Long bizId; + + @Schema(description = "业务单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + private String bizNo; + + @Schema(description = "应收金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + private BigDecimal totalPrice; + + @Schema(description = "已收金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + private BigDecimal receiptedPrice; + + @Schema(description = "本次收款,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + @NotNull(message = "本次收款不能为空") + private BigDecimal receiptPrice; + + @Schema(description = "备注", example = "随便") + private String remark; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptSaveReqVO.java new file mode 100644 index 000000000..126edf805 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptSaveReqVO.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 收款单新增/修改 Request VO") +@Data +public class ErpFinanceReceiptSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23752") + private Long id; + + @Schema(description = "收款时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "收款时间不能为空") + private LocalDateTime receiptTime; + + @Schema(description = "财务人员编号", example = "19690") + private Long financeUserId; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29399") + @NotNull(message = "客户编号不能为空") + private Long customerId; + + @Schema(description = "收款账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28989") + @NotNull(message = "收款账户编号不能为空") + private Long accountId; + + @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "11600") + @NotNull(message = "优惠金额不能为空") + private BigDecimal discountPrice; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "收款项列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "收款项列表不能为空") + @Valid + private List items; + + @Data + public static class Item { + + @Schema(description = "收款项编号", example = "11756") + private Long id; + + @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "业务类型不能为空") + private Integer bizType; + + @Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + @NotNull(message = "业务编号不能为空") + private Long bizId; + + @Schema(description = "已收金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + @NotNull(message = "已收金额不能为空") + private BigDecimal receiptedPrice; + + @Schema(description = "本次收款,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000") + @NotNull(message = "本次收款不能为空") + private BigDecimal receiptPrice; + + @Schema(description = "备注", example = "随便") + private String remark; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductCategoryController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductCategoryController.java new file mode 100644 index 000000000..85f51c1c6 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductCategoryController.java @@ -0,0 +1,101 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.category.ErpProductCategoryListReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.category.ErpProductCategoryRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.category.ErpProductCategorySaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductCategoryDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductCategoryService; +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.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 产品分类") +@RestController +@RequestMapping("/erp/product-category") +@Validated +public class ErpProductCategoryController { + + @Resource + private ErpProductCategoryService productCategoryService; + + @PostMapping("/create") + @Operation(summary = "创建产品分类") + @PreAuthorize("@ss.hasPermission('erp:product-category:create')") + public CommonResult createProductCategory(@Valid @RequestBody ErpProductCategorySaveReqVO createReqVO) { + return success(productCategoryService.createProductCategory(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新产品分类") + @PreAuthorize("@ss.hasPermission('erp:product-category:update')") + public CommonResult updateProductCategory(@Valid @RequestBody ErpProductCategorySaveReqVO updateReqVO) { + productCategoryService.updateProductCategory(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除产品分类") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('erp:product-category:delete')") + public CommonResult deleteProductCategory(@RequestParam("id") Long id) { + productCategoryService.deleteProductCategory(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得产品分类") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:product-category:query')") + public CommonResult getProductCategory(@RequestParam("id") Long id) { + ErpProductCategoryDO category = productCategoryService.getProductCategory(id); + return success(BeanUtils.toBean(category, ErpProductCategoryRespVO.class)); + } + + @GetMapping("/list") + @Operation(summary = "获得产品分类列表") + @PreAuthorize("@ss.hasPermission('erp:product-category:query')") + public CommonResult> getProductCategoryList(@Valid ErpProductCategoryListReqVO listReqVO) { + List list = productCategoryService.getProductCategoryList(listReqVO); + return success(BeanUtils.toBean(list, ErpProductCategoryRespVO.class)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得产品分类精简列表", description = "只包含被开启的分类,主要用于前端的下拉选项") + public CommonResult> getProductCategorySimpleList() { + List list = productCategoryService.getProductCategoryList( + new ErpProductCategoryListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus())); + return success(convertList(list, category -> new ErpProductCategoryRespVO() + .setId(category.getId()).setName(category.getName()).setParentId(category.getParentId()))); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出产品分类 Excel") + @PreAuthorize("@ss.hasPermission('erp:product-category:export')") + @OperateLog(type = EXPORT) + public void exportProductCategoryExcel(@Valid ErpProductCategoryListReqVO listReqVO, + HttpServletResponse response) throws IOException { + List list = productCategoryService.getProductCategoryList(listReqVO); + // 导出 Excel + ExcelUtils.write(response, "产品分类.xls", "数据", ErpProductCategoryRespVO.class, + BeanUtils.toBean(list, ErpProductCategoryRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductController.java new file mode 100644 index 000000000..cde7bd704 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductController.java @@ -0,0 +1,105 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +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.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 产品") +@RestController +@RequestMapping("/erp/product") +@Validated +public class ErpProductController { + + @Resource + private ErpProductService productService; + + @PostMapping("/create") + @Operation(summary = "创建产品") + @PreAuthorize("@ss.hasPermission('erp:product:create')") + public CommonResult createProduct(@Valid @RequestBody ProductSaveReqVO createReqVO) { + return success(productService.createProduct(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新产品") + @PreAuthorize("@ss.hasPermission('erp:product:update')") + public CommonResult updateProduct(@Valid @RequestBody ProductSaveReqVO updateReqVO) { + productService.updateProduct(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除产品") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('erp:product:delete')") + public CommonResult deleteProduct(@RequestParam("id") Long id) { + productService.deleteProduct(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得产品") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:product:query')") + public CommonResult getProduct(@RequestParam("id") Long id) { + ErpProductDO product = productService.getProduct(id); + return success(BeanUtils.toBean(product, ErpProductRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得产品分页") + @PreAuthorize("@ss.hasPermission('erp:product:query')") + public CommonResult> getProductPage(@Valid ErpProductPageReqVO pageReqVO) { + return success(productService.getProductVOPage(pageReqVO)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得产品精简列表", description = "只包含被开启的产品,主要用于前端的下拉选项") + public CommonResult> getProductSimpleList() { + List list = productService.getProductVOListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(convertList(list, product -> new ErpProductRespVO().setId(product.getId()) + .setName(product.getName()).setBarCode(product.getBarCode()) + .setCategoryId(product.getCategoryId()).setCategoryName(product.getCategoryName()) + .setUnitId(product.getUnitId()).setUnitName(product.getUnitName()) + .setPurchasePrice(product.getPurchasePrice()).setSalePrice(product.getSalePrice()).setMinPrice(product.getMinPrice()))); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出产品 Excel") + @PreAuthorize("@ss.hasPermission('erp:product:export')") + @OperateLog(type = EXPORT) + public void exportProductExcel(@Valid ErpProductPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + PageResult pageResult = productService.getProductVOPage(pageReqVO); + // 导出 Excel + ExcelUtils.write(response, "产品.xls", "数据", ErpProductRespVO.class, + pageResult.getList()); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductUnitController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductUnitController.java new file mode 100644 index 000000000..0be3db01c --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/ErpProductUnitController.java @@ -0,0 +1,102 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductUnitService; +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.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 产品单位") +@RestController +@RequestMapping("/erp/product-unit") +@Validated +public class ErpProductUnitController { + + @Resource + private ErpProductUnitService productUnitService; + + @PostMapping("/create") + @Operation(summary = "创建产品单位") + @PreAuthorize("@ss.hasPermission('erp:product-unit:create')") + public CommonResult createProductUnit(@Valid @RequestBody ErpProductUnitSaveReqVO createReqVO) { + return success(productUnitService.createProductUnit(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新产品单位") + @PreAuthorize("@ss.hasPermission('erp:product-unit:update')") + public CommonResult updateProductUnit(@Valid @RequestBody ErpProductUnitSaveReqVO updateReqVO) { + productUnitService.updateProductUnit(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除产品单位") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('erp:product-unit:delete')") + public CommonResult deleteProductUnit(@RequestParam("id") Long id) { + productUnitService.deleteProductUnit(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得产品单位") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:product-unit:query')") + public CommonResult getProductUnit(@RequestParam("id") Long id) { + ErpProductUnitDO productUnit = productUnitService.getProductUnit(id); + return success(BeanUtils.toBean(productUnit, ErpProductUnitRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得产品单位分页") + @PreAuthorize("@ss.hasPermission('erp:product-unit:query')") + public CommonResult> getProductUnitPage(@Valid ErpProductUnitPageReqVO pageReqVO) { + PageResult pageResult = productUnitService.getProductUnitPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ErpProductUnitRespVO.class)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得产品单位精简列表", description = "只包含被开启的单位,主要用于前端的下拉选项") + public CommonResult> getProductUnitSimpleList() { + List list = productUnitService.getProductUnitListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(convertList(list, unit -> new ErpProductUnitRespVO().setId(unit.getId()).setName(unit.getName()))); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出产品单位 Excel") + @PreAuthorize("@ss.hasPermission('erp:product-unit:export')") + @OperateLog(type = EXPORT) + public void exportProductUnitExcel(@Valid ErpProductUnitPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = productUnitService.getProductUnitPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "产品单位.xls", "数据", ErpProductUnitRespVO.class, + BeanUtils.toBean(list, ErpProductUnitRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/category/ErpProductCategoryListReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/category/ErpProductCategoryListReqVO.java new file mode 100644 index 000000000..b9b530e7d --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/category/ErpProductCategoryListReqVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product.vo.category; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - ERP 产品分类列表 Request VO") +@Data +public class ErpProductCategoryListReqVO { + + @Schema(description = "分类名称", example = "芋艿") + private String name; + + @Schema(description = "开启状态", example = "1") + private Integer status; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/category/ErpProductCategoryRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/category/ErpProductCategoryRespVO.java new file mode 100644 index 000000000..23d7d9e8f --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/category/ErpProductCategoryRespVO.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product.vo.category; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.module.system.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - ERP 产品分类 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpProductCategoryRespVO { + + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5860") + @ExcelProperty("分类编号") + private Long id; + + @Schema(description = "父分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21829") + @ExcelProperty("父分类编号") + private Long parentId; + + @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @ExcelProperty("分类名称") + private String name; + + @Schema(description = "分类编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "S110") + @ExcelProperty("分类编码") + private String code; + + @Schema(description = "分类排序", example = "10") + @ExcelProperty("分类排序") + private Integer sort; + + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty(value = "开启状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/category/ErpProductCategorySaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/category/ErpProductCategorySaveReqVO.java new file mode 100644 index 000000000..a3c82dc93 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/category/ErpProductCategorySaveReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product.vo.category; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - ERP 产品分类新增/修改 Request VO") +@Data +public class ErpProductCategorySaveReqVO { + + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5860") + private Long id; + + @Schema(description = "父分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21829") + @NotNull(message = "父分类编号不能为空") + private Long parentId; + + @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @NotEmpty(message = "分类名称不能为空") + private String name; + + @Schema(description = "分类编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "S110") + @NotEmpty(message = "分类编码不能为空") + private String code; + + @Schema(description = "分类排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @NotNull(message = "分类排序不能为空") + private Integer sort; + + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "开启状态不能为空") + private Integer status; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java new file mode 100644 index 000000000..de4f8142a --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductPageReqVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product.vo.product; + +import lombok.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 产品分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpProductPageReqVO extends PageParam { + + @Schema(description = "产品名称", example = "李四") + private String name; + + @Schema(description = "产品分类编号", example = "11161") + private Long categoryId; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductRespVO.java new file mode 100644 index 000000000..9be9bc255 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ErpProductRespVO.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product.vo.product; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - ERP 产品 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpProductRespVO { + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15672") + @ExcelProperty("产品编号") + private Long id; + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @ExcelProperty("产品名称") + private String name; + + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "X110") + @ExcelProperty("产品条码") + private String barCode; + + @Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11161") + private Long categoryId; + @Schema(description = "产品分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "水果") + @ExcelProperty("产品分类") + private String categoryName; + + @Schema(description = "单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8869") + private Long unitId; + @Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "个") + @ExcelProperty("单位") + private String unitName; + + @Schema(description = "产品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("产品状态") + private Integer status; + + @Schema(description = "产品规格", example = "红色") + @ExcelProperty("产品规格") + private String standard; + + @Schema(description = "产品备注", example = "你猜") + @ExcelProperty("产品备注") + private String remark; + + @Schema(description = "保质期天数", example = "10") + @ExcelProperty("保质期天数") + private Integer expiryDay; + + @Schema(description = "基础重量(kg)", example = "1.00") + @ExcelProperty("基础重量(kg)") + private BigDecimal weight; + + @Schema(description = "采购价格,单位:元", example = "10.30") + @ExcelProperty("采购价格,单位:元") + private BigDecimal purchasePrice; + + @Schema(description = "销售价格,单位:元", example = "74.32") + @ExcelProperty("销售价格,单位:元") + private BigDecimal salePrice; + + @Schema(description = "最低价格,单位:元", example = "161.87") + @ExcelProperty("最低价格,单位:元") + private BigDecimal minPrice; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ProductSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ProductSaveReqVO.java new file mode 100644 index 000000000..6cf806e3e --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/product/ProductSaveReqVO.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product.vo.product; + +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 = "管理后台 - ERP 产品新增/修改 Request VO") +@Data +public class ProductSaveReqVO { + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15672") + private Long id; + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @NotEmpty(message = "产品名称不能为空") + private String name; + + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "X110") + @NotEmpty(message = "产品条码不能为空") + private String barCode; + + @Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11161") + @NotNull(message = "产品分类编号不能为空") + private Long categoryId; + + @Schema(description = "单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8869") + @NotNull(message = "单位编号不能为空") + private Long unitId; + + @Schema(description = "产品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "产品状态不能为空") + private Integer status; + + @Schema(description = "产品规格", example = "红色") + private String standard; + + @Schema(description = "产品备注", example = "你猜") + private String remark; + + @Schema(description = "保质期天数", example = "10") + private Integer expiryDay; + + @Schema(description = "基础重量(kg)", example = "1.00") + private BigDecimal weight; + + @Schema(description = "采购价格,单位:元", example = "10.30") + private BigDecimal purchasePrice; + + @Schema(description = "销售价格,单位:元", example = "74.32") + private BigDecimal salePrice; + + @Schema(description = "最低价格,单位:元", example = "161.87") + private BigDecimal minPrice; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/unit/ErpProductUnitPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/unit/ErpProductUnitPageReqVO.java new file mode 100644 index 000000000..87119c126 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/unit/ErpProductUnitPageReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit; + +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 = "管理后台 - ERP 产品单位分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpProductUnitPageReqVO extends PageParam { + + @Schema(description = "单位名字", example = "芋艿") + private String name; + + @Schema(description = "单位状态", example = "1") + private Integer status; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/unit/ErpProductUnitRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/unit/ErpProductUnitRespVO.java new file mode 100644 index 000000000..06f604920 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/unit/ErpProductUnitRespVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.module.system.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - ERP 产品单位 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpProductUnitRespVO { + + @Schema(description = "单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31254") + @ExcelProperty("单位编号") + private Long id; + + @Schema(description = "单位名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @ExcelProperty("单位名字") + private String name; + + @Schema(description = "单位状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("单位状态") + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/unit/ErpProductUnitSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/unit/ErpProductUnitSaveReqVO.java new file mode 100644 index 000000000..e413ec1bf --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/product/vo/unit/ErpProductUnitSaveReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - ERP 产品单位新增/修改 Request VO") +@Data +public class ErpProductUnitSaveReqVO { + + @Schema(description = "单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31254") + private Long id; + + @Schema(description = "单位名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @NotEmpty(message = "单位名字不能为空") + private String name; + + @Schema(description = "单位状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "单位状态不能为空") + @InEnum(CommonStatusEnum.class) + private Integer status; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInController.java new file mode 100644 index 000000000..d33c7ae4d --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInController.java @@ -0,0 +1,165 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInItemDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInService; +import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 采购入库") +@RestController +@RequestMapping("/erp/purchase-in") +@Validated +public class ErpPurchaseInController { + + @Resource + private ErpPurchaseInService purchaseInService; + @Resource + private ErpStockService stockService; + @Resource + private ErpProductService productService; + @Resource + private ErpSupplierService supplierService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建采购入库") + @PreAuthorize("@ss.hasPermission('erp:purchase-in:create')") + public CommonResult createPurchaseIn(@Valid @RequestBody ErpPurchaseInSaveReqVO createReqVO) { + return success(purchaseInService.createPurchaseIn(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新采购入库") + @PreAuthorize("@ss.hasPermission('erp:purchase-in:update')") + public CommonResult updatePurchaseIn(@Valid @RequestBody ErpPurchaseInSaveReqVO updateReqVO) { + purchaseInService.updatePurchaseIn(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新采购入库的状态") + @PreAuthorize("@ss.hasPermission('erp:purchase-in:update-status')") + public CommonResult updatePurchaseInStatus(@RequestParam("id") Long id, + @RequestParam("status") Integer status) { + purchaseInService.updatePurchaseInStatus(id, status); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除采购入库") + @Parameter(name = "ids", description = "编号数组", required = true) + @PreAuthorize("@ss.hasPermission('erp:purchase-in:delete')") + public CommonResult deletePurchaseIn(@RequestParam("ids") List ids) { + purchaseInService.deletePurchaseIn(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得采购入库") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:purchase-in:query')") + public CommonResult getPurchaseIn(@RequestParam("id") Long id) { + ErpPurchaseInDO purchaseIn = purchaseInService.getPurchaseIn(id); + if (purchaseIn == null) { + return success(null); + } + List purchaseInItemList = purchaseInService.getPurchaseInItemListByInId(id); + Map productMap = productService.getProductVOMap( + convertSet(purchaseInItemList, ErpPurchaseInItemDO::getProductId)); + return success(BeanUtils.toBean(purchaseIn, ErpPurchaseInRespVO.class, purchaseInVO -> + purchaseInVO.setItems(BeanUtils.toBean(purchaseInItemList, ErpPurchaseInRespVO.Item.class, item -> { + ErpStockDO stock = stockService.getStock(item.getProductId(), item.getWarehouseId()); + item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO); + MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())); + })))); + } + + @GetMapping("/page") + @Operation(summary = "获得采购入库分页") + @PreAuthorize("@ss.hasPermission('erp:purchase-in:query')") + public CommonResult> getPurchaseInPage(@Valid ErpPurchaseInPageReqVO pageReqVO) { + PageResult pageResult = purchaseInService.getPurchaseInPage(pageReqVO); + return success(buildPurchaseInVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出采购入库 Excel") + @PreAuthorize("@ss.hasPermission('erp:purchase-in:export')") + @OperateLog(type = EXPORT) + public void exportPurchaseInExcel(@Valid ErpPurchaseInPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildPurchaseInVOPageResult(purchaseInService.getPurchaseInPage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "采购入库.xls", "数据", ErpPurchaseInRespVO.class, list); + } + + private PageResult buildPurchaseInVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + // 1.1 入库项 + List purchaseInItemList = purchaseInService.getPurchaseInItemListByInIds( + convertSet(pageResult.getList(), ErpPurchaseInDO::getId)); + Map> purchaseInItemMap = convertMultiMap(purchaseInItemList, ErpPurchaseInItemDO::getInId); + // 1.2 产品信息 + Map productMap = productService.getProductVOMap( + convertSet(purchaseInItemList, ErpPurchaseInItemDO::getProductId)); + // 1.3 供应商信息 + Map supplierMap = supplierService.getSupplierMap( + convertSet(pageResult.getList(), ErpPurchaseInDO::getSupplierId)); + // 1.4 管理员信息 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), purchaseIn -> Long.parseLong(purchaseIn.getCreator()))); + // 2. 开始拼接 + return BeanUtils.toBean(pageResult, ErpPurchaseInRespVO.class, purchaseIn -> { + purchaseIn.setItems(BeanUtils.toBean(purchaseInItemMap.get(purchaseIn.getId()), ErpPurchaseInRespVO.Item.class, + item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())))); + purchaseIn.setProductNames(CollUtil.join(purchaseIn.getItems(), ",", ErpPurchaseInRespVO.Item::getProductName)); + MapUtils.findAndThen(supplierMap, purchaseIn.getSupplierId(), supplier -> purchaseIn.setSupplierName(supplier.getName())); + MapUtils.findAndThen(userMap, Long.parseLong(purchaseIn.getCreator()), user -> purchaseIn.setCreatorName(user.getNickname())); + }); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java new file mode 100644 index 000000000..203d2fec0 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java @@ -0,0 +1,164 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseOrderService; +import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 采购订单") +@RestController +@RequestMapping("/erp/purchase-order") +@Validated +public class ErpPurchaseOrderController { + + @Resource + private ErpPurchaseOrderService purchaseOrderService; + @Resource + private ErpStockService stockService; + @Resource + private ErpProductService productService; + @Resource + private ErpSupplierService supplierService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建采购订单") + @PreAuthorize("@ss.hasPermission('erp:purchase-create:create')") + public CommonResult createPurchaseOrder(@Valid @RequestBody ErpPurchaseOrderSaveReqVO createReqVO) { + return success(purchaseOrderService.createPurchaseOrder(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新采购订单") + @PreAuthorize("@ss.hasPermission('erp:purchase-create:update')") + public CommonResult updatePurchaseOrder(@Valid @RequestBody ErpPurchaseOrderSaveReqVO updateReqVO) { + purchaseOrderService.updatePurchaseOrder(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新采购订单的状态") + @PreAuthorize("@ss.hasPermission('erp:purchase-create:update-status')") + public CommonResult updatePurchaseOrderStatus(@RequestParam("id") Long id, + @RequestParam("status") Integer status) { + purchaseOrderService.updatePurchaseOrderStatus(id, status); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除采购订单") + @Parameter(name = "ids", description = "编号数组", required = true) + @PreAuthorize("@ss.hasPermission('erp:purchase-create:delete')") + public CommonResult deletePurchaseOrder(@RequestParam("ids") List ids) { + purchaseOrderService.deletePurchaseOrder(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得采购订单") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:purchase-create:query')") + public CommonResult getPurchaseOrder(@RequestParam("id") Long id) { + ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.getPurchaseOrder(id); + if (purchaseOrder == null) { + return success(null); + } + List purchaseOrderItemList = purchaseOrderService.getPurchaseOrderItemListByOrderId(id); + Map productMap = productService.getProductVOMap( + convertSet(purchaseOrderItemList, ErpPurchaseOrderItemDO::getProductId)); + return success(BeanUtils.toBean(purchaseOrder, ErpPurchaseOrderRespVO.class, purchaseOrderVO -> + purchaseOrderVO.setItems(BeanUtils.toBean(purchaseOrderItemList, ErpPurchaseOrderRespVO.Item.class, item -> { + BigDecimal purchaseCount = stockService.getStockCount(item.getProductId()); + item.setStockCount(purchaseCount != null ? purchaseCount : BigDecimal.ZERO); + MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())); + })))); + } + + @GetMapping("/page") + @Operation(summary = "获得采购订单分页") + @PreAuthorize("@ss.hasPermission('erp:purchase-create:query')") + public CommonResult> getPurchaseOrderPage(@Valid ErpPurchaseOrderPageReqVO pageReqVO) { + PageResult pageResult = purchaseOrderService.getPurchaseOrderPage(pageReqVO); + return success(buildPurchaseOrderVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出采购订单 Excel") + @PreAuthorize("@ss.hasPermission('erp:purchase-create:export')") + @OperateLog(type = EXPORT) + public void exportPurchaseOrderExcel(@Valid ErpPurchaseOrderPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildPurchaseOrderVOPageResult(purchaseOrderService.getPurchaseOrderPage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "采购订单.xls", "数据", ErpPurchaseOrderRespVO.class, list); + } + + private PageResult buildPurchaseOrderVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + // 1.1 订单项 + List purchaseOrderItemList = purchaseOrderService.getPurchaseOrderItemListByOrderIds( + convertSet(pageResult.getList(), ErpPurchaseOrderDO::getId)); + Map> purchaseOrderItemMap = convertMultiMap(purchaseOrderItemList, ErpPurchaseOrderItemDO::getOrderId); + // 1.2 产品信息 + Map productMap = productService.getProductVOMap( + convertSet(purchaseOrderItemList, ErpPurchaseOrderItemDO::getProductId)); + // 1.3 供应商信息 + Map supplierMap = supplierService.getSupplierMap( + convertSet(pageResult.getList(), ErpPurchaseOrderDO::getSupplierId)); + // 1.4 管理员信息 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), purchaseOrder -> Long.parseLong(purchaseOrder.getCreator()))); + // 2. 开始拼接 + return BeanUtils.toBean(pageResult, ErpPurchaseOrderRespVO.class, purchaseOrder -> { + purchaseOrder.setItems(BeanUtils.toBean(purchaseOrderItemMap.get(purchaseOrder.getId()), ErpPurchaseOrderRespVO.Item.class, + item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())))); + purchaseOrder.setProductNames(CollUtil.join(purchaseOrder.getItems(), ",", ErpPurchaseOrderRespVO.Item::getProductName)); + MapUtils.findAndThen(supplierMap, purchaseOrder.getSupplierId(), supplier -> purchaseOrder.setSupplierName(supplier.getName())); + MapUtils.findAndThen(userMap, Long.parseLong(purchaseOrder.getCreator()), user -> purchaseOrder.setCreatorName(user.getNickname())); + }); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseReturnController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseReturnController.java new file mode 100644 index 000000000..0df31bcf1 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseReturnController.java @@ -0,0 +1,165 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnItemDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseReturnService; +import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 采购退货") +@RestController +@RequestMapping("/erp/purchase-return") +@Validated +public class ErpPurchaseReturnController { + + @Resource + private ErpPurchaseReturnService purchaseReturnService; + @Resource + private ErpStockService stockService; + @Resource + private ErpProductService productService; + @Resource + private ErpSupplierService supplierService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建采购退货") + @PreAuthorize("@ss.hasPermission('erp:purchase-return:create')") + public CommonResult createPurchaseReturn(@Valid @RequestBody ErpPurchaseReturnSaveReqVO createReqVO) { + return success(purchaseReturnService.createPurchaseReturn(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新采购退货") + @PreAuthorize("@ss.hasPermission('erp:purchase-return:update')") + public CommonResult updatePurchaseReturn(@Valid @RequestBody ErpPurchaseReturnSaveReqVO updateReqVO) { + purchaseReturnService.updatePurchaseReturn(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新采购退货的状态") + @PreAuthorize("@ss.hasPermission('erp:purchase-return:update-status')") + public CommonResult updatePurchaseReturnStatus(@RequestParam("id") Long id, + @RequestParam("status") Integer status) { + purchaseReturnService.updatePurchaseReturnStatus(id, status); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除采购退货") + @Parameter(name = "ids", description = "编号数组", required = true) + @PreAuthorize("@ss.hasPermission('erp:purchase-return:delete')") + public CommonResult deletePurchaseReturn(@RequestParam("ids") List ids) { + purchaseReturnService.deletePurchaseReturn(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得采购退货") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:purchase-return:query')") + public CommonResult getPurchaseReturn(@RequestParam("id") Long id) { + ErpPurchaseReturnDO purchaseReturn = purchaseReturnService.getPurchaseReturn(id); + if (purchaseReturn == null) { + return success(null); + } + List purchaseReturnItemList = purchaseReturnService.getPurchaseReturnItemListByReturnId(id); + Map productMap = productService.getProductVOMap( + convertSet(purchaseReturnItemList, ErpPurchaseReturnItemDO::getProductId)); + return success(BeanUtils.toBean(purchaseReturn, ErpPurchaseReturnRespVO.class, purchaseReturnVO -> + purchaseReturnVO.setItems(BeanUtils.toBean(purchaseReturnItemList, ErpPurchaseReturnRespVO.Item.class, item -> { + ErpStockDO stock = stockService.getStock(item.getProductId(), item.getWarehouseId()); + item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO); + MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())); + })))); + } + + @GetMapping("/page") + @Operation(summary = "获得采购退货分页") + @PreAuthorize("@ss.hasPermission('erp:purchase-return:query')") + public CommonResult> getPurchaseReturnPage(@Valid ErpPurchaseReturnPageReqVO pageReqVO) { + PageResult pageResult = purchaseReturnService.getPurchaseReturnPage(pageReqVO); + return success(buildPurchaseReturnVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出采购退货 Excel") + @PreAuthorize("@ss.hasPermission('erp:purchase-return:export')") + @OperateLog(type = EXPORT) + public void exportPurchaseReturnExcel(@Valid ErpPurchaseReturnPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildPurchaseReturnVOPageResult(purchaseReturnService.getPurchaseReturnPage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "采购退货.xls", "数据", ErpPurchaseReturnRespVO.class, list); + } + + private PageResult buildPurchaseReturnVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + // 1.1 退货项 + List purchaseReturnItemList = purchaseReturnService.getPurchaseReturnItemListByReturnIds( + convertSet(pageResult.getList(), ErpPurchaseReturnDO::getId)); + Map> purchaseReturnItemMap = convertMultiMap(purchaseReturnItemList, ErpPurchaseReturnItemDO::getReturnId); + // 1.2 产品信息 + Map productMap = productService.getProductVOMap( + convertSet(purchaseReturnItemList, ErpPurchaseReturnItemDO::getProductId)); + // 1.3 供应商信息 + Map supplierMap = supplierService.getSupplierMap( + convertSet(pageResult.getList(), ErpPurchaseReturnDO::getSupplierId)); + // 1.4 管理员信息 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), purchaseReturn -> Long.parseLong(purchaseReturn.getCreator()))); + // 2. 开始拼接 + return BeanUtils.toBean(pageResult, ErpPurchaseReturnRespVO.class, purchaseReturn -> { + purchaseReturn.setItems(BeanUtils.toBean(purchaseReturnItemMap.get(purchaseReturn.getId()), ErpPurchaseReturnRespVO.Item.class, + item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())))); + purchaseReturn.setProductNames(CollUtil.join(purchaseReturn.getItems(), ",", ErpPurchaseReturnRespVO.Item::getProductName)); + MapUtils.findAndThen(supplierMap, purchaseReturn.getSupplierId(), supplier -> purchaseReturn.setSupplierName(supplier.getName())); + MapUtils.findAndThen(userMap, Long.parseLong(purchaseReturn.getCreator()), user -> purchaseReturn.setCreatorName(user.getNickname())); + }); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpSupplierController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpSupplierController.java new file mode 100644 index 000000000..88253286d --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpSupplierController.java @@ -0,0 +1,102 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier.ErpSupplierPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier.ErpSupplierRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier.ErpSupplierSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO; +import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService; +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.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 供应商") +@RestController +@RequestMapping("/erp/supplier") +@Validated +public class ErpSupplierController { + + @Resource + private ErpSupplierService supplierService; + + @PostMapping("/create") + @Operation(summary = "创建供应商") + @PreAuthorize("@ss.hasPermission('erp:supplier:create')") + public CommonResult createSupplier(@Valid @RequestBody ErpSupplierSaveReqVO createReqVO) { + return success(supplierService.createSupplier(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新供应商") + @PreAuthorize("@ss.hasPermission('erp:supplier:update')") + public CommonResult updateSupplier(@Valid @RequestBody ErpSupplierSaveReqVO updateReqVO) { + supplierService.updateSupplier(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除供应商") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('erp:supplier:delete')") + public CommonResult deleteSupplier(@RequestParam("id") Long id) { + supplierService.deleteSupplier(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得供应商") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:supplier:query')") + public CommonResult getSupplier(@RequestParam("id") Long id) { + ErpSupplierDO supplier = supplierService.getSupplier(id); + return success(BeanUtils.toBean(supplier, ErpSupplierRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得供应商分页") + @PreAuthorize("@ss.hasPermission('erp:supplier:query')") + public CommonResult> getSupplierPage(@Valid ErpSupplierPageReqVO pageReqVO) { + PageResult pageResult = supplierService.getSupplierPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ErpSupplierRespVO.class)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得供应商精简列表", description = "只包含被开启的供应商,主要用于前端的下拉选项") + public CommonResult> getSupplierSimpleList() { + List list = supplierService.getSupplierListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(convertList(list, supplier -> new ErpSupplierRespVO().setId(supplier.getId()).setName(supplier.getName()))); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出供应商 Excel") + @PreAuthorize("@ss.hasPermission('erp:supplier:export')") + @OperateLog(type = EXPORT) + public void exportSupplierExcel(@Valid ErpSupplierPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = supplierService.getSupplierPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "供应商.xls", "数据", ErpSupplierRespVO.class, + BeanUtils.toBean(list, ErpSupplierRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInPageReqVO.java new file mode 100644 index 000000000..e84607ce4 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInPageReqVO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in; + +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; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 采购入库分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpPurchaseInPageReqVO extends PageParam { + + public static final Integer PAYMENT_STATUS_NONE = 0; + public static final Integer PAYMENT_STATUS_PART = 1; + public static final Integer PAYMENT_STATUS_ALL = 2; + + @Schema(description = "采购单编号", example = "XS001") + private String no; + + @Schema(description = "供应商编号", example = "1724") + private Long supplierId; + + @Schema(description = "入库时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] inTime; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "入库状态", example = "2") + private Integer status; + + @Schema(description = "创建者") + private String creator; + + @Schema(description = "产品编号", example = "1") + private Long productId; + + @Schema(description = "仓库编号", example = "1") + private Long warehouseId; + + @Schema(description = "结算账号编号", example = "1") + private Long accountId; + + @Schema(description = "付款状态", example = "1") + private Integer paymentStatus; + + @Schema(description = "是否可付款", example = "true") + private Boolean paymentEnable; // 对应 paymentStatus = [0, 1] + + @Schema(description = "采购单号", example = "1") + private String orderNo; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInRespVO.java new file mode 100644 index 000000000..beeeab869 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInRespVO.java @@ -0,0 +1,145 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 采购入库 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpPurchaseInRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + @ExcelProperty("编号") + private Long id; + + @Schema(description = "入库单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001") + @ExcelProperty("入库单编号") + private String no; + + @Schema(description = "入库状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("入库状态") + private Integer status; + + @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724") + private Long supplierId; + @Schema(description = "供应商名称", example = "芋道") + @ExcelProperty("供应商名称") + private String supplierName; + + @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "311.89") + @ExcelProperty("结算账户编号") + private Long accountId; + + @Schema(description = "入库时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("入库时间") + private LocalDateTime inTime; + + @Schema(description = "采购订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + private Long orderId; + @Schema(description = "采购订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001") + private String orderNo; + + @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663") + @ExcelProperty("合计数量") + private BigDecimal totalCount; + @Schema(description = "最终合计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "24906") + @ExcelProperty("最终合计价格") + private BigDecimal totalPrice; + @Schema(description = "已付款金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal paymentPrice; + + @Schema(description = "合计产品价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal totalProductPrice; + + @Schema(description = "合计税额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal totalTaxPrice; + + @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88") + private BigDecimal discountPercent; + + @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal discountPrice; + + @Schema(description = "定金金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal otherPrice; + + @Schema(description = "附件地址", example = "https://www.iocoder.cn") + @ExcelProperty("附件地址") + private String fileUrl; + + @Schema(description = "备注", example = "你猜") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "创建人", example = "芋道") + private String creator; + @Schema(description = "创建人名称", example = "芋道") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "入库项列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品信息") + private String productNames; + + @Data + public static class Item { + + @Schema(description = "入库项编号", example = "11756") + private Long id; + + @Schema(description = "采购订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + private Long orderItemId; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productId; + + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productUnitId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "税率,百分比", example = "99.88") + private BigDecimal taxPercent; + + @Schema(description = "税额,单位:元", example = "100.00") + private BigDecimal taxPrice; + + @Schema(description = "备注", example = "随便") + private String remark; + + // ========== 关联字段 ========== + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力") + private String productName; + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985") + private String productBarCode; + @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒") + private String productUnitName; + + @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal stockCount; // 该字段仅仅在“详情”和“编辑”时使用 + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInSaveReqVO.java new file mode 100644 index 000000000..80edeec6d --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/in/ErpPurchaseInSaveReqVO.java @@ -0,0 +1,81 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 采购入库新增/修改 Request VO") +@Data +public class ErpPurchaseInSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + private Long id; + + @Schema(description = "结算账户编号", example = "31189") + private Long accountId; + + @Schema(description = "入库时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "入库时间不能为空") + private LocalDateTime inTime; + + @Schema(description = "采购订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + @NotNull(message = "采购订单编号不能为空") + private Long orderId; + + @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88") + private BigDecimal discountPercent; + + @Schema(description = "其它金额,单位:元", example = "7127") + private BigDecimal otherPrice; + + @Schema(description = "附件地址", example = "https://www.iocoder.cn") + private String fileUrl; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "入库清单列表") + private List items; + + @Data + public static class Item { + + @Schema(description = "入库项编号", example = "11756") + private Long id; + + @Schema(description = "采购订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + @NotNull(message = "采购订单项编号不能为空") + private Long orderItemId; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "仓库编号不能为空") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品编号不能为空") + private Long productId; + + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品单位单位不能为空") + private Long productUnitId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "税率,百分比", example = "99.88") + private BigDecimal taxPercent; + + @Schema(description = "备注", example = "随便") + private String remark; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderPageReqVO.java new file mode 100644 index 000000000..8bf70d427 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderPageReqVO.java @@ -0,0 +1,80 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order; + +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; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 采购订单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpPurchaseOrderPageReqVO extends PageParam { + + /** + * 入库状态 - 无 + */ + public static final Integer IN_STATUS_NONE = 0; + /** + * 入库状态 - 部分 + */ + public static final Integer IN_STATUS_PART = 1; + /** + * 入库状态 - 全部 + */ + public static final Integer IN_STATUS_ALL = 2; + + /** + * 退货状态 - 无 + */ + public static final Integer RETURN_STATUS_NONE = 0; + /** + * 退货状态 - 部分 + */ + public static final Integer RETURN_STATUS_PART = 1; + /** + * 退货状态 - 全部 + */ + public static final Integer RETURN_STATUS_ALL = 2; + + @Schema(description = "采购单编号", example = "XS001") + private String no; + + @Schema(description = "供应商编号", example = "1724") + private Long supplierId; + + @Schema(description = "采购时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] orderTime; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "采购状态", example = "2") + private Integer status; + + @Schema(description = "创建者") + private String creator; + + @Schema(description = "产品编号", example = "1") + private Long productId; + + @Schema(description = "入库状态", example = "2") + private Integer inStatus; + + @Schema(description = "退货状态", example = "2") + private Integer returnStatus; + + @Schema(description = "是否可入库", example = "true") + private Boolean inEnable; + + @Schema(description = "是否可退货", example = "true") + private Boolean returnEnable; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java new file mode 100644 index 000000000..bc76720ee --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java @@ -0,0 +1,152 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 采购订单 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpPurchaseOrderRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + @ExcelProperty("编号") + private Long id; + + @Schema(description = "采购单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001") + @ExcelProperty("采购单编号") + private String no; + + @Schema(description = "采购状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("采购状态") + private Integer status; + + @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724") + private Long supplierId; + @Schema(description = "供应商名称", example = "芋道") + @ExcelProperty("供应商名称") + private String supplierName; + + @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "311.89") + @ExcelProperty("结算账户编号") + private Long accountId; + + @Schema(description = "采购时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("采购时间") + private LocalDateTime orderTime; + + @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663") + @ExcelProperty("合计数量") + private BigDecimal totalCount; + @Schema(description = "最终合计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "24906") + @ExcelProperty("最终合计价格") + private BigDecimal totalPrice; + + @Schema(description = "合计产品价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal totalProductPrice; + + @Schema(description = "合计税额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal totalTaxPrice; + + @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88") + private BigDecimal discountPercent; + + @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal discountPrice; + + @Schema(description = "定金金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal depositPrice; + + @Schema(description = "附件地址", example = "https://www.iocoder.cn") + @ExcelProperty("附件地址") + private String fileUrl; + + @Schema(description = "备注", example = "你猜") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "创建人", example = "芋道") + private String creator; + @Schema(description = "创建人名称", example = "芋道") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "订单项列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品信息") + private String productNames; + + // ========== 采购入库 ========== + + @Schema(description = "采购入库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal inCount; + + // ========== 采购退货(出库)) ========== + + @Schema(description = "采购退货数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal returnCount; + + @Data + public static class Item { + + @Schema(description = "订单项编号", example = "11756") + private Long id; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productId; + + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productUnitId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "税率,百分比", example = "99.88") + private BigDecimal taxPercent; + + @Schema(description = "税额,单位:元", example = "100.00") + private BigDecimal taxPrice; + + @Schema(description = "备注", example = "随便") + private String remark; + + // ========== 采购入库 ========== + + @Schema(description = "采购入库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal inCount; + + // ========== 采购退货(入库)) ========== + + @Schema(description = "采购退货数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal returnCount; + + // ========== 关联字段 ========== + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力") + private String productName; + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985") + private String productBarCode; + @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒") + private String productUnitName; + + @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal stockCount; // 该字段仅仅在“详情”和“编辑”时使用 + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java new file mode 100644 index 000000000..061ed9dcc --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 采购订单新增/修改 Request VO") +@Data +public class ErpPurchaseOrderSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + private Long id; + + @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724") + @NotNull(message = "供应商编号不能为空") + private Long supplierId; + + @Schema(description = "结算账户编号", example = "31189") + private Long accountId; + + @Schema(description = "采购时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "采购时间不能为空") + private LocalDateTime orderTime; + + @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88") + private BigDecimal discountPercent; + + @Schema(description = "定金金额,单位:元", example = "7127") + private BigDecimal depositPrice; + + @Schema(description = "附件地址", example = "https://www.iocoder.cn") + private String fileUrl; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "订单清单列表") + private List items; + + @Data + public static class Item { + + @Schema(description = "订单项编号", example = "11756") + private Long id; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品编号不能为空") + private Long productId; + + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品单位单位不能为空") + private Long productUnitId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "税率,百分比", example = "99.88") + private BigDecimal taxPercent; + + @Schema(description = "备注", example = "随便") + private String remark; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnPageReqVO.java new file mode 100644 index 000000000..a534d2e3e --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnPageReqVO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns; + +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; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 采购退货分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpPurchaseReturnPageReqVO extends PageParam { + + public static final Integer REFUND_STATUS_NONE = 0; + public static final Integer REFUND_STATUS_PART = 1; + public static final Integer REFUND_STATUS_ALL = 2; + + @Schema(description = "采购单编号", example = "XS001") + private String no; + + @Schema(description = "供应商编号", example = "1724") + private Long supplierId; + + @Schema(description = "退货时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] returnTime; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "退货状态", example = "2") + private Integer status; + + @Schema(description = "创建者") + private String creator; + + @Schema(description = "产品编号", example = "1") + private Long productId; + + @Schema(description = "仓库编号", example = "1") + private Long warehouseId; + + @Schema(description = "结算账号编号", example = "1") + private Long accountId; + + @Schema(description = "采购单号", example = "1") + private String orderNo; + + @Schema(description = "退款状态", example = "1") + private Integer refundStatus; + + @Schema(description = "是否可退款", example = "true") + private Boolean refundEnable; // 对应 refundStatus = [0, 1] + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnRespVO.java new file mode 100644 index 000000000..223b9327e --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnRespVO.java @@ -0,0 +1,145 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 采购退货 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpPurchaseReturnRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + @ExcelProperty("编号") + private Long id; + + @Schema(description = "退货单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001") + @ExcelProperty("退货单编号") + private String no; + + @Schema(description = "退货状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("退货状态") + private Integer status; + + @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724") + private Long supplierId; + @Schema(description = "供应商名称", example = "芋道") + @ExcelProperty("供应商名称") + private String supplierName; + + @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "311.89") + @ExcelProperty("结算账户编号") + private Long accountId; + + @Schema(description = "退货时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("退货时间") + private LocalDateTime returnTime; + + @Schema(description = "采购订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + private Long orderId; + @Schema(description = "采购订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001") + private String orderNo; + + @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663") + @ExcelProperty("合计数量") + private BigDecimal totalCount; + @Schema(description = "最终合计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "24906") + @ExcelProperty("最终合计价格") + private BigDecimal totalPrice; + @Schema(description = "已退款金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal refundPrice; + + @Schema(description = "合计产品价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal totalProductPrice; + + @Schema(description = "合计税额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal totalTaxPrice; + + @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88") + private BigDecimal discountPercent; + + @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal discountPrice; + + @Schema(description = "定金金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal otherPrice; + + @Schema(description = "附件地址", example = "https://www.iocoder.cn") + @ExcelProperty("附件地址") + private String fileUrl; + + @Schema(description = "备注", example = "你猜") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "创建人", example = "芋道") + private String creator; + @Schema(description = "创建人名称", example = "芋道") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "退货项列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品信息") + private String productNames; + + @Data + public static class Item { + + @Schema(description = "退货项编号", example = "11756") + private Long id; + + @Schema(description = "采购订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + private Long orderItemId; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productId; + + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productUnitId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "税率,百分比", example = "99.88") + private BigDecimal taxPercent; + + @Schema(description = "税额,单位:元", example = "100.00") + private BigDecimal taxPrice; + + @Schema(description = "备注", example = "随便") + private String remark; + + // ========== 关联字段 ========== + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力") + private String productName; + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985") + private String productBarCode; + @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒") + private String productUnitName; + + @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal stockCount; // 该字段仅仅在“详情”和“编辑”时使用 + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnSaveReqVO.java new file mode 100644 index 000000000..9254bd583 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/returns/ErpPurchaseReturnSaveReqVO.java @@ -0,0 +1,81 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 采购退货新增/修改 Request VO") +@Data +public class ErpPurchaseReturnSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + private Long id; + + @Schema(description = "结算账户编号", example = "31189") + private Long accountId; + + @Schema(description = "退货时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "退货时间不能为空") + private LocalDateTime returnTime; + + @Schema(description = "采购订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + @NotNull(message = "采购订单编号不能为空") + private Long orderId; + + @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88") + private BigDecimal discountPercent; + + @Schema(description = "其它金额,单位:元", example = "7127") + private BigDecimal otherPrice; + + @Schema(description = "附件地址", example = "https://www.iocoder.cn") + private String fileUrl; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "退货清单列表") + private List items; + + @Data + public static class Item { + + @Schema(description = "退货项编号", example = "11756") + private Long id; + + @Schema(description = "采购订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + @NotNull(message = "采购订单项编号不能为空") + private Long orderItemId; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "仓库编号不能为空") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品编号不能为空") + private Long productId; + + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品单位单位不能为空") + private Long productUnitId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "税率,百分比", example = "99.88") + private BigDecimal taxPercent; + + @Schema(description = "备注", example = "随便") + private String remark; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/supplier/ErpSupplierPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/supplier/ErpSupplierPageReqVO.java new file mode 100644 index 000000000..229ab63d9 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/supplier/ErpSupplierPageReqVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier; + +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 = "管理后台 - ERP 供应商分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpSupplierPageReqVO extends PageParam { + + @Schema(description = "供应商名称", example = "芋道源码") + private String name; + + @Schema(description = "手机号码", example = "15601691300") + private String mobile; + + @Schema(description = "联系电话", example = "18818288888") + private String telephone; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/supplier/ErpSupplierRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/supplier/ErpSupplierRespVO.java new file mode 100644 index 000000000..5ba5892c1 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/supplier/ErpSupplierRespVO.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.module.system.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - ERP 供应商 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpSupplierRespVO { + + @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17791") + @ExcelProperty("供应商编号") + private Long id; + + @Schema(description = "供应商名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") + @ExcelProperty("供应商名称") + private String name; + + @Schema(description = "联系人", example = "芋艿") + @ExcelProperty("联系人") + private String contact; + + @Schema(description = "手机号码", example = "15601691300") + @ExcelProperty("手机号码") + private String mobile; + + @Schema(description = "联系电话", example = "18818288888") + @ExcelProperty("联系电话") + private String telephone; + + @Schema(description = "电子邮箱", example = "76853@qq.com") + @ExcelProperty("电子邮箱") + private String email; + + @Schema(description = "传真", example = "20 7123 4567") + @ExcelProperty("传真") + private String fax; + + @Schema(description = "备注", example = "你猜") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty(value = "开启状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @ExcelProperty("排序") + private Integer sort; + + @Schema(description = "纳税人识别号", example = "91130803MA098BY05W") + @ExcelProperty("纳税人识别号") + private String taxNo; + + @Schema(description = "税率", example = "10") + @ExcelProperty("税率") + private BigDecimal taxPercent; + + @Schema(description = "开户行", example = "张三") + @ExcelProperty("开户行") + private String bankName; + + @Schema(description = "开户账号", example = "622908212277228617") + @ExcelProperty("开户账号") + private String bankAccount; + + @Schema(description = "开户地址", example = "兴业银行浦东支行") + @ExcelProperty("开户地址") + private String bankAddress; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/supplier/ErpSupplierSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/supplier/ErpSupplierSaveReqVO.java new file mode 100644 index 000000000..2291de050 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/supplier/ErpSupplierSaveReqVO.java @@ -0,0 +1,71 @@ +package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.framework.common.validation.Mobile; +import cn.iocoder.yudao.framework.common.validation.Telephone; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - ERP 供应商新增/修改 Request VO") +@Data +public class ErpSupplierSaveReqVO { + + @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17791") + private Long id; + + @Schema(description = "供应商名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") + @NotEmpty(message = "供应商名称不能为空") + private String name; + + @Schema(description = "联系人", example = "芋艿") + private String contact; + + @Schema(description = "手机号码", example = "15601691300") + @Mobile + private String mobile; + + @Schema(description = "联系电话", example = "18818288888") + @Telephone + private String telephone; + + @Schema(description = "电子邮箱", example = "76853@qq.com") + @Email + private String email; + + @Schema(description = "传真", example = "20 7123 4567") + private String fax; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "开启状态不能为空") + @InEnum(value = CommonStatusEnum.class) + private Integer status; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @NotNull(message = "排序不能为空") + private Integer sort; + + @Schema(description = "纳税人识别号", example = "91130803MA098BY05W") + private String taxNo; + + @Schema(description = "税率", example = "10") + private BigDecimal taxPercent; + + @Schema(description = "开户行", example = "张三") + private String bankName; + + @Schema(description = "开户账号", example = "622908212277228617") + private String bankAccount; + + @Schema(description = "开户地址", example = "兴业银行浦东支行") + private String bankAddress; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpCustomerController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpCustomerController.java new file mode 100644 index 000000000..2c2886460 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpCustomerController.java @@ -0,0 +1,102 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.customer.ErpCustomerPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.customer.ErpCustomerRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.customer.ErpCustomerSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpCustomerDO; +import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService; +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.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 客户") +@RestController +@RequestMapping("/erp/customer") +@Validated +public class ErpCustomerController { + + @Resource + private ErpCustomerService customerService; + + @PostMapping("/create") + @Operation(summary = "创建客户") + @PreAuthorize("@ss.hasPermission('erp:customer:create')") + public CommonResult createCustomer(@Valid @RequestBody ErpCustomerSaveReqVO createReqVO) { + return success(customerService.createCustomer(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新客户") + @PreAuthorize("@ss.hasPermission('erp:customer:update')") + public CommonResult updateCustomer(@Valid @RequestBody ErpCustomerSaveReqVO updateReqVO) { + customerService.updateCustomer(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除客户") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('erp:customer:delete')") + public CommonResult deleteCustomer(@RequestParam("id") Long id) { + customerService.deleteCustomer(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得客户") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:customer:query')") + public CommonResult getCustomer(@RequestParam("id") Long id) { + ErpCustomerDO customer = customerService.getCustomer(id); + return success(BeanUtils.toBean(customer, ErpCustomerRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得客户分页") + @PreAuthorize("@ss.hasPermission('erp:customer:query')") + public CommonResult> getCustomerPage(@Valid ErpCustomerPageReqVO pageReqVO) { + PageResult pageResult = customerService.getCustomerPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ErpCustomerRespVO.class)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得客户精简列表", description = "只包含被开启的客户,主要用于前端的下拉选项") + public CommonResult> getCustomerSimpleList() { + List list = customerService.getCustomerListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(convertList(list, customer -> new ErpCustomerRespVO().setId(customer.getId()).setName(customer.getName()))); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出客户 Excel") + @PreAuthorize("@ss.hasPermission('erp:customer:export')") + @OperateLog(type = EXPORT) + public void exportCustomerExcel(@Valid ErpCustomerPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = customerService.getCustomerPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "客户.xls", "数据", ErpCustomerRespVO.class, + BeanUtils.toBean(list, ErpCustomerRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java new file mode 100644 index 000000000..0ca56a45e --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java @@ -0,0 +1,165 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpCustomerDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService; +import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOrderService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + + +@Tag(name = "管理后台 - ERP 销售订单") +@RestController +@RequestMapping("/erp/sale-order") +@Validated +public class ErpSaleOrderController { + + @Resource + private ErpSaleOrderService saleOrderService; + @Resource + private ErpStockService stockService; + @Resource + private ErpProductService productService; + @Resource + private ErpCustomerService customerService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建销售订单") + @PreAuthorize("@ss.hasPermission('erp:sale-out:create')") + public CommonResult createSaleOrder(@Valid @RequestBody ErpSaleOrderSaveReqVO createReqVO) { + return success(saleOrderService.createSaleOrder(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新销售订单") + @PreAuthorize("@ss.hasPermission('erp:sale-out:update')") + public CommonResult updateSaleOrder(@Valid @RequestBody ErpSaleOrderSaveReqVO updateReqVO) { + saleOrderService.updateSaleOrder(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新销售订单的状态") + @PreAuthorize("@ss.hasPermission('erp:sale-out:update-status')") + public CommonResult updateSaleOrderStatus(@RequestParam("id") Long id, + @RequestParam("status") Integer status) { + saleOrderService.updateSaleOrderStatus(id, status); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除销售订单") + @Parameter(name = "ids", description = "编号数组", required = true) + @PreAuthorize("@ss.hasPermission('erp:sale-out:delete')") + public CommonResult deleteSaleOrder(@RequestParam("ids") List ids) { + saleOrderService.deleteSaleOrder(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得销售订单") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:sale-out:query')") + public CommonResult getSaleOrder(@RequestParam("id") Long id) { + ErpSaleOrderDO saleOrder = saleOrderService.getSaleOrder(id); + if (saleOrder == null) { + return success(null); + } + List saleOrderItemList = saleOrderService.getSaleOrderItemListByOrderId(id); + Map productMap = productService.getProductVOMap( + convertSet(saleOrderItemList, ErpSaleOrderItemDO::getProductId)); + return success(BeanUtils.toBean(saleOrder, ErpSaleOrderRespVO.class, saleOrderVO -> + saleOrderVO.setItems(BeanUtils.toBean(saleOrderItemList, ErpSaleOrderRespVO.Item.class, item -> { + BigDecimal stockCount = stockService.getStockCount(item.getProductId()); + item.setStockCount(stockCount != null ? stockCount : BigDecimal.ZERO); + MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())); + })))); + } + + @GetMapping("/page") + @Operation(summary = "获得销售订单分页") + @PreAuthorize("@ss.hasPermission('erp:sale-out:query')") + public CommonResult> getSaleOrderPage(@Valid ErpSaleOrderPageReqVO pageReqVO) { + PageResult pageResult = saleOrderService.getSaleOrderPage(pageReqVO); + return success(buildSaleOrderVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出销售订单 Excel") + @PreAuthorize("@ss.hasPermission('erp:sale-out:export')") + @OperateLog(type = EXPORT) + public void exportSaleOrderExcel(@Valid ErpSaleOrderPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildSaleOrderVOPageResult(saleOrderService.getSaleOrderPage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "销售订单.xls", "数据", ErpSaleOrderRespVO.class, list); + } + + private PageResult buildSaleOrderVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + // 1.1 订单项 + List saleOrderItemList = saleOrderService.getSaleOrderItemListByOrderIds( + convertSet(pageResult.getList(), ErpSaleOrderDO::getId)); + Map> saleOrderItemMap = convertMultiMap(saleOrderItemList, ErpSaleOrderItemDO::getOrderId); + // 1.2 产品信息 + Map productMap = productService.getProductVOMap( + convertSet(saleOrderItemList, ErpSaleOrderItemDO::getProductId)); + // 1.3 客户信息 + Map customerMap = customerService.getCustomerMap( + convertSet(pageResult.getList(), ErpSaleOrderDO::getCustomerId)); + // 1.4 管理员信息 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), saleOrder -> Long.parseLong(saleOrder.getCreator()))); + // 2. 开始拼接 + return BeanUtils.toBean(pageResult, ErpSaleOrderRespVO.class, saleOrder -> { + saleOrder.setItems(BeanUtils.toBean(saleOrderItemMap.get(saleOrder.getId()), ErpSaleOrderRespVO.Item.class, + item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())))); + saleOrder.setProductNames(CollUtil.join(saleOrder.getItems(), ",", ErpSaleOrderRespVO.Item::getProductName)); + MapUtils.findAndThen(customerMap, saleOrder.getCustomerId(), supplier -> saleOrder.setCustomerName(supplier.getName())); + MapUtils.findAndThen(userMap, Long.parseLong(saleOrder.getCreator()), user -> saleOrder.setCreatorName(user.getNickname())); + }); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOutController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOutController.java new file mode 100644 index 000000000..5875ea39f --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOutController.java @@ -0,0 +1,165 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpCustomerDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutItemDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService; +import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOutService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 销售出库") +@RestController +@RequestMapping("/erp/sale-out") +@Validated +public class ErpSaleOutController { + + @Resource + private ErpSaleOutService saleOutService; + @Resource + private ErpStockService stockService; + @Resource + private ErpProductService productService; + @Resource + private ErpCustomerService customerService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建销售出库") + @PreAuthorize("@ss.hasPermission('erp:sale-out:create')") + public CommonResult createSaleOut(@Valid @RequestBody ErpSaleOutSaveReqVO createReqVO) { + return success(saleOutService.createSaleOut(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新销售出库") + @PreAuthorize("@ss.hasPermission('erp:sale-out:update')") + public CommonResult updateSaleOut(@Valid @RequestBody ErpSaleOutSaveReqVO updateReqVO) { + saleOutService.updateSaleOut(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新销售出库的状态") + @PreAuthorize("@ss.hasPermission('erp:sale-out:update-status')") + public CommonResult updateSaleOutStatus(@RequestParam("id") Long id, + @RequestParam("status") Integer status) { + saleOutService.updateSaleOutStatus(id, status); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除销售出库") + @Parameter(name = "ids", description = "编号数组", required = true) + @PreAuthorize("@ss.hasPermission('erp:sale-out:delete')") + public CommonResult deleteSaleOut(@RequestParam("ids") List ids) { + saleOutService.deleteSaleOut(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得销售出库") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:sale-out:query')") + public CommonResult getSaleOut(@RequestParam("id") Long id) { + ErpSaleOutDO saleOut = saleOutService.getSaleOut(id); + if (saleOut == null) { + return success(null); + } + List saleOutItemList = saleOutService.getSaleOutItemListByOutId(id); + Map productMap = productService.getProductVOMap( + convertSet(saleOutItemList, ErpSaleOutItemDO::getProductId)); + return success(BeanUtils.toBean(saleOut, ErpSaleOutRespVO.class, saleOutVO -> + saleOutVO.setItems(BeanUtils.toBean(saleOutItemList, ErpSaleOutRespVO.Item.class, item -> { + ErpStockDO stock = stockService.getStock(item.getProductId(), item.getWarehouseId()); + item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO); + MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())); + })))); + } + + @GetMapping("/page") + @Operation(summary = "获得销售出库分页") + @PreAuthorize("@ss.hasPermission('erp:sale-out:query')") + public CommonResult> getSaleOutPage(@Valid ErpSaleOutPageReqVO pageReqVO) { + PageResult pageResult = saleOutService.getSaleOutPage(pageReqVO); + return success(buildSaleOutVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出销售出库 Excel") + @PreAuthorize("@ss.hasPermission('erp:sale-out:export')") + @OperateLog(type = EXPORT) + public void exportSaleOutExcel(@Valid ErpSaleOutPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildSaleOutVOPageResult(saleOutService.getSaleOutPage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "销售出库.xls", "数据", ErpSaleOutRespVO.class, list); + } + + private PageResult buildSaleOutVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + // 1.1 出库项 + List saleOutItemList = saleOutService.getSaleOutItemListByOutIds( + convertSet(pageResult.getList(), ErpSaleOutDO::getId)); + Map> saleOutItemMap = convertMultiMap(saleOutItemList, ErpSaleOutItemDO::getOutId); + // 1.2 产品信息 + Map productMap = productService.getProductVOMap( + convertSet(saleOutItemList, ErpSaleOutItemDO::getProductId)); + // 1.3 客户信息 + Map customerMap = customerService.getCustomerMap( + convertSet(pageResult.getList(), ErpSaleOutDO::getCustomerId)); + // 1.4 管理员信息 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), stockOut -> Long.parseLong(stockOut.getCreator()))); + // 2. 开始拼接 + return BeanUtils.toBean(pageResult, ErpSaleOutRespVO.class, saleOut -> { + saleOut.setItems(BeanUtils.toBean(saleOutItemMap.get(saleOut.getId()), ErpSaleOutRespVO.Item.class, + item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())))); + saleOut.setProductNames(CollUtil.join(saleOut.getItems(), ",", ErpSaleOutRespVO.Item::getProductName)); + MapUtils.findAndThen(customerMap, saleOut.getCustomerId(), supplier -> saleOut.setCustomerName(supplier.getName())); + MapUtils.findAndThen(userMap, Long.parseLong(saleOut.getCreator()), user -> saleOut.setCreatorName(user.getNickname())); + }); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleReturnController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleReturnController.java new file mode 100644 index 000000000..0dfba67e9 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleReturnController.java @@ -0,0 +1,165 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.returns.ErpSaleReturnPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.returns.ErpSaleReturnRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.returns.ErpSaleReturnSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpCustomerDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleReturnDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleReturnItemDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService; +import cn.iocoder.yudao.module.erp.service.sale.ErpSaleReturnService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 销售退货") +@RestController +@RequestMapping("/erp/sale-return") +@Validated +public class ErpSaleReturnController { + + @Resource + private ErpSaleReturnService saleReturnService; + @Resource + private ErpStockService stockService; + @Resource + private ErpProductService productService; + @Resource + private ErpCustomerService customerService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建销售退货") + @PreAuthorize("@ss.hasPermission('erp:sale-return:create')") + public CommonResult createSaleReturn(@Valid @RequestBody ErpSaleReturnSaveReqVO createReqVO) { + return success(saleReturnService.createSaleReturn(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新销售退货") + @PreAuthorize("@ss.hasPermission('erp:sale-return:update')") + public CommonResult updateSaleReturn(@Valid @RequestBody ErpSaleReturnSaveReqVO updateReqVO) { + saleReturnService.updateSaleReturn(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新销售退货的状态") + @PreAuthorize("@ss.hasPermission('erp:sale-return:update-status')") + public CommonResult updateSaleReturnStatus(@RequestParam("id") Long id, + @RequestParam("status") Integer status) { + saleReturnService.updateSaleReturnStatus(id, status); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除销售退货") + @Parameter(name = "ids", description = "编号数组", required = true) + @PreAuthorize("@ss.hasPermission('erp:sale-return:delete')") + public CommonResult deleteSaleReturn(@RequestParam("ids") List ids) { + saleReturnService.deleteSaleReturn(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得销售退货") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:sale-return:query')") + public CommonResult getSaleReturn(@RequestParam("id") Long id) { + ErpSaleReturnDO saleReturn = saleReturnService.getSaleReturn(id); + if (saleReturn == null) { + return success(null); + } + List saleReturnItemList = saleReturnService.getSaleReturnItemListByReturnId(id); + Map productMap = productService.getProductVOMap( + convertSet(saleReturnItemList, ErpSaleReturnItemDO::getProductId)); + return success(BeanUtils.toBean(saleReturn, ErpSaleReturnRespVO.class, saleReturnVO -> + saleReturnVO.setItems(BeanUtils.toBean(saleReturnItemList, ErpSaleReturnRespVO.Item.class, item -> { + ErpStockDO stock = stockService.getStock(item.getProductId(), item.getWarehouseId()); + item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO); + MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())); + })))); + } + + @GetMapping("/page") + @Operation(summary = "获得销售退货分页") + @PreAuthorize("@ss.hasPermission('erp:sale-return:query')") + public CommonResult> getSaleReturnPage(@Valid ErpSaleReturnPageReqVO pageReqVO) { + PageResult pageResult = saleReturnService.getSaleReturnPage(pageReqVO); + return success(buildSaleReturnVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出销售退货 Excel") + @PreAuthorize("@ss.hasPermission('erp:sale-return:export')") + @OperateLog(type = EXPORT) + public void exportSaleReturnExcel(@Valid ErpSaleReturnPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildSaleReturnVOPageResult(saleReturnService.getSaleReturnPage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "销售退货.xls", "数据", ErpSaleReturnRespVO.class, list); + } + + private PageResult buildSaleReturnVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + // 1.1 退货项 + List saleReturnItemList = saleReturnService.getSaleReturnItemListByReturnIds( + convertSet(pageResult.getList(), ErpSaleReturnDO::getId)); + Map> saleReturnItemMap = convertMultiMap(saleReturnItemList, ErpSaleReturnItemDO::getReturnId); + // 1.2 产品信息 + Map productMap = productService.getProductVOMap( + convertSet(saleReturnItemList, ErpSaleReturnItemDO::getProductId)); + // 1.3 客户信息 + Map customerMap = customerService.getCustomerMap( + convertSet(pageResult.getList(), ErpSaleReturnDO::getCustomerId)); + // 1.4 管理员信息 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), saleReturn -> Long.parseLong(saleReturn.getCreator()))); + // 2. 开始拼接 + return BeanUtils.toBean(pageResult, ErpSaleReturnRespVO.class, saleReturn -> { + saleReturn.setItems(BeanUtils.toBean(saleReturnItemMap.get(saleReturn.getId()), ErpSaleReturnRespVO.Item.class, + item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())))); + saleReturn.setProductNames(CollUtil.join(saleReturn.getItems(), ",", ErpSaleReturnRespVO.Item::getProductName)); + MapUtils.findAndThen(customerMap, saleReturn.getCustomerId(), supplier -> saleReturn.setCustomerName(supplier.getName())); + MapUtils.findAndThen(userMap, Long.parseLong(saleReturn.getCreator()), user -> saleReturn.setCreatorName(user.getNickname())); + }); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/customer/ErpCustomerPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/customer/ErpCustomerPageReqVO.java new file mode 100644 index 000000000..e790cb958 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/customer/ErpCustomerPageReqVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.customer; + +import lombok.*; +import java.util.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import java.math.BigDecimal; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 客户分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpCustomerPageReqVO extends PageParam { + + @Schema(description = "客户名称", example = "张三") + private String name; + + @Schema(description = "手机号码", example = "15601691300") + private String mobile; + + @Schema(description = "联系电话", example = "15601691300") + private String telephone; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/customer/ErpCustomerRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/customer/ErpCustomerRespVO.java new file mode 100644 index 000000000..f1a58a03d --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/customer/ErpCustomerRespVO.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.customer; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import java.util.*; +import java.math.BigDecimal; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; +import com.alibaba.excel.annotation.*; +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; + +@Schema(description = "管理后台 - ERP 客户 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpCustomerRespVO { + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "27520") + @ExcelProperty("客户编号") + private Long id; + + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + @ExcelProperty("客户名称") + private String name; + + @Schema(description = "联系人", example = "老王") + @ExcelProperty("联系人") + private String contact; + + @Schema(description = "手机号码", example = "15601691300") + @ExcelProperty("手机号码") + private String mobile; + + @Schema(description = "联系电话", example = "15601691300") + @ExcelProperty("联系电话") + private String telephone; + + @Schema(description = "电子邮箱", example = "7685323@qq.com") + @ExcelProperty("电子邮箱") + private String email; + + @Schema(description = "传真", example = "20 7123 4567") + @ExcelProperty("传真") + private String fax; + + @Schema(description = "备注", example = "你猜") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty(value = "开启状态", converter = DictConvert.class) + @DictFormat("common_status") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中 + private Integer status; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @ExcelProperty("排序") + private Integer sort; + + @Schema(description = "纳税人识别号", example = "91130803MA098BY05W") + @ExcelProperty("纳税人识别号") + private String taxNo; + + @Schema(description = "税率", example = "10") + @ExcelProperty("税率") + private BigDecimal taxPercent; + + @Schema(description = "开户行", example = "芋艿") + @ExcelProperty("开户行") + private String bankName; + + @Schema(description = "开户账号", example = "622908212277228617") + @ExcelProperty("开户账号") + private String bankAccount; + + @Schema(description = "开户地址", example = "兴业银行浦东支行") + @ExcelProperty("开户地址") + private String bankAddress; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/customer/ErpCustomerSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/customer/ErpCustomerSaveReqVO.java new file mode 100644 index 000000000..aef0b2df1 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/customer/ErpCustomerSaveReqVO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.customer; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import jakarta.validation.constraints.*; +import java.math.BigDecimal; + +@Schema(description = "管理后台 - ERP 客户新增/修改 Request VO") +@Data +public class ErpCustomerSaveReqVO { + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "27520") + private Long id; + + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + @NotEmpty(message = "客户名称不能为空") + private String name; + + @Schema(description = "联系人", example = "老王") + private String contact; + + @Schema(description = "手机号码", example = "15601691300") + private String mobile; + + @Schema(description = "联系电话", example = "15601691300") + private String telephone; + + @Schema(description = "电子邮箱", example = "7685323@qq.com") + private String email; + + @Schema(description = "传真", example = "20 7123 4567") + private String fax; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "开启状态不能为空") + private Integer status; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @NotNull(message = "排序不能为空") + private Integer sort; + + @Schema(description = "纳税人识别号", example = "91130803MA098BY05W") + private String taxNo; + + @Schema(description = "税率", example = "10") + private BigDecimal taxPercent; + + @Schema(description = "开户行", example = "芋艿") + private String bankName; + + @Schema(description = "开户账号", example = "622908212277228617") + private String bankAccount; + + @Schema(description = "开户地址", example = "兴业银行浦东支行") + private String bankAddress; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderPageReqVO.java new file mode 100644 index 000000000..84d92fb67 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderPageReqVO.java @@ -0,0 +1,80 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order; + +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; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 销售订单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpSaleOrderPageReqVO extends PageParam { + + /** + * 出库状态 - 无 + */ + public static final Integer OUT_STATUS_NONE = 0; + /** + * 出库状态 - 部分 + */ + public static final Integer OUT_STATUS_PART = 1; + /** + * 出库状态 - 全部 + */ + public static final Integer OUT_STATUS_ALL = 2; + + /** + * 退货状态 - 无 + */ + public static final Integer RETURN_STATUS_NONE = 0; + /** + * 退货状态 - 部分 + */ + public static final Integer RETURN_STATUS_PART = 1; + /** + * 退货状态 - 全部 + */ + public static final Integer RETURN_STATUS_ALL = 2; + + @Schema(description = "销售单编号", example = "XS001") + private String no; + + @Schema(description = "客户编号", example = "1724") + private Long customerId; + + @Schema(description = "下单时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] orderTime; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "销售状态", example = "2") + private Integer status; + + @Schema(description = "创建者") + private String creator; + + @Schema(description = "产品编号", example = "1") + private Long productId; + + @Schema(description = "出库状态", example = "2") + private Integer outStatus; + + @Schema(description = "退货状态", example = "2") + private Integer returnStatus; + + @Schema(description = "是否可出库", example = "true") + private Boolean outEnable; + + @Schema(description = "是否可退货", example = "true") + private Boolean returnEnable; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java new file mode 100644 index 000000000..e5958a841 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java @@ -0,0 +1,155 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 销售订单 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpSaleOrderRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + @ExcelProperty("编号") + private Long id; + + @Schema(description = "销售单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001") + @ExcelProperty("销售单编号") + private String no; + + @Schema(description = "销售状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("销售状态") + private Integer status; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724") + private Long customerId; + @Schema(description = "客户名称", example = "芋道") + @ExcelProperty("客户名称") + private String customerName; + + @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "311.89") + @ExcelProperty("结算账户编号") + private Long accountId; + + @Schema(description = "销售员编号", example = "1888") + private Long saleUserId; + + @Schema(description = "下单时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("下单时间") + private LocalDateTime orderTime; + + @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663") + @ExcelProperty("合计数量") + private BigDecimal totalCount; + @Schema(description = "最终合计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "24906") + @ExcelProperty("最终合计价格") + private BigDecimal totalPrice; + + @Schema(description = "合计产品价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal totalProductPrice; + + @Schema(description = "合计税额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal totalTaxPrice; + + @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88") + private BigDecimal discountPercent; + + @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal discountPrice; + + @Schema(description = "定金金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal depositPrice; + + @Schema(description = "附件地址", example = "https://www.iocoder.cn") + @ExcelProperty("附件地址") + private String fileUrl; + + @Schema(description = "备注", example = "你猜") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "创建人", example = "芋道") + private String creator; + @Schema(description = "创建人名称", example = "芋道") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "订单项列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品信息") + private String productNames; + + // ========== 销售出库 ========== + + @Schema(description = "销售出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal outCount; + + // ========== 销售退货(出库)) ========== + + @Schema(description = "销售退货数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal returnCount; + + @Data + public static class Item { + + @Schema(description = "订单项编号", example = "11756") + private Long id; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productId; + + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productUnitId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "税率,百分比", example = "99.88") + private BigDecimal taxPercent; + + @Schema(description = "税额,单位:元", example = "100.00") + private BigDecimal taxPrice; + + @Schema(description = "备注", example = "随便") + private String remark; + + // ========== 销售出库 ========== + + @Schema(description = "销售出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal outCount; + + // ========== 销售退货(入库)) ========== + + @Schema(description = "销售退货数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal returnCount; + + // ========== 关联字段 ========== + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力") + private String productName; + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985") + private String productBarCode; + @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒") + private String productUnitName; + + @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal stockCount; // 该字段仅仅在“详情”和“编辑”时使用 + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java new file mode 100644 index 000000000..e23a1fab3 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 销售订单新增/修改 Request VO") +@Data +public class ErpSaleOrderSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + private Long id; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724") + @NotNull(message = "客户编号不能为空") + private Long customerId; + + @Schema(description = "下单时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "下单时间不能为空") + private LocalDateTime orderTime; + + @Schema(description = "销售员编号", example = "1888") + private Long saleUserId; + + @Schema(description = "结算账户编号", example = "31189") + private Long accountId; + + @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88") + private BigDecimal discountPercent; + + @Schema(description = "定金金额,单位:元", example = "7127") + private BigDecimal depositPrice; + + @Schema(description = "附件地址", example = "https://www.iocoder.cn") + private String fileUrl; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "订单清单列表") + private List items; + + @Data + public static class Item { + + @Schema(description = "订单项编号", example = "11756") + private Long id; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品编号不能为空") + private Long productId; + + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品单位单位不能为空") + private Long productUnitId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "税率,百分比", example = "99.88") + private BigDecimal taxPercent; + + @Schema(description = "备注", example = "随便") + private String remark; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutPageReqVO.java new file mode 100644 index 000000000..5afeeea84 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutPageReqVO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out; + +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; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 销售出库分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpSaleOutPageReqVO extends PageParam { + + public static final Integer RECEIPT_STATUS_NONE = 0; + public static final Integer RECEIPT_STATUS_PART = 1; + public static final Integer RECEIPT_STATUS_ALL = 2; + + @Schema(description = "销售单编号", example = "XS001") + private String no; + + @Schema(description = "客户编号", example = "1724") + private Long customerId; + + @Schema(description = "出库时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] outTime; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "出库状态", example = "2") + private Integer status; + + @Schema(description = "创建者") + private String creator; + + @Schema(description = "产品编号", example = "1") + private Long productId; + + @Schema(description = "仓库编号", example = "1") + private Long warehouseId; + + @Schema(description = "结算账号编号", example = "1") + private Long accountId; + + @Schema(description = "收款状态", example = "1") + private Integer receiptStatus; + + @Schema(description = "是否可收款", example = "true") + private Boolean receiptEnable; // 对应 receiptStatus = [0, 1] + + @Schema(description = "销售单号", example = "1") + private String orderNo; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutRespVO.java new file mode 100644 index 000000000..bc15a1339 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutRespVO.java @@ -0,0 +1,148 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 销售出库 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpSaleOutRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + @ExcelProperty("编号") + private Long id; + + @Schema(description = "出库单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001") + @ExcelProperty("出库单编号") + private String no; + + @Schema(description = "出库状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("出库状态") + private Integer status; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724") + private Long customerId; + @Schema(description = "客户名称", example = "芋道") + @ExcelProperty("客户名称") + private String customerName; + + @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "311.89") + @ExcelProperty("结算账户编号") + private Long accountId; + + @Schema(description = "出库员编号", example = "1888") + private Long saleUserId; + + @Schema(description = "出库时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("出库时间") + private LocalDateTime outTime; + + @Schema(description = "销售订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + private Long orderId; + @Schema(description = "销售订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001") + private String orderNo; + + @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663") + @ExcelProperty("合计数量") + private BigDecimal totalCount; + @Schema(description = "最终合计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "24906") + @ExcelProperty("最终合计价格") + private BigDecimal totalPrice; + @Schema(description = "已收款金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal receiptPrice; + + @Schema(description = "合计产品价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal totalProductPrice; + + @Schema(description = "合计税额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal totalTaxPrice; + + @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88") + private BigDecimal discountPercent; + + @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal discountPrice; + + @Schema(description = "其它金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal otherPrice; + + @Schema(description = "附件地址", example = "https://www.iocoder.cn") + @ExcelProperty("附件地址") + private String fileUrl; + + @Schema(description = "备注", example = "你猜") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "创建人", example = "芋道") + private String creator; + @Schema(description = "创建人名称", example = "芋道") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "出库项列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品信息") + private String productNames; + + @Data + public static class Item { + + @Schema(description = "出库项编号", example = "11756") + private Long id; + + @Schema(description = "销售订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + private Long orderItemId; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productId; + + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productUnitId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "税率,百分比", example = "99.88") + private BigDecimal taxPercent; + + @Schema(description = "税额,单位:元", example = "100.00") + private BigDecimal taxPrice; + + @Schema(description = "备注", example = "随便") + private String remark; + + // ========== 关联字段 ========== + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力") + private String productName; + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985") + private String productBarCode; + @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒") + private String productUnitName; + + @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal stockCount; // 该字段仅仅在“详情”和“编辑”时使用 + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutSaveReqVO.java new file mode 100644 index 000000000..3a2216a33 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/out/ErpSaleOutSaveReqVO.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 销售出库新增/修改 Request VO") +@Data +public class ErpSaleOutSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + private Long id; + + @Schema(description = "结算账户编号", example = "31189") + private Long accountId; + + @Schema(description = "销售员编号", example = "1888") + private Long saleUserId; + + @Schema(description = "出库时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "出库时间不能为空") + private LocalDateTime outTime; + + @Schema(description = "销售订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + @NotNull(message = "销售订单编号不能为空") + private Long orderId; + + @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88") + private BigDecimal discountPercent; + + @Schema(description = "其它金额,单位:元", example = "7127") + private BigDecimal otherPrice; + + @Schema(description = "附件地址", example = "https://www.iocoder.cn") + private String fileUrl; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "出库清单列表") + private List items; + + @Data + public static class Item { + + @Schema(description = "出库项编号", example = "11756") + private Long id; + + @Schema(description = "销售订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + @NotNull(message = "销售订单项编号不能为空") + private Long orderItemId; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "仓库编号不能为空") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品编号不能为空") + private Long productId; + + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品单位单位不能为空") + private Long productUnitId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "税率,百分比", example = "99.88") + private BigDecimal taxPercent; + + @Schema(description = "备注", example = "随便") + private String remark; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/returns/ErpSaleReturnPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/returns/ErpSaleReturnPageReqVO.java new file mode 100644 index 000000000..a9be73beb --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/returns/ErpSaleReturnPageReqVO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.returns; + +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; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 销售退货分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpSaleReturnPageReqVO extends PageParam { + + public static final Integer REFUND_STATUS_NONE = 0; + public static final Integer REFUND_STATUS_PART = 1; + public static final Integer REFUND_STATUS_ALL = 2; + + @Schema(description = "销售单编号", example = "XS001") + private String no; + + @Schema(description = "客户编号", example = "1724") + private Long customerId; + + @Schema(description = "退货时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] returnTime; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "退货状态", example = "2") + private Integer status; + + @Schema(description = "创建者") + private String creator; + + @Schema(description = "产品编号", example = "1") + private Long productId; + + @Schema(description = "仓库编号", example = "1") + private Long warehouseId; + + @Schema(description = "结算账号编号", example = "1") + private Long accountId; + + @Schema(description = "销售单号", example = "1") + private String orderNo; + + @Schema(description = "退款状态", example = "1") + private Integer refundStatus; + + @Schema(description = "是否可退款", example = "true") + private Boolean refundEnable; // 对应 refundStatus = [0, 1] + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/returns/ErpSaleReturnRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/returns/ErpSaleReturnRespVO.java new file mode 100644 index 000000000..ba52f4f80 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/returns/ErpSaleReturnRespVO.java @@ -0,0 +1,148 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.returns; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 销售退货 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpSaleReturnRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + @ExcelProperty("编号") + private Long id; + + @Schema(description = "退货单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001") + @ExcelProperty("退货单编号") + private String no; + + @Schema(description = "退货状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("退货状态") + private Integer status; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724") + private Long customerId; + @Schema(description = "客户名称", example = "芋道") + @ExcelProperty("客户名称") + private String customerName; + + @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "311.89") + @ExcelProperty("结算账户编号") + private Long accountId; + + @Schema(description = "退货员编号", example = "1888") + private Long saleUserId; + + @Schema(description = "退货时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("退货时间") + private LocalDateTime returnTime; + + @Schema(description = "销售订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + private Long orderId; + @Schema(description = "销售订单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "XS001") + private String orderNo; + + @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663") + @ExcelProperty("合计数量") + private BigDecimal totalCount; + @Schema(description = "最终合计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "24906") + @ExcelProperty("最终合计价格") + private BigDecimal totalPrice; + @Schema(description = "已退款金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal refundPrice; + + @Schema(description = "合计产品价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal totalProductPrice; + + @Schema(description = "合计税额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal totalTaxPrice; + + @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88") + private BigDecimal discountPercent; + + @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal discountPrice; + + @Schema(description = "其它金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127") + private BigDecimal otherPrice; + + @Schema(description = "附件地址", example = "https://www.iocoder.cn") + @ExcelProperty("附件地址") + private String fileUrl; + + @Schema(description = "备注", example = "你猜") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "创建人", example = "芋道") + private String creator; + @Schema(description = "创建人名称", example = "芋道") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "退货项列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品信息") + private String productNames; + + @Data + public static class Item { + + @Schema(description = "退货项编号", example = "11756") + private Long id; + + @Schema(description = "销售订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + private Long orderItemId; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productId; + + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productUnitId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "税率,百分比", example = "99.88") + private BigDecimal taxPercent; + + @Schema(description = "税额,单位:元", example = "100.00") + private BigDecimal taxPrice; + + @Schema(description = "备注", example = "随便") + private String remark; + + // ========== 关联字段 ========== + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力") + private String productName; + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985") + private String productBarCode; + @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒") + private String productUnitName; + + @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal stockCount; // 该字段仅仅在“详情”和“编辑”时使用 + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/returns/ErpSaleReturnSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/returns/ErpSaleReturnSaveReqVO.java new file mode 100644 index 000000000..81c046f7d --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/returns/ErpSaleReturnSaveReqVO.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.returns; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 销售退货新增/修改 Request VO") +@Data +public class ErpSaleReturnSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + private Long id; + + @Schema(description = "结算账户编号", example = "31189") + private Long accountId; + + @Schema(description = "销售员编号", example = "1888") + private Long saleUserId; + + @Schema(description = "退货时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "退货时间不能为空") + private LocalDateTime returnTime; + + @Schema(description = "销售订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17386") + @NotNull(message = "销售订单编号不能为空") + private Long orderId; + + @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88") + private BigDecimal discountPercent; + + @Schema(description = "其它金额,单位:元", example = "7127") + private BigDecimal otherPrice; + + @Schema(description = "附件地址", example = "https://www.iocoder.cn") + private String fileUrl; + + @Schema(description = "备注", example = "你猜") + private String remark; + + @Schema(description = "退货清单列表") + private List items; + + @Data + public static class Item { + + @Schema(description = "退货项编号", example = "11756") + private Long id; + + @Schema(description = "销售订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + @NotNull(message = "销售订单项编号不能为空") + private Long orderItemId; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "仓库编号不能为空") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品编号不能为空") + private Long productId; + + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品单位单位不能为空") + private Long productUnitId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "税率,百分比", example = "99.88") + private BigDecimal taxPercent; + + @Schema(description = "备注", example = "随便") + private String remark; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockCheckController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockCheckController.java new file mode 100644 index 000000000..298ed54fa --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockCheckController.java @@ -0,0 +1,149 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.check.ErpStockCheckPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.check.ErpStockCheckRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.check.ErpStockCheckSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockCheckDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockCheckItemDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockCheckService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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 java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 库存调拨单") +@RestController +@RequestMapping("/erp/stock-check") +@Validated +public class ErpStockCheckController { + + @Resource + private ErpStockCheckService stockCheckService; + @Resource + private ErpProductService productService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建库存调拨单") + @PreAuthorize("@ss.hasPermission('erp:stock-check:create')") + public CommonResult createStockCheck(@Valid @RequestBody ErpStockCheckSaveReqVO createReqVO) { + return success(stockCheckService.createStockCheck(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新库存调拨单") + @PreAuthorize("@ss.hasPermission('erp:stock-check:update')") + public CommonResult updateStockCheck(@Valid @RequestBody ErpStockCheckSaveReqVO updateReqVO) { + stockCheckService.updateStockCheck(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新库存调拨单的状态") + @PreAuthorize("@ss.hasPermission('erp:stock-check:update-status')") + public CommonResult updateStockCheckStatus(@RequestParam("id") Long id, + @RequestParam("status") Integer status) { + stockCheckService.updateStockCheckStatus(id, status); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除库存调拨单") + @Parameter(name = "ids", description = "编号数组", required = true) + @PreAuthorize("@ss.hasPermission('erp:stock-check:delete')") + public CommonResult deleteStockCheck(@RequestParam("ids") List ids) { + stockCheckService.deleteStockCheck(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得库存调拨单") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:stock-check:query')") + public CommonResult getStockCheck(@RequestParam("id") Long id) { + ErpStockCheckDO stockCheck = stockCheckService.getStockCheck(id); + if (stockCheck == null) { + return success(null); + } + List stockCheckItemList = stockCheckService.getStockCheckItemListByCheckId(id); + Map productMap = productService.getProductVOMap( + convertSet(stockCheckItemList, ErpStockCheckItemDO::getProductId)); + return success(BeanUtils.toBean(stockCheck, ErpStockCheckRespVO.class, stockCheckVO -> + stockCheckVO.setItems(BeanUtils.toBean(stockCheckItemList, ErpStockCheckRespVO.Item.class, item -> + MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())))))); + } + + @GetMapping("/page") + @Operation(summary = "获得库存调拨单分页") + @PreAuthorize("@ss.hasPermission('erp:stock-check:query')") + public CommonResult> getStockCheckPage(@Valid ErpStockCheckPageReqVO pageReqVO) { + PageResult pageResult = stockCheckService.getStockCheckPage(pageReqVO); + return success(buildStockCheckVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出库存调拨单 Excel") + @PreAuthorize("@ss.hasPermission('erp:stock-check:export')") + @OperateLog(type = EXPORT) + public void exportStockCheckExcel(@Valid ErpStockCheckPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildStockCheckVOPageResult(stockCheckService.getStockCheckPage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "库存调拨单.xls", "数据", ErpStockCheckRespVO.class, list); + } + + private PageResult buildStockCheckVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + // 1.1 盘点项 + List stockCheckItemList = stockCheckService.getStockCheckItemListByCheckIds( + convertSet(pageResult.getList(), ErpStockCheckDO::getId)); + Map> stockCheckItemMap = convertMultiMap(stockCheckItemList, ErpStockCheckItemDO::getCheckId); + // 1.2 产品信息 + Map productMap = productService.getProductVOMap( + convertSet(stockCheckItemList, ErpStockCheckItemDO::getProductId)); + // 1.3 管理员信息 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), stockCheck -> Long.parseLong(stockCheck.getCreator()))); + // 2. 开始拼接 + return BeanUtils.toBean(pageResult, ErpStockCheckRespVO.class, stockCheck -> { + stockCheck.setItems(BeanUtils.toBean(stockCheckItemMap.get(stockCheck.getId()), ErpStockCheckRespVO.Item.class, + item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())))); + stockCheck.setProductNames(CollUtil.join(stockCheck.getItems(), ",", ErpStockCheckRespVO.Item::getProductName)); + MapUtils.findAndThen(userMap, Long.parseLong(stockCheck.getCreator()), user -> stockCheck.setCreatorName(user.getNickname())); + }); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockController.java new file mode 100644 index 000000000..912f59731 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockController.java @@ -0,0 +1,112 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockRespVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import cn.iocoder.yudao.module.erp.service.stock.ErpWarehouseService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +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.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 产品库存") +@RestController +@RequestMapping("/erp/stock") +@Validated +public class ErpStockController { + + @Resource + private ErpStockService stockService; + @Resource + private ErpProductService productService; + @Resource + private ErpWarehouseService warehouseService; + + @GetMapping("/get") + @Operation(summary = "获得产品库存") + @Parameters({ + @Parameter(name = "id", description = "编号", example = "1"), // 方案一:传递 id + @Parameter(name = "productId", description = "产品编号", example = "10"), // 方案二:传递 productId + warehouseId + @Parameter(name = "warehouseId", description = "仓库编号", example = "2") + }) + @PreAuthorize("@ss.hasPermission('erp:stock:query')") + public CommonResult getStock(@RequestParam(value = "id", required = false) Long id, + @RequestParam(value = "productId", required = false) Long productId, + @RequestParam(value = "warehouseId", required = false) Long warehouseId) { + ErpStockDO stock = id != null ? stockService.getStock(id) : stockService.getStock(productId, warehouseId); + return success(BeanUtils.toBean(stock, ErpStockRespVO.class)); + } + + @GetMapping("/get-count") + @Operation(summary = "获得产品库存数量") + @Parameter(name = "productId", description = "产品编号", example = "10") + public CommonResult getStockCount(@RequestParam("productId") Long productId) { + return success(stockService.getStockCount(productId)); + } + + @GetMapping("/page") + @Operation(summary = "获得产品库存分页") + @PreAuthorize("@ss.hasPermission('erp:stock:query')") + public CommonResult> getStockPage(@Valid ErpStockPageReqVO pageReqVO) { + PageResult pageResult = stockService.getStockPage(pageReqVO); + return success(buildStockVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出产品库存 Excel") + @PreAuthorize("@ss.hasPermission('erp:stock:export')") + @OperateLog(type = EXPORT) + public void exportStockExcel(@Valid ErpStockPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildStockVOPageResult(stockService.getStockPage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "产品库存.xls", "数据", ErpStockRespVO.class, list); + } + + private PageResult buildStockVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + Map productMap = productService.getProductVOMap( + convertSet(pageResult.getList(), ErpStockDO::getProductId)); + Map warehouseMap = warehouseService.getWarehouseMap( + convertSet(pageResult.getList(), ErpStockDO::getWarehouseId)); + return BeanUtils.toBean(pageResult, ErpStockRespVO.class, stock -> { + MapUtils.findAndThen(productMap, stock.getProductId(), product -> stock.setProductName(product.getName()) + .setCategoryName(product.getCategoryName()).setUnitName(product.getUnitName())); + MapUtils.findAndThen(warehouseMap, stock.getWarehouseId(), warehouse -> stock.setWarehouseName(warehouse.getName())); + }); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockInController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockInController.java new file mode 100644 index 000000000..8813da89a --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockInController.java @@ -0,0 +1,165 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInItemDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockInService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 其它入库单") +@RestController +@RequestMapping("/erp/stock-in") +@Validated +public class ErpStockInController { + + @Resource + private ErpStockInService stockInService; + @Resource + private ErpStockService stockService; + @Resource + private ErpProductService productService; + @Resource + private ErpSupplierService supplierService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建其它入库单") + @PreAuthorize("@ss.hasPermission('erp:stock-in:create')") + public CommonResult createStockIn(@Valid @RequestBody ErpStockInSaveReqVO createReqVO) { + return success(stockInService.createStockIn(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新其它入库单") + @PreAuthorize("@ss.hasPermission('erp:stock-in:update')") + public CommonResult updateStockIn(@Valid @RequestBody ErpStockInSaveReqVO updateReqVO) { + stockInService.updateStockIn(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新其它入库单的状态") + @PreAuthorize("@ss.hasPermission('erp:stock-in:update-status')") + public CommonResult updateStockInStatus(@RequestParam("id") Long id, + @RequestParam("status") Integer status) { + stockInService.updateStockInStatus(id, status); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除其它入库单") + @Parameter(name = "ids", description = "编号数组", required = true) + @PreAuthorize("@ss.hasPermission('erp:stock-in:delete')") + public CommonResult deleteStockIn(@RequestParam("ids") List ids) { + stockInService.deleteStockIn(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得其它入库单") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:stock-in:query')") + public CommonResult getStockIn(@RequestParam("id") Long id) { + ErpStockInDO stockIn = stockInService.getStockIn(id); + if (stockIn == null) { + return success(null); + } + List stockInItemList = stockInService.getStockInItemListByInId(id); + Map productMap = productService.getProductVOMap( + convertSet(stockInItemList, ErpStockInItemDO::getProductId)); + return success(BeanUtils.toBean(stockIn, ErpStockInRespVO.class, stockInVO -> + stockInVO.setItems(BeanUtils.toBean(stockInItemList, ErpStockInRespVO.Item.class, item -> { + ErpStockDO stock = stockService.getStock(item.getProductId(), item.getWarehouseId()); + item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO); + MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())); + })))); + } + + @GetMapping("/page") + @Operation(summary = "获得其它入库单分页") + @PreAuthorize("@ss.hasPermission('erp:stock-in:query')") + public CommonResult> getStockInPage(@Valid ErpStockInPageReqVO pageReqVO) { + PageResult pageResult = stockInService.getStockInPage(pageReqVO); + return success(buildStockInVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出其它入库单 Excel") + @PreAuthorize("@ss.hasPermission('erp:stock-in:export')") + @OperateLog(type = EXPORT) + public void exportStockInExcel(@Valid ErpStockInPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildStockInVOPageResult(stockInService.getStockInPage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "其它入库单.xls", "数据", ErpStockInRespVO.class, list); + } + + private PageResult buildStockInVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + // 1.1 入库项 + List stockInItemList = stockInService.getStockInItemListByInIds( + convertSet(pageResult.getList(), ErpStockInDO::getId)); + Map> stockInItemMap = convertMultiMap(stockInItemList, ErpStockInItemDO::getInId); + // 1.2 产品信息 + Map productMap = productService.getProductVOMap( + convertSet(stockInItemList, ErpStockInItemDO::getProductId)); + // 1.3 供应商信息 + Map supplierMap = supplierService.getSupplierMap( + convertSet(pageResult.getList(), ErpStockInDO::getSupplierId)); + // 1.4 管理员信息 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), stockIn -> Long.parseLong(stockIn.getCreator()))); + // 2. 开始拼接 + return BeanUtils.toBean(pageResult, ErpStockInRespVO.class, stockIn -> { + stockIn.setItems(BeanUtils.toBean(stockInItemMap.get(stockIn.getId()), ErpStockInRespVO.Item.class, + item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())))); + stockIn.setProductNames(CollUtil.join(stockIn.getItems(), ",", ErpStockInRespVO.Item::getProductName)); + MapUtils.findAndThen(supplierMap, stockIn.getSupplierId(), supplier -> stockIn.setSupplierName(supplier.getName())); + MapUtils.findAndThen(userMap, Long.parseLong(stockIn.getCreator()), user -> stockIn.setCreatorName(user.getNickname())); + }); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockMoveController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockMoveController.java new file mode 100644 index 000000000..1df3fd7fc --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockMoveController.java @@ -0,0 +1,160 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMovePageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveItemDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockMoveService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 库存调拨单") +@RestController +@RequestMapping("/erp/stock-move") +@Validated +public class ErpStockMoveController { + + @Resource + private ErpStockMoveService stockMoveService; + @Resource + private ErpStockService stockService; + @Resource + private ErpProductService productService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建库存调拨单") + @PreAuthorize("@ss.hasPermission('erp:stock-move:create')") + public CommonResult createStockMove(@Valid @RequestBody ErpStockMoveSaveReqVO createReqVO) { + return success(stockMoveService.createStockMove(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新库存调拨单") + @PreAuthorize("@ss.hasPermission('erp:stock-move:update')") + public CommonResult updateStockMove(@Valid @RequestBody ErpStockMoveSaveReqVO updateReqVO) { + stockMoveService.updateStockMove(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新库存调拨单的状态") + @PreAuthorize("@ss.hasPermission('erp:stock-move:update-status')") + public CommonResult updateStockMoveStatus(@RequestParam("id") Long id, + @RequestParam("status") Integer status) { + stockMoveService.updateStockMoveStatus(id, status); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除库存调拨单") + @Parameter(name = "ids", description = "编号数组", required = true) + @PreAuthorize("@ss.hasPermission('erp:stock-move:delete')") + public CommonResult deleteStockMove(@RequestParam("ids") List ids) { + stockMoveService.deleteStockMove(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得库存调拨单") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:stock-move:query')") + public CommonResult getStockMove(@RequestParam("id") Long id) { + ErpStockMoveDO stockMove = stockMoveService.getStockMove(id); + if (stockMove == null) { + return success(null); + } + List stockMoveItemList = stockMoveService.getStockMoveItemListByMoveId(id); + Map productMap = productService.getProductVOMap( + convertSet(stockMoveItemList, ErpStockMoveItemDO::getProductId)); + return success(BeanUtils.toBean(stockMove, ErpStockMoveRespVO.class, stockMoveVO -> + stockMoveVO.setItems(BeanUtils.toBean(stockMoveItemList, ErpStockMoveRespVO.Item.class, item -> { + ErpStockDO stock = stockService.getStock(item.getProductId(), item.getFromWarehouseId()); + item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO); + MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())); + })))); + } + + @GetMapping("/page") + @Operation(summary = "获得库存调拨单分页") + @PreAuthorize("@ss.hasPermission('erp:stock-move:query')") + public CommonResult> getStockMovePage(@Valid ErpStockMovePageReqVO pageReqVO) { + PageResult pageResult = stockMoveService.getStockMovePage(pageReqVO); + return success(buildStockMoveVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出库存调拨单 Excel") + @PreAuthorize("@ss.hasPermission('erp:stock-move:export')") + @OperateLog(type = EXPORT) + public void exportStockMoveExcel(@Valid ErpStockMovePageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildStockMoveVOPageResult(stockMoveService.getStockMovePage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "库存调拨单.xls", "数据", ErpStockMoveRespVO.class, list); + } + + private PageResult buildStockMoveVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + // 1.1 调拨项 + List stockMoveItemList = stockMoveService.getStockMoveItemListByMoveIds( + convertSet(pageResult.getList(), ErpStockMoveDO::getId)); + Map> stockMoveItemMap = convertMultiMap(stockMoveItemList, ErpStockMoveItemDO::getMoveId); + // 1.2 产品信息 + Map productMap = productService.getProductVOMap( + convertSet(stockMoveItemList, ErpStockMoveItemDO::getProductId)); + // 1.3 TODO 芋艿:搞仓库信息 + // 1.4 管理员信息 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), stockMove -> Long.parseLong(stockMove.getCreator()))); + // 2. 开始拼接 + return BeanUtils.toBean(pageResult, ErpStockMoveRespVO.class, stockMove -> { + stockMove.setItems(BeanUtils.toBean(stockMoveItemMap.get(stockMove.getId()), ErpStockMoveRespVO.Item.class, + item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())))); + stockMove.setProductNames(CollUtil.join(stockMove.getItems(), ",", ErpStockMoveRespVO.Item::getProductName)); + // TODO 芋艿: +// MapUtils.findAndThen(customerMap, stockMove.getCustomerId(), supplier -> stockMove.setCustomerName(supplier.getName())); + MapUtils.findAndThen(userMap, Long.parseLong(stockMove.getCreator()), user -> stockMove.setCreatorName(user.getNickname())); + }); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockOutController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockOutController.java new file mode 100644 index 000000000..9ad592f1a --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockOutController.java @@ -0,0 +1,165 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpCustomerDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutItemDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockOutService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 其它出库单") +@RestController +@RequestMapping("/erp/stock-out") +@Validated +public class ErpStockOutController { + + @Resource + private ErpStockOutService stockOutService; + @Resource + private ErpStockService stockService; + @Resource + private ErpProductService productService; + @Resource + private ErpCustomerService customerService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建其它出库单") + @PreAuthorize("@ss.hasPermission('erp:stock-out:create')") + public CommonResult createStockOut(@Valid @RequestBody ErpStockOutSaveReqVO createReqVO) { + return success(stockOutService.createStockOut(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新其它出库单") + @PreAuthorize("@ss.hasPermission('erp:stock-out:update')") + public CommonResult updateStockOut(@Valid @RequestBody ErpStockOutSaveReqVO updateReqVO) { + stockOutService.updateStockOut(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + @Operation(summary = "更新其它出库单的状态") + @PreAuthorize("@ss.hasPermission('erp:stock-out:update-status')") + public CommonResult updateStockOutStatus(@RequestParam("id") Long id, + @RequestParam("status") Integer status) { + stockOutService.updateStockOutStatus(id, status); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除其它出库单") + @Parameter(name = "ids", description = "编号数组", required = true) + @PreAuthorize("@ss.hasPermission('erp:stock-out:delete')") + public CommonResult deleteStockOut(@RequestParam("ids") List ids) { + stockOutService.deleteStockOut(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得其它出库单") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:stock-out:query')") + public CommonResult getStockOut(@RequestParam("id") Long id) { + ErpStockOutDO stockOut = stockOutService.getStockOut(id); + if (stockOut == null) { + return success(null); + } + List stockOutItemList = stockOutService.getStockOutItemListByOutId(id); + Map productMap = productService.getProductVOMap( + convertSet(stockOutItemList, ErpStockOutItemDO::getProductId)); + return success(BeanUtils.toBean(stockOut, ErpStockOutRespVO.class, stockOutVO -> + stockOutVO.setItems(BeanUtils.toBean(stockOutItemList, ErpStockOutRespVO.Item.class, item -> { + ErpStockDO stock = stockService.getStock(item.getProductId(), item.getWarehouseId()); + item.setStockCount(stock != null ? stock.getCount() : BigDecimal.ZERO); + MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())); + })))); + } + + @GetMapping("/page") + @Operation(summary = "获得其它出库单分页") + @PreAuthorize("@ss.hasPermission('erp:stock-out:query')") + public CommonResult> getStockOutPage(@Valid ErpStockOutPageReqVO pageReqVO) { + PageResult pageResult = stockOutService.getStockOutPage(pageReqVO); + return success(buildStockOutVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出其它出库单 Excel") + @PreAuthorize("@ss.hasPermission('erp:stock-out:export')") + @OperateLog(type = EXPORT) + public void exportStockOutExcel(@Valid ErpStockOutPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildStockOutVOPageResult(stockOutService.getStockOutPage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "其它出库单.xls", "数据", ErpStockOutRespVO.class, list); + } + + private PageResult buildStockOutVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + // 1.1 出库项 + List stockOutItemList = stockOutService.getStockOutItemListByOutIds( + convertSet(pageResult.getList(), ErpStockOutDO::getId)); + Map> stockOutItemMap = convertMultiMap(stockOutItemList, ErpStockOutItemDO::getOutId); + // 1.2 产品信息 + Map productMap = productService.getProductVOMap( + convertSet(stockOutItemList, ErpStockOutItemDO::getProductId)); + // 1.3 客户信息 + Map customerMap = customerService.getCustomerMap( + convertSet(pageResult.getList(), ErpStockOutDO::getCustomerId)); + // 1.4 管理员信息 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), stockOut -> Long.parseLong(stockOut.getCreator()))); + // 2. 开始拼接 + return BeanUtils.toBean(pageResult, ErpStockOutRespVO.class, stockOut -> { + stockOut.setItems(BeanUtils.toBean(stockOutItemMap.get(stockOut.getId()), ErpStockOutRespVO.Item.class, + item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName()) + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName())))); + stockOut.setProductNames(CollUtil.join(stockOut.getItems(), ",", ErpStockOutRespVO.Item::getProductName)); + MapUtils.findAndThen(customerMap, stockOut.getCustomerId(), supplier -> stockOut.setCustomerName(supplier.getName())); + MapUtils.findAndThen(userMap, Long.parseLong(stockOut.getCreator()), user -> stockOut.setCreatorName(user.getNickname())); + }); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockRecordController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockRecordController.java new file mode 100644 index 000000000..6ed453894 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpStockRecordController.java @@ -0,0 +1,105 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record.ErpStockRecordPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record.ErpStockRecordRespVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockRecordDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockRecordService; +import cn.iocoder.yudao.module.erp.service.stock.ErpWarehouseService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 产品库存明细") +@RestController +@RequestMapping("/erp/stock-record") +@Validated +public class ErpStockRecordController { + + @Resource + private ErpStockRecordService stockRecordService; + @Resource + private ErpProductService productService; + @Resource + private ErpWarehouseService warehouseService; + + @Resource + private AdminUserApi adminUserApi; + + @GetMapping("/get") + @Operation(summary = "获得产品库存明细") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:stock-record:query')") + public CommonResult getStockRecord(@RequestParam("id") Long id) { + ErpStockRecordDO stockRecord = stockRecordService.getStockRecord(id); + return success(BeanUtils.toBean(stockRecord, ErpStockRecordRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得产品库存明细分页") + @PreAuthorize("@ss.hasPermission('erp:stock-record:query')") + public CommonResult> getStockRecordPage(@Valid ErpStockRecordPageReqVO pageReqVO) { + PageResult pageResult = stockRecordService.getStockRecordPage(pageReqVO); + return success(buildStockRecrodVOPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出产品库存明细 Excel") + @PreAuthorize("@ss.hasPermission('erp:stock-record:export')") + @OperateLog(type = EXPORT) + public void exportStockRecordExcel(@Valid ErpStockRecordPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = buildStockRecrodVOPageResult(stockRecordService.getStockRecordPage(pageReqVO)).getList(); + // 导出 Excel + ExcelUtils.write(response, "产品库存明细.xls", "数据", ErpStockRecordRespVO.class, list); + } + + private PageResult buildStockRecrodVOPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + Map productMap = productService.getProductVOMap( + convertSet(pageResult.getList(), ErpStockRecordDO::getProductId)); + Map warehouseMap = warehouseService.getWarehouseMap( + convertSet(pageResult.getList(), ErpStockRecordDO::getWarehouseId)); + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), record -> Long.parseLong(record.getCreator()))); + return BeanUtils.toBean(pageResult, ErpStockRecordRespVO.class, stock -> { + MapUtils.findAndThen(productMap, stock.getProductId(), product -> stock.setProductName(product.getName()) + .setCategoryName(product.getCategoryName()).setUnitName(product.getUnitName())); + MapUtils.findAndThen(warehouseMap, stock.getWarehouseId(), warehouse -> stock.setWarehouseName(warehouse.getName())); + MapUtils.findAndThen(userMap, Long.parseLong(stock.getCreator()), user -> stock.setCreatorName(user.getNickname())); + }); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpWarehouseController.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpWarehouseController.java new file mode 100644 index 000000000..744f439f5 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/ErpWarehouseController.java @@ -0,0 +1,116 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.ErpWarehouseSaveReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.warehouse.ErpWarehousePageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.warehouse.ErpWarehouseRespVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO; +import cn.iocoder.yudao.module.erp.service.stock.ErpWarehouseService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +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.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - ERP 仓库") +@RestController +@RequestMapping("/erp/warehouse") +@Validated +public class ErpWarehouseController { + + @Resource + private ErpWarehouseService warehouseService; + + @PostMapping("/create") + @Operation(summary = "创建仓库") + @PreAuthorize("@ss.hasPermission('erp:warehouse:create')") + public CommonResult createWarehouse(@Valid @RequestBody ErpWarehouseSaveReqVO createReqVO) { + return success(warehouseService.createWarehouse(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新仓库") + @PreAuthorize("@ss.hasPermission('erp:warehouse:update')") + public CommonResult updateWarehouse(@Valid @RequestBody ErpWarehouseSaveReqVO updateReqVO) { + warehouseService.updateWarehouse(updateReqVO); + return success(true); + } + + @PutMapping("/update-default-status") + @Operation(summary = "更新仓库默认状态") + @Parameters({ + @Parameter(name = "id", description = "编号", required = true), + @Parameter(name = "status", description = "状态", required = true) + }) + public CommonResult updateWarehouseDefaultStatus(@RequestParam("id") Long id, + @RequestParam("defaultStatus") Boolean defaultStatus) { + warehouseService.updateWarehouseDefaultStatus(id, defaultStatus); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除仓库") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('erp:warehouse:delete')") + public CommonResult deleteWarehouse(@RequestParam("id") Long id) { + warehouseService.deleteWarehouse(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得仓库") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('erp:warehouse:query')") + public CommonResult getWarehouse(@RequestParam("id") Long id) { + ErpWarehouseDO warehouse = warehouseService.getWarehouse(id); + return success(BeanUtils.toBean(warehouse, ErpWarehouseRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得仓库分页") + @PreAuthorize("@ss.hasPermission('erp:warehouse:query')") + public CommonResult> getWarehousePage(@Valid ErpWarehousePageReqVO pageReqVO) { + PageResult pageResult = warehouseService.getWarehousePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ErpWarehouseRespVO.class)); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得仓库精简列表", description = "只包含被开启的仓库,主要用于前端的下拉选项") + public CommonResult> getWarehouseSimpleList() { + List list = warehouseService.getWarehouseListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(convertList(list, warehouse -> new ErpWarehouseRespVO().setId(warehouse.getId()) + .setName(warehouse.getName()).setDefaultStatus(warehouse.getDefaultStatus()))); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出仓库 Excel") + @PreAuthorize("@ss.hasPermission('erp:warehouse:export')") + @OperateLog(type = EXPORT) + public void exportWarehouseExcel(@Valid ErpWarehousePageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = warehouseService.getWarehousePage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "仓库.xls", "数据", ErpWarehouseRespVO.class, + BeanUtils.toBean(list, ErpWarehouseRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/check/ErpStockCheckPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/check/ErpStockCheckPageReqVO.java new file mode 100644 index 000000000..2bae14c1e --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/check/ErpStockCheckPageReqVO.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.check; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 库存盘点单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpStockCheckPageReqVO extends PageParam { + + @Schema(description = "盘点单号", example = "S123") + private String no; + + @Schema(description = "仓库编号", example = "3113") + private Long warehouseId; + + @Schema(description = "盘点时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] checkTime; + + @Schema(description = "状态", example = "10") + @InEnum(ErpAuditStatus.class) + private Integer status; + + @Schema(description = "备注", example = "随便") + private String remark; + + @Schema(description = "创建者") + private String creator; + + @Schema(description = "产品编号", example = "1") + private Long productId; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/check/ErpStockCheckRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/check/ErpStockCheckRespVO.java new file mode 100644 index 000000000..af53e3c72 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/check/ErpStockCheckRespVO.java @@ -0,0 +1,111 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.check; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.module.erp.enums.DictTypeConstants.AUDIT_STATUS; + +@Schema(description = "管理后台 - ERP 库存盘点单 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpStockCheckRespVO { + + @Schema(description = "盘点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + @ExcelProperty("盘点编号") + private Long id; + + @Schema(description = "盘点单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "S123") + @ExcelProperty("盘点单号") + private String no; + + @Schema(description = "盘点时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("盘点时间") + private LocalDateTime checkTime; + + @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663") + @ExcelProperty("合计数量") + private BigDecimal totalCount; + + @Schema(description = "合计金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "24906") + @ExcelProperty("合计金额") + private BigDecimal totalPrice; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @ExcelProperty(value = "状态", converter = DictConvert.class) + @DictFormat(AUDIT_STATUS) + private Integer status; + + @Schema(description = "备注", example = "随便") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "附件 URL", example = "https://www.iocoder.cn/1.doc") + private String fileUrl; + + @Schema(description = "创建人", example = "芋道") + private String creator; + @Schema(description = "创建人名称", example = "芋道") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "盘点项列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品信息") + private String productNames; + + @Data + public static class Item { + + @Schema(description = "盘点项编号", example = "11756") + private Long id; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productId; + + @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "账面数量(当前库存)", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "账面数量不能为空") + private BigDecimal stockCount; + + @Schema(description = "实际数量(实际库存)", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "实际数量不能为空") + private BigDecimal actualCount; + + @Schema(description = "盈亏数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "盈亏数量不能为空") + private BigDecimal count; + + @Schema(description = "备注", example = "随便") + private String remark; + + // ========== 关联字段 ========== + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力") + private String productName; + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985") + private String productBarCode; + @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒") + private String productUnitName; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/check/ErpStockCheckSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/check/ErpStockCheckSaveReqVO.java new file mode 100644 index 000000000..0af223fb5 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/check/ErpStockCheckSaveReqVO.java @@ -0,0 +1,69 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.check; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 其它出库单新增/修改 Request VO") +@Data +public class ErpStockCheckSaveReqVO { + + @Schema(description = "出库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + private Long id; + + @Schema(description = "出库时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "出库时间不能为空") + private LocalDateTime checkTime; + + @Schema(description = "备注", example = "随便") + private String remark; + + @Schema(description = "附件 URL", example = "https://www.iocoder.cn/1.doc") + private String fileUrl; + + @Schema(description = "出库项列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "出库项列表不能为空") + @Valid + private List items; + + @Data + public static class Item { + + @Schema(description = "出库项编号", example = "11756") + private Long id; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "仓库编号不能为空") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品编号不能为空") + private Long productId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "账面数量(当前库存)", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "账面数量不能为空") + private BigDecimal stockCount; + + @Schema(description = "实际数量(实际库存)", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "实际数量不能为空") + private BigDecimal actualCount; + + @Schema(description = "盈亏数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "盈亏数量不能为空") + private BigDecimal count; + + @Schema(description = "备注", example = "随便") + private String remark; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInPageReqVO.java new file mode 100644 index 000000000..02e3db37a --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInPageReqVO.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 其它入库单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpStockInPageReqVO extends PageParam { + + @Schema(description = "入库单号", example = "S123") + private String no; + + @Schema(description = "供应商编号", example = "3113") + private Long supplierId; + + @Schema(description = "入库时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] inTime; + + @Schema(description = "状态", example = "10") + @InEnum(ErpAuditStatus.class) + private Integer status; + + @Schema(description = "备注", example = "随便") + private String remark; + + @Schema(description = "创建者") + private String creator; + + @Schema(description = "产品编号", example = "1") + private Long productId; + + @Schema(description = "仓库编号", example = "1") + private Long warehouseId; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInRespVO.java new file mode 100644 index 000000000..077b9dd1b --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInRespVO.java @@ -0,0 +1,110 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.module.erp.enums.DictTypeConstants.AUDIT_STATUS; + +@Schema(description = "管理后台 - ERP 其它入库单 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpStockInRespVO { + + @Schema(description = "入库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + @ExcelProperty("入库编号") + private Long id; + + @Schema(description = "入库单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "S123") + @ExcelProperty("入库单号") + private String no; + + @Schema(description = "供应商编号", example = "3113") + private Long supplierId; + @Schema(description = "供应商名称", example = "芋道") + @ExcelProperty("供应商名称") + private String supplierName; + + @Schema(description = "入库时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("入库时间") + private LocalDateTime inTime; + + @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663") + @ExcelProperty("合计数量") + private BigDecimal totalCount; + + @Schema(description = "合计金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "24906") + @ExcelProperty("合计金额") + private BigDecimal totalPrice; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @ExcelProperty(value = "状态", converter = DictConvert.class) + @DictFormat(AUDIT_STATUS) + private Integer status; + + @Schema(description = "备注", example = "随便") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "附件 URL", example = "https://www.iocoder.cn/1.doc") + private String fileUrl; + + @Schema(description = "创建人", example = "芋道") + private String creator; + @Schema(description = "创建人名称", example = "芋道") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "入库项列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品信息") + private String productNames; + + @Data + public static class Item { + + @Schema(description = "入库项编号", example = "11756") + private Long id; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productId; + + @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal count; + + @Schema(description = "备注", example = "随便") + private String remark; + + // ========== 关联字段 ========== + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力") + private String productName; + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985") + private String productBarCode; + @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒") + private String productUnitName; + + @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal stockCount; // 该字段仅仅在“详情”和“编辑”时使用 + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInSaveReqVO.java new file mode 100644 index 000000000..0187872c8 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/in/ErpStockInSaveReqVO.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 其它入库单新增/修改 Request VO") +@Data +public class ErpStockInSaveReqVO { + + @Schema(description = "入库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + private Long id; + + @Schema(description = "供应商编号", example = "3113") + private Long supplierId; + + @Schema(description = "入库时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "入库时间不能为空") + private LocalDateTime inTime; + + @Schema(description = "备注", example = "随便") + private String remark; + + @Schema(description = "附件 URL", example = "https://www.iocoder.cn/1.doc") + private String fileUrl; + + @Schema(description = "入库项列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "入库项列表不能为空") + @Valid + private List items; + + @Data + public static class Item { + + @Schema(description = "入库项编号", example = "11756") + private Long id; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "仓库编号不能为空") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品编号不能为空") + private Long productId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "备注", example = "随便") + private String remark; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMovePageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMovePageReqVO.java new file mode 100644 index 000000000..98a1fe95e --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMovePageReqVO.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 库存调拨单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpStockMovePageReqVO extends PageParam { + + @Schema(description = "调拨单号", example = "S123") + private String no; + + @Schema(description = "调拨时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] moveTime; + + @Schema(description = "状态", example = "10") + @InEnum(ErpAuditStatus.class) + private Integer status; + + @Schema(description = "备注", example = "随便") + private String remark; + + @Schema(description = "创建者") + private String creator; + + @Schema(description = "产品编号", example = "1") + private Long productId; + + @Schema(description = "调出仓库编号", example = "1") + private Long fromWarehouseId; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveRespVO.java new file mode 100644 index 000000000..799ddc3f1 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveRespVO.java @@ -0,0 +1,107 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.module.erp.enums.DictTypeConstants.AUDIT_STATUS; + +@Schema(description = "管理后台 - ERP 库存调拨单 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpStockMoveRespVO { + + @Schema(description = "调拨编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + @ExcelProperty("调拨编号") + private Long id; + + @Schema(description = "调拨单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "S123") + @ExcelProperty("调拨单号") + private String no; + + @Schema(description = "调拨时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("调拨时间") + private LocalDateTime moveTime; + + @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663") + @ExcelProperty("合计数量") + private BigDecimal totalCount; + + @Schema(description = "合计金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "24906") + @ExcelProperty("合计金额") + private BigDecimal totalPrice; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @ExcelProperty(value = "状态", converter = DictConvert.class) + @DictFormat(AUDIT_STATUS) + private Integer status; + + @Schema(description = "备注", example = "随便") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "附件 URL", example = "https://www.iocoder.cn/1.doc") + private String fileUrl; + + @Schema(description = "创建人", example = "芋道") + private String creator; + @Schema(description = "创建人名称", example = "芋道") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "调拨项列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品信息") + private String productNames; + + @Data + public static class Item { + + @Schema(description = "调拨项编号", example = "11756") + private Long id; + + @Schema(description = "调出仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long fromWarehouseId; + + @Schema(description = "调入仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") + private Long toWarehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productId; + + @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal count; + + @Schema(description = "备注", example = "随便") + private String remark; + + // ========== 关联字段 ========== + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力") + private String productName; + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985") + private String productBarCode; + @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒") + private String productUnitName; + + @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal stockCount; // 该字段仅仅在“详情”和“编辑”时使用 + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveSaveReqVO.java new file mode 100644 index 000000000..17a431561 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/move/ErpStockMoveSaveReqVO.java @@ -0,0 +1,77 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move; + +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 库存调拨单新增/修改 Request VO") +@Data +public class ErpStockMoveSaveReqVO { + + @Schema(description = "调拨编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + private Long id; + + @Schema(description = "客户编号", example = "3113") + private Long customerId; + + @Schema(description = "调拨时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "调拨时间不能为空") + private LocalDateTime moveTime; + + @Schema(description = "备注", example = "随便") + private String remark; + + @Schema(description = "附件 URL", example = "https://www.iocoder.cn/1.doc") + private String fileUrl; + + @Schema(description = "调拨项列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "调拨项列表不能为空") + @Valid + private List items; + + @Data + public static class Item { + + @Schema(description = "调拨项编号", example = "11756") + private Long id; + + @Schema(description = "调出仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "调出仓库编号不能为空") + private Long fromWarehouseId; + + @Schema(description = "调入仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") + @NotNull(message = "调入仓库编号不能为空") + private Long toWarehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品编号不能为空") + private Long productId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "备注", example = "随便") + private String remark; + + @AssertTrue(message = "调出、调仓仓库不能相同") + @JsonIgnore + public boolean isWarehouseValid() { + return ObjectUtil.notEqual(fromWarehouseId, toWarehouseId); + } + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/out/ErpStockOutPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/out/ErpStockOutPageReqVO.java new file mode 100644 index 000000000..5f6558b19 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/out/ErpStockOutPageReqVO.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 其它出库单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpStockOutPageReqVO extends PageParam { + + @Schema(description = "出库单号", example = "S123") + private String no; + + @Schema(description = "客户编号", example = "3113") + private Long customerId; + + @Schema(description = "出库时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] outTime; + + @Schema(description = "状态", example = "10") + @InEnum(ErpAuditStatus.class) + private Integer status; + + @Schema(description = "备注", example = "随便") + private String remark; + + @Schema(description = "创建者") + private String creator; + + @Schema(description = "产品编号", example = "1") + private Long productId; + + @Schema(description = "仓库编号", example = "1") + private Long warehouseId; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/out/ErpStockOutRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/out/ErpStockOutRespVO.java new file mode 100644 index 000000000..22a88e7c9 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/out/ErpStockOutRespVO.java @@ -0,0 +1,110 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.module.erp.enums.DictTypeConstants.AUDIT_STATUS; + +@Schema(description = "管理后台 - ERP 其它出库单 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpStockOutRespVO { + + @Schema(description = "出库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + @ExcelProperty("出库编号") + private Long id; + + @Schema(description = "出库单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "S123") + @ExcelProperty("出库单号") + private String no; + + @Schema(description = "客户编号", example = "3113") + private Long customerId; + @Schema(description = "客户名称", example = "芋道") + @ExcelProperty("客户名称") + private String customerName; + + @Schema(description = "出库时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("出库时间") + private LocalDateTime outTime; + + @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "15663") + @ExcelProperty("合计数量") + private BigDecimal totalCount; + + @Schema(description = "合计金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "24906") + @ExcelProperty("合计金额") + private BigDecimal totalPrice; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @ExcelProperty(value = "状态", converter = DictConvert.class) + @DictFormat(AUDIT_STATUS) + private Integer status; + + @Schema(description = "备注", example = "随便") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "附件 URL", example = "https://www.iocoder.cn/1.doc") + private String fileUrl; + + @Schema(description = "创建人", example = "芋道") + private String creator; + @Schema(description = "创建人名称", example = "芋道") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "出库项列表", requiredMode = Schema.RequiredMode.REQUIRED) + private List items; + + @Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("产品信息") + private String productNames; + + @Data + public static class Item { + + @Schema(description = "出库项编号", example = "11756") + private Long id; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + private Long productId; + + @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal count; + + @Schema(description = "备注", example = "随便") + private String remark; + + // ========== 关联字段 ========== + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力") + private String productName; + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985") + private String productBarCode; + @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒") + private String productUnitName; + + @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal stockCount; // 该字段仅仅在“详情”和“编辑”时使用 + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/out/ErpStockOutSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/out/ErpStockOutSaveReqVO.java new file mode 100644 index 000000000..5a903798e --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/out/ErpStockOutSaveReqVO.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - ERP 其它出库单新增/修改 Request VO") +@Data +public class ErpStockOutSaveReqVO { + + @Schema(description = "出库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11756") + private Long id; + + @Schema(description = "客户编号", example = "3113") + private Long customerId; + + @Schema(description = "出库时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "出库时间不能为空") + private LocalDateTime outTime; + + @Schema(description = "备注", example = "随便") + private String remark; + + @Schema(description = "附件 URL", example = "https://www.iocoder.cn/1.doc") + private String fileUrl; + + @Schema(description = "出库项列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "出库项列表不能为空") + @Valid + private List items; + + @Data + public static class Item { + + @Schema(description = "出库项编号", example = "11756") + private Long id; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "仓库编号不能为空") + private Long warehouseId; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113") + @NotNull(message = "产品编号不能为空") + private Long productId; + + @Schema(description = "产品单价", example = "100.00") + private BigDecimal productPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "产品数量不能为空") + private BigDecimal count; + + @Schema(description = "备注", example = "随便") + private String remark; + + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/record/ErpStockRecordPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/record/ErpStockRecordPageReqVO.java new file mode 100644 index 000000000..c478e4fef --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/record/ErpStockRecordPageReqVO.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record; + +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; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - ERP 产品库存明细分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpStockRecordPageReqVO extends PageParam { + + @Schema(description = "产品编号", example = "10625") + private Long productId; + + @Schema(description = "仓库编号", example = "32407") + private Long warehouseId; + + @Schema(description = "业务类型", example = "10") + private Integer bizType; + + @Schema(description = "业务单号", example = "Z110") + private String bizNo; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/record/ErpStockRecordRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/record/ErpStockRecordRespVO.java new file mode 100644 index 000000000..ff4b3e12a --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/record/ErpStockRecordRespVO.java @@ -0,0 +1,87 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.module.erp.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - ERP 产品库存明细 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpStockRecordRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18909") + @ExcelProperty("编号") + private Long id; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10625") + private Long productId; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32407") + private Long warehouseId; + + @Schema(description = "出入库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "11084") + @ExcelProperty("出入库数量") + private BigDecimal count; + + @Schema(description = "总库存量", requiredMode = Schema.RequiredMode.REQUIRED, example = "4307") + @ExcelProperty("总库存量") + private BigDecimal totalCount; + + @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @ExcelProperty(value = "业务类型", converter = DictConvert.class) + @DictFormat(DictTypeConstants.STOCK_RECORD_BIZ_TYPE) + private Integer bizType; + + @Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "27093") + @ExcelProperty("业务编号") + private Long bizId; + + @Schema(description = "业务项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23516") + @ExcelProperty("业务项编号") + private Long bizItemId; + + @Schema(description = "业务单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "Z110") + @ExcelProperty("业务单号") + private String bizNo; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED, example = "25682") + private String creator; + + // ========== 产品信息 ========== + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "苹果") + @ExcelProperty("产品名称") + private String productName; + + @Schema(description = "产品分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "水果") + @ExcelProperty("产品分类") + private String categoryName; + + @Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "个") + @ExcelProperty("单位") + private String unitName; + + // ========== 仓库信息 ========== + + @Schema(description = "仓库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @ExcelProperty("仓库名称") + private String warehouseName; + + // ========== 用户信息 ========== + + @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + @ExcelProperty("创建人") + private String creatorName; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockPageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockPageReqVO.java new file mode 100644 index 000000000..f7f3fa343 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockPageReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock; + +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 = "管理后台 - ERP 库存分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpStockPageReqVO extends PageParam { + + @Schema(description = "产品编号", example = "19614") + private Long productId; + + @Schema(description = "仓库编号", example = "2802") + private Long warehouseId; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockRespVO.java new file mode 100644 index 000000000..06366a0dd --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/stock/ErpStockRespVO.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Schema(description = "管理后台 - ERP 库存 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpStockRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17086") + @ExcelProperty("编号") + private Long id; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19614") + private Long productId; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2802") + private Long warehouseId; + + @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "21935") + @ExcelProperty("库存数量") + private BigDecimal count; + + // ========== 产品信息 ========== + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "苹果") + @ExcelProperty("产品名称") + private String productName; + + @Schema(description = "产品分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "水果") + @ExcelProperty("产品分类") + private String categoryName; + + @Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "个") + @ExcelProperty("单位") + private String unitName; + + // ========== 仓库信息 ========== + + @Schema(description = "仓库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @ExcelProperty("仓库名称") + private String warehouseName; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/warehouse/ErpWarehousePageReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/warehouse/ErpWarehousePageReqVO.java new file mode 100644 index 000000000..2accf9a0f --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/warehouse/ErpWarehousePageReqVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.warehouse; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - ERP 仓库分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class ErpWarehousePageReqVO extends PageParam { + + @Schema(description = "仓库名称", example = "李四") + private String name; + + @Schema(description = "开启状态", example = "1") + @InEnum(CommonStatusEnum.class) + private Integer status; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/warehouse/ErpWarehouseRespVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/warehouse/ErpWarehouseRespVO.java new file mode 100644 index 000000000..188d42699 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/warehouse/ErpWarehouseRespVO.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo.warehouse; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.module.system.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - ERP 仓库 Response VO") +@Data +@ExcelIgnoreUnannotated +public class ErpWarehouseRespVO { + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11614") + @ExcelProperty("仓库编号") + private Long id; + + @Schema(description = "仓库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @ExcelProperty("仓库名称") + private String name; + + @Schema(description = "仓库地址", example = "上海陆家嘴") + @ExcelProperty("仓库地址") + private String address; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @ExcelProperty("排序") + private Long sort; + + @Schema(description = "备注", example = "随便") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "负责人", example = "芋头") + @ExcelProperty("负责人") + private String principal; + + @Schema(description = "仓储费,单位:元", example = "13973") + @ExcelProperty("仓储费,单位:元") + private BigDecimal warehousePrice; + + @Schema(description = "搬运费,单位:元", example = "9903") + @ExcelProperty("搬运费,单位:元") + private BigDecimal truckagePrice; + + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty(value = "开启状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; + + @Schema(description = "是否默认", example = "1") + @ExcelProperty("是否默认") + private Boolean defaultStatus; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/warehouse/ErpWarehouseSaveReqVO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/warehouse/ErpWarehouseSaveReqVO.java new file mode 100644 index 000000000..8a6f1ce78 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/stock/vo/warehouse/ErpWarehouseSaveReqVO.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.erp.controller.admin.stock.vo; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; +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 = "管理后台 - ERP 仓库新增/修改 Request VO") +@Data +public class ErpWarehouseSaveReqVO { + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11614") + private Long id; + + @Schema(description = "仓库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @NotEmpty(message = "仓库名称不能为空") + private String name; + + @Schema(description = "仓库地址", example = "上海陆家嘴") + private String address; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @NotNull(message = "排序不能为空") + private Long sort; + + @Schema(description = "备注", example = "随便") + private String remark; + + @Schema(description = "负责人", example = "芋头") + private String principal; + + @Schema(description = "仓储费,单位:元", example = "13973") + private BigDecimal warehousePrice; + + @Schema(description = "搬运费,单位:元", example = "9903") + private BigDecimal truckagePrice; + + @Schema(description = "开启状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "开启状态不能为空") + @InEnum(CommonStatusEnum.class) + private Integer status; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/package-info.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/package-info.java new file mode 100644 index 000000000..ef0e5accf --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 RESTful API 给前端: + * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 + * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 + */ +package cn.iocoder.yudao.module.erp.controller; diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpAccountDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpAccountDO.java new file mode 100644 index 000000000..fe01cc228 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpAccountDO.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.finance; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * ERP 结算账户 DO + * + * @author 芋道源码 + */ +@TableName("erp_account") +@KeySequence("erp_account_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpAccountDO extends BaseDO { + + /** + * 结算账户编号 + */ + @TableId + private Long id; + /** + * 账户名称 + */ + private String name; + /** + * 账户编码 + */ + private String no; + /** + * 备注 + */ + private String remark; + /** + * 开启状态 + * + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} + */ + private Integer status; + /** + * 排序 + */ + private Integer sort; + /** + * 是否默认 + */ + private Boolean defaultStatus; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinancePaymentDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinancePaymentDO.java new file mode 100644 index 000000000..edb55edbf --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinancePaymentDO.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.finance; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO; +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; + +/** + * ERP 付款单 DO + * + * @author 芋道源码 + */ +@TableName("erp_finance_payment") +@KeySequence("erp_finance_payment_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpFinancePaymentDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 付款单号 + */ + private String no; + /** + * 付款状态 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus} + */ + private Integer status; + /** + * 付款时间 + */ + private LocalDateTime paymentTime; + /** + * 财务人员编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long financeUserId; + /** + * 供应商编号 + * + * 关联 {@link ErpSupplierDO#getId()} + */ + private Long supplierId; + /** + * 付款账户编号 + * + * 关联 {@link ErpAccountDO#getId()} + */ + private Long accountId; + + /** + * 合计价格,单位:元 + */ + private BigDecimal totalPrice; + /** + * 优惠金额,单位:元 + */ + private BigDecimal discountPrice; + /** + * 实付金额,单位:分 + * + * paymentPrice = totalPrice - discountPrice + */ + private BigDecimal paymentPrice; + + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinancePaymentItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinancePaymentItemDO.java new file mode 100644 index 000000000..dd5bc5f69 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinancePaymentItemDO.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.finance; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 付款项 DO + * + * @author 芋道源码 + */ +@TableName("erp_finance_payment_item") +@KeySequence("erp_finance_payment_item_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpFinancePaymentItemDO extends BaseDO { + + /** + * 入库项编号 + */ + @TableId + private Long id; + /** + * 付款单编号 + * + * 关联 {@link ErpFinancePaymentDO#getId()} + */ + private Long paymentId; + + /** + * 业务类型 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.common.ErpBizTypeEnum} 的采购入库、退货 + */ + private Integer bizType; + /** + * 业务编号 + * + * 例如说:{@link ErpPurchaseInDO#getId()} + */ + private Long bizId; + /** + * 业务单号 + * + * 例如说:{@link ErpPurchaseInDO#getNo()} + */ + private String bizNo; + + /** + * 应付金额,单位:分 + */ + private BigDecimal totalPrice; + /** + * 已付金额,单位:分 + */ + private BigDecimal paidPrice; + /** + * 本次付款,单位:分 + */ + private BigDecimal paymentPrice; + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinanceReceiptDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinanceReceiptDO.java new file mode 100644 index 000000000..46a559505 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinanceReceiptDO.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.finance; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpCustomerDO; +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; + +/** + * ERP 收款单 DO + * + * @author 芋道源码 + */ +@TableName("erp_finance_receipt") +@KeySequence("erp_finance_receipt_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpFinanceReceiptDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 收款单号 + */ + private String no; + /** + * 收款状态 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus} + */ + private Integer status; + /** + * 收款时间 + */ + private LocalDateTime receiptTime; + /** + * 财务人员编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long financeUserId; + /** + * 客户编号 + * + * 关联 {@link ErpCustomerDO#getId()} + */ + private Long customerId; + /** + * 收款账户编号 + * + * 关联 {@link ErpAccountDO#getId()} + */ + private Long accountId; + + /** + * 合计价格,单位:元 + */ + private BigDecimal totalPrice; + /** + * 优惠金额,单位:元 + */ + private BigDecimal discountPrice; + /** + * 实付金额,单位:分 + * + * receiptPrice = totalPrice - discountPrice + */ + private BigDecimal receiptPrice; + + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinanceReceiptItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinanceReceiptItemDO.java new file mode 100644 index 000000000..87f051d5c --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/finance/ErpFinanceReceiptItemDO.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.finance; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 收款项 DO + * + * @author 芋道源码 + */ +@TableName("erp_finance_receipt_item") +@KeySequence("erp_finance_receipt_item_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpFinanceReceiptItemDO extends BaseDO { + + /** + * 入库项编号 + */ + @TableId + private Long id; + /** + * 收款单编号 + * + * 关联 {@link ErpFinanceReceiptDO#getId()} + */ + private Long receiptId; + + /** + * 业务类型 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.common.ErpBizTypeEnum} 的销售出库、退货 + */ + private Integer bizType; + /** + * 业务编号 + * + * 例如说:{@link ErpSaleOutDO#getId()} + */ + private Long bizId; + /** + * 业务单号 + * + * 例如说:{@link ErpSaleOutDO#getNo()} + */ + private String bizNo; + + /** + * 应收金额,单位:分 + */ + private BigDecimal totalPrice; + /** + * 已收金额,单位:分 + */ + private BigDecimal receiptedPrice; + /** + * 本次收款,单位:分 + */ + private BigDecimal receiptPrice; + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/product/ErpProductCategoryDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/product/ErpProductCategoryDO.java new file mode 100644 index 000000000..4c6225158 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/product/ErpProductCategoryDO.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.product; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * ERP 产品分类 DO + * + * @author 芋道源码 + */ +@TableName("erp_product_category") +@KeySequence("erp_product_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpProductCategoryDO extends BaseDO { + + public static final Long PARENT_ID_ROOT = 0L; + + /** + * 分类编号 + */ + @TableId + private Long id; + /** + * 父分类编号 + */ + private Long parentId; + /** + * 分类名称 + */ + private String name; + /** + * 分类编码 + */ + private String code; + /** + * 分类排序 + */ + private Integer sort; + /** + * 开启状态 + * + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} + */ + private Integer status; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/product/ErpProductDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/product/ErpProductDO.java new file mode 100644 index 000000000..31e4aa2d9 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/product/ErpProductDO.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.product; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 产品 DO + * + * @author 芋道源码 + */ +@TableName("erp_product") +@KeySequence("erp_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpProductDO extends BaseDO { + + /** + * 产品编号 + */ + @TableId + private Long id; + /** + * 产品名称 + */ + private String name; + /** + * 产品条码 + */ + private String barCode; + /** + * 产品分类编号 + * + * 关联 {@link ErpProductCategoryDO#getId()} + */ + private Long categoryId; + /** + * 单位编号 + * + * 关联 {@link ErpProductUnitDO#getId()} + */ + private Long unitId; + /** + * 产品状态 + * + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} + */ + private Integer status; + /** + * 产品规格 + */ + private String standard; + /** + * 产品备注 + */ + private String remark; + /** + * 保质期天数 + */ + private Integer expiryDay; + /** + * 基础重量(kg) + */ + private BigDecimal weight; + /** + * 采购价格,单位:元 + */ + private BigDecimal purchasePrice; + /** + * 销售价格,单位:元 + */ + private BigDecimal salePrice; + /** + * 最低价格,单位:元 + */ + private BigDecimal minPrice; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/product/ErpProductUnitDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/product/ErpProductUnitDO.java new file mode 100644 index 000000000..73c96c290 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/product/ErpProductUnitDO.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.product; + +import lombok.*; +import java.util.*; +import java.time.LocalDateTime; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.*; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; + +/** + * ERP 产品单位 DO + * + * @author 芋道源码 + */ +@TableName("erp_product_unit") +@KeySequence("erp_product_unit_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpProductUnitDO extends BaseDO { + + /** + * 单位编号 + */ + @TableId + private Long id; + /** + * 单位名字 + */ + private String name; + /** + * 单位状态 + */ + private Integer status; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInDO.java new file mode 100644 index 000000000..b8186f509 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInDO.java @@ -0,0 +1,122 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.purchase; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO; +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; + +/** + * ERP 采购入库 DO + * + * @author 芋道源码 + */ +@TableName(value = "erp_purchase_in") +@KeySequence("erp_purchase_in_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpPurchaseInDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 采购入库单号 + */ + private String no; + /** + * 入库状态 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus} + */ + private Integer status; + /** + * 供应商编号 + * + * 关联 {@link ErpSupplierDO#getId()} + */ + private Long supplierId; + /** + * 结算账户编号 + * + * 关联 {@link ErpAccountDO#getId()} + */ + private Long accountId; + /** + * 入库时间 + */ + private LocalDateTime inTime; + + /** + * 采购订单编号 + * + * 关联 {@link ErpPurchaseOrderDO#getId()} + */ + private Long orderId; + /** + * 采购订单号 + * + * 冗余 {@link ErpPurchaseOrderDO#getNo()} + */ + private String orderNo; + + /** + * 合计数量 + */ + private BigDecimal totalCount; + /** + * 最终合计价格,单位:元 + * + * totalPrice = totalProductPrice + totalTaxPrice - discountPrice + otherPrice + */ + private BigDecimal totalPrice; + /** + * 已支付金额,单位:元 + * + * 目的:和 {@link cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO} 结合,记录已支付金额 + */ + private BigDecimal paymentPrice; + + /** + * 合计产品价格,单位:元 + */ + private BigDecimal totalProductPrice; + /** + * 合计税额,单位:元 + */ + private BigDecimal totalTaxPrice; + /** + * 优惠率,百分比 + */ + private BigDecimal discountPercent; + /** + * 优惠金额,单位:元 + * + * discountPrice = (totalProductPrice + totalTaxPrice) * discountPercent + */ + private BigDecimal discountPrice; + /** + * 其它金额,单位:元 + */ + private BigDecimal otherPrice; + + /** + * 附件地址 + */ + private String fileUrl; + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInItemDO.java new file mode 100644 index 000000000..1597bc10b --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInItemDO.java @@ -0,0 +1,95 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.purchase; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 采购入库项 DO + * + * @author 芋道源码 + */ +@TableName("erp_purchase_in_items") +@KeySequence("erp_purchase_in_items_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpPurchaseInItemDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 采购入库编号 + * + * 关联 {@link ErpPurchaseInDO##getId()} + */ + private Long inId; + /** + * 采购订单项编号 + * + * 关联 {@link ErpPurchaseOrderItemDO#getId()} + * 目的:方便更新关联的采购订单项的入库数量 + */ + private Long orderItemId; + /** + * 仓库编号 + * + * 关联 {@link ErpWarehouseDO#getId()} + */ + private Long warehouseId; + /** + * 产品编号 + * + * 关联 {@link ErpProductDO#getId()} + */ + private Long productId; + /** + * 产品单位单位 + * + * 冗余 {@link ErpProductDO#getUnitId()} + */ + private Long productUnitId; + + /** + * 产品单位单价,单位:元 + */ + private BigDecimal productPrice; + /** + * 数量 + */ + private BigDecimal count; + /** + * 总价,单位:元 + * + * totalPrice = productPrice * count + */ + private BigDecimal totalPrice; + /** + * 税率,百分比 + */ + private BigDecimal taxPercent; + /** + * 税额,单位:元 + * + * taxPrice = totalPrice * taxPercent + */ + private BigDecimal taxPrice; + + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java new file mode 100644 index 000000000..bba1542e3 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java @@ -0,0 +1,115 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.purchase; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO; +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; + +/** + * ERP 采购订单 DO + * + * @author 芋道源码 + */ +@TableName(value = "erp_purchase_order") +@KeySequence("erp_purchase_order_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpPurchaseOrderDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 采购订单号 + */ + private String no; + /** + * 采购状态 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus} + */ + private Integer status; + /** + * 供应商编号 + * + * 关联 {@link ErpSupplierDO#getId()} + */ + private Long supplierId; + /** + * 结算账户编号 + * + * 关联 {@link ErpAccountDO#getId()} + */ + private Long accountId; + /** + * 下单时间 + */ + private LocalDateTime orderTime; + + /** + * 合计数量 + */ + private BigDecimal totalCount; + /** + * 最终合计价格,单位:元 + * + * totalPrice = totalProductPrice + totalTaxPrice - discountPrice + */ + private BigDecimal totalPrice; + + /** + * 合计产品价格,单位:元 + */ + private BigDecimal totalProductPrice; + /** + * 合计税额,单位:元 + */ + private BigDecimal totalTaxPrice; + /** + * 优惠率,百分比 + */ + private BigDecimal discountPercent; + /** + * 优惠金额,单位:元 + * + * discountPrice = (totalProductPrice + totalTaxPrice) * discountPercent + */ + private BigDecimal discountPrice; + /** + * 定金金额,单位:元 + */ + private BigDecimal depositPrice; + + /** + * 附件地址 + */ + private String fileUrl; + /** + * 备注 + */ + private String remark; + + // ========== 采购入库 ========== + /** + * 采购入库数量 + */ + private BigDecimal inCount; + + // ========== 采购退货(出库)) ========== + /** + * 采购退货数量 + */ + private BigDecimal returnCount; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java new file mode 100644 index 000000000..aa54d336b --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java @@ -0,0 +1,93 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.purchase; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 采购订单项 DO + * + * @author 芋道源码 + */ +@TableName("erp_purchase_order_items") +@KeySequence("erp_purchase_order_items_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpPurchaseOrderItemDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 采购订单编号 + * + * 关联 {@link ErpPurchaseOrderDO#getId()} + */ + private Long orderId; + /** + * 产品编号 + * + * 关联 {@link ErpProductDO#getId()} + */ + private Long productId; + /** + * 产品单位单位 + * + * 冗余 {@link ErpProductDO#getUnitId()} + */ + private Long productUnitId; + + /** + * 产品单位单价,单位:元 + */ + private BigDecimal productPrice; + /** + * 数量 + */ + private BigDecimal count; + /** + * 总价,单位:元 + * + * totalPrice = productPrice * count + */ + private BigDecimal totalPrice; + /** + * 税率,百分比 + */ + private BigDecimal taxPercent; + /** + * 税额,单位:元 + * + * taxPrice = totalPrice * taxPercent + */ + private BigDecimal taxPrice; + + /** + * 备注 + */ + private String remark; + + // ========== 采购入库 ========== + /** + * 采购入库数量 + */ + private BigDecimal inCount; + + // ========== 采购退货(出库)) ========== + /** + * 采购退货数量 + */ + private BigDecimal returnCount; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseReturnDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseReturnDO.java new file mode 100644 index 000000000..4189e0adc --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseReturnDO.java @@ -0,0 +1,122 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.purchase; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO; +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; + +/** + * ERP 采购退货 DO + * + * @author 芋道源码 + */ +@TableName(value = "erp_purchase_return") +@KeySequence("erp_purchase_return_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpPurchaseReturnDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 采购退货单号 + */ + private String no; + /** + * 退货状态 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus} + */ + private Integer status; + /** + * 供应商编号 + * + * 关联 {@link ErpSupplierDO#getId()} + */ + private Long supplierId; + /** + * 结算账户编号 + * + * 关联 {@link ErpAccountDO#getId()} + */ + private Long accountId; + /** + * 退货时间 + */ + private LocalDateTime returnTime; + + /** + * 采购订单编号 + * + * 关联 {@link ErpPurchaseOrderDO#getId()} + */ + private Long orderId; + /** + * 采购订单号 + * + * 冗余 {@link ErpPurchaseOrderDO#getNo()} + */ + private String orderNo; + + /** + * 合计数量 + */ + private BigDecimal totalCount; + /** + * 最终合计价格,单位:元 + * + * totalPrice = totalProductPrice + totalTaxPrice - discountPrice + otherPrice + */ + private BigDecimal totalPrice; + /** + * 已退款金额,单位:元 + * + * 目的:和 {@link cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO} 结合,记录已支付金额 + */ + private BigDecimal refundPrice; + + /** + * 合计产品价格,单位:元 + */ + private BigDecimal totalProductPrice; + /** + * 合计税额,单位:元 + */ + private BigDecimal totalTaxPrice; + /** + * 优惠率,百分比 + */ + private BigDecimal discountPercent; + /** + * 优惠金额,单位:元 + * + * discountPrice = (totalProductPrice + totalTaxPrice) * discountPercent + */ + private BigDecimal discountPrice; + /** + * 其它金额,单位:元 + */ + private BigDecimal otherPrice; + + /** + * 附件地址 + */ + private String fileUrl; + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseReturnItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseReturnItemDO.java new file mode 100644 index 000000000..1e1713277 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseReturnItemDO.java @@ -0,0 +1,95 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.purchase; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 采购退货项 DO + * + * @author 芋道源码 + */ +@TableName("erp_purchase_return_items") +@KeySequence("erp_purchase_return_items_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpPurchaseReturnItemDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 采购退货编号 + * + * 关联 {@link ErpPurchaseReturnDO##getId()} + */ + private Long returnId; + /** + * 采购订单项编号 + * + * 关联 {@link ErpPurchaseOrderItemDO#getId()} + * 目的:方便更新关联的采购订单项的退货数量 + */ + private Long orderItemId; + /** + * 仓库编号 + * + * 关联 {@link ErpWarehouseDO#getId()} + */ + private Long warehouseId; + /** + * 产品编号 + * + * 关联 {@link ErpProductDO#getId()} + */ + private Long productId; + /** + * 产品单位单位 + * + * 冗余 {@link ErpProductDO#getUnitId()} + */ + private Long productUnitId; + + /** + * 产品单位单价,单位:元 + */ + private BigDecimal productPrice; + /** + * 数量 + */ + private BigDecimal count; + /** + * 总价,单位:元 + * + * totalPrice = productPrice * count + */ + private BigDecimal totalPrice; + /** + * 税率,百分比 + */ + private BigDecimal taxPercent; + /** + * 税额,单位:元 + * + * taxPrice = totalPrice * taxPercent + */ + private BigDecimal taxPrice; + + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpSupplierDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpSupplierDO.java new file mode 100644 index 000000000..6e94c6669 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpSupplierDO.java @@ -0,0 +1,90 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.purchase; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 供应商 DO + * + * @author 芋道源码 + */ +@TableName("erp_supplier") +@KeySequence("erp_supplier_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpSupplierDO extends BaseDO { + + /** + * 供应商编号 + */ + @TableId + private Long id; + /** + * 供应商名称 + */ + private String name; + /** + * 联系人 + */ + private String contact; + /** + * 手机号码 + */ + private String mobile; + /** + * 联系电话 + */ + private String telephone; + /** + * 电子邮箱 + */ + private String email; + /** + * 传真 + */ + private String fax; + /** + * 备注 + */ + private String remark; + /** + * 开启状态 + * + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} + */ + private Integer status; + /** + * 排序 + */ + private Integer sort; + /** + * 纳税人识别号 + */ + private String taxNo; + /** + * 税率 + */ + private BigDecimal taxPercent; + /** + * 开户行 + */ + private String bankName; + /** + * 开户账号 + */ + private String bankAccount; + /** + * 开户地址 + */ + private String bankAddress; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpCustomerDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpCustomerDO.java new file mode 100644 index 000000000..7bffcc17c --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpCustomerDO.java @@ -0,0 +1,90 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.sale; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 客户 DO + * + * @author 芋道源码 + */ +@TableName("erp_customer") +@KeySequence("erp_customer_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpCustomerDO extends BaseDO { + + /** + * 客户编号 + */ + @TableId + private Long id; + /** + * 客户名称 + */ + private String name; + /** + * 联系人 + */ + private String contact; + /** + * 手机号码 + */ + private String mobile; + /** + * 联系电话 + */ + private String telephone; + /** + * 电子邮箱 + */ + private String email; + /** + * 传真 + */ + private String fax; + /** + * 备注 + */ + private String remark; + /** + * 开启状态 + * + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} + */ + private Integer status; + /** + * 排序 + */ + private Integer sort; + /** + * 纳税人识别号 + */ + private String taxNo; + /** + * 税率 + */ + private BigDecimal taxPercent; + /** + * 开户行 + */ + private String bankName; + /** + * 开户账号 + */ + private String bankAccount; + /** + * 开户地址 + */ + private String bankAddress; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java new file mode 100644 index 000000000..5cdd4344e --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java @@ -0,0 +1,121 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.sale; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO; +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; + +/** + * ERP 销售订单 DO + * + * @author 芋道源码 + */ +@TableName(value = "erp_sale_order") +@KeySequence("erp_sale_order_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpSaleOrderDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 销售订单号 + */ + private String no; + /** + * 销售状态 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus} + */ + private Integer status; + /** + * 客户编号 + * + * 关联 {@link ErpCustomerDO#getId()} + */ + private Long customerId; + /** + * 结算账户编号 + * + * 关联 {@link ErpAccountDO#getId()} + */ + private Long accountId; + /** + * 销售员编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long saleUserId; + /** + * 下单时间 + */ + private LocalDateTime orderTime; + + /** + * 合计数量 + */ + private BigDecimal totalCount; + /** + * 最终合计价格,单位:元 + * + * totalPrice = totalProductPrice + totalTaxPrice - discountPrice + */ + private BigDecimal totalPrice; + + /** + * 合计产品价格,单位:元 + */ + private BigDecimal totalProductPrice; + /** + * 合计税额,单位:元 + */ + private BigDecimal totalTaxPrice; + /** + * 优惠率,百分比 + */ + private BigDecimal discountPercent; + /** + * 优惠金额,单位:元 + * + * discountPrice = (totalProductPrice + totalTaxPrice) * discountPercent + */ + private BigDecimal discountPrice; + /** + * 定金金额,单位:元 + */ + private BigDecimal depositPrice; + + /** + * 附件地址 + */ + private String fileUrl; + /** + * 备注 + */ + private String remark; + + // ========== 销售出库 ========== + /** + * 销售出库数量 + */ + private BigDecimal outCount; + + // ========== 销售退货(入库)) ========== + /** + * 销售退货数量 + */ + private BigDecimal returnCount; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderItemDO.java new file mode 100644 index 000000000..4c829765b --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderItemDO.java @@ -0,0 +1,93 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.sale; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 销售订单项 DO + * + * @author 芋道源码 + */ +@TableName("erp_sale_order_items") +@KeySequence("erp_sale_order_items_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpSaleOrderItemDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 销售订单编号 + * + * 关联 {@link ErpSaleOrderDO#getId()} + */ + private Long orderId; + /** + * 产品编号 + * + * 关联 {@link ErpProductDO#getId()} + */ + private Long productId; + /** + * 产品单位单位 + * + * 冗余 {@link ErpProductDO#getUnitId()} + */ + private Long productUnitId; + + /** + * 产品单位单价,单位:元 + */ + private BigDecimal productPrice; + /** + * 数量 + */ + private BigDecimal count; + /** + * 总价,单位:元 + * + * totalPrice = productPrice * count + */ + private BigDecimal totalPrice; + /** + * 税率,百分比 + */ + private BigDecimal taxPercent; + /** + * 税额,单位:元 + * + * taxPrice = totalPrice * taxPercent + */ + private BigDecimal taxPrice; + + /** + * 备注 + */ + private String remark; + + // ========== 销售出库 ========== + /** + * 销售出库数量 + */ + private BigDecimal outCount; + + // ========== 销售退货(入库)) ========== + /** + * 销售退货数量 + */ + private BigDecimal returnCount; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java new file mode 100644 index 000000000..65b9d941f --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutDO.java @@ -0,0 +1,128 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.sale; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO; +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; + +/** + * ERP 销售出库 DO + * + * @author 芋道源码 + */ +@TableName(value = "erp_sale_out") +@KeySequence("erp_sale_out_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpSaleOutDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 销售出库单号 + */ + private String no; + /** + * 出库状态 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus} + */ + private Integer status; + /** + * 客户编号 + * + * 关联 {@link ErpCustomerDO#getId()} + */ + private Long customerId; + /** + * 结算账户编号 + * + * 关联 {@link ErpAccountDO#getId()} + */ + private Long accountId; + /** + * 销售员编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long saleUserId; + /** + * 出库时间 + */ + private LocalDateTime outTime; + + /** + * 销售订单编号 + * + * 关联 {@link ErpSaleOrderDO#getId()} + */ + private Long orderId; + /** + * 销售订单号 + * + * 冗余 {@link ErpSaleOrderDO#getNo()} + */ + private String orderNo; + + /** + * 合计数量 + */ + private BigDecimal totalCount; + /** + * 最终合计价格,单位:元 + * + * totalPrice = totalProductPrice + totalTaxPrice - discountPrice + otherPrice + */ + private BigDecimal totalPrice; + /** + * 已收款金额,单位:元 + * + * 目的:和 {@link cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptDO} 结合,记录已收款金额 + */ + private BigDecimal receiptPrice; + + /** + * 合计产品价格,单位:元 + */ + private BigDecimal totalProductPrice; + /** + * 合计税额,单位:元 + */ + private BigDecimal totalTaxPrice; + /** + * 优惠率,百分比 + */ + private BigDecimal discountPercent; + /** + * 优惠金额,单位:元 + * + * discountPrice = (totalProductPrice + totalTaxPrice) * discountPercent + */ + private BigDecimal discountPrice; + /** + * 其它金额,单位:元 + */ + private BigDecimal otherPrice; + + /** + * 附件地址 + */ + private String fileUrl; + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutItemDO.java new file mode 100644 index 000000000..b9b406413 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOutItemDO.java @@ -0,0 +1,96 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.sale; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 销售出库项 DO + * + * @author 芋道源码 + */ +@TableName("erp_sale_out_items") +@KeySequence("erp_sale_out_items_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpSaleOutItemDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 销售出库编号 + * + * 关联 {@link ErpStockOutDO##getId()} + */ + private Long outId; + /** + * 销售订单项编号 + * + * 关联 {@link ErpSaleOrderItemDO#getId()} + * 目的:方便更新关联的销售订单项的出库数量 + */ + private Long orderItemId; + /** + * 仓库编号 + * + * 关联 {@link ErpWarehouseDO#getId()} + */ + private Long warehouseId; + /** + * 产品编号 + * + * 关联 {@link ErpProductDO#getId()} + */ + private Long productId; + /** + * 产品单位单位 + * + * 冗余 {@link ErpProductDO#getUnitId()} + */ + private Long productUnitId; + + /** + * 产品单位单价,单位:元 + */ + private BigDecimal productPrice; + /** + * 数量 + */ + private BigDecimal count; + /** + * 总价,单位:元 + * + * totalPrice = productPrice * count + */ + private BigDecimal totalPrice; + /** + * 税率,百分比 + */ + private BigDecimal taxPercent; + /** + * 税额,单位:元 + * + * taxPrice = totalPrice * taxPercent + */ + private BigDecimal taxPrice; + + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleReturnDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleReturnDO.java new file mode 100644 index 000000000..ba41ac91b --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleReturnDO.java @@ -0,0 +1,128 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.sale; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO; +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; + +/** + * ERP 销售退货 DO + * + * @author 芋道源码 + */ +@TableName(value = "erp_sale_return") +@KeySequence("erp_sale_return_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpSaleReturnDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 销售退货单号 + */ + private String no; + /** + * 退货状态 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus} + */ + private Integer status; + /** + * 客户编号 + * + * 关联 {@link ErpCustomerDO#getId()} + */ + private Long customerId; + /** + * 结算账户编号 + * + * 关联 {@link ErpAccountDO#getId()} + */ + private Long accountId; + /** + * 销售员编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long saleUserId; + /** + * 退货时间 + */ + private LocalDateTime returnTime; + + /** + * 销售订单编号 + * + * 关联 {@link ErpSaleOrderDO#getId()} + */ + private Long orderId; + /** + * 销售订单号 + * + * 冗余 {@link ErpSaleOrderDO#getNo()} + */ + private String orderNo; + + /** + * 合计数量 + */ + private BigDecimal totalCount; + /** + * 最终合计价格,单位:元 + * + * totalPrice = totalProductPrice + totalTaxPrice - discountPrice + otherPrice + */ + private BigDecimal totalPrice; + /** + * 已退款金额,单位:元 + * + * 目的:和 {@link cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptDO} 结合,记录已退款金额 + */ + private BigDecimal refundPrice; + + /** + * 合计产品价格,单位:元 + */ + private BigDecimal totalProductPrice; + /** + * 合计税额,单位:元 + */ + private BigDecimal totalTaxPrice; + /** + * 优惠率,百分比 + */ + private BigDecimal discountPercent; + /** + * 优惠金额,单位:元 + * + * discountPrice = (totalProductPrice + totalTaxPrice) * discountPercent + */ + private BigDecimal discountPrice; + /** + * 其它金额,单位:元 + */ + private BigDecimal otherPrice; + + /** + * 附件地址 + */ + private String fileUrl; + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleReturnItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleReturnItemDO.java new file mode 100644 index 000000000..8851d157c --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleReturnItemDO.java @@ -0,0 +1,95 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.sale; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 销售退货项 DO + * + * @author 芋道源码 + */ +@TableName("erp_sale_return_items") +@KeySequence("erp_sale_return_items_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpSaleReturnItemDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 销售退货编号 + * + * 关联 {@link ErpSaleReturnDO##getId()} + */ + private Long returnId; + /** + * 销售订单项编号 + * + * 关联 {@link ErpSaleOrderItemDO#getId()} + * 目的:方便更新关联的销售订单项的退货数量 + */ + private Long orderItemId; + /** + * 仓库编号 + * + * 关联 {@link ErpWarehouseDO#getId()} + */ + private Long warehouseId; + /** + * 产品编号 + * + * 关联 {@link ErpProductDO#getId()} + */ + private Long productId; + /** + * 产品单位单位 + * + * 冗余 {@link ErpProductDO#getUnitId()} + */ + private Long productUnitId; + + /** + * 产品单位单价,单位:元 + */ + private BigDecimal productPrice; + /** + * 数量 + */ + private BigDecimal count; + /** + * 总价,单位:元 + * + * totalPrice = productPrice * count + */ + private BigDecimal totalPrice; + /** + * 税率,百分比 + */ + private BigDecimal taxPercent; + /** + * 税额,单位:元 + * + * taxPrice = totalPrice * taxPercent + */ + private BigDecimal taxPrice; + + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockCheckDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockCheckDO.java new file mode 100644 index 000000000..e9168275f --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockCheckDO.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.stock; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +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; + +/** + * ERP 库存盘点单 DO + * + * @author 芋道源码 + */ +@TableName("erp_stock_check") +@KeySequence("erp_stock_check_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpStockCheckDO extends BaseDO { + + /** + * 盘点编号 + */ + @TableId + private Long id; + /** + * 盘点单号 + */ + private String no; + /** + * 盘点时间 + */ + private LocalDateTime checkTime; + /** + * 合计数量 + */ + private BigDecimal totalCount; + /** + * 合计金额,单位:元 + */ + private BigDecimal totalPrice; + /** + * 状态 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + /** + * 附件 URL + */ + private String fileUrl; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockCheckItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockCheckItemDO.java new file mode 100644 index 000000000..c3c4dbf99 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockCheckItemDO.java @@ -0,0 +1,83 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.stock; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 库存盘点单项 DO + * + * @author 芋道源码 + */ +@TableName("erp_stock_check_item") +@KeySequence("erp_stock_check_item_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpStockCheckItemDO extends BaseDO { + + /** + * 盘点项编号 + */ + @TableId + private Long id; + /** + * 盘点编号 + * + * 关联 {@link ErpStockCheckDO#getId()} + */ + private Long checkId; + /** + * 仓库编号 + * + * 关联 {@link ErpWarehouseDO#getId()} + */ + private Long warehouseId; + /** + * 产品编号 + * + * 关联 {@link ErpProductDO#getId()} + */ + private Long productId; + /** + * 产品单位编号 + * + * 冗余 {@link ErpProductDO#getUnitId()} + */ + private Long productUnitId; + /** + * 产品单价 + */ + private BigDecimal productPrice; + /** + * 账面数量(当前库存) + */ + private BigDecimal stockCount; + /** + * 实际数量(实际库存) + */ + private BigDecimal actualCount; + /** + * 盈亏数量 + * + * count = stockCount - actualCount + */ + private BigDecimal count; + /** + * 合计金额,单位:元 + */ + private BigDecimal totalPrice; + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockDO.java new file mode 100644 index 000000000..558c6c6e5 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockDO.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.stock; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 产品库存 DO + * + * @author 芋道源码 + */ +@TableName("erp_stock") +@KeySequence("erp_stock_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpStockDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 产品编号 + * + * 关联 {@link ErpProductDO#getId()} + */ + private Long productId; + /** + * 仓库编号 + * + * 关联 {@link ErpWarehouseDO#getId()} + */ + private Long warehouseId; + /** + * 库存数量 + */ + private BigDecimal count; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockInDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockInDO.java new file mode 100644 index 000000000..ee2512ab6 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockInDO.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.stock; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO; +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; + +/** + * ERP 其它入库单 DO + * + * @author 芋道源码 + */ +@TableName("erp_stock_in") +@KeySequence("erp_stock_in_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpStockInDO extends BaseDO { + + /** + * 入库编号 + */ + @TableId + private Long id; + /** + * 入库单号 + */ + private String no; + /** + * 供应商编号 + * + * 关联 {@link ErpSupplierDO#getId()} + */ + private Long supplierId; + /** + * 入库时间 + */ + private LocalDateTime inTime; + /** + * 合计数量 + */ + private BigDecimal totalCount; + /** + * 合计金额,单位:元 + */ + private BigDecimal totalPrice; + /** + * 状态 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + /** + * 附件 URL + */ + private String fileUrl; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockInItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockInItemDO.java new file mode 100644 index 000000000..3e3ca9c5c --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockInItemDO.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.stock; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 其它入库单项 DO + * + * @author 芋道源码 + */ +@TableName("erp_stock_in_item") +@KeySequence("erp_stock_in_item_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpStockInItemDO extends BaseDO { + + /** + * 入库项编号 + */ + @TableId + private Long id; + /** + * 入库编号 + * + * 关联 {@link ErpStockInDO#getId()} + */ + private Long inId; + /** + * 仓库编号 + * + * 关联 {@link ErpWarehouseDO#getId()} + */ + private Long warehouseId; + /** + * 产品编号 + * + * 关联 {@link ErpProductDO#getId()} + */ + private Long productId; + /** + * 产品单位编号 + * + * 冗余 {@link ErpProductDO#getUnitId()} + */ + private Long productUnitId; + /** + * 产品单价 + */ + private BigDecimal productPrice; + /** + * 产品数量 + */ + private BigDecimal count; + /** + * 合计金额,单位:元 + */ + private BigDecimal totalPrice; + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveDO.java new file mode 100644 index 000000000..682b33104 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveDO.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.stock; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +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; + +/** + * ERP 库存调拨单 DO + * + * @author 芋道源码 + */ +@TableName("erp_stock_move") +@KeySequence("erp_stock_move_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpStockMoveDO extends BaseDO { + + /** + * 调拨编号 + */ + @TableId + private Long id; + /** + * 调拨单号 + */ + private String no; + /** + * 调拨时间 + */ + private LocalDateTime moveTime; + /** + * 合计数量 + */ + private BigDecimal totalCount; + /** + * 合计金额,单位:元 + */ + private BigDecimal totalPrice; + /** + * 状态 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + /** + * 附件 URL + */ + private String fileUrl; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveItemDO.java new file mode 100644 index 000000000..aee203670 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockMoveItemDO.java @@ -0,0 +1,79 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.stock; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 库存调拨单项 DO + * + * @author 芋道源码 + */ +@TableName("erp_stock_move_item") +@KeySequence("erp_stock_move_item_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpStockMoveItemDO extends BaseDO { + + /** + * 调拨项编号 + */ + @TableId + private Long id; + /** + * 调拨编号 + * + * 关联 {@link ErpStockMoveDO#getId()} + */ + private Long moveId; + /** + * 调出仓库编号 + * + * 关联 {@link ErpWarehouseDO#getId()} + */ + private Long fromWarehouseId; + /** + * 调入仓库编号 + * + * 关联 {@link ErpWarehouseDO#getId()} + */ + private Long toWarehouseId; + /** + * 产品编号 + * + * 关联 {@link ErpProductDO#getId()} + */ + private Long productId; + /** + * 产品单位编号 + * + * 冗余 {@link ErpProductDO#getUnitId()} + */ + private Long productUnitId; + /** + * 产品单价 + */ + private BigDecimal productPrice; + /** + * 产品数量 + */ + private BigDecimal count; + /** + * 合计金额,单位:元 + */ + private BigDecimal totalPrice; + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockOutDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockOutDO.java new file mode 100644 index 000000000..e0b337adb --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockOutDO.java @@ -0,0 +1,69 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.stock; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +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; + +/** + * ERP 其它出库单 DO + * + * @author 芋道源码 + */ +@TableName("erp_stock_out") +@KeySequence("erp_stock_out_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpStockOutDO extends BaseDO { + + /** + * 出库编号 + */ + @TableId + private Long id; + /** + * 出库单号 + */ + private String no; + /** + * 客户编号 + * + * TODO 芋艿:待关联 + */ + private Long customerId; + /** + * 出库时间 + */ + private LocalDateTime outTime; + /** + * 合计数量 + */ + private BigDecimal totalCount; + /** + * 合计金额,单位:元 + */ + private BigDecimal totalPrice; + /** + * 状态 + * + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + /** + * 附件 URL + */ + private String fileUrl; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockOutItemDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockOutItemDO.java new file mode 100644 index 000000000..065c5255a --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockOutItemDO.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.stock; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 其它出库单项 DO + * + * @author 芋道源码 + */ +@TableName("erp_stock_out_item") +@KeySequence("erp_stock_out_item_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpStockOutItemDO extends BaseDO { + + /** + * 出库项编号 + */ + @TableId + private Long id; + /** + * 出库编号 + * + * 关联 {@link ErpStockOutDO#getId()} + */ + private Long outId; + /** + * 仓库编号 + * + * 关联 {@link ErpWarehouseDO#getId()} + */ + private Long warehouseId; + /** + * 产品编号 + * + * 关联 {@link ErpProductDO#getId()} + */ + private Long productId; + /** + * 产品单位编号 + * + * 冗余 {@link ErpProductDO#getUnitId()} + */ + private Long productUnitId; + /** + * 产品单价 + */ + private BigDecimal productPrice; + /** + * 产品数量 + */ + private BigDecimal count; + /** + * 合计金额,单位:元 + */ + private BigDecimal totalPrice; + /** + * 备注 + */ + private String remark; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockRecordDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockRecordDO.java new file mode 100644 index 000000000..7bc5e5a01 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpStockRecordDO.java @@ -0,0 +1,82 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.stock; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 产品库存明细 DO + * + * @author 芋道源码 + */ +@TableName("erp_stock_record") +@KeySequence("erp_stock_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpStockRecordDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 产品编号 + * + * 关联 {@link ErpProductDO#getId()} + */ + private Long productId; + /** + * 仓库编号 + * + * 关联 {@link ErpWarehouseDO#getId()} + */ + private Long warehouseId; + /** + * 出入库数量 + * + * 正数,表示入库;负数,表示出库 + */ + private BigDecimal count; + /** + * 总库存量 + * + * 出入库之后,目前的库存量 + */ + private BigDecimal totalCount; + /** + * 业务类型 + * + * 枚举 {@link ErpStockRecordBizTypeEnum} + */ + private Integer bizType; + /** + * 业务编号 + * + * 例如说:{@link ErpStockInDO#getId()} + */ + private Long bizId; + /** + * 业务项编号 + * + * 例如说:{@link ErpStockInItemDO#getId()} + */ + private Long bizItemId; + /** + * 业务单号 + * + * 例如说:{@link ErpStockInDO#getNo()} + */ + private String bizNo; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpWarehouseDO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpWarehouseDO.java new file mode 100644 index 000000000..4f206173f --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/stock/ErpWarehouseDO.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.erp.dal.dataobject.stock; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * ERP 仓库 DO + * + * @author 芋道源码 + */ +@TableName("erp_warehouse") +@KeySequence("erp_warehouse_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ErpWarehouseDO extends BaseDO { + + /** + * 仓库编号 + */ + @TableId + private Long id; + /** + * 仓库名称 + */ + private String name; + /** + * 仓库地址 + */ + private String address; + /** + * 排序 + */ + private Long sort; + /** + * 备注 + */ + private String remark; + /** + * 负责人 + */ + private String principal; + /** + * 仓储费,单位:元 + */ + private BigDecimal warehousePrice; + /** + * 搬运费,单位:元 + */ + private BigDecimal truckagePrice; + /** + * 开启状态 + * + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} + */ + private Integer status; + /** + * 是否默认 + */ + private Boolean defaultStatus; + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpAccountMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpAccountMapper.java new file mode 100644 index 000000000..2f98147e2 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpAccountMapper.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.finance; + +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.erp.controller.admin.finance.vo.account.ErpAccountPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * ERP 结算账户 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpAccountMapper extends BaseMapperX { + + default PageResult selectPage(ErpAccountPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ErpAccountDO::getName, reqVO.getName()) + .likeIfPresent(ErpAccountDO::getNo, reqVO.getNo()) + .eqIfPresent(ErpAccountDO::getRemark, reqVO.getRemark()) + .orderByDesc(ErpAccountDO::getId)); + } + + default ErpAccountDO selectByDefaultStatus() { + return selectOne(ErpAccountDO::getDefaultStatus, true); + } + + default List selectListByStatus(Integer status) { + return selectList(ErpAccountDO::getStatus, status); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinancePaymentItemMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinancePaymentItemMapper.java new file mode 100644 index 000000000..7787e8d70 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinancePaymentItemMapper.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.finance; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentItemDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * ERP 付款单项 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpFinancePaymentItemMapper extends BaseMapperX { + + default List selectListByPaymentId(Long paymentId) { + return selectList(ErpFinancePaymentItemDO::getPaymentId, paymentId); + } + + default List selectListByPaymentIds(Collection paymentIds) { + return selectList(ErpFinancePaymentItemDO::getPaymentId, paymentIds); + } + + default BigDecimal selectPaymentPriceSumByBizIdAndBizType(Long bizId, Integer bizType) { + // SQL sum 查询 + List> result = selectMaps(new QueryWrapper() + .select("SUM(payment_price) AS paymentPriceSum") + .eq("biz_id", bizId) + .eq("biz_type", bizType)); + // 获得数量 + if (CollUtil.isEmpty(result)) { + return BigDecimal.ZERO; + } + return BigDecimal.valueOf(MapUtil.getDouble(result.get(0), "paymentPriceSum", 0D)); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinancePaymentMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinancePaymentMapper.java new file mode 100644 index 000000000..5ad0cccfd --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinancePaymentMapper.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.finance; + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentItemDO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * ERP 付款单 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpFinancePaymentMapper extends BaseMapperX { + + default PageResult selectPage(ErpFinancePaymentPageReqVO reqVO) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX() + .likeIfPresent(ErpFinancePaymentDO::getNo, reqVO.getNo()) + .betweenIfPresent(ErpFinancePaymentDO::getPaymentTime, reqVO.getPaymentTime()) + .eqIfPresent(ErpFinancePaymentDO::getSupplierId, reqVO.getSupplierId()) + .eqIfPresent(ErpFinancePaymentDO::getCreator, reqVO.getCreator()) + .eqIfPresent(ErpFinancePaymentDO::getFinanceUserId, reqVO.getFinanceUserId()) + .eqIfPresent(ErpFinancePaymentDO::getAccountId, reqVO.getAccountId()) + .eqIfPresent(ErpFinancePaymentDO::getStatus, reqVO.getStatus()) + .likeIfPresent(ErpFinancePaymentDO::getRemark, reqVO.getRemark()) + .orderByDesc(ErpFinancePaymentDO::getId); + if (reqVO.getBizNo() != null) { + query.leftJoin(ErpFinancePaymentItemDO.class, ErpFinancePaymentItemDO::getPaymentId, ErpFinancePaymentDO::getId) + .eq(reqVO.getBizNo() != null, ErpFinancePaymentItemDO::getBizNo, reqVO.getBizNo()) + .groupBy(ErpFinancePaymentDO::getId); // 避免 1 对多查询,产生相同的 1 + } + return selectJoinPage(reqVO, ErpFinancePaymentDO.class, query); + } + + default int updateByIdAndStatus(Long id, Integer status, ErpFinancePaymentDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ErpFinancePaymentDO::getId, id).eq(ErpFinancePaymentDO::getStatus, status)); + } + + default ErpFinancePaymentDO selectByNo(String no) { + return selectOne(ErpFinancePaymentDO::getNo, no); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinanceReceiptItemMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinanceReceiptItemMapper.java new file mode 100644 index 000000000..cb6082b0e --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinanceReceiptItemMapper.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.finance; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptItemDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * ERP 收款单项 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpFinanceReceiptItemMapper extends BaseMapperX { + + default List selectListByReceiptId(Long receiptId) { + return selectList(ErpFinanceReceiptItemDO::getReceiptId, receiptId); + } + + default List selectListByReceiptIds(Collection receiptIds) { + return selectList(ErpFinanceReceiptItemDO::getReceiptId, receiptIds); + } + + default BigDecimal selectReceiptPriceSumByBizIdAndBizType(Long bizId, Integer bizType) { + // SQL sum 查询 + List> result = selectMaps(new QueryWrapper() + .select("SUM(receipt_price) AS receiptPriceSum") + .eq("biz_id", bizId) + .eq("biz_type", bizType)); + // 获得数量 + if (CollUtil.isEmpty(result)) { + return BigDecimal.ZERO; + } + return BigDecimal.valueOf(MapUtil.getDouble(result.get(0), "receiptPriceSum", 0D)); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinanceReceiptMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinanceReceiptMapper.java new file mode 100644 index 000000000..d895adea4 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinanceReceiptMapper.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.finance; + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt.ErpFinanceReceiptPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptItemDO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * ERP 收款单 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpFinanceReceiptMapper extends BaseMapperX { + + default PageResult selectPage(ErpFinanceReceiptPageReqVO reqVO) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX() + .likeIfPresent(ErpFinanceReceiptDO::getNo, reqVO.getNo()) + .betweenIfPresent(ErpFinanceReceiptDO::getReceiptTime, reqVO.getReceiptTime()) + .eqIfPresent(ErpFinanceReceiptDO::getCustomerId, reqVO.getCustomerId()) + .eqIfPresent(ErpFinanceReceiptDO::getCreator, reqVO.getCreator()) + .eqIfPresent(ErpFinanceReceiptDO::getFinanceUserId, reqVO.getFinanceUserId()) + .eqIfPresent(ErpFinanceReceiptDO::getAccountId, reqVO.getAccountId()) + .eqIfPresent(ErpFinanceReceiptDO::getStatus, reqVO.getStatus()) + .likeIfPresent(ErpFinanceReceiptDO::getRemark, reqVO.getRemark()) + .orderByDesc(ErpFinanceReceiptDO::getId); + if (reqVO.getBizNo() != null) { + query.leftJoin(ErpFinanceReceiptItemDO.class, ErpFinanceReceiptItemDO::getReceiptId, ErpFinanceReceiptDO::getId) + .eq(reqVO.getBizNo() != null, ErpFinanceReceiptItemDO::getBizNo, reqVO.getBizNo()) + .groupBy(ErpFinanceReceiptDO::getId); // 避免 1 对多查询,产生相同的 1 + } + return selectJoinPage(reqVO, ErpFinanceReceiptDO.class, query); + } + + default int updateByIdAndStatus(Long id, Integer status, ErpFinanceReceiptDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ErpFinanceReceiptDO::getId, id).eq(ErpFinanceReceiptDO::getStatus, status)); + } + + default ErpFinanceReceiptDO selectByNo(String no) { + return selectOne(ErpFinanceReceiptDO::getNo, no); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductCategoryMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductCategoryMapper.java new file mode 100644 index 000000000..70bbc6429 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductCategoryMapper.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.product; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.category.ErpProductCategoryListReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductCategoryDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * ERP 产品分类 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpProductCategoryMapper extends BaseMapperX { + + default List selectList(ErpProductCategoryListReqVO reqVO) { + return selectList(new LambdaQueryWrapperX() + .likeIfPresent(ErpProductCategoryDO::getName, reqVO.getName()) + .eqIfPresent(ErpProductCategoryDO::getStatus, reqVO.getStatus()) + .orderByDesc(ErpProductCategoryDO::getId)); + } + + default ErpProductCategoryDO selectByParentIdAndName(Long parentId, String name) { + return selectOne(ErpProductCategoryDO::getParentId, parentId, ErpProductCategoryDO::getName, name); + } + + default Long selectCountByParentId(Long parentId) { + return selectCount(ErpProductCategoryDO::getParentId, parentId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductMapper.java new file mode 100644 index 000000000..ff491fe22 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductMapper.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.product; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * ERP 产品 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpProductMapper extends BaseMapperX { + + default PageResult selectPage(ErpProductPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ErpProductDO::getName, reqVO.getName()) + .eqIfPresent(ErpProductDO::getCategoryId, reqVO.getCategoryId()) + .betweenIfPresent(ErpProductDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ErpProductDO::getId)); + } + + default Long selectCountByCategoryId(Long categoryId) { + return selectCount(ErpProductDO::getCategoryId, categoryId); + } + + default Long selectCountByUnitId(Long unitId) { + return selectCount(ErpProductDO::getUnitId, unitId); + } + + default List selectListByStatus(Integer status) { + return selectList(ErpProductDO::getStatus, status); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductUnitMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductUnitMapper.java new file mode 100644 index 000000000..45db6a829 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/product/ErpProductUnitMapper.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.product; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * ERP 产品单位 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpProductUnitMapper extends BaseMapperX { + + default PageResult selectPage(ErpProductUnitPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ErpProductUnitDO::getName, reqVO.getName()) + .eqIfPresent(ErpProductUnitDO::getStatus, reqVO.getStatus()) + .orderByDesc(ErpProductUnitDO::getId)); + } + + default ErpProductUnitDO selectByName(String name) { + return selectOne(ErpProductUnitDO::getName, name); + } + + default List selectListByStatus(Integer status) { + return selectList(ErpProductUnitDO::getStatus, status); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInItemMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInItemMapper.java new file mode 100644 index 000000000..9140f9548 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInItemMapper.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.purchase; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInItemDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * ERP 采购入库项 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpPurchaseInItemMapper extends BaseMapperX { + + default List selectListByInId(Long inId) { + return selectList(ErpPurchaseInItemDO::getInId, inId); + } + + default List selectListByInIds(Collection inIds) { + return selectList(ErpPurchaseInItemDO::getInId, inIds); + } + + default int deleteByInId(Long inId) { + return delete(ErpPurchaseInItemDO::getInId, inId); + } + + /** + * 基于采购订单编号,查询每个采购订单项的入库数量之和 + * + * @param inIds 入库订单项编号数组 + * @return key:采购订单项编号;value:入库数量之和 + */ + default Map selectOrderItemCountSumMapByInIds(Collection inIds) { + if (CollUtil.isEmpty(inIds)) { + return Collections.emptyMap(); + } + // SQL sum 查询 + List> result = selectMaps(new QueryWrapper() + .select("order_item_id, SUM(count) AS sumCount") + .groupBy("order_item_id") + .in("in_id", inIds)); + // 获得数量 + return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sumCount")); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInMapper.java new file mode 100644 index 000000000..c155d8cbe --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInMapper.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.purchase; + + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInItemDO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; +import java.util.Objects; + +/** + * ERP 采购入库 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpPurchaseInMapper extends BaseMapperX { + + default PageResult selectPage(ErpPurchaseInPageReqVO reqVO) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX() + .likeIfPresent(ErpPurchaseInDO::getNo, reqVO.getNo()) + .eqIfPresent(ErpPurchaseInDO::getSupplierId, reqVO.getSupplierId()) + .betweenIfPresent(ErpPurchaseInDO::getInTime, reqVO.getInTime()) + .eqIfPresent(ErpPurchaseInDO::getStatus, reqVO.getStatus()) + .likeIfPresent(ErpPurchaseInDO::getRemark, reqVO.getRemark()) + .eqIfPresent(ErpPurchaseInDO::getCreator, reqVO.getCreator()) + .eqIfPresent(ErpPurchaseInDO::getAccountId, reqVO.getAccountId()) + .likeIfPresent(ErpPurchaseInDO::getOrderNo, reqVO.getOrderNo()) + .orderByDesc(ErpPurchaseInDO::getId); + // 付款状态。为什么需要 t. 的原因,是因为联表查询时,需要指定表名,不然会报字段不存在的错误 + if (Objects.equals(reqVO.getPaymentStatus(), ErpPurchaseInPageReqVO.PAYMENT_STATUS_NONE)) { + query.eq(ErpPurchaseInDO::getPaymentPrice, 0); + } else if (Objects.equals(reqVO.getPaymentStatus(), ErpPurchaseInPageReqVO.PAYMENT_STATUS_PART)) { + query.gt(ErpPurchaseInDO::getPaymentPrice, 0).apply("t.payment_price < t.total_price"); + } else if (Objects.equals(reqVO.getPaymentStatus(), ErpPurchaseInPageReqVO.PAYMENT_STATUS_ALL)) { + query.apply("t.payment_price = t.total_price"); + } + if (Boolean.TRUE.equals(reqVO.getPaymentEnable())) { + query.eq(ErpPurchaseInDO::getStatus, ErpAuditStatus.APPROVE.getStatus()) + .apply("t.payment_price < t.total_price"); + } + if (reqVO.getWarehouseId() != null || reqVO.getProductId() != null) { + query.leftJoin(ErpPurchaseInItemDO.class, ErpPurchaseInItemDO::getInId, ErpPurchaseInDO::getId) + .eq(reqVO.getWarehouseId() != null, ErpPurchaseInItemDO::getWarehouseId, reqVO.getWarehouseId()) + .eq(reqVO.getProductId() != null, ErpPurchaseInItemDO::getProductId, reqVO.getProductId()) + .groupBy(ErpPurchaseInDO::getId); // 避免 1 对多查询,产生相同的 1 + } + return selectJoinPage(reqVO, ErpPurchaseInDO.class, query); + } + + default int updateByIdAndStatus(Long id, Integer status, ErpPurchaseInDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ErpPurchaseInDO::getId, id).eq(ErpPurchaseInDO::getStatus, status)); + } + + default ErpPurchaseInDO selectByNo(String no) { + return selectOne(ErpPurchaseInDO::getNo, no); + } + + default List selectListByOrderId(Long orderId) { + return selectList(ErpPurchaseInDO::getOrderId, orderId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderItemMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderItemMapper.java new file mode 100644 index 000000000..17f1fe290 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderItemMapper.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.purchase; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * ERP 采购订单明项目 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpPurchaseOrderItemMapper extends BaseMapperX { + + default List selectListByOrderId(Long orderId) { + return selectList(ErpPurchaseOrderItemDO::getOrderId, orderId); + } + + default List selectListByOrderIds(Collection orderIds) { + return selectList(ErpPurchaseOrderItemDO::getOrderId, orderIds); + } + + default int deleteByOrderId(Long orderId) { + return delete(ErpPurchaseOrderItemDO::getOrderId, orderId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java new file mode 100644 index 000000000..01f0303f9 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.purchase; + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Objects; + +/** + * ERP 采购订单 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpPurchaseOrderMapper extends BaseMapperX { + + default PageResult selectPage(ErpPurchaseOrderPageReqVO reqVO) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX() + .likeIfPresent(ErpPurchaseOrderDO::getNo, reqVO.getNo()) + .eqIfPresent(ErpPurchaseOrderDO::getSupplierId, reqVO.getSupplierId()) + .betweenIfPresent(ErpPurchaseOrderDO::getOrderTime, reqVO.getOrderTime()) + .eqIfPresent(ErpPurchaseOrderDO::getStatus, reqVO.getStatus()) + .likeIfPresent(ErpPurchaseOrderDO::getRemark, reqVO.getRemark()) + .eqIfPresent(ErpPurchaseOrderDO::getCreator, reqVO.getCreator()) + .orderByDesc(ErpPurchaseOrderDO::getId); + // 入库状态。为什么需要 t. 的原因,是因为联表查询时,需要指定表名,不然会报 in_count 错误 + if (Objects.equals(reqVO.getInStatus(), ErpPurchaseOrderPageReqVO.IN_STATUS_NONE)) { + query.eq(ErpPurchaseOrderDO::getInCount, 0); + } else if (Objects.equals(reqVO.getInStatus(), ErpPurchaseOrderPageReqVO.IN_STATUS_PART)) { + query.gt(ErpPurchaseOrderDO::getInCount, 0).apply("t.in_count < t.total_count"); + } else if (Objects.equals(reqVO.getInStatus(), ErpPurchaseOrderPageReqVO.IN_STATUS_ALL)) { + query.apply("t.in_count = t.total_count"); + } + // 退货状态 + if (Objects.equals(reqVO.getReturnStatus(), ErpPurchaseOrderPageReqVO.RETURN_STATUS_NONE)) { + query.eq(ErpPurchaseOrderDO::getReturnCount, 0); + } else if (Objects.equals(reqVO.getReturnStatus(), ErpPurchaseOrderPageReqVO.RETURN_STATUS_PART)) { + query.gt(ErpPurchaseOrderDO::getReturnCount, 0).apply("t.return_count < t.total_count"); + } else if (Objects.equals(reqVO.getReturnStatus(), ErpPurchaseOrderPageReqVO.RETURN_STATUS_ALL)) { + query.apply("t.return_count = t.total_count"); + } + // 可采购入库 + if (Boolean.TRUE.equals(reqVO.getInEnable())) { + query.eq(ErpPurchaseOrderDO::getStatus, ErpAuditStatus.APPROVE.getStatus()) + .apply("t.in_count < t.total_count"); + } + // 可采购退货 + if (Boolean.TRUE.equals(reqVO.getReturnEnable())) { + query.eq(ErpPurchaseOrderDO::getStatus, ErpAuditStatus.APPROVE.getStatus()) + .apply("t.return_count < t.in_count"); + } + if (reqVO.getProductId() != null) { + query.leftJoin(ErpPurchaseOrderItemDO.class, ErpPurchaseOrderItemDO::getOrderId, ErpPurchaseOrderDO::getId) + .eq(reqVO.getProductId() != null, ErpPurchaseOrderItemDO::getProductId, reqVO.getProductId()) + .groupBy(ErpPurchaseOrderDO::getId); // 避免 1 对多查询,产生相同的 1 + } + return selectJoinPage(reqVO, ErpPurchaseOrderDO.class, query); + } + + default int updateByIdAndStatus(Long id, Integer status, ErpPurchaseOrderDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ErpPurchaseOrderDO::getId, id).eq(ErpPurchaseOrderDO::getStatus, status)); + } + + default ErpPurchaseOrderDO selectByNo(String no) { + return selectOne(ErpPurchaseOrderDO::getNo, no); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseReturnItemMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseReturnItemMapper.java new file mode 100644 index 000000000..2a8011900 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseReturnItemMapper.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.purchase; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnItemDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * ERP 采购退货项 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpPurchaseReturnItemMapper extends BaseMapperX { + + default List selectListByReturnId(Long returnId) { + return selectList(ErpPurchaseReturnItemDO::getReturnId, returnId); + } + + default List selectListByReturnIds(Collection returnIds) { + return selectList(ErpPurchaseReturnItemDO::getReturnId, returnIds); + } + + default int deleteByReturnId(Long returnId) { + return delete(ErpPurchaseReturnItemDO::getReturnId, returnId); + } + + /** + * 基于采购订单编号,查询每个采购订单项的退货数量之和 + * + * @param returnIds 入库订单项编号数组 + * @return key:采购订单项编号;value:退货数量之和 + */ + default Map selectOrderItemCountSumMapByReturnIds(Collection returnIds) { + if (CollUtil.isEmpty(returnIds)) { + return Collections.emptyMap(); + } + // SQL sum 查询 + List> result = selectMaps(new QueryWrapper() + .select("order_item_id, SUM(count) AS sumCount") + .groupBy("order_item_id") + .in("return_id", returnIds)); + // 获得数量 + return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sumCount")); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseReturnMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseReturnMapper.java new file mode 100644 index 000000000..689a55dfd --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseReturnMapper.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.purchase; + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnItemDO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; +import java.util.Objects; + +/** + * ERP 采购退货 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpPurchaseReturnMapper extends BaseMapperX { + + default PageResult selectPage(ErpPurchaseReturnPageReqVO reqVO) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX() + .likeIfPresent(ErpPurchaseReturnDO::getNo, reqVO.getNo()) + .eqIfPresent(ErpPurchaseReturnDO::getSupplierId, reqVO.getSupplierId()) + .betweenIfPresent(ErpPurchaseReturnDO::getReturnTime, reqVO.getReturnTime()) + .eqIfPresent(ErpPurchaseReturnDO::getStatus, reqVO.getStatus()) + .likeIfPresent(ErpPurchaseReturnDO::getRemark, reqVO.getRemark()) + .eqIfPresent(ErpPurchaseReturnDO::getCreator, reqVO.getCreator()) + .eqIfPresent(ErpPurchaseReturnDO::getAccountId, reqVO.getAccountId()) + .likeIfPresent(ErpPurchaseReturnDO::getOrderNo, reqVO.getOrderNo()) + .orderByDesc(ErpPurchaseReturnDO::getId); + // 退款状态。为什么需要 t. 的原因,是因为联表查询时,需要指定表名,不然会报字段不存在的错误 + if (Objects.equals(reqVO.getRefundStatus(), ErpPurchaseReturnPageReqVO.REFUND_STATUS_NONE)) { + query.eq(ErpPurchaseReturnDO::getRefundPrice, 0); + } else if (Objects.equals(reqVO.getRefundStatus(), ErpPurchaseReturnPageReqVO.REFUND_STATUS_PART)) { + query.gt(ErpPurchaseReturnDO::getRefundPrice, 0).apply("t.refund_price < t.total_price"); + } else if (Objects.equals(reqVO.getRefundStatus(), ErpPurchaseReturnPageReqVO.REFUND_STATUS_ALL)) { + query.apply("t.refund_price = t.total_price"); + } + if (Boolean.TRUE.equals(reqVO.getRefundEnable())) { + query.eq(ErpPurchaseInDO::getStatus, ErpAuditStatus.APPROVE.getStatus()) + .apply("t.refund_price < t.total_price"); + } + if (reqVO.getWarehouseId() != null || reqVO.getProductId() != null) { + query.leftJoin(ErpPurchaseReturnItemDO.class, ErpPurchaseReturnItemDO::getReturnId, ErpPurchaseReturnDO::getId) + .eq(reqVO.getWarehouseId() != null, ErpPurchaseReturnItemDO::getWarehouseId, reqVO.getWarehouseId()) + .eq(reqVO.getProductId() != null, ErpPurchaseReturnItemDO::getProductId, reqVO.getProductId()) + .groupBy(ErpPurchaseReturnDO::getId); // 避免 1 对多查询,产生相同的 1 + } + return selectJoinPage(reqVO, ErpPurchaseReturnDO.class, query); + } + + default int updateByIdAndStatus(Long id, Integer status, ErpPurchaseReturnDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ErpPurchaseReturnDO::getId, id).eq(ErpPurchaseReturnDO::getStatus, status)); + } + + default ErpPurchaseReturnDO selectByNo(String no) { + return selectOne(ErpPurchaseReturnDO::getNo, no); + } + + default List selectListByOrderId(Long orderId) { + return selectList(ErpPurchaseReturnDO::getOrderId, orderId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpSupplierMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpSupplierMapper.java new file mode 100644 index 000000000..c74f1e6f8 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpSupplierMapper.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.purchase; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier.ErpSupplierPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * ERP 供应商 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpSupplierMapper extends BaseMapperX { + + default PageResult selectPage(ErpSupplierPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ErpSupplierDO::getName, reqVO.getName()) + .likeIfPresent(ErpSupplierDO::getMobile, reqVO.getMobile()) + .likeIfPresent(ErpSupplierDO::getTelephone, reqVO.getTelephone()) + .orderByDesc(ErpSupplierDO::getId)); + } + + default List selectListByStatus(Integer status) { + return selectList(ErpSupplierDO::getStatus, status); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpCustomerMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpCustomerMapper.java new file mode 100644 index 000000000..4970f9ad5 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpCustomerMapper.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.sale; + +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.erp.controller.admin.sale.vo.customer.ErpCustomerPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpCustomerDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * ERP 客户 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpCustomerMapper extends BaseMapperX { + + default PageResult selectPage(ErpCustomerPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ErpCustomerDO::getName, reqVO.getName()) + .eqIfPresent(ErpCustomerDO::getMobile, reqVO.getMobile()) + .eqIfPresent(ErpCustomerDO::getTelephone, reqVO.getTelephone()) + .orderByDesc(ErpCustomerDO::getId)); + } + + default List selectListByStatus(Integer status) { + return selectList(ErpCustomerDO::getStatus, status); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderItemMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderItemMapper.java new file mode 100644 index 000000000..d2825e563 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderItemMapper.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.sale; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * ERP 销售订单明项目 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpSaleOrderItemMapper extends BaseMapperX { + + default List selectListByOrderId(Long orderId) { + return selectList(ErpSaleOrderItemDO::getOrderId, orderId); + } + + default List selectListByOrderIds(Collection orderIds) { + return selectList(ErpSaleOrderItemDO::getOrderId, orderIds); + } + + default int deleteByOrderId(Long orderId) { + return delete(ErpSaleOrderItemDO::getOrderId, orderId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java new file mode 100644 index 000000000..8ed3b6fcd --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.sale; + + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Objects; + +/** + * ERP 销售订单 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpSaleOrderMapper extends BaseMapperX { + + default PageResult selectPage(ErpSaleOrderPageReqVO reqVO) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX() + .likeIfPresent(ErpSaleOrderDO::getNo, reqVO.getNo()) + .eqIfPresent(ErpSaleOrderDO::getCustomerId, reqVO.getCustomerId()) + .betweenIfPresent(ErpSaleOrderDO::getOrderTime, reqVO.getOrderTime()) + .eqIfPresent(ErpSaleOrderDO::getStatus, reqVO.getStatus()) + .likeIfPresent(ErpSaleOrderDO::getRemark, reqVO.getRemark()) + .eqIfPresent(ErpSaleOrderDO::getCreator, reqVO.getCreator()) + .orderByDesc(ErpSaleOrderDO::getId); + // 入库状态。为什么需要 t. 的原因,是因为联表查询时,需要指定表名,不然会报 out_count 错误 + if (Objects.equals(reqVO.getOutStatus(), ErpSaleOrderPageReqVO.OUT_STATUS_NONE)) { + query.eq(ErpSaleOrderDO::getOutCount, 0); + } else if (Objects.equals(reqVO.getOutStatus(), ErpSaleOrderPageReqVO.OUT_STATUS_PART)) { + query.gt(ErpSaleOrderDO::getOutCount, 0).apply("t.out_count < t.total_count"); + } else if (Objects.equals(reqVO.getOutStatus(), ErpSaleOrderPageReqVO.OUT_STATUS_ALL)) { + query.apply("t.out_count = t.total_count"); + } + // 退货状态 + if (Objects.equals(reqVO.getReturnStatus(), ErpSaleOrderPageReqVO.RETURN_STATUS_NONE)) { + query.eq(ErpSaleOrderDO::getReturnCount, 0); + } else if (Objects.equals(reqVO.getReturnStatus(), ErpSaleOrderPageReqVO.RETURN_STATUS_PART)) { + query.gt(ErpSaleOrderDO::getReturnCount, 0).apply("t.return_count < t.total_count"); + } else if (Objects.equals(reqVO.getReturnStatus(), ErpSaleOrderPageReqVO.RETURN_STATUS_ALL)) { + query.apply("t.return_count = t.total_count"); + } + // 可销售出库 + if (Boolean.TRUE.equals(reqVO.getOutEnable())) { + query.eq(ErpSaleOrderDO::getStatus, ErpAuditStatus.APPROVE.getStatus()) + .apply("t.out_count < t.total_count"); + } + // 可销售退货 + if (Boolean.TRUE.equals(reqVO.getReturnEnable())) { + query.eq(ErpSaleOrderDO::getStatus, ErpAuditStatus.APPROVE.getStatus()) + .apply("t.return_count < t.out_count"); + } + if (reqVO.getProductId() != null) { + query.leftJoin(ErpSaleOrderItemDO.class, ErpSaleOrderItemDO::getOrderId, ErpSaleOrderDO::getId) + .eq(reqVO.getProductId() != null, ErpSaleOrderItemDO::getProductId, reqVO.getProductId()) + .groupBy(ErpSaleOrderDO::getId); // 避免 1 对多查询,产生相同的 1 + } + return selectJoinPage(reqVO, ErpSaleOrderDO.class, query); + } + + default int updateByIdAndStatus(Long id, Integer status, ErpSaleOrderDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ErpSaleOrderDO::getId, id).eq(ErpSaleOrderDO::getStatus, status)); + } + + default ErpSaleOrderDO selectByNo(String no) { + return selectOne(ErpSaleOrderDO::getNo, no); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutItemMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutItemMapper.java new file mode 100644 index 000000000..9cd5dede0 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutItemMapper.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.sale; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutItemDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * ERP 销售出库项 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpSaleOutItemMapper extends BaseMapperX { + + default List selectListByOutId(Long outId) { + return selectList(ErpSaleOutItemDO::getOutId, outId); + } + + default List selectListByOutIds(Collection outIds) { + return selectList(ErpSaleOutItemDO::getOutId, outIds); + } + + default int deleteByOutId(Long outId) { + return delete(ErpSaleOutItemDO::getOutId, outId); + } + + /** + * 基于销售订单编号,查询每个销售订单项的出库数量之和 + * + * @param outIds 出库订单项编号数组 + * @return key:销售订单项编号;value:出库数量之和 + */ + default Map selectOrderItemCountSumMapByOutIds(Collection outIds) { + if (CollUtil.isEmpty(outIds)) { + return Collections.emptyMap(); + } + // SQL sum 查询 + List> result = selectMaps(new QueryWrapper() + .select("order_item_id, SUM(count) AS sumCount") + .groupBy("order_item_id") + .in("out_id", outIds)); + // 获得数量 + return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sumCount")); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutMapper.java new file mode 100644 index 000000000..128913fcd --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOutMapper.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.sale; + + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutItemDO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; +import java.util.Objects; + +/** + * ERP 销售出库 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpSaleOutMapper extends BaseMapperX { + + default PageResult selectPage(ErpSaleOutPageReqVO reqVO) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX() + .likeIfPresent(ErpSaleOutDO::getNo, reqVO.getNo()) + .eqIfPresent(ErpSaleOutDO::getCustomerId, reqVO.getCustomerId()) + .betweenIfPresent(ErpSaleOutDO::getOutTime, reqVO.getOutTime()) + .eqIfPresent(ErpSaleOutDO::getStatus, reqVO.getStatus()) + .likeIfPresent(ErpSaleOutDO::getRemark, reqVO.getRemark()) + .eqIfPresent(ErpSaleOutDO::getCreator, reqVO.getCreator()) + .eqIfPresent(ErpSaleOutDO::getAccountId, reqVO.getAccountId()) + .likeIfPresent(ErpSaleOutDO::getOrderNo, reqVO.getOrderNo()) + .orderByDesc(ErpSaleOutDO::getId); + // 收款状态。为什么需要 t. 的原因,是因为联表查询时,需要指定表名,不然会报字段不存在的错误 + if (Objects.equals(reqVO.getReceiptStatus(), ErpSaleOutPageReqVO.RECEIPT_STATUS_NONE)) { + query.eq(ErpSaleOutDO::getReceiptPrice, 0); + } else if (Objects.equals(reqVO.getReceiptStatus(), ErpSaleOutPageReqVO.RECEIPT_STATUS_PART)) { + query.gt(ErpSaleOutDO::getReceiptPrice, 0).apply("t.receipt_price < t.total_price"); + } else if (Objects.equals(reqVO.getReceiptStatus(), ErpSaleOutPageReqVO.RECEIPT_STATUS_ALL)) { + query.apply("t.receipt_price = t.total_price"); + } + if (Boolean.TRUE.equals(reqVO.getReceiptEnable())) { + query.eq(ErpSaleOutDO::getStatus, ErpAuditStatus.APPROVE.getStatus()) + .apply("t.receipt_price < t.total_price"); + } + if (reqVO.getWarehouseId() != null || reqVO.getProductId() != null) { + query.leftJoin(ErpSaleOutItemDO.class, ErpSaleOutItemDO::getOutId, ErpSaleOutDO::getId) + .eq(reqVO.getWarehouseId() != null, ErpSaleOutItemDO::getWarehouseId, reqVO.getWarehouseId()) + .eq(reqVO.getProductId() != null, ErpSaleOutItemDO::getProductId, reqVO.getProductId()) + .groupBy(ErpSaleOutDO::getId); // 避免 1 对多查询,产生相同的 1 + } + return selectJoinPage(reqVO, ErpSaleOutDO.class, query); + } + + default int updateByIdAndStatus(Long id, Integer status, ErpSaleOutDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ErpSaleOutDO::getId, id).eq(ErpSaleOutDO::getStatus, status)); + } + + default ErpSaleOutDO selectByNo(String no) { + return selectOne(ErpSaleOutDO::getNo, no); + } + + default List selectListByOrderId(Long orderId) { + return selectList(ErpSaleOutDO::getOrderId, orderId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleReturnItemMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleReturnItemMapper.java new file mode 100644 index 000000000..fdc572964 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleReturnItemMapper.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.sale; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleReturnItemDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * ERP 销售退货项 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpSaleReturnItemMapper extends BaseMapperX { + + default List selectListByReturnId(Long returnId) { + return selectList(ErpSaleReturnItemDO::getReturnId, returnId); + } + + default List selectListByReturnIds(Collection returnIds) { + return selectList(ErpSaleReturnItemDO::getReturnId, returnIds); + } + + default int deleteByReturnId(Long returnId) { + return delete(ErpSaleReturnItemDO::getReturnId, returnId); + } + + /** + * 基于销售订单编号,查询每个销售订单项的退货数量之和 + * + * @param returnIds 出库订单项编号数组 + * @return key:销售订单项编号;value:退货数量之和 + */ + default Map selectOrderItemCountSumMapByReturnIds(Collection returnIds) { + if (CollUtil.isEmpty(returnIds)) { + return Collections.emptyMap(); + } + // SQL sum 查询 + List> result = selectMaps(new QueryWrapper() + .select("order_item_id, SUM(count) AS sumCount") + .groupBy("order_item_id") + .in("return_id", returnIds)); + // 获得数量 + return convertMap(result, obj -> (Long) obj.get("order_item_id"), obj -> (BigDecimal) obj.get("sumCount")); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleReturnMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleReturnMapper.java new file mode 100644 index 000000000..867b45499 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleReturnMapper.java @@ -0,0 +1,71 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.sale; + + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.returns.ErpSaleReturnPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleReturnDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleReturnItemDO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; +import java.util.Objects; + +/** + * ERP 销售退货 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpSaleReturnMapper extends BaseMapperX { + + default PageResult selectPage(ErpSaleReturnPageReqVO reqVO) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX() + .likeIfPresent(ErpSaleReturnDO::getNo, reqVO.getNo()) + .eqIfPresent(ErpSaleReturnDO::getCustomerId, reqVO.getCustomerId()) + .betweenIfPresent(ErpSaleReturnDO::getReturnTime, reqVO.getReturnTime()) + .eqIfPresent(ErpSaleReturnDO::getStatus, reqVO.getStatus()) + .likeIfPresent(ErpSaleReturnDO::getRemark, reqVO.getRemark()) + .eqIfPresent(ErpSaleReturnDO::getCreator, reqVO.getCreator()) + .eqIfPresent(ErpSaleReturnDO::getAccountId, reqVO.getAccountId()) + .likeIfPresent(ErpSaleReturnDO::getOrderNo, reqVO.getOrderNo()) + .orderByDesc(ErpSaleReturnDO::getId); + // 退款状态。为什么需要 t. 的原因,是因为联表查询时,需要指定表名,不然会报字段不存在的错误 + if (Objects.equals(reqVO.getRefundStatus(), ErpSaleReturnPageReqVO.REFUND_STATUS_NONE)) { + query.eq(ErpSaleReturnDO::getRefundPrice, 0); + } else if (Objects.equals(reqVO.getRefundStatus(), ErpSaleReturnPageReqVO.REFUND_STATUS_PART)) { + query.gt(ErpSaleReturnDO::getRefundPrice, 0).apply("t.refund_price < t.total_price"); + } else if (Objects.equals(reqVO.getRefundStatus(), ErpSaleReturnPageReqVO.REFUND_STATUS_ALL)) { + query.apply("t.refund_price = t.total_price"); + } + if (Boolean.TRUE.equals(reqVO.getRefundEnable())) { + query.eq(ErpSaleOutDO::getStatus, ErpAuditStatus.APPROVE.getStatus()) + .apply("t.refund_price < t.total_price"); + } + if (reqVO.getWarehouseId() != null || reqVO.getProductId() != null) { + query.leftJoin(ErpSaleReturnItemDO.class, ErpSaleReturnItemDO::getReturnId, ErpSaleReturnDO::getId) + .eq(reqVO.getWarehouseId() != null, ErpSaleReturnItemDO::getWarehouseId, reqVO.getWarehouseId()) + .eq(reqVO.getProductId() != null, ErpSaleReturnItemDO::getProductId, reqVO.getProductId()) + .groupBy(ErpSaleReturnDO::getId); // 避免 1 对多查询,产生相同的 1 + } + return selectJoinPage(reqVO, ErpSaleReturnDO.class, query); + } + + default int updateByIdAndStatus(Long id, Integer status, ErpSaleReturnDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ErpSaleReturnDO::getId, id).eq(ErpSaleReturnDO::getStatus, status)); + } + + default ErpSaleReturnDO selectByNo(String no) { + return selectOne(ErpSaleReturnDO::getNo, no); + } + + default List selectListByOrderId(Long orderId) { + return selectList(ErpSaleReturnDO::getOrderId, orderId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockCheckItemMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockCheckItemMapper.java new file mode 100644 index 000000000..ae13f9f96 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockCheckItemMapper.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.stock; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockCheckItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * ERP 库存盘点单项 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpStockCheckItemMapper extends BaseMapperX { + + default List selectListByCheckId(Long checkId) { + return selectList(ErpStockCheckItemDO::getCheckId, checkId); + } + + default List selectListByCheckIds(Collection checkIds) { + return selectList(ErpStockCheckItemDO::getCheckId, checkIds); + } + + default int deleteByCheckId(Long checkId) { + return delete(ErpStockCheckItemDO::getCheckId, checkId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockCheckMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockCheckMapper.java new file mode 100644 index 000000000..dd976df3d --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockCheckMapper.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.stock; + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.check.ErpStockCheckPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockCheckDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockCheckItemDO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * ERP 库存调拨单 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpStockCheckMapper extends BaseMapperX { + + default PageResult selectPage(ErpStockCheckPageReqVO reqVO) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX() + .likeIfPresent(ErpStockCheckDO::getNo, reqVO.getNo()) + .betweenIfPresent(ErpStockCheckDO::getCheckTime, reqVO.getCheckTime()) + .eqIfPresent(ErpStockCheckDO::getStatus, reqVO.getStatus()) + .likeIfPresent(ErpStockCheckDO::getRemark, reqVO.getRemark()) + .eqIfPresent(ErpStockCheckDO::getCreator, reqVO.getCreator()) + .orderByDesc(ErpStockCheckDO::getId); + if (reqVO.getWarehouseId() != null || reqVO.getProductId() != null) { + query.leftJoin(ErpStockCheckItemDO.class, ErpStockCheckItemDO::getCheckId, ErpStockCheckDO::getId) + .eq(reqVO.getWarehouseId() != null, ErpStockCheckItemDO::getWarehouseId, reqVO.getWarehouseId()) + .eq(reqVO.getProductId() != null, ErpStockCheckItemDO::getProductId, reqVO.getProductId()) + .groupBy(ErpStockCheckDO::getId); // 避免 1 对多查询,产生相同的 1 + } + return selectJoinPage(reqVO, ErpStockCheckDO.class, query); + } + + default int updateByIdAndStatus(Long id, Integer status, ErpStockCheckDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ErpStockCheckDO::getId, id).eq(ErpStockCheckDO::getStatus, status)); + } + + default ErpStockCheckDO selectByNo(String no) { + return selectOne(ErpStockCheckDO::getNo, no); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockInItemMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockInItemMapper.java new file mode 100644 index 000000000..2731aa7bb --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockInItemMapper.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.stock; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * ERP 其它入库单项 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpStockInItemMapper extends BaseMapperX { + + default List selectListByInId(Long inId) { + return selectList(ErpStockInItemDO::getInId, inId); + } + + default List selectListByInIds(Collection inIds) { + return selectList(ErpStockInItemDO::getInId, inIds); + } + + default int deleteByInId(Long inId) { + return delete(ErpStockInItemDO::getInId, inId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockInMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockInMapper.java new file mode 100644 index 000000000..e815583ac --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockInMapper.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.stock; + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInItemDO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * ERP 其它入库单 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpStockInMapper extends BaseMapperX { + + default PageResult selectPage(ErpStockInPageReqVO reqVO) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX() + .likeIfPresent(ErpStockInDO::getNo, reqVO.getNo()) + .eqIfPresent(ErpStockInDO::getSupplierId, reqVO.getSupplierId()) + .betweenIfPresent(ErpStockInDO::getInTime, reqVO.getInTime()) + .eqIfPresent(ErpStockInDO::getStatus, reqVO.getStatus()) + .likeIfPresent(ErpStockInDO::getRemark, reqVO.getRemark()) + .eqIfPresent(ErpStockInDO::getCreator, reqVO.getCreator()) + .orderByDesc(ErpStockInDO::getId); + if (reqVO.getWarehouseId() != null || reqVO.getProductId() != null) { + query.leftJoin(ErpStockInItemDO.class, ErpStockInItemDO::getInId, ErpStockInDO::getId) + .eq(reqVO.getWarehouseId() != null, ErpStockInItemDO::getWarehouseId, reqVO.getWarehouseId()) + .eq(reqVO.getProductId() != null, ErpStockInItemDO::getProductId, reqVO.getProductId()) + .groupBy(ErpStockInDO::getId); // 避免 1 对多查询,产生相同的 1 + } + return selectJoinPage(reqVO, ErpStockInDO.class, query); + } + + default int updateByIdAndStatus(Long id, Integer status, ErpStockInDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ErpStockInDO::getId, id).eq(ErpStockInDO::getStatus, status)); + } + + default ErpStockInDO selectByNo(String no) { + return selectOne(ErpStockInDO::getNo, no); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMapper.java new file mode 100644 index 000000000..0ebc98597 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMapper.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.stock; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +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.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +/** + * ERP 产品库存 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpStockMapper extends BaseMapperX { + + default PageResult selectPage(ErpStockPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ErpStockDO::getProductId, reqVO.getProductId()) + .eqIfPresent(ErpStockDO::getWarehouseId, reqVO.getWarehouseId()) + .orderByDesc(ErpStockDO::getId)); + } + + default ErpStockDO selectByProductIdAndWarehouseId(Long productId, Long warehouseId) { + return selectOne(ErpStockDO::getProductId, productId, + ErpStockDO::getWarehouseId, warehouseId); + } + + default int updateCountIncrement(Long id, BigDecimal count, boolean negativeEnable) { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper() + .eq(ErpStockDO::getId, id); + if (count.compareTo(BigDecimal.ZERO) > 0) { + updateWrapper.setSql("count = count + " + count); + } else if (count.compareTo(BigDecimal.ZERO) < 0) { + if (!negativeEnable) { + updateWrapper.ge(ErpStockDO::getCount, count.abs()); + } + updateWrapper.setSql("count = count - " + count.abs()); + } + return update(null, updateWrapper); + } + + default BigDecimal selectSumByProductId(Long productId) { + // SQL sum 查询 + List> result = selectMaps(new QueryWrapper() + .select("SUM(count) AS sumCount") + .eq("product_id", productId)); + // 获得数量 + if (CollUtil.isEmpty(result)) { + return BigDecimal.ZERO; + } + return BigDecimal.valueOf(MapUtil.getDouble(result.get(0), "sumCount", 0D)); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMoveItemMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMoveItemMapper.java new file mode 100644 index 000000000..21a267029 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMoveItemMapper.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.stock; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * ERP 库存调拨单项 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpStockMoveItemMapper extends BaseMapperX { + + default List selectListByMoveId(Long moveId) { + return selectList(ErpStockMoveItemDO::getMoveId, moveId); + } + + default List selectListByMoveIds(Collection moveIds) { + return selectList(ErpStockMoveItemDO::getMoveId, moveIds); + } + + default int deleteByMoveId(Long moveId) { + return delete(ErpStockMoveItemDO::getMoveId, moveId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMoveMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMoveMapper.java new file mode 100644 index 000000000..9a8ce0b64 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMoveMapper.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.stock; + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMovePageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveItemDO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * ERP 库存调拨单 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpStockMoveMapper extends BaseMapperX { + + default PageResult selectPage(ErpStockMovePageReqVO reqVO) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX() + .likeIfPresent(ErpStockMoveDO::getNo, reqVO.getNo()) + .betweenIfPresent(ErpStockMoveDO::getMoveTime, reqVO.getMoveTime()) + .eqIfPresent(ErpStockMoveDO::getStatus, reqVO.getStatus()) + .likeIfPresent(ErpStockMoveDO::getRemark, reqVO.getRemark()) + .eqIfPresent(ErpStockMoveDO::getCreator, reqVO.getCreator()) + .orderByDesc(ErpStockMoveDO::getId); + if (reqVO.getFromWarehouseId() != null || reqVO.getProductId() != null) { + query.leftJoin(ErpStockMoveItemDO.class, ErpStockMoveItemDO::getMoveId, ErpStockMoveDO::getId) + .eq(reqVO.getFromWarehouseId() != null, ErpStockMoveItemDO::getFromWarehouseId, reqVO.getFromWarehouseId()) + .eq(reqVO.getProductId() != null, ErpStockMoveItemDO::getProductId, reqVO.getProductId()) + .groupBy(ErpStockMoveDO::getId); // 避免 1 对多查询,产生相同的 1 + } + return selectJoinPage(reqVO, ErpStockMoveDO.class, query); + } + + default int updateByIdAndStatus(Long id, Integer status, ErpStockMoveDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ErpStockMoveDO::getId, id).eq(ErpStockMoveDO::getStatus, status)); + } + + default ErpStockMoveDO selectByNo(String no) { + return selectOne(ErpStockMoveDO::getNo, no); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockOutItemMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockOutItemMapper.java new file mode 100644 index 000000000..3b27cd3dc --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockOutItemMapper.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.stock; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutItemDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * ERP 其它出库单项 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpStockOutItemMapper extends BaseMapperX { + + default List selectListByOutId(Long outId) { + return selectList(ErpStockOutItemDO::getOutId, outId); + } + + default List selectListByOutIds(Collection outIds) { + return selectList(ErpStockOutItemDO::getOutId, outIds); + } + + default int deleteByOutId(Long outId) { + return delete(ErpStockOutItemDO::getOutId, outId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockOutMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockOutMapper.java new file mode 100644 index 000000000..a73dd3ccf --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockOutMapper.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.stock; + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutItemDO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * ERP 其它出库单 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpStockOutMapper extends BaseMapperX { + + default PageResult selectPage(ErpStockOutPageReqVO reqVO) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX() + .likeIfPresent(ErpStockOutDO::getNo, reqVO.getNo()) + .eqIfPresent(ErpStockOutDO::getCustomerId, reqVO.getCustomerId()) + .betweenIfPresent(ErpStockOutDO::getOutTime, reqVO.getOutTime()) + .eqIfPresent(ErpStockOutDO::getStatus, reqVO.getStatus()) + .likeIfPresent(ErpStockOutDO::getRemark, reqVO.getRemark()) + .eqIfPresent(ErpStockOutDO::getCreator, reqVO.getCreator()) + .orderByDesc(ErpStockOutDO::getId); + if (reqVO.getWarehouseId() != null || reqVO.getProductId() != null) { + query.leftJoin(ErpStockOutItemDO.class, ErpStockOutItemDO::getOutId, ErpStockOutDO::getId) + .eq(reqVO.getWarehouseId() != null, ErpStockOutItemDO::getWarehouseId, reqVO.getWarehouseId()) + .eq(reqVO.getProductId() != null, ErpStockOutItemDO::getProductId, reqVO.getProductId()) + .groupBy(ErpStockOutDO::getId); // 避免 1 对多查询,产生相同的 1 + } + return selectJoinPage(reqVO, ErpStockOutDO.class, query); + } + + default int updateByIdAndStatus(Long id, Integer status, ErpStockOutDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(ErpStockOutDO::getId, id).eq(ErpStockOutDO::getStatus, status)); + } + + default ErpStockOutDO selectByNo(String no) { + return selectOne(ErpStockOutDO::getNo, no); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockRecordMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockRecordMapper.java new file mode 100644 index 000000000..bfd8b6751 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockRecordMapper.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.stock; + +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.erp.controller.admin.stock.vo.record.ErpStockRecordPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockRecordDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * ERP 产品库存明细 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpStockRecordMapper extends BaseMapperX { + + default PageResult selectPage(ErpStockRecordPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ErpStockRecordDO::getProductId, reqVO.getProductId()) + .eqIfPresent(ErpStockRecordDO::getWarehouseId, reqVO.getWarehouseId()) + .eqIfPresent(ErpStockRecordDO::getBizType, reqVO.getBizType()) + .likeIfPresent(ErpStockRecordDO::getBizNo, reqVO.getBizNo()) + .betweenIfPresent(ErpStockRecordDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ErpStockRecordDO::getId)); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpWarehouseMapper.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpWarehouseMapper.java new file mode 100644 index 000000000..0442011f2 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpWarehouseMapper.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.erp.dal.mysql.stock; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.warehouse.ErpWarehousePageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * ERP 仓库 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface ErpWarehouseMapper extends BaseMapperX { + + default PageResult selectPage(ErpWarehousePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ErpWarehouseDO::getName, reqVO.getName()) + .eqIfPresent(ErpWarehouseDO::getStatus, reqVO.getStatus()) + .orderByDesc(ErpWarehouseDO::getId)); + } + + default ErpWarehouseDO selectByDefaultStatus() { + return selectOne(ErpWarehouseDO::getDefaultStatus, true); + } + + default List selectListByStatus(Integer status) { + return selectList(ErpWarehouseDO::getStatus, status); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/RedisKeyConstants.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/RedisKeyConstants.java new file mode 100644 index 000000000..f0ba46807 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/RedisKeyConstants.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.erp.dal.redis; + +/** + * ERP Redis Key 枚举类 + * + * @author 芋道源码 + */ +public interface RedisKeyConstants { + + /** + * 序号的缓存 + * + * KEY 格式:trade_no:{prefix} + * VALUE 数据格式:编号自增 + */ + String NO = "seq_no:"; + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java new file mode 100644 index 000000000..98fb8e8ff --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java @@ -0,0 +1,96 @@ +package cn.iocoder.yudao.module.erp.dal.redis.no; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.iocoder.yudao.module.erp.dal.redis.RedisKeyConstants; +import jakarta.annotation.Resource; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import java.time.Duration; +import java.time.LocalDateTime; + + +/** + * 订单序号的 Redis DAO + * + * @author HUIHUI + */ +@Repository +public class ErpNoRedisDAO { + + /** + * 其它入库 {@link cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInDO} + */ + public static final String STOCK_IN_NO_PREFIX = "QTRK"; + /** + * 其它出库 {@link cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutDO} + */ + public static final String STOCK_OUT_NO_PREFIX = "QCKD"; + + /** + * 库存调拨 {@link cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveDO} + */ + public static final String STOCK_MOVE_NO_PREFIX = "QCDB"; + + /** + * 库存盘点 {@link cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockCheckDO} + */ + public static final String STOCK_CHECK_NO_PREFIX = "QCPD"; + + /** + * 销售订单 {@link cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO} + */ + public static final String SALE_ORDER_NO_PREFIX = "XSDD"; + /** + * 销售出库 {@link cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO} + */ + public static final String SALE_OUT_NO_PREFIX = "XSCK"; + /** + * 销售退货 {@link cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleReturnDO} + */ + public static final String SALE_RETURN_NO_PREFIX = "XSTH"; + + /** + * 采购订单 {@link cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO} + */ + public static final String PURCHASE_ORDER_NO_PREFIX = "CGDD"; + /** + * 采购入库 {@link cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO} + */ + public static final String PURCHASE_IN_NO_PREFIX = "CGRK"; + /** + * 采购退货 {@link cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnDO} + */ + public static final String PURCHASE_RETURN_NO_PREFIX = "CGTH"; + + /** + * 付款单 {@link cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO} + */ + public static final String FINANCE_PAYMENT_NO_PREFIX = "FKD"; + /** + * 收款单 {@link cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptDO} + */ + public static final String FINANCE_RECEIPT_NO_PREFIX = "SKD"; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 生成序号,使用当前日期,格式为 {PREFIX} + yyyyMMdd + 6 位自增 + * 例如说:QTRK 202109 000001 (没有中间空格) + * + * @param prefix 前缀 + * @return 序号 + */ + public String generate(String prefix) { + // 递增序号 + String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATE_PATTERN); + String key = RedisKeyConstants.NO + noPrefix; + Long no = stringRedisTemplate.opsForValue().increment(key); + // 设置过期时间 + stringRedisTemplate.expire(key, Duration.ofDays(1L)); + return noPrefix + String.format("%06d", no); + } + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/package-info.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/package-info.java new file mode 100644 index 000000000..af7bc123c --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 erp 模块的 framework 封装 + * + * @author 芋道源码 + */ +package cn.iocoder.yudao.module.erp.framework; diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/rpc/config/RpcConfiguration.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/rpc/config/RpcConfiguration.java new file mode 100644 index 000000000..c49608e21 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/rpc/config/RpcConfiguration.java @@ -0,0 +1,10 @@ +package cn.iocoder.yudao.module.erp.framework.rpc.config; + +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableFeignClients(clients = AdminUserApi.class) +public class RpcConfiguration { +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/rpc/package-info.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/rpc/package-info.java new file mode 100644 index 000000000..9f5d622be --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/rpc/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package cn.iocoder.yudao.module.erp.framework.rpc; diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/security/config/SecurityConfiguration.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/security/config/SecurityConfiguration.java new file mode 100644 index 000000000..e54e16ca5 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/security/config/SecurityConfiguration.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.erp.framework.security.config; + +import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer; +import cn.iocoder.yudao.module.erp.enums.ApiConstants; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; + +/** + * Pay 模块的 Security 配置 + */ +@Configuration("paySecurityConfiguration") +public class SecurityConfiguration { + + @Bean("payAuthorizeRequestsCustomizer") + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + return new AuthorizeRequestsCustomizer() { + + @Override + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + // Swagger 接口文档 + registry.requestMatchers("/v3/api-docs/**").permitAll() // 元数据 + .requestMatchers("/swagger-ui.html").permitAll(); // Swagger UI + // Spring Boot Actuator 的安全配置 + registry.requestMatchers("/actuator").permitAll() + .requestMatchers("/actuator/**").permitAll(); + // Druid 监控 + registry.requestMatchers("/druid/**").permitAll(); + // RPC 服务的安全配置 + registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll(); + } + + }; + } + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/security/core/package-info.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/security/core/package-info.java new file mode 100644 index 000000000..f01cc8be5 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/security/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package cn.iocoder.yudao.module.erp.framework.security.core; diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/config/ErpWebConfiguration.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/config/ErpWebConfiguration.java new file mode 100644 index 000000000..4a2bc7fa3 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/config/ErpWebConfiguration.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.erp.framework.web.config; + +import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * erp 模块的 web 组件的 Configuration + * + * @author 芋道源码 + */ +@Configuration(proxyBeanMethods = false) +public class ErpWebConfiguration { + + /** + * erp 模块的 API 分组 + */ + @Bean + public GroupedOpenApi erpGroupedOpenApi() { + return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("erp"); + } + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/package-info.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/package-info.java new file mode 100644 index 000000000..70f70c035 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/web/package-info.java @@ -0,0 +1,4 @@ +/** + * trade 模块的 web 配置 + */ +package cn.iocoder.yudao.module.erp.framework.web; diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/package-info.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/package-info.java new file mode 100644 index 000000000..2608a9889 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/package-info.java @@ -0,0 +1,10 @@ +/** + * erp 包下,企业资源管理(Enterprise Resource Planning)。 + * 例如说:采购、销售、库存、财务、产品等等 + * + * 1. Controller URL:以 /erp/ 开头,避免和其它 Module 冲突 + * 2. DataObject 表名:以 erp_ 开头,方便在数据库中区分 + * + * 注意,由于 Erp 模块下,容易和其它模块重名,所以类名都加载 Erp 的前缀~ + */ +package cn.iocoder.yudao.module.erp; diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpAccountService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpAccountService.java new file mode 100644 index 000000000..502423fb2 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpAccountService.java @@ -0,0 +1,102 @@ +package cn.iocoder.yudao.module.erp.service.finance; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account.ErpAccountPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account.ErpAccountSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * ERP 结算账户 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpAccountService { + + /** + * 创建结算账户 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createAccount(@Valid ErpAccountSaveReqVO createReqVO); + + /** + * 更新ERP 结算账户 + * + * @param updateReqVO 更新信息 + */ + void updateAccount(@Valid ErpAccountSaveReqVO updateReqVO); + + /** + * 更新结算账户默认状态 + * + * @param id 编号 + * @param defaultStatus 默认状态 + */ + void updateAccountDefaultStatus(Long id, Boolean defaultStatus); + + /** + * 删除结算账户 + * + * @param id 编号 + */ + void deleteAccount(Long id); + + /** + * 获得结算账户 + * + * @param id 编号 + * @return 结算账户 + */ + ErpAccountDO getAccount(Long id); + + /** + * 校验结算账户 + * + * @param id 编号 + * @return 结算账户 + */ + ErpAccountDO validateAccount(Long id); + + /** + * 获得指定状态的结算账户列表 + * + * @param status 状态 + * @return 结算账户 + */ + List getAccountListByStatus(Integer status); + + /** + * 获得结算账户列表 + * + * @param ids 编号数组 + * @return 结算账户列表 + */ + List getAccountList(Collection ids); + + /** + * 获得结算账户 Map + * + * @param ids 编号数组 + * @return 结算账户 Map + */ + default Map getAccountMap(Collection ids) { + return convertMap(getAccountList(ids), ErpAccountDO::getId); + } + + /** + * 获得结算账户分页 + * + * @param pageReqVO 分页查询 + * @return 结算账户分页 + */ + PageResult getAccountPage(ErpAccountPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpAccountServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpAccountServiceImpl.java new file mode 100644 index 000000000..9a8e85828 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpAccountServiceImpl.java @@ -0,0 +1,112 @@ +package cn.iocoder.yudao.module.erp.service.finance; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account.ErpAccountPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.account.ErpAccountSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpAccountDO; +import cn.iocoder.yudao.module.erp.dal.mysql.finance.ErpAccountMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +/** + * ERP 结算账户 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpAccountServiceImpl implements ErpAccountService { + + @Resource + private ErpAccountMapper accountMapper; + + @Override + public Long createAccount(ErpAccountSaveReqVO createReqVO) { + // 插入 + ErpAccountDO account = BeanUtils.toBean(createReqVO, ErpAccountDO.class); + accountMapper.insert(account); + // 返回 + return account.getId(); + } + + @Override + public void updateAccount(ErpAccountSaveReqVO updateReqVO) { + // 校验存在 + validateAccountExists(updateReqVO.getId()); + // 更新 + ErpAccountDO updateObj = BeanUtils.toBean(updateReqVO, ErpAccountDO.class); + accountMapper.updateById(updateObj); + } + + @Override + public void updateAccountDefaultStatus(Long id, Boolean defaultStatus) { + // 1. 校验存在 + validateAccountExists(id); + + // 2.1 如果开启,则需要关闭所有其它的默认 + if (defaultStatus) { + ErpAccountDO account = accountMapper.selectByDefaultStatus(); + if (account != null) { + accountMapper.updateById(new ErpAccountDO().setId(account.getId()).setDefaultStatus(false)); + } + } + // 2.2 更新对应的默认状态 + accountMapper.updateById(new ErpAccountDO().setId(id).setDefaultStatus(defaultStatus)); + } + + @Override + public void deleteAccount(Long id) { + // 校验存在 + validateAccountExists(id); + // 删除 + accountMapper.deleteById(id); + } + + private void validateAccountExists(Long id) { + if (accountMapper.selectById(id) == null) { + throw exception(ACCOUNT_NOT_EXISTS); + } + } + + @Override + public ErpAccountDO getAccount(Long id) { + return accountMapper.selectById(id); + } + + @Override + public ErpAccountDO validateAccount(Long id) { + ErpAccountDO account = accountMapper.selectById(id); + if (account == null) { + throw exception(ACCOUNT_NOT_EXISTS); + } + if (CommonStatusEnum.isDisable(account.getStatus())) { + throw exception(ACCOUNT_NOT_ENABLE, account.getName()); + } + return account; + } + + @Override + public List getAccountListByStatus(Integer status) { + return accountMapper.selectListByStatus(status); + } + + @Override + public List getAccountList(Collection ids) { + return accountMapper.selectBatchIds(ids); + } + + @Override + public PageResult getAccountPage(ErpAccountPageReqVO pageReqVO) { + return accountMapper.selectPage(pageReqVO); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentService.java new file mode 100644 index 000000000..b2f27917d --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentService.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.erp.service.finance; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentItemDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * ERP 付款单 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpFinancePaymentService { + + /** + * 创建付款单 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createFinancePayment(@Valid ErpFinancePaymentSaveReqVO createReqVO); + + /** + * 更新付款单 + * + * @param updateReqVO 更新信息 + */ + void updateFinancePayment(@Valid ErpFinancePaymentSaveReqVO updateReqVO); + + /** + * 更新付款单的状态 + * + * @param id 编号 + * @param status 状态 + */ + void updateFinancePaymentStatus(Long id, Integer status); + + /** + * 删除付款单 + * + * @param ids 编号数组 + */ + void deleteFinancePayment(List ids); + + /** + * 获得付款单 + * + * @param id 编号 + * @return 付款单 + */ + ErpFinancePaymentDO getFinancePayment(Long id); + + /** + * 获得付款单分页 + * + * @param pageReqVO 分页查询 + * @return 付款单分页 + */ + PageResult getFinancePaymentPage(ErpFinancePaymentPageReqVO pageReqVO); + + // ==================== 付款单项 ==================== + + /** + * 获得付款单项列表 + * + * @param paymentId 付款单编号 + * @return 付款单项列表 + */ + List getFinancePaymentItemListByPaymentId(Long paymentId); + + /** + * 获得付款单项 List + * + * @param paymentIds 付款单编号数组 + * @return 付款单项 List + */ + List getFinancePaymentItemListByPaymentIds(Collection paymentIds); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java new file mode 100644 index 000000000..ab94dad53 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java @@ -0,0 +1,273 @@ +package cn.iocoder.yudao.module.erp.service.finance; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.payment.ErpFinancePaymentSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentItemDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnDO; +import cn.iocoder.yudao.module.erp.dal.mysql.finance.ErpFinancePaymentItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.finance.ErpFinancePaymentMapper; +import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.enums.common.ErpBizTypeEnum; +import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInService; +import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseReturnService; +import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +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.util.Collection; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +// TODO 芋艿:记录操作日志 + +/** + * ERP 付款单 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpFinancePaymentServiceImpl implements ErpFinancePaymentService { + + @Resource + private ErpFinancePaymentMapper financePaymentMapper; + @Resource + private ErpFinancePaymentItemMapper financePaymentItemMapper; + + @Resource + private ErpNoRedisDAO noRedisDAO; + + @Resource + private ErpSupplierService supplierService; + @Resource + private ErpAccountService accountService; + @Resource + private ErpPurchaseInService purchaseInService; + @Resource + private ErpPurchaseReturnService purchaseReturnService; + + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createFinancePayment(ErpFinancePaymentSaveReqVO createReqVO) { + // 1.1 校验订单项的有效性 + List paymentItems = validateFinancePaymentItems( + createReqVO.getSupplierId(), createReqVO.getItems()); + // 1.2 校验供应商 + supplierService.validateSupplier(createReqVO.getSupplierId()); + // 1.3 校验结算账户 + if (createReqVO.getAccountId() != null) { + accountService.validateAccount(createReqVO.getAccountId()); + } + // 1.4 校验财务人员 + if (createReqVO.getFinanceUserId() != null) { + adminUserApi.validateUser(createReqVO.getFinanceUserId()); + } + // 1.5 生成付款单号,并校验唯一性 + String no = noRedisDAO.generate(ErpNoRedisDAO.FINANCE_PAYMENT_NO_PREFIX); + if (financePaymentMapper.selectByNo(no) != null) { + throw exception(FINANCE_PAYMENT_NO_EXISTS); + } + + // 2.1 插入付款单 + ErpFinancePaymentDO payment = BeanUtils.toBean(createReqVO, ErpFinancePaymentDO.class, in -> in + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())); + calculateTotalPrice(payment, paymentItems); + financePaymentMapper.insert(payment); + // 2.2 插入付款单项 + paymentItems.forEach(o -> o.setPaymentId(payment.getId())); + financePaymentItemMapper.insertBatch(paymentItems); + + // 3. 更新采购入库、退货的付款金额情况 + updatePurchasePrice(paymentItems); + return payment.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateFinancePayment(ErpFinancePaymentSaveReqVO updateReqVO) { + // 1.1 校验存在 + ErpFinancePaymentDO payment = validateFinancePaymentExists(updateReqVO.getId()); + if (ErpAuditStatus.APPROVE.getStatus().equals(payment.getStatus())) { + throw exception(FINANCE_PAYMENT_UPDATE_FAIL_APPROVE, payment.getNo()); + } + // 1.2 校验供应商 + supplierService.validateSupplier(updateReqVO.getSupplierId()); + // 1.3 校验结算账户 + if (updateReqVO.getAccountId() != null) { + accountService.validateAccount(updateReqVO.getAccountId()); + } + // 1.4 校验财务人员 + if (updateReqVO.getFinanceUserId() != null) { + adminUserApi.validateUser(updateReqVO.getFinanceUserId()); + } + // 1.5 校验付款单项的有效性 + List paymentItems = validateFinancePaymentItems( + updateReqVO.getSupplierId(), updateReqVO.getItems()); + + // 2.1 更新付款单 + ErpFinancePaymentDO updateObj = BeanUtils.toBean(updateReqVO, ErpFinancePaymentDO.class); + calculateTotalPrice(updateObj, paymentItems); + financePaymentMapper.updateById(updateObj); + // 2.2 更新付款单项 + updateFinancePaymentItemList(updateReqVO.getId(), paymentItems); + } + + private void calculateTotalPrice(ErpFinancePaymentDO payment, List paymentItems) { + payment.setTotalPrice(getSumValue(paymentItems, ErpFinancePaymentItemDO::getPaymentPrice, BigDecimal::add, BigDecimal.ZERO)); + payment.setPaymentPrice(payment.getTotalPrice().subtract(payment.getDiscountPrice())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateFinancePaymentStatus(Long id, Integer status) { + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); + // 1.1 校验存在 + ErpFinancePaymentDO payment = validateFinancePaymentExists(id); + // 1.2 校验状态 + if (payment.getStatus().equals(status)) { + throw exception(approve ? FINANCE_PAYMENT_APPROVE_FAIL : FINANCE_PAYMENT_PROCESS_FAIL); + } + + // 2. 更新状态 + int updateCount = financePaymentMapper.updateByIdAndStatus(id, payment.getStatus(), + new ErpFinancePaymentDO().setStatus(status)); + if (updateCount == 0) { + throw exception(approve ? FINANCE_PAYMENT_APPROVE_FAIL : FINANCE_PAYMENT_PROCESS_FAIL); + } + } + + private List validateFinancePaymentItems( + Long supplierId, + List list) { + return convertList(list, o -> BeanUtils.toBean(o, ErpFinancePaymentItemDO.class, item -> { + if (ObjectUtil.equal(item.getBizType(), ErpBizTypeEnum.PURCHASE_IN.getType())) { + ErpPurchaseInDO purchaseIn = purchaseInService.validatePurchaseIn(item.getBizId()); + Assert.equals(purchaseIn.getSupplierId(), supplierId, "供应商必须相同"); + item.setTotalPrice(purchaseIn.getTotalPrice()).setBizNo(purchaseIn.getNo()); + } else if (ObjectUtil.equal(item.getBizType(), ErpBizTypeEnum.PURCHASE_RETURN.getType())) { + ErpPurchaseReturnDO purchaseReturn = purchaseReturnService.validatePurchaseReturn(item.getBizId()); + Assert.equals(purchaseReturn.getSupplierId(), supplierId, "供应商必须相同"); + item.setTotalPrice(purchaseReturn.getTotalPrice().negate()).setBizNo(purchaseReturn.getNo()); + } else { + throw new IllegalArgumentException("业务类型不正确:" + item.getBizType()); + } + })); + } + + private void updateFinancePaymentItemList(Long id, List newList) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = financePaymentItemMapper.selectListByPaymentId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setPaymentId(id)); + financePaymentItemMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + financePaymentItemMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + financePaymentItemMapper.deleteBatchIds(convertList(diffList.get(2), ErpFinancePaymentItemDO::getId)); + } + + // 第三步,更新采购入库、退货的付款金额情况 + updatePurchasePrice(CollectionUtils.newArrayList(diffList)); + } + + private void updatePurchasePrice(List paymentItems) { + paymentItems.forEach(paymentItem -> { + BigDecimal totalPaymentPrice = financePaymentItemMapper.selectPaymentPriceSumByBizIdAndBizType( + paymentItem.getBizId(), paymentItem.getBizType()); + if (ErpBizTypeEnum.PURCHASE_IN.getType().equals(paymentItem.getBizType())) { + purchaseInService.updatePurchaseInPaymentPrice(paymentItem.getBizId(), totalPaymentPrice); + } else if (ErpBizTypeEnum.PURCHASE_RETURN.getType().equals(paymentItem.getBizType())) { + purchaseReturnService.updatePurchaseReturnRefundPrice(paymentItem.getBizId(), totalPaymentPrice.negate()); + } else { + throw new IllegalArgumentException("业务类型不正确:" + paymentItem.getBizType()); + } + }); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteFinancePayment(List ids) { + // 1. 校验不处于已审批 + List payments = financePaymentMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(payments)) { + return; + } + payments.forEach(payment -> { + if (ErpAuditStatus.APPROVE.getStatus().equals(payment.getStatus())) { + throw exception(FINANCE_PAYMENT_DELETE_FAIL_APPROVE, payment.getNo()); + } + }); + + // 2. 遍历删除,并记录操作日志 + payments.forEach(payment -> { + // 2.1 删除付款单 + financePaymentMapper.deleteById(payment.getId()); + // 2.2 删除付款单项 + List paymentItems = financePaymentItemMapper.selectListByPaymentId(payment.getId()); + financePaymentItemMapper.deleteBatchIds(convertSet(paymentItems, ErpFinancePaymentItemDO::getId)); + + // 2.3 更新采购入库、退货的付款金额情况 + updatePurchasePrice(paymentItems); + }); + } + + private ErpFinancePaymentDO validateFinancePaymentExists(Long id) { + ErpFinancePaymentDO payment = financePaymentMapper.selectById(id); + if (payment == null) { + throw exception(FINANCE_PAYMENT_NOT_EXISTS); + } + return payment; + } + + @Override + public ErpFinancePaymentDO getFinancePayment(Long id) { + return financePaymentMapper.selectById(id); + } + + @Override + public PageResult getFinancePaymentPage(ErpFinancePaymentPageReqVO pageReqVO) { + return financePaymentMapper.selectPage(pageReqVO); + } + + // ==================== 付款单项 ==================== + + @Override + public List getFinancePaymentItemListByPaymentId(Long paymentId) { + return financePaymentItemMapper.selectListByPaymentId(paymentId); + } + + @Override + public List getFinancePaymentItemListByPaymentIds(Collection paymentIds) { + if (CollUtil.isEmpty(paymentIds)) { + return Collections.emptyList(); + } + return financePaymentItemMapper.selectListByPaymentIds(paymentIds); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptService.java new file mode 100644 index 000000000..179648195 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptService.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.erp.service.finance; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt.ErpFinanceReceiptPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt.ErpFinanceReceiptSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptItemDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * ERP 收款单 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpFinanceReceiptService { + + /** + * 创建收款单 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createFinanceReceipt(@Valid ErpFinanceReceiptSaveReqVO createReqVO); + + /** + * 更新收款单 + * + * @param updateReqVO 更新信息 + */ + void updateFinanceReceipt(@Valid ErpFinanceReceiptSaveReqVO updateReqVO); + + /** + * 更新收款单的状态 + * + * @param id 编号 + * @param status 状态 + */ + void updateFinanceReceiptStatus(Long id, Integer status); + + /** + * 删除收款单 + * + * @param ids 编号数组 + */ + void deleteFinanceReceipt(List ids); + + /** + * 获得收款单 + * + * @param id 编号 + * @return 收款单 + */ + ErpFinanceReceiptDO getFinanceReceipt(Long id); + + /** + * 获得收款单分页 + * + * @param pageReqVO 分页查询 + * @return 收款单分页 + */ + PageResult getFinanceReceiptPage(ErpFinanceReceiptPageReqVO pageReqVO); + + // ==================== 收款单项 ==================== + + /** + * 获得收款单项列表 + * + * @param receiptId 收款单编号 + * @return 收款单项列表 + */ + List getFinanceReceiptItemListByReceiptId(Long receiptId); + + /** + * 获得收款单项 List + * + * @param receiptIds 收款单编号数组 + * @return 收款单项 List + */ + List getFinanceReceiptItemListByReceiptIds(Collection receiptIds); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptServiceImpl.java new file mode 100644 index 000000000..42b81d876 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptServiceImpl.java @@ -0,0 +1,273 @@ +package cn.iocoder.yudao.module.erp.service.finance; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt.ErpFinanceReceiptPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.finance.vo.receipt.ErpFinanceReceiptSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinanceReceiptItemDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleReturnDO; +import cn.iocoder.yudao.module.erp.dal.mysql.finance.ErpFinanceReceiptItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.finance.ErpFinanceReceiptMapper; +import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.enums.common.ErpBizTypeEnum; +import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService; +import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOutService; +import cn.iocoder.yudao.module.erp.service.sale.ErpSaleReturnService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +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.util.Collection; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +// TODO 芋艿:记录操作日志 + +/** + * ERP 收款单 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpFinanceReceiptServiceImpl implements ErpFinanceReceiptService { + + @Resource + private ErpFinanceReceiptMapper financeReceiptMapper; + @Resource + private ErpFinanceReceiptItemMapper financeReceiptItemMapper; + + @Resource + private ErpNoRedisDAO noRedisDAO; + + @Resource + private ErpCustomerService customerService; + @Resource + private ErpAccountService accountService; + @Resource + private ErpSaleOutService saleOutService; + @Resource + private ErpSaleReturnService saleReturnService; + + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createFinanceReceipt(ErpFinanceReceiptSaveReqVO createReqVO) { + // 1.1 校验订单项的有效性 + List receiptItems = validateFinanceReceiptItems( + createReqVO.getCustomerId(), createReqVO.getItems()); + // 1.2 校验客户 + customerService.validateCustomer(createReqVO.getCustomerId()); + // 1.3 校验结算账户 + if (createReqVO.getAccountId() != null) { + accountService.validateAccount(createReqVO.getAccountId()); + } + // 1.4 校验财务人员 + if (createReqVO.getFinanceUserId() != null) { + adminUserApi.validateUser(createReqVO.getFinanceUserId()); + } + // 1.5 生成收款单号,并校验唯一性 + String no = noRedisDAO.generate(ErpNoRedisDAO.FINANCE_RECEIPT_NO_PREFIX); + if (financeReceiptMapper.selectByNo(no) != null) { + throw exception(FINANCE_RECEIPT_NO_EXISTS); + } + + // 2.1 插入收款单 + ErpFinanceReceiptDO receipt = BeanUtils.toBean(createReqVO, ErpFinanceReceiptDO.class, in -> in + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())); + calculateTotalPrice(receipt, receiptItems); + financeReceiptMapper.insert(receipt); + // 2.2 插入收款单项 + receiptItems.forEach(o -> o.setReceiptId(receipt.getId())); + financeReceiptItemMapper.insertBatch(receiptItems); + + // 3. 更新销售出库、退货的收款金额情况 + updateSalePrice(receiptItems); + return receipt.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateFinanceReceipt(ErpFinanceReceiptSaveReqVO updateReqVO) { + // 1.1 校验存在 + ErpFinanceReceiptDO receipt = validateFinanceReceiptExists(updateReqVO.getId()); + if (ErpAuditStatus.APPROVE.getStatus().equals(receipt.getStatus())) { + throw exception(FINANCE_RECEIPT_UPDATE_FAIL_APPROVE, receipt.getNo()); + } + // 1.2 校验客户 + customerService.validateCustomer(updateReqVO.getCustomerId()); + // 1.3 校验结算账户 + if (updateReqVO.getAccountId() != null) { + accountService.validateAccount(updateReqVO.getAccountId()); + } + // 1.4 校验财务人员 + if (updateReqVO.getFinanceUserId() != null) { + adminUserApi.validateUser(updateReqVO.getFinanceUserId()); + } + // 1.5 校验收款单项的有效性 + List receiptItems = validateFinanceReceiptItems( + updateReqVO.getCustomerId(), updateReqVO.getItems()); + + // 2.1 更新收款单 + ErpFinanceReceiptDO updateObj = BeanUtils.toBean(updateReqVO, ErpFinanceReceiptDO.class); + calculateTotalPrice(updateObj, receiptItems); + financeReceiptMapper.updateById(updateObj); + // 2.2 更新收款单项 + updateFinanceReceiptItemList(updateReqVO.getId(), receiptItems); + } + + private void calculateTotalPrice(ErpFinanceReceiptDO receipt, List receiptItems) { + receipt.setTotalPrice(getSumValue(receiptItems, ErpFinanceReceiptItemDO::getReceiptPrice, BigDecimal::add, BigDecimal.ZERO)); + receipt.setReceiptPrice(receipt.getTotalPrice().subtract(receipt.getDiscountPrice())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateFinanceReceiptStatus(Long id, Integer status) { + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); + // 1.1 校验存在 + ErpFinanceReceiptDO receipt = validateFinanceReceiptExists(id); + // 1.2 校验状态 + if (receipt.getStatus().equals(status)) { + throw exception(approve ? FINANCE_RECEIPT_APPROVE_FAIL : FINANCE_RECEIPT_PROCESS_FAIL); + } + + // 2. 更新状态 + int updateCount = financeReceiptMapper.updateByIdAndStatus(id, receipt.getStatus(), + new ErpFinanceReceiptDO().setStatus(status)); + if (updateCount == 0) { + throw exception(approve ? FINANCE_RECEIPT_APPROVE_FAIL : FINANCE_RECEIPT_PROCESS_FAIL); + } + } + + private List validateFinanceReceiptItems( + Long customerId, + List list) { + return convertList(list, o -> BeanUtils.toBean(o, ErpFinanceReceiptItemDO.class, item -> { + if (ObjectUtil.equal(item.getBizType(), ErpBizTypeEnum.SALE_OUT.getType())) { + ErpSaleOutDO saleOut = saleOutService.validateSaleOut(item.getBizId()); + Assert.equals(saleOut.getCustomerId(), customerId, "客户必须相同"); + item.setTotalPrice(saleOut.getTotalPrice()).setBizNo(saleOut.getNo()); + } else if (ObjectUtil.equal(item.getBizType(), ErpBizTypeEnum.SALE_RETURN.getType())) { + ErpSaleReturnDO saleReturn = saleReturnService.validateSaleReturn(item.getBizId()); + Assert.equals(saleReturn.getCustomerId(), customerId, "客户必须相同"); + item.setTotalPrice(saleReturn.getTotalPrice().negate()).setBizNo(saleReturn.getNo()); + } else { + throw new IllegalArgumentException("业务类型不正确:" + item.getBizType()); + } + })); + } + + private void updateFinanceReceiptItemList(Long id, List newList) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = financeReceiptItemMapper.selectListByReceiptId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setReceiptId(id)); + financeReceiptItemMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + financeReceiptItemMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + financeReceiptItemMapper.deleteBatchIds(convertList(diffList.get(2), ErpFinanceReceiptItemDO::getId)); + } + + // 第三步,更新销售出库、退货的收款金额情况 + updateSalePrice(CollectionUtils.newArrayList(diffList)); + } + + private void updateSalePrice(List receiptItems) { + receiptItems.forEach(receiptItem -> { + BigDecimal totalReceiptPrice = financeReceiptItemMapper.selectReceiptPriceSumByBizIdAndBizType( + receiptItem.getBizId(), receiptItem.getBizType()); + if (ErpBizTypeEnum.SALE_OUT.getType().equals(receiptItem.getBizType())) { + saleOutService.updateSaleInReceiptPrice(receiptItem.getBizId(), totalReceiptPrice); + } else if (ErpBizTypeEnum.SALE_RETURN.getType().equals(receiptItem.getBizType())) { + saleReturnService.updateSaleReturnRefundPrice(receiptItem.getBizId(), totalReceiptPrice.negate()); + } else { + throw new IllegalArgumentException("业务类型不正确:" + receiptItem.getBizType()); + } + }); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteFinanceReceipt(List ids) { + // 1. 校验不处于已审批 + List receipts = financeReceiptMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(receipts)) { + return; + } + receipts.forEach(receipt -> { + if (ErpAuditStatus.APPROVE.getStatus().equals(receipt.getStatus())) { + throw exception(FINANCE_RECEIPT_DELETE_FAIL_APPROVE, receipt.getNo()); + } + }); + + // 2. 遍历删除,并记录操作日志 + receipts.forEach(receipt -> { + // 2.1 删除收款单 + financeReceiptMapper.deleteById(receipt.getId()); + // 2.2 删除收款单项 + List receiptItems = financeReceiptItemMapper.selectListByReceiptId(receipt.getId()); + financeReceiptItemMapper.deleteBatchIds(convertSet(receiptItems, ErpFinanceReceiptItemDO::getId)); + + // 2.3 更新销售出库、退货的收款金额情况 + updateSalePrice(receiptItems); + }); + } + + private ErpFinanceReceiptDO validateFinanceReceiptExists(Long id) { + ErpFinanceReceiptDO receipt = financeReceiptMapper.selectById(id); + if (receipt == null) { + throw exception(FINANCE_RECEIPT_NOT_EXISTS); + } + return receipt; + } + + @Override + public ErpFinanceReceiptDO getFinanceReceipt(Long id) { + return financeReceiptMapper.selectById(id); + } + + @Override + public PageResult getFinanceReceiptPage(ErpFinanceReceiptPageReqVO pageReqVO) { + return financeReceiptMapper.selectPage(pageReqVO); + } + + // ==================== 收款单项 ==================== + + @Override + public List getFinanceReceiptItemListByReceiptId(Long receiptId) { + return financeReceiptItemMapper.selectListByReceiptId(receiptId); + } + + @Override + public List getFinanceReceiptItemListByReceiptIds(Collection receiptIds) { + if (CollUtil.isEmpty(receiptIds)) { + return Collections.emptyList(); + } + return financeReceiptItemMapper.selectListByReceiptIds(receiptIds); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryService.java new file mode 100644 index 000000000..2b11faf61 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryService.java @@ -0,0 +1,77 @@ +package cn.iocoder.yudao.module.erp.service.product; + +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.category.ErpProductCategoryListReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.category.ErpProductCategorySaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductCategoryDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * ERP 产品分类 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpProductCategoryService { + + /** + * 创建产品分类 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProductCategory(@Valid ErpProductCategorySaveReqVO createReqVO); + + /** + * 更新产品分类 + * + * @param updateReqVO 更新信息 + */ + void updateProductCategory(@Valid ErpProductCategorySaveReqVO updateReqVO); + + /** + * 删除产品分类 + * + * @param id 编号 + */ + void deleteProductCategory(Long id); + + /** + * 获得产品分类 + * + * @param id 编号 + * @return 产品分类 + */ + ErpProductCategoryDO getProductCategory(Long id); + + /** + * 获得产品分类列表 + * + * @param listReqVO 查询条件 + * @return 产品分类列表 + */ + List getProductCategoryList(ErpProductCategoryListReqVO listReqVO); + + /** + * 获得产品分类列表 + * + * @param ids 编号数组 + * @return 产品分类列表 + */ + List getProductCategoryList(Collection ids); + + /** + * 获得产品分类 Map + * + * @param ids 编号数组 + * @return 产品分类 Map + */ + default Map getProductCategoryMap(Collection ids) { + return convertMap(getProductCategoryList(ids), ErpProductCategoryDO::getId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryServiceImpl.java new file mode 100644 index 000000000..bd045aced --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductCategoryServiceImpl.java @@ -0,0 +1,149 @@ +package cn.iocoder.yudao.module.erp.service.product; + +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.category.ErpProductCategoryListReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.category.ErpProductCategorySaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductCategoryDO; +import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductCategoryMapper; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +/** + * ERP 产品分类 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpProductCategoryServiceImpl implements ErpProductCategoryService { + + @Resource + private ErpProductCategoryMapper erpProductCategoryMapper; + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private ErpProductService productService; + + @Override + public Long createProductCategory(ErpProductCategorySaveReqVO createReqVO) { + // 校验父分类编号的有效性 + validateParentProductCategory(null, createReqVO.getParentId()); + // 校验分类名称的唯一性 + validateProductCategoryNameUnique(null, createReqVO.getParentId(), createReqVO.getName()); + + // 插入 + ErpProductCategoryDO category = BeanUtils.toBean(createReqVO, ErpProductCategoryDO.class); + erpProductCategoryMapper.insert(category); + // 返回 + return category.getId(); + } + + @Override + public void updateProductCategory(ErpProductCategorySaveReqVO updateReqVO) { + // 校验存在 + validateProductCategoryExists(updateReqVO.getId()); + // 校验父分类编号的有效性 + validateParentProductCategory(updateReqVO.getId(), updateReqVO.getParentId()); + // 校验分类名称的唯一性 + validateProductCategoryNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName()); + + // 更新 + ErpProductCategoryDO updateObj = BeanUtils.toBean(updateReqVO, ErpProductCategoryDO.class); + erpProductCategoryMapper.updateById(updateObj); + } + + @Override + public void deleteProductCategory(Long id) { + // 1.1 校验存在 + validateProductCategoryExists(id); + // 1.2 校验是否有子产品分类 + if (erpProductCategoryMapper.selectCountByParentId(id) > 0) { + throw exception(PRODUCT_CATEGORY_EXITS_CHILDREN); + } + // 1.3 校验是否有产品 + if (productService.getProductCountByCategoryId(id) > 0) { + throw exception(PRODUCT_CATEGORY_EXITS_PRODUCT); + } + // 2. 删除 + erpProductCategoryMapper.deleteById(id); + } + + private void validateProductCategoryExists(Long id) { + if (erpProductCategoryMapper.selectById(id) == null) { + throw exception(PRODUCT_CATEGORY_NOT_EXISTS); + } + } + + private void validateParentProductCategory(Long id, Long parentId) { + if (parentId == null || ErpProductCategoryDO.PARENT_ID_ROOT.equals(parentId)) { + return; + } + // 1. 不能设置自己为父产品分类 + if (Objects.equals(id, parentId)) { + throw exception(PRODUCT_CATEGORY_PARENT_ERROR); + } + // 2. 父产品分类不存在 + ErpProductCategoryDO parentCategory = erpProductCategoryMapper.selectById(parentId); + if (parentCategory == null) { + throw exception(PRODUCT_CATEGORY_PARENT_NOT_EXITS); + } + // 3. 递归校验父产品分类,如果父产品分类是自己的子产品分类,则报错,避免形成环路 + if (id == null) { // id 为空,说明新增,不需要考虑环路 + return; + } + for (int i = 0; i < Short.MAX_VALUE; i++) { + // 3.1 校验环路 + parentId = parentCategory.getParentId(); + if (Objects.equals(id, parentId)) { + throw exception(PRODUCT_CATEGORY_PARENT_IS_CHILD); + } + // 3.2 继续递归下一级父产品分类 + if (parentId == null || ErpProductCategoryDO.PARENT_ID_ROOT.equals(parentId)) { + break; + } + parentCategory = erpProductCategoryMapper.selectById(parentId); + if (parentCategory == null) { + break; + } + } + } + + private void validateProductCategoryNameUnique(Long id, Long parentId, String name) { + ErpProductCategoryDO productCategory = erpProductCategoryMapper.selectByParentIdAndName(parentId, name); + if (productCategory == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的产品分类 + if (id == null) { + throw exception(PRODUCT_CATEGORY_NAME_DUPLICATE); + } + if (!Objects.equals(productCategory.getId(), id)) { + throw exception(PRODUCT_CATEGORY_NAME_DUPLICATE); + } + } + + @Override + public ErpProductCategoryDO getProductCategory(Long id) { + return erpProductCategoryMapper.selectById(id); + } + + @Override + public List getProductCategoryList(ErpProductCategoryListReqVO listReqVO) { + return erpProductCategoryMapper.selectList(listReqVO); + } + + @Override + public List getProductCategoryList(Collection ids) { + return erpProductCategoryMapper.selectBatchIds(ids); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductService.java new file mode 100644 index 000000000..778c7f29a --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductService.java @@ -0,0 +1,111 @@ +package cn.iocoder.yudao.module.erp.service.product; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * ERP 产品 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpProductService { + + /** + * 创建产品 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProduct(@Valid ProductSaveReqVO createReqVO); + + /** + * 更新产品 + * + * @param updateReqVO 更新信息 + */ + void updateProduct(@Valid ProductSaveReqVO updateReqVO); + + /** + * 删除产品 + * + * @param id 编号 + */ + void deleteProduct(Long id); + + /** + * 校验产品们的有效性 + * + * @param ids 编号数组 + * @return 产品列表 + */ + List validProductList(Collection ids); + + /** + * 获得产品 + * + * @param id 编号 + * @return 产品 + */ + ErpProductDO getProduct(Long id); + + /** + * 获得指定状态的产品 VO 列表 + * + * @param status 状态 + * @return 产品 VO 列表 + */ + List getProductVOListByStatus(Integer status); + + /** + * 获得产品 VO 列表 + * + * @param ids 编号数组 + * @return 产品 VO 列表 + */ + List getProductVOList(Collection ids); + + /** + * 获得产品 VO Map + * + * @param ids 编号数组 + * @return 产品 VO Map + */ + default Map getProductVOMap(Collection ids) { + return convertMap(getProductVOList(ids), ErpProductRespVO::getId); + } + + /** + * 获得产品 VO 分页 + * + * @param pageReqVO 分页查询 + * @return 产品分页 + */ + PageResult getProductVOPage(ErpProductPageReqVO pageReqVO); + + /** + * 基于产品分类编号,获得产品数量 + * + * @param categoryId 产品分类编号 + * @return 产品数量 + */ + Long getProductCountByCategoryId(Long categoryId); + + /** + * 基于产品单位编号,获得产品数量 + * + * @param unitId 产品单位编号 + * @return 产品数量 + */ + Long getProductCountByUnitId(Long unitId); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java new file mode 100644 index 000000000..cbe689c6a --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java @@ -0,0 +1,147 @@ +package cn.iocoder.yudao.module.erp.service.product; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductCategoryDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO; +import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.*; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +/** + * ERP 产品 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpProductServiceImpl implements ErpProductService { + + @Resource + private ErpProductMapper productMapper; + + @Resource + private ErpProductCategoryService productCategoryService; + @Resource + private ErpProductUnitService productUnitService; + + @Override + public Long createProduct(ProductSaveReqVO createReqVO) { + // TODO 芋艿:校验分类 + // 插入 + ErpProductDO product = BeanUtils.toBean(createReqVO, ErpProductDO.class); + productMapper.insert(product); + // 返回 + return product.getId(); + } + + @Override + public void updateProduct(ProductSaveReqVO updateReqVO) { + // TODO 芋艿:校验分类 + // 校验存在 + validateProductExists(updateReqVO.getId()); + // 更新 + ErpProductDO updateObj = BeanUtils.toBean(updateReqVO, ErpProductDO.class); + productMapper.updateById(updateObj); + } + + @Override + public void deleteProduct(Long id) { + // 校验存在 + validateProductExists(id); + // 删除 + productMapper.deleteById(id); + } + + @Override + public List validProductList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + List list = productMapper.selectBatchIds(ids); + Map productMap = convertMap(list, ErpProductDO::getId); + for (Long id : ids) { + ErpProductDO product = productMap.get(id); + if (productMap.get(id) == null) { + throw exception(PRODUCT_NOT_EXISTS); + } + if (CommonStatusEnum.isDisable(product.getStatus())) { + throw exception(PRODUCT_NOT_ENABLE, product.getName()); + } + } + return list; + } + + private void validateProductExists(Long id) { + if (productMapper.selectById(id) == null) { + throw exception(PRODUCT_NOT_EXISTS); + } + } + + @Override + public ErpProductDO getProduct(Long id) { + return productMapper.selectById(id); + } + + @Override + public List getProductVOListByStatus(Integer status) { + List list = productMapper.selectListByStatus(status); + return buildProductVOList(list); + } + + @Override + public List getProductVOList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + List list = productMapper.selectBatchIds(ids); + return buildProductVOList(list); + } + + @Override + public PageResult getProductVOPage(ErpProductPageReqVO pageReqVO) { + PageResult pageResult = productMapper.selectPage(pageReqVO); + return new PageResult<>(buildProductVOList(pageResult.getList()), pageResult.getTotal()); + } + + private List buildProductVOList(List list) { + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); + } + Map categoryMap = productCategoryService.getProductCategoryMap( + convertSet(list, ErpProductDO::getCategoryId)); + Map unitMap = productUnitService.getProductUnitMap( + convertSet(list, ErpProductDO::getUnitId)); + return BeanUtils.toBean(list, ErpProductRespVO.class, product -> { + MapUtils.findAndThen(categoryMap, product.getCategoryId(), + category -> product.setCategoryName(category.getName())); + MapUtils.findAndThen(unitMap, product.getUnitId(), + unit -> product.setUnitName(unit.getName())); + }); + } + + @Override + public Long getProductCountByCategoryId(Long categoryId) { + return productMapper.selectCountByCategoryId(categoryId); + } + + @Override + public Long getProductCountByUnitId(Long unitId) { + return productMapper.selectCountByUnitId(unitId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitService.java new file mode 100644 index 000000000..340278ab9 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitService.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.module.erp.service.product; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * ERP 产品单位 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpProductUnitService { + + /** + * 创建产品单位 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProductUnit(@Valid ErpProductUnitSaveReqVO createReqVO); + + /** + * 更新产品单位 + * + * @param updateReqVO 更新信息 + */ + void updateProductUnit(@Valid ErpProductUnitSaveReqVO updateReqVO); + + /** + * 删除产品单位 + * + * @param id 编号 + */ + void deleteProductUnit(Long id); + + /** + * 获得产品单位 + * + * @param id 编号 + * @return 产品单位 + */ + ErpProductUnitDO getProductUnit(Long id); + + /** + * 获得产品单位分页 + * + * @param pageReqVO 分页查询 + * @return 产品单位分页 + */ + PageResult getProductUnitPage(ErpProductUnitPageReqVO pageReqVO); + + /** + * 获得指定状态的产品单位列表 + * + * @param status 状态 + * @return 产品单位列表 + */ + List getProductUnitListByStatus(Integer status); + + /** + * 获得产品单位列表 + * + * @param ids 编号数组 + * @return 产品单位列表 + */ + List getProductUnitList(Collection ids); + + /** + * 获得产品单位 Map + * + * @param ids 编号数组 + * @return 产品单位 Map + */ + default Map getProductUnitMap(Collection ids) { + return convertMap(getProductUnitList(ids), ErpProductUnitDO::getId); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitServiceImpl.java new file mode 100644 index 000000000..89ba6b08e --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitServiceImpl.java @@ -0,0 +1,111 @@ +package cn.iocoder.yudao.module.erp.service.product; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO; +import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductUnitMapper; +import com.google.common.annotations.VisibleForTesting; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +/** + * ERP 产品单位 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpProductUnitServiceImpl implements ErpProductUnitService { + + @Resource + private ErpProductUnitMapper productUnitMapper; + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private ErpProductService productService; + + @Override + public Long createProductUnit(ErpProductUnitSaveReqVO createReqVO) { + // 1. 校验名字唯一 + validateProductUnitNameUnique(null, createReqVO.getName()); + // 2. 插入 + ErpProductUnitDO unit = BeanUtils.toBean(createReqVO, ErpProductUnitDO.class); + productUnitMapper.insert(unit); + return unit.getId(); + } + + @Override + public void updateProductUnit(ErpProductUnitSaveReqVO updateReqVO) { + // 1.1 校验存在 + validateProductUnitExists(updateReqVO.getId()); + // 1.2 校验名字唯一 + validateProductUnitNameUnique(updateReqVO.getId(), updateReqVO.getName()); + // 2. 更新 + ErpProductUnitDO updateObj = BeanUtils.toBean(updateReqVO, ErpProductUnitDO.class); + productUnitMapper.updateById(updateObj); + } + + @VisibleForTesting + void validateProductUnitNameUnique(Long id, String name) { + ErpProductUnitDO unit = productUnitMapper.selectByName(name); + if (unit == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的字典类型 + if (id == null) { + throw exception(PRODUCT_UNIT_NAME_DUPLICATE); + } + if (!unit.getId().equals(id)) { + throw exception(PRODUCT_UNIT_NAME_DUPLICATE); + } + } + + @Override + public void deleteProductUnit(Long id) { + // 1.1 校验存在 + validateProductUnitExists(id); + // 1.2 校验产品是否使用 + if (productService.getProductCountByUnitId(id) > 0) { + throw exception(PRODUCT_UNIT_EXITS_PRODUCT); + } + // 2. 删除 + productUnitMapper.deleteById(id); + } + + private void validateProductUnitExists(Long id) { + if (productUnitMapper.selectById(id) == null) { + throw exception(PRODUCT_UNIT_NOT_EXISTS); + } + } + + @Override + public ErpProductUnitDO getProductUnit(Long id) { + return productUnitMapper.selectById(id); + } + + @Override + public PageResult getProductUnitPage(ErpProductUnitPageReqVO pageReqVO) { + return productUnitMapper.selectPage(pageReqVO); + } + + @Override + public List getProductUnitListByStatus(Integer status) { + return productUnitMapper.selectListByStatus(status); + } + + @Override + public List getProductUnitList(Collection ids) { + return productUnitMapper.selectBatchIds(ids); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInService.java new file mode 100644 index 000000000..37aa452cd --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInService.java @@ -0,0 +1,101 @@ +package cn.iocoder.yudao.module.erp.service.purchase; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInItemDO; +import jakarta.validation.Valid; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; + +/** + * ERP 采购入库 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpPurchaseInService { + + /** + * 创建采购入库 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createPurchaseIn(@Valid ErpPurchaseInSaveReqVO createReqVO); + + /** + * 更新采购入库 + * + * @param updateReqVO 更新信息 + */ + void updatePurchaseIn(@Valid ErpPurchaseInSaveReqVO updateReqVO); + + /** + * 更新采购入库的状态 + * + * @param id 编号 + * @param status 状态 + */ + void updatePurchaseInStatus(Long id, Integer status); + + /** + * 更新采购入库的付款金额 + * + * @param id 编号 + * @param paymentPrice 付款金额 + */ + void updatePurchaseInPaymentPrice(Long id, BigDecimal paymentPrice); + + /** + * 删除采购入库 + * + * @param ids 编号数组 + */ + void deletePurchaseIn(List ids); + + /** + * 获得采购入库 + * + * @param id 编号 + * @return 采购入库 + */ + ErpPurchaseInDO getPurchaseIn(Long id); + + /** + * 校验采购入库,已经审核通过 + * + * @param id 编号 + * @return 采购入库 + */ + ErpPurchaseInDO validatePurchaseIn(Long id); + + /** + * 获得采购入库分页 + * + * @param pageReqVO 分页查询 + * @return 采购入库分页 + */ + PageResult getPurchaseInPage(ErpPurchaseInPageReqVO pageReqVO); + + // ==================== 采购入库项 ==================== + + /** + * 获得采购入库项列表 + * + * @param inId 采购入库编号 + * @return 采购入库项列表 + */ + List getPurchaseInItemListByInId(Long inId); + + /** + * 获得采购入库项 List + * + * @param inIds 采购入库编号数组 + * @return 采购入库项 List + */ + List getPurchaseInItemListByInIds(Collection inIds); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java new file mode 100644 index 000000000..5d6ad6230 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInServiceImpl.java @@ -0,0 +1,308 @@ +package cn.iocoder.yudao.module.erp.service.purchase; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.in.ErpPurchaseInSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInItemDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO; +import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseInItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseInMapper; +import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum; +import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockRecordService; +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +// TODO 芋艿:记录操作日志 + +/** + * ERP 采购入库 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpPurchaseInServiceImpl implements ErpPurchaseInService { + + @Resource + private ErpPurchaseInMapper purchaseInMapper; + @Resource + private ErpPurchaseInItemMapper purchaseInItemMapper; + + @Resource + private ErpNoRedisDAO noRedisDAO; + + @Resource + private ErpProductService productService; + @Resource + @Lazy // 延迟加载,避免循环依赖 + private ErpPurchaseOrderService purchaseOrderService; + @Resource + private ErpAccountService accountService; + @Resource + private ErpStockRecordService stockRecordService; + + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createPurchaseIn(ErpPurchaseInSaveReqVO createReqVO) { + // 1.1 校验采购订单已审核 + ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.validatePurchaseOrder(createReqVO.getOrderId()); + // 1.2 校验入库项的有效性 + List purchaseInItems = validatePurchaseInItems(createReqVO.getItems()); + // 1.3 校验结算账户 + accountService.validateAccount(createReqVO.getAccountId()); + // 1.4 生成入库单号,并校验唯一性 + String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_IN_NO_PREFIX); + if (purchaseInMapper.selectByNo(no) != null) { + throw exception(PURCHASE_IN_NO_EXISTS); + } + + // 2.1 插入入库 + ErpPurchaseInDO purchaseIn = BeanUtils.toBean(createReqVO, ErpPurchaseInDO.class, in -> in + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())) + .setOrderNo(purchaseOrder.getNo()).setSupplierId(purchaseOrder.getSupplierId()); + calculateTotalPrice(purchaseIn, purchaseInItems); + purchaseInMapper.insert(purchaseIn); + // 2.2 插入入库项 + purchaseInItems.forEach(o -> o.setInId(purchaseIn.getId())); + purchaseInItemMapper.insertBatch(purchaseInItems); + + // 3. 更新采购订单的入库数量 + updatePurchaseOrderInCount(createReqVO.getOrderId()); + return purchaseIn.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePurchaseIn(ErpPurchaseInSaveReqVO updateReqVO) { + // 1.1 校验存在 + ErpPurchaseInDO purchaseIn = validatePurchaseInExists(updateReqVO.getId()); + if (ErpAuditStatus.APPROVE.getStatus().equals(purchaseIn.getStatus())) { + throw exception(PURCHASE_IN_UPDATE_FAIL_APPROVE, purchaseIn.getNo()); + } + // 1.2 校验采购订单已审核 + ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.validatePurchaseOrder(updateReqVO.getOrderId()); + // 1.3 校验结算账户 + accountService.validateAccount(updateReqVO.getAccountId()); + // 1.4 校验订单项的有效性 + List purchaseInItems = validatePurchaseInItems(updateReqVO.getItems()); + + // 2.1 更新入库 + ErpPurchaseInDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseInDO.class) + .setOrderNo(purchaseOrder.getNo()).setSupplierId(purchaseOrder.getSupplierId()); + calculateTotalPrice(updateObj, purchaseInItems); + purchaseInMapper.updateById(updateObj); + // 2.2 更新入库项 + updatePurchaseInItemList(updateReqVO.getId(), purchaseInItems); + + // 3.1 更新采购订单的入库数量 + updatePurchaseOrderInCount(updateObj.getOrderId()); + // 3.2 注意:如果采购订单编号变更了,需要更新“老”采购订单的入库数量 + if (ObjectUtil.notEqual(purchaseIn.getOrderId(), updateObj.getOrderId())) { + updatePurchaseOrderInCount(purchaseIn.getOrderId()); + } + } + + private void calculateTotalPrice(ErpPurchaseInDO purchaseIn, List purchaseInItems) { + purchaseIn.setTotalCount(getSumValue(purchaseInItems, ErpPurchaseInItemDO::getCount, BigDecimal::add)); + purchaseIn.setTotalProductPrice(getSumValue(purchaseInItems, ErpPurchaseInItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)); + purchaseIn.setTotalTaxPrice(getSumValue(purchaseInItems, ErpPurchaseInItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO)); + purchaseIn.setTotalPrice(purchaseIn.getTotalProductPrice().add(purchaseIn.getTotalTaxPrice())); + // 计算优惠价格 + if (purchaseIn.getDiscountPercent() == null) { + purchaseIn.setDiscountPercent(BigDecimal.ZERO); + } + purchaseIn.setDiscountPrice(MoneyUtils.priceMultiplyPercent(purchaseIn.getTotalPrice(), purchaseIn.getDiscountPercent())); + purchaseIn.setTotalPrice(purchaseIn.getTotalPrice().subtract(purchaseIn.getDiscountPrice().add(purchaseIn.getOtherPrice()))); + } + + private void updatePurchaseOrderInCount(Long orderId) { + // 1.1 查询采购订单对应的采购入库单列表 + List purchaseIns = purchaseInMapper.selectListByOrderId(orderId); + // 1.2 查询对应的采购订单项的入库数量 + Map returnCountMap = purchaseInItemMapper.selectOrderItemCountSumMapByInIds( + convertList(purchaseIns, ErpPurchaseInDO::getId)); + // 2. 更新采购订单的入库数量 + purchaseOrderService.updatePurchaseOrderInCount(orderId, returnCountMap); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePurchaseInStatus(Long id, Integer status) { + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); + // 1.1 校验存在 + ErpPurchaseInDO purchaseIn = validatePurchaseInExists(id); + // 1.2 校验状态 + if (purchaseIn.getStatus().equals(status)) { + throw exception(approve ? PURCHASE_IN_APPROVE_FAIL : PURCHASE_IN_PROCESS_FAIL); + } + // 1.3 校验已付款 + if (approve && purchaseIn.getPaymentPrice().compareTo(BigDecimal.ZERO) > 0) { + throw exception(PURCHASE_IN_PROCESS_FAIL_EXISTS_PAYMENT); + } + + // 2. 更新状态 + int updateCount = purchaseInMapper.updateByIdAndStatus(id, purchaseIn.getStatus(), + new ErpPurchaseInDO().setStatus(status)); + if (updateCount == 0) { + throw exception(approve ? PURCHASE_IN_APPROVE_FAIL : PURCHASE_IN_PROCESS_FAIL); + } + + // 3. 变更库存 + List purchaseInItems = purchaseInItemMapper.selectListByInId(id); + Integer bizType = approve ? ErpStockRecordBizTypeEnum.PURCHASE_IN.getType() + : ErpStockRecordBizTypeEnum.PURCHASE_IN_CANCEL.getType(); + purchaseInItems.forEach(purchaseInItem -> { + BigDecimal count = approve ? purchaseInItem.getCount() : purchaseInItem.getCount().negate(); + stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO( + purchaseInItem.getProductId(), purchaseInItem.getWarehouseId(), count, + bizType, purchaseInItem.getInId(), purchaseInItem.getId(), purchaseIn.getNo())); + }); + } + + @Override + public void updatePurchaseInPaymentPrice(Long id, BigDecimal paymentPrice) { + ErpPurchaseInDO purchaseIn = purchaseInMapper.selectById(id); + if (purchaseIn.getPaymentPrice().equals(paymentPrice)) { + return; + } + if (paymentPrice.compareTo(purchaseIn.getTotalPrice()) > 0) { + throw exception(PURCHASE_IN_FAIL_PAYMENT_PRICE_EXCEED, paymentPrice, purchaseIn.getTotalPrice()); + } + purchaseInMapper.updateById(new ErpPurchaseInDO().setId(id).setPaymentPrice(paymentPrice)); + } + + private List validatePurchaseInItems(List list) { + // 1. 校验产品存在 + List productList = productService.validProductList( + convertSet(list, ErpPurchaseInSaveReqVO.Item::getProductId)); + Map productMap = convertMap(productList, ErpProductDO::getId); + // 2. 转化为 ErpPurchaseInItemDO 列表 + return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseInItemDO.class, item -> { + item.setProductUnitId(productMap.get(item.getProductId()).getUnitId()); + item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount())); + if (item.getTotalPrice() == null) { + return; + } + if (item.getTaxPercent() != null) { + item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent())); + } + })); + } + + private void updatePurchaseInItemList(Long id, List newList) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = purchaseInItemMapper.selectListByInId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setInId(id)); + purchaseInItemMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + purchaseInItemMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + purchaseInItemMapper.deleteBatchIds(convertList(diffList.get(2), ErpPurchaseInItemDO::getId)); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deletePurchaseIn(List ids) { + // 1. 校验不处于已审批 + List purchaseIns = purchaseInMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(purchaseIns)) { + return; + } + purchaseIns.forEach(purchaseIn -> { + if (ErpAuditStatus.APPROVE.getStatus().equals(purchaseIn.getStatus())) { + throw exception(PURCHASE_IN_DELETE_FAIL_APPROVE, purchaseIn.getNo()); + } + }); + + // 2. 遍历删除,并记录操作日志 + purchaseIns.forEach(purchaseIn -> { + // 2.1 删除订单 + purchaseInMapper.deleteById(purchaseIn.getId()); + // 2.2 删除订单项 + purchaseInItemMapper.deleteByInId(purchaseIn.getId()); + + // 2.3 更新采购订单的入库数量 + updatePurchaseOrderInCount(purchaseIn.getOrderId()); + }); + + } + + private ErpPurchaseInDO validatePurchaseInExists(Long id) { + ErpPurchaseInDO purchaseIn = purchaseInMapper.selectById(id); + if (purchaseIn == null) { + throw exception(PURCHASE_IN_NOT_EXISTS); + } + return purchaseIn; + } + + @Override + public ErpPurchaseInDO getPurchaseIn(Long id) { + return purchaseInMapper.selectById(id); + } + + @Override + public ErpPurchaseInDO validatePurchaseIn(Long id) { + ErpPurchaseInDO purchaseIn = validatePurchaseInExists(id); + if (ObjectUtil.notEqual(purchaseIn.getStatus(), ErpAuditStatus.APPROVE.getStatus())) { + throw exception(PURCHASE_IN_NOT_APPROVE); + } + return purchaseIn; + } + + @Override + public PageResult getPurchaseInPage(ErpPurchaseInPageReqVO pageReqVO) { + return purchaseInMapper.selectPage(pageReqVO); + } + + // ==================== 采购入库项 ==================== + + @Override + public List getPurchaseInItemListByInId(Long inId) { + return purchaseInItemMapper.selectListByInId(inId); + } + + @Override + public List getPurchaseInItemListByInIds(Collection inIds) { + if (CollUtil.isEmpty(inIds)) { + return Collections.emptyList(); + } + return purchaseInItemMapper.selectListByInIds(inIds); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java new file mode 100644 index 000000000..bfea076bb --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java @@ -0,0 +1,110 @@ +package cn.iocoder.yudao.module.erp.service.purchase; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO; +import jakarta.validation.Valid; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * ERP 采购订单 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpPurchaseOrderService { + + /** + * 创建采购订单 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createPurchaseOrder(@Valid ErpPurchaseOrderSaveReqVO createReqVO); + + /** + * 更新采购订单 + * + * @param updateReqVO 更新信息 + */ + void updatePurchaseOrder(@Valid ErpPurchaseOrderSaveReqVO updateReqVO); + + /** + * 更新采购订单的状态 + * + * @param id 编号 + * @param status 状态 + */ + void updatePurchaseOrderStatus(Long id, Integer status); + + /** + * 更新采购订单的入库数量 + * + * @param id 编号 + * @param inCountMap 入库数量 Map:key 采购订单项编号;value 入库数量 + */ + void updatePurchaseOrderInCount(Long id, Map inCountMap); + + /** + * 更新采购订单的退货数量 + * + * @param orderId 编号 + * @param returnCountMap 退货数量 Map:key 采购订单项编号;value 退货数量 + */ + void updatePurchaseOrderReturnCount(Long orderId, Map returnCountMap); + + /** + * 删除采购订单 + * + * @param ids 编号数组 + */ + void deletePurchaseOrder(List ids); + + /** + * 获得采购订单 + * + * @param id 编号 + * @return 采购订单 + */ + ErpPurchaseOrderDO getPurchaseOrder(Long id); + + /** + * 校验采购订单,已经审核通过 + * + * @param id 编号 + * @return 采购订单 + */ + ErpPurchaseOrderDO validatePurchaseOrder(Long id); + + /** + * 获得采购订单分页 + * + * @param pageReqVO 分页查询 + * @return 采购订单分页 + */ + PageResult getPurchaseOrderPage(ErpPurchaseOrderPageReqVO pageReqVO); + + // ==================== 采购订单项 ==================== + + /** + * 获得采购订单项列表 + * + * @param orderId 采购订单编号 + * @return 采购订单项列表 + */ + List getPurchaseOrderItemListByOrderId(Long orderId); + + /** + * 获得采购订单项 List + * + * @param orderIds 采购订单编号数组 + * @return 采购订单项 List + */ + List getPurchaseOrderItemListByOrderIds(Collection orderIds); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java new file mode 100644 index 000000000..74b5b18d2 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java @@ -0,0 +1,295 @@ +package cn.iocoder.yudao.module.erp.service.purchase; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO; +import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderMapper; +import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +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.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +// TODO 芋艿:记录操作日志 + +/** + * ERP 采购订单 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpPurchaseOrderServiceImpl implements ErpPurchaseOrderService { + + @Resource + private ErpPurchaseOrderMapper purchaseOrderMapper; + @Resource + private ErpPurchaseOrderItemMapper purchaseOrderItemMapper; + + @Resource + private ErpNoRedisDAO noRedisDAO; + + @Resource + private ErpProductService productService; + @Resource + private ErpSupplierService supplierService; + @Resource + private ErpAccountService accountService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createPurchaseOrder(ErpPurchaseOrderSaveReqVO createReqVO) { + // 1.1 校验订单项的有效性 + List purchaseOrderItems = validatePurchaseOrderItems(createReqVO.getItems()); + // 1.2 校验供应商 + supplierService.validateSupplier(createReqVO.getSupplierId()); + // 1.3 校验结算账户 + if (createReqVO.getAccountId() != null) { + accountService.validateAccount(createReqVO.getAccountId()); + } + // 1.4 生成订单号,并校验唯一性 + String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX); + if (purchaseOrderMapper.selectByNo(no) != null) { + throw exception(PURCHASE_ORDER_NO_EXISTS); + } + + // 2.1 插入订单 + ErpPurchaseOrderDO purchaseOrder = BeanUtils.toBean(createReqVO, ErpPurchaseOrderDO.class, in -> in + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())); + calculateTotalPrice(purchaseOrder, purchaseOrderItems); + purchaseOrderMapper.insert(purchaseOrder); + // 2.2 插入订单项 + purchaseOrderItems.forEach(o -> o.setOrderId(purchaseOrder.getId())); + purchaseOrderItemMapper.insertBatch(purchaseOrderItems); + return purchaseOrder.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePurchaseOrder(ErpPurchaseOrderSaveReqVO updateReqVO) { + // 1.1 校验存在 + ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(updateReqVO.getId()); + if (ErpAuditStatus.APPROVE.getStatus().equals(purchaseOrder.getStatus())) { + throw exception(PURCHASE_ORDER_UPDATE_FAIL_APPROVE, purchaseOrder.getNo()); + } + // 1.2 校验供应商 + supplierService.validateSupplier(updateReqVO.getSupplierId()); + // 1.3 校验结算账户 + if (updateReqVO.getAccountId() != null) { + accountService.validateAccount(updateReqVO.getAccountId()); + } + // 1.4 校验订单项的有效性 + List purchaseOrderItems = validatePurchaseOrderItems(updateReqVO.getItems()); + + // 2.1 更新订单 + ErpPurchaseOrderDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseOrderDO.class); + calculateTotalPrice(updateObj, purchaseOrderItems); + purchaseOrderMapper.updateById(updateObj); + // 2.2 更新订单项 + updatePurchaseOrderItemList(updateReqVO.getId(), purchaseOrderItems); + } + + private void calculateTotalPrice(ErpPurchaseOrderDO purchaseOrder, List purchaseOrderItems) { + purchaseOrder.setTotalCount(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getCount, BigDecimal::add)); + purchaseOrder.setTotalProductPrice(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)); + purchaseOrder.setTotalTaxPrice(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO)); + purchaseOrder.setTotalPrice(purchaseOrder.getTotalProductPrice().add(purchaseOrder.getTotalTaxPrice())); + // 计算优惠价格 + if (purchaseOrder.getDiscountPercent() == null) { + purchaseOrder.setDiscountPercent(BigDecimal.ZERO); + } + purchaseOrder.setDiscountPrice(MoneyUtils.priceMultiplyPercent(purchaseOrder.getTotalPrice(), purchaseOrder.getDiscountPercent())); + purchaseOrder.setTotalPrice(purchaseOrder.getTotalPrice().subtract(purchaseOrder.getDiscountPrice())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePurchaseOrderStatus(Long id, Integer status) { + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); + // 1.1 校验存在 + ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id); + // 1.2 校验状态 + if (purchaseOrder.getStatus().equals(status)) { + throw exception(approve ? PURCHASE_ORDER_APPROVE_FAIL : PURCHASE_ORDER_PROCESS_FAIL); + } + // 1.3 存在采购入单,无法反审核 + if (!approve && purchaseOrder.getInCount().compareTo(BigDecimal.ZERO) > 0) { + throw exception(PURCHASE_ORDER_PROCESS_FAIL_EXISTS_IN); + } + // 1.4 存在采购退货单,无法反审核 + if (!approve && purchaseOrder.getReturnCount().compareTo(BigDecimal.ZERO) > 0) { + throw exception(PURCHASE_ORDER_PROCESS_FAIL_EXISTS_RETURN); + } + + // 2. 更新状态 + int updateCount = purchaseOrderMapper.updateByIdAndStatus(id, purchaseOrder.getStatus(), + new ErpPurchaseOrderDO().setStatus(status)); + if (updateCount == 0) { + throw exception(approve ? PURCHASE_ORDER_APPROVE_FAIL : PURCHASE_ORDER_PROCESS_FAIL); + } + } + + private List validatePurchaseOrderItems(List list) { + // 1. 校验产品存在 + List productList = productService.validProductList( + convertSet(list, ErpPurchaseOrderSaveReqVO.Item::getProductId)); + Map productMap = convertMap(productList, ErpProductDO::getId); + // 2. 转化为 ErpPurchaseOrderItemDO 列表 + return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseOrderItemDO.class, item -> { + item.setProductUnitId(productMap.get(item.getProductId()).getUnitId()); + item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount())); + if (item.getTotalPrice() == null) { + return; + } + if (item.getTaxPercent() != null) { + item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent())); + } + })); + } + + private void updatePurchaseOrderItemList(Long id, List newList) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = purchaseOrderItemMapper.selectListByOrderId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setOrderId(id)); + purchaseOrderItemMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + purchaseOrderItemMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + purchaseOrderItemMapper.deleteBatchIds(convertList(diffList.get(2), ErpPurchaseOrderItemDO::getId)); + } + } + + @Override + public void updatePurchaseOrderInCount(Long id, Map inCountMap) { + List orderItems = purchaseOrderItemMapper.selectListByOrderId(id); + // 1. 更新每个采购订单项 + orderItems.forEach(item -> { + BigDecimal inCount = inCountMap.getOrDefault(item.getId(), BigDecimal.ZERO); + if (item.getInCount().equals(inCount)) { + return; + } + if (inCount.compareTo(item.getCount()) > 0) { + throw exception(PURCHASE_ORDER_ITEM_IN_FAIL_PRODUCT_EXCEED, + productService.getProduct(item.getProductId()).getName(), item.getCount()); + } + purchaseOrderItemMapper.updateById(new ErpPurchaseOrderItemDO().setId(item.getId()).setInCount(inCount)); + }); + // 2. 更新采购订单 + BigDecimal totalInCount = getSumValue(inCountMap.values(), value -> value, BigDecimal::add, BigDecimal.ZERO); + purchaseOrderMapper.updateById(new ErpPurchaseOrderDO().setId(id).setInCount(totalInCount)); + } + + @Override + public void updatePurchaseOrderReturnCount(Long orderId, Map returnCountMap) { + List orderItems = purchaseOrderItemMapper.selectListByOrderId(orderId); + // 1. 更新每个采购订单项 + orderItems.forEach(item -> { + BigDecimal returnCount = returnCountMap.getOrDefault(item.getId(), BigDecimal.ZERO); + if (item.getReturnCount().equals(returnCount)) { + return; + } + if (returnCount.compareTo(item.getInCount()) > 0) { + throw exception(PURCHASE_ORDER_ITEM_RETURN_FAIL_IN_EXCEED, + productService.getProduct(item.getProductId()).getName(), item.getInCount()); + } + purchaseOrderItemMapper.updateById(new ErpPurchaseOrderItemDO().setId(item.getId()).setReturnCount(returnCount)); + }); + // 2. 更新采购订单 + BigDecimal totalReturnCount = getSumValue(returnCountMap.values(), value -> value, BigDecimal::add, BigDecimal.ZERO); + purchaseOrderMapper.updateById(new ErpPurchaseOrderDO().setId(orderId).setReturnCount(totalReturnCount)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deletePurchaseOrder(List ids) { + // 1. 校验不处于已审批 + List purchaseOrders = purchaseOrderMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(purchaseOrders)) { + return; + } + purchaseOrders.forEach(purchaseOrder -> { + if (ErpAuditStatus.APPROVE.getStatus().equals(purchaseOrder.getStatus())) { + throw exception(PURCHASE_ORDER_DELETE_FAIL_APPROVE, purchaseOrder.getNo()); + } + }); + + // 2. 遍历删除,并记录操作日志 + purchaseOrders.forEach(purchaseOrder -> { + // 2.1 删除订单 + purchaseOrderMapper.deleteById(purchaseOrder.getId()); + // 2.2 删除订单项 + purchaseOrderItemMapper.deleteByOrderId(purchaseOrder.getId()); + }); + } + + private ErpPurchaseOrderDO validatePurchaseOrderExists(Long id) { + ErpPurchaseOrderDO purchaseOrder = purchaseOrderMapper.selectById(id); + if (purchaseOrder == null) { + throw exception(PURCHASE_ORDER_NOT_EXISTS); + } + return purchaseOrder; + } + + @Override + public ErpPurchaseOrderDO getPurchaseOrder(Long id) { + return purchaseOrderMapper.selectById(id); + } + + @Override + public ErpPurchaseOrderDO validatePurchaseOrder(Long id) { + ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id); + if (ObjectUtil.notEqual(purchaseOrder.getStatus(), ErpAuditStatus.APPROVE.getStatus())) { + throw exception(PURCHASE_ORDER_NOT_APPROVE); + } + return purchaseOrder; + } + + @Override + public PageResult getPurchaseOrderPage(ErpPurchaseOrderPageReqVO pageReqVO) { + return purchaseOrderMapper.selectPage(pageReqVO); + } + + // ==================== 订单项 ==================== + + @Override + public List getPurchaseOrderItemListByOrderId(Long orderId) { + return purchaseOrderItemMapper.selectListByOrderId(orderId); + } + + @Override + public List getPurchaseOrderItemListByOrderIds(Collection orderIds) { + if (CollUtil.isEmpty(orderIds)) { + return Collections.emptyList(); + } + return purchaseOrderItemMapper.selectListByOrderIds(orderIds); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnService.java new file mode 100644 index 000000000..b6826fa88 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnService.java @@ -0,0 +1,101 @@ +package cn.iocoder.yudao.module.erp.service.purchase; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnItemDO; +import jakarta.validation.Valid; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; + +/** + * ERP 采购退货 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpPurchaseReturnService { + + /** + * 创建采购退货 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createPurchaseReturn(@Valid ErpPurchaseReturnSaveReqVO createReqVO); + + /** + * 更新采购退货 + * + * @param updateReqVO 更新信息 + */ + void updatePurchaseReturn(@Valid ErpPurchaseReturnSaveReqVO updateReqVO); + + /** + * 更新采购退货的状态 + * + * @param id 编号 + * @param status 状态 + */ + void updatePurchaseReturnStatus(Long id, Integer status); + + /** + * 更新采购退货的退款金额 + * + * @param id 编号 + * @param refundPrice 退款金额 + */ + void updatePurchaseReturnRefundPrice(Long id, BigDecimal refundPrice); + + /** + * 删除采购退货 + * + * @param ids 编号数组 + */ + void deletePurchaseReturn(List ids); + + /** + * 获得采购退货 + * + * @param id 编号 + * @return 采购退货 + */ + ErpPurchaseReturnDO getPurchaseReturn(Long id); + + /** + * 校验采购退货,已经审核通过 + * + * @param id 编号 + * @return 采购退货 + */ + ErpPurchaseReturnDO validatePurchaseReturn(Long id); + + /** + * 获得采购退货分页 + * + * @param pageReqVO 分页查询 + * @return 采购退货分页 + */ + PageResult getPurchaseReturnPage(ErpPurchaseReturnPageReqVO pageReqVO); + + // ==================== 采购退货项 ==================== + + /** + * 获得采购退货项列表 + * + * @param returnId 采购退货编号 + * @return 采购退货项列表 + */ + List getPurchaseReturnItemListByReturnId(Long returnId); + + /** + * 获得采购退货项 List + * + * @param returnIds 采购退货编号数组 + * @return 采购退货项 List + */ + List getPurchaseReturnItemListByReturnIds(Collection returnIds); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnServiceImpl.java new file mode 100644 index 000000000..05960e896 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseReturnServiceImpl.java @@ -0,0 +1,304 @@ +package cn.iocoder.yudao.module.erp.service.purchase; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.returns.ErpPurchaseReturnSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseReturnItemDO; +import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseReturnItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseReturnMapper; +import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum; +import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockRecordService; +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +// TODO 芋艿:记录操作日志 + +/** + * ERP 采购退货 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpPurchaseReturnServiceImpl implements ErpPurchaseReturnService { + + @Resource + private ErpPurchaseReturnMapper purchaseReturnMapper; + @Resource + private ErpPurchaseReturnItemMapper purchaseReturnItemMapper; + + @Resource + private ErpNoRedisDAO noRedisDAO; + + @Resource + private ErpProductService productService; + @Resource + @Lazy // 延迟加载,避免循环依赖 + private ErpPurchaseOrderService purchaseOrderService; + @Resource + private ErpAccountService accountService; + @Resource + private ErpStockRecordService stockRecordService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createPurchaseReturn(ErpPurchaseReturnSaveReqVO createReqVO) { + // 1.1 校验采购订单已审核 + ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.validatePurchaseOrder(createReqVO.getOrderId()); + // 1.2 校验退货项的有效性 + List purchaseReturnItems = validatePurchaseReturnItems(createReqVO.getItems()); + // 1.3 校验结算账户 + accountService.validateAccount(createReqVO.getAccountId()); + // 1.4 生成退货单号,并校验唯一性 + String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_RETURN_NO_PREFIX); + if (purchaseReturnMapper.selectByNo(no) != null) { + throw exception(PURCHASE_RETURN_NO_EXISTS); + } + + // 2.1 插入退货 + ErpPurchaseReturnDO purchaseReturn = BeanUtils.toBean(createReqVO, ErpPurchaseReturnDO.class, in -> in + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())) + .setOrderNo(purchaseOrder.getNo()).setSupplierId(purchaseOrder.getSupplierId()); + calculateTotalPrice(purchaseReturn, purchaseReturnItems); + purchaseReturnMapper.insert(purchaseReturn); + // 2.2 插入退货项 + purchaseReturnItems.forEach(o -> o.setReturnId(purchaseReturn.getId())); + purchaseReturnItemMapper.insertBatch(purchaseReturnItems); + + // 3. 更新采购订单的退货数量 + updatePurchaseOrderReturnCount(createReqVO.getOrderId()); + return purchaseReturn.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePurchaseReturn(ErpPurchaseReturnSaveReqVO updateReqVO) { + // 1.1 校验存在 + ErpPurchaseReturnDO purchaseReturn = validatePurchaseReturnExists(updateReqVO.getId()); + if (ErpAuditStatus.APPROVE.getStatus().equals(purchaseReturn.getStatus())) { + throw exception(PURCHASE_RETURN_UPDATE_FAIL_APPROVE, purchaseReturn.getNo()); + } + // 1.2 校验采购订单已审核 + ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.validatePurchaseOrder(updateReqVO.getOrderId()); + // 1.3 校验结算账户 + accountService.validateAccount(updateReqVO.getAccountId()); + // 1.4 校验订单项的有效性 + List purchaseReturnItems = validatePurchaseReturnItems(updateReqVO.getItems()); + + // 2.1 更新退货 + ErpPurchaseReturnDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseReturnDO.class) + .setOrderNo(purchaseOrder.getNo()).setSupplierId(purchaseOrder.getSupplierId()); + calculateTotalPrice(updateObj, purchaseReturnItems); + purchaseReturnMapper.updateById(updateObj); + // 2.2 更新退货项 + updatePurchaseReturnItemList(updateReqVO.getId(), purchaseReturnItems); + + // 3.1 更新采购订单的出库数量 + updatePurchaseOrderReturnCount(updateObj.getOrderId()); + // 3.2 注意:如果采购订单编号变更了,需要更新“老”采购订单的出库数量 + if (ObjectUtil.notEqual(purchaseReturn.getOrderId(), updateObj.getOrderId())) { + updatePurchaseOrderReturnCount(purchaseReturn.getOrderId()); + } + } + + private void calculateTotalPrice(ErpPurchaseReturnDO purchaseReturn, List purchaseReturnItems) { + purchaseReturn.setTotalCount(getSumValue(purchaseReturnItems, ErpPurchaseReturnItemDO::getCount, BigDecimal::add)); + purchaseReturn.setTotalProductPrice(getSumValue(purchaseReturnItems, ErpPurchaseReturnItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)); + purchaseReturn.setTotalTaxPrice(getSumValue(purchaseReturnItems, ErpPurchaseReturnItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO)); + purchaseReturn.setTotalPrice(purchaseReturn.getTotalProductPrice().add(purchaseReturn.getTotalTaxPrice())); + // 计算优惠价格 + if (purchaseReturn.getDiscountPercent() == null) { + purchaseReturn.setDiscountPercent(BigDecimal.ZERO); + } + purchaseReturn.setDiscountPrice(MoneyUtils.priceMultiplyPercent(purchaseReturn.getTotalPrice(), purchaseReturn.getDiscountPercent())); + purchaseReturn.setTotalPrice(purchaseReturn.getTotalPrice().subtract(purchaseReturn.getDiscountPrice().add(purchaseReturn.getOtherPrice()))); + } + + private void updatePurchaseOrderReturnCount(Long orderId) { + // 1.1 查询采购订单对应的采购出库单列表 + List purchaseReturns = purchaseReturnMapper.selectListByOrderId(orderId); + // 1.2 查询对应的采购订单项的退货数量 + Map returnCountMap = purchaseReturnItemMapper.selectOrderItemCountSumMapByReturnIds( + convertList(purchaseReturns, ErpPurchaseReturnDO::getId)); + // 2. 更新采购订单的出库数量 + purchaseOrderService.updatePurchaseOrderReturnCount(orderId, returnCountMap); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePurchaseReturnStatus(Long id, Integer status) { + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); + // 1.1 校验存在 + ErpPurchaseReturnDO purchaseReturn = validatePurchaseReturnExists(id); + // 1.2 校验状态 + if (purchaseReturn.getStatus().equals(status)) { + throw exception(approve ? PURCHASE_RETURN_APPROVE_FAIL : PURCHASE_RETURN_PROCESS_FAIL); + } + // 1.3 校验已退款 + if (approve && purchaseReturn.getRefundPrice().compareTo(BigDecimal.ZERO) > 0) { + throw exception(PURCHASE_RETURN_PROCESS_FAIL_EXISTS_REFUND); + } + + // 2. 更新状态 + int updateCount = purchaseReturnMapper.updateByIdAndStatus(id, purchaseReturn.getStatus(), + new ErpPurchaseReturnDO().setStatus(status)); + if (updateCount == 0) { + throw exception(approve ? PURCHASE_RETURN_APPROVE_FAIL : PURCHASE_RETURN_PROCESS_FAIL); + } + + // 3. 变更库存 + List purchaseReturnItems = purchaseReturnItemMapper.selectListByReturnId(id); + Integer bizType = approve ? ErpStockRecordBizTypeEnum.PURCHASE_RETURN.getType() + : ErpStockRecordBizTypeEnum.PURCHASE_RETURN_CANCEL.getType(); + purchaseReturnItems.forEach(purchaseReturnItem -> { + BigDecimal count = approve ? purchaseReturnItem.getCount().negate() : purchaseReturnItem.getCount(); + stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO( + purchaseReturnItem.getProductId(), purchaseReturnItem.getWarehouseId(), count, + bizType, purchaseReturnItem.getReturnId(), purchaseReturnItem.getId(), purchaseReturn.getNo())); + }); + } + + @Override + public void updatePurchaseReturnRefundPrice(Long id, BigDecimal refundPrice) { + ErpPurchaseReturnDO purchaseReturn = purchaseReturnMapper.selectById(id); + if (purchaseReturn.getRefundPrice().equals(refundPrice)) { + return; + } + if (refundPrice.compareTo(purchaseReturn.getTotalPrice()) > 0) { + throw exception(PURCHASE_RETURN_FAIL_REFUND_PRICE_EXCEED, refundPrice, purchaseReturn.getTotalPrice()); + } + purchaseReturnMapper.updateById(new ErpPurchaseReturnDO().setId(id).setRefundPrice(refundPrice)); + } + + private List validatePurchaseReturnItems(List list) { + // 1. 校验产品存在 + List productList = productService.validProductList( + convertSet(list, ErpPurchaseReturnSaveReqVO.Item::getProductId)); + Map productMap = convertMap(productList, ErpProductDO::getId); + // 2. 转化为 ErpPurchaseReturnItemDO 列表 + return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseReturnItemDO.class, item -> { + item.setProductUnitId(productMap.get(item.getProductId()).getUnitId()); + item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount())); + if (item.getTotalPrice() == null) { + return; + } + if (item.getTaxPercent() != null) { + item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent())); + } + })); + } + + private void updatePurchaseReturnItemList(Long id, List newList) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = purchaseReturnItemMapper.selectListByReturnId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setReturnId(id)); + purchaseReturnItemMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + purchaseReturnItemMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + purchaseReturnItemMapper.deleteBatchIds(convertList(diffList.get(2), ErpPurchaseReturnItemDO::getId)); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deletePurchaseReturn(List ids) { + // 1. 校验不处于已审批 + List purchaseReturns = purchaseReturnMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(purchaseReturns)) { + return; + } + purchaseReturns.forEach(purchaseReturn -> { + if (ErpAuditStatus.APPROVE.getStatus().equals(purchaseReturn.getStatus())) { + throw exception(PURCHASE_RETURN_DELETE_FAIL_APPROVE, purchaseReturn.getNo()); + } + }); + + // 2. 遍历删除,并记录操作日志 + purchaseReturns.forEach(purchaseReturn -> { + // 2.1 删除订单 + purchaseReturnMapper.deleteById(purchaseReturn.getId()); + // 2.2 删除订单项 + purchaseReturnItemMapper.deleteByReturnId(purchaseReturn.getId()); + + // 2.3 更新采购订单的出库数量 + updatePurchaseOrderReturnCount(purchaseReturn.getOrderId()); + }); + + } + + private ErpPurchaseReturnDO validatePurchaseReturnExists(Long id) { + ErpPurchaseReturnDO purchaseReturn = purchaseReturnMapper.selectById(id); + if (purchaseReturn == null) { + throw exception(PURCHASE_RETURN_NOT_EXISTS); + } + return purchaseReturn; + } + + @Override + public ErpPurchaseReturnDO getPurchaseReturn(Long id) { + return purchaseReturnMapper.selectById(id); + } + + @Override + public ErpPurchaseReturnDO validatePurchaseReturn(Long id) { + ErpPurchaseReturnDO purchaseReturn = getPurchaseReturn(id); + if (ObjectUtil.notEqual(purchaseReturn.getStatus(), ErpAuditStatus.APPROVE.getStatus())) { + throw exception(PURCHASE_RETURN_NOT_APPROVE); + } + return purchaseReturn; + } + + @Override + public PageResult getPurchaseReturnPage(ErpPurchaseReturnPageReqVO pageReqVO) { + return purchaseReturnMapper.selectPage(pageReqVO); + } + + // ==================== 采购退货项 ==================== + + @Override + public List getPurchaseReturnItemListByReturnId(Long returnId) { + return purchaseReturnItemMapper.selectListByReturnId(returnId); + } + + @Override + public List getPurchaseReturnItemListByReturnIds(Collection returnIds) { + if (CollUtil.isEmpty(returnIds)) { + return Collections.emptyList(); + } + return purchaseReturnItemMapper.selectListByReturnIds(returnIds); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierService.java new file mode 100644 index 000000000..495474d13 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierService.java @@ -0,0 +1,94 @@ +package cn.iocoder.yudao.module.erp.service.purchase; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier.ErpSupplierPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier.ErpSupplierSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * ERP 供应商 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpSupplierService { + + /** + * 创建供应商 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSupplier(@Valid ErpSupplierSaveReqVO createReqVO); + + /** + * 更新供应商 + * + * @param updateReqVO 更新信息 + */ + void updateSupplier(@Valid ErpSupplierSaveReqVO updateReqVO); + + /** + * 删除供应商 + * + * @param id 编号 + */ + void deleteSupplier(Long id); + + /** + * 获得供应商 + * + * @param id 编号 + * @return 供应商 + */ + ErpSupplierDO getSupplier(Long id); + + /** + * 校验供应商 + * + * @param id 编号 + * @return 供应商 + */ + ErpSupplierDO validateSupplier(Long id); + + /** + * 获得供应商列表 + * + * @param ids 编号列表 + * @return 供应商列表 + */ + List getSupplierList(Collection ids); + + /** + * 获得供应商 Map + * + * @param ids 编号列表 + * @return 供应商 Map + */ + default Map getSupplierMap(Collection ids) { + return convertMap(getSupplierList(ids), ErpSupplierDO::getId); + } + + /** + * 获得供应商分页 + * + * @param pageReqVO 分页查询 + * @return 供应商分页 + */ + PageResult getSupplierPage(ErpSupplierPageReqVO pageReqVO); + + /** + * 获得指定状态的供应商列表 + * + * @param status 状态 + * @return 供应商列表 + */ + List getSupplierListByStatus(Integer status); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierServiceImpl.java new file mode 100644 index 000000000..1c90ff3f6 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierServiceImpl.java @@ -0,0 +1,94 @@ +package cn.iocoder.yudao.module.erp.service.purchase; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier.ErpSupplierPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.supplier.ErpSupplierSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO; +import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpSupplierMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +/** + * ERP 供应商 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpSupplierServiceImpl implements ErpSupplierService { + + @Resource + private ErpSupplierMapper supplierMapper; + + @Override + public Long createSupplier(ErpSupplierSaveReqVO createReqVO) { + ErpSupplierDO supplier = BeanUtils.toBean(createReqVO, ErpSupplierDO.class); + supplierMapper.insert(supplier); + return supplier.getId(); + } + + @Override + public void updateSupplier(ErpSupplierSaveReqVO updateReqVO) { + // 校验存在 + validateSupplierExists(updateReqVO.getId()); + // 更新 + ErpSupplierDO updateObj = BeanUtils.toBean(updateReqVO, ErpSupplierDO.class); + supplierMapper.updateById(updateObj); + } + + @Override + public void deleteSupplier(Long id) { + // 校验存在 + validateSupplierExists(id); + // 删除 + supplierMapper.deleteById(id); + } + + private void validateSupplierExists(Long id) { + if (supplierMapper.selectById(id) == null) { + throw exception(SUPPLIER_NOT_EXISTS); + } + } + + @Override + public ErpSupplierDO getSupplier(Long id) { + return supplierMapper.selectById(id); + } + + @Override + public ErpSupplierDO validateSupplier(Long id) { + ErpSupplierDO supplier = supplierMapper.selectById(id); + if (supplier == null) { + throw exception(SUPPLIER_NOT_EXISTS); + } + if (CommonStatusEnum.isDisable(supplier.getStatus())) { + throw exception(SUPPLIER_NOT_ENABLE, supplier.getName()); + } + return supplier; + } + + @Override + public List getSupplierList(Collection ids) { + return supplierMapper.selectBatchIds(ids); + } + + @Override + public PageResult getSupplierPage(ErpSupplierPageReqVO pageReqVO) { + return supplierMapper.selectPage(pageReqVO); + } + + @Override + public List getSupplierListByStatus(Integer status) { + return supplierMapper.selectListByStatus(status); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpCustomerService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpCustomerService.java new file mode 100644 index 000000000..d047300e8 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpCustomerService.java @@ -0,0 +1,94 @@ +package cn.iocoder.yudao.module.erp.service.sale; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.customer.ErpCustomerPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.customer.ErpCustomerSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpCustomerDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * ERP 客户 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpCustomerService { + + /** + * 创建客户 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createCustomer(@Valid ErpCustomerSaveReqVO createReqVO); + + /** + * 更新客户 + * + * @param updateReqVO 更新信息 + */ + void updateCustomer(@Valid ErpCustomerSaveReqVO updateReqVO); + + /** + * 删除客户 + * + * @param id 编号 + */ + void deleteCustomer(Long id); + + /** + * 获得客户 + * + * @param id 编号 + * @return 客户 + */ + ErpCustomerDO getCustomer(Long id); + + /** + * 校验客户 + * + * @param id 编号 + * @return 客户 + */ + ErpCustomerDO validateCustomer(Long id); + + /** + * 获得客户列表 + * + * @param ids 编号列表 + * @return 客户列表 + */ + List getCustomerList(Collection ids); + + /** + * 获得客户 Map + * + * @param ids 编号列表 + * @return 客户 Map + */ + default Map getCustomerMap(Collection ids) { + return convertMap(getCustomerList(ids), ErpCustomerDO::getId); + } + + /** + * 获得客户分页 + * + * @param pageReqVO 分页查询 + * @return 客户分页 + */ + PageResult getCustomerPage(ErpCustomerPageReqVO pageReqVO); + + /** + * 获得指定状态的客户列表 + * + * @param status 状态 + * @return 客户列表 + */ + List getCustomerListByStatus(Integer status); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpCustomerServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpCustomerServiceImpl.java new file mode 100644 index 000000000..73d2aa9d5 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpCustomerServiceImpl.java @@ -0,0 +1,97 @@ +package cn.iocoder.yudao.module.erp.service.sale; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.customer.ErpCustomerPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.customer.ErpCustomerSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpCustomerDO; +import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpCustomerMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.CUSTOMER_NOT_ENABLE; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS; + +/** + * ERP 客户 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpCustomerServiceImpl implements ErpCustomerService { + + @Resource + private ErpCustomerMapper customerMapper; + + @Override + public Long createCustomer(ErpCustomerSaveReqVO createReqVO) { + // 插入 + ErpCustomerDO customer = BeanUtils.toBean(createReqVO, ErpCustomerDO.class); + customerMapper.insert(customer); + // 返回 + return customer.getId(); + } + + @Override + public void updateCustomer(ErpCustomerSaveReqVO updateReqVO) { + // 校验存在 + validateCustomerExists(updateReqVO.getId()); + // 更新 + ErpCustomerDO updateObj = BeanUtils.toBean(updateReqVO, ErpCustomerDO.class); + customerMapper.updateById(updateObj); + } + + @Override + public void deleteCustomer(Long id) { + // 校验存在 + validateCustomerExists(id); + // 删除 + customerMapper.deleteById(id); + } + + private void validateCustomerExists(Long id) { + if (customerMapper.selectById(id) == null) { + throw exception(CUSTOMER_NOT_EXISTS); + } + } + + @Override + public ErpCustomerDO getCustomer(Long id) { + return customerMapper.selectById(id); + } + + @Override + public ErpCustomerDO validateCustomer(Long id) { + ErpCustomerDO customer = customerMapper.selectById(id); + if (customer == null) { + throw exception(CUSTOMER_NOT_EXISTS); + } + if (CommonStatusEnum.isDisable(customer.getStatus())) { + throw exception(CUSTOMER_NOT_ENABLE, customer.getName()); + } + return customer; + } + + @Override + public List getCustomerList(Collection ids) { + return customerMapper.selectBatchIds(ids); + } + + @Override + public PageResult getCustomerPage(ErpCustomerPageReqVO pageReqVO) { + return customerMapper.selectPage(pageReqVO); + } + + @Override + public List getCustomerListByStatus(Integer status) { + return customerMapper.selectListByStatus(status); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java new file mode 100644 index 000000000..c75f201f3 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java @@ -0,0 +1,110 @@ +package cn.iocoder.yudao.module.erp.service.sale; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO; +import jakarta.validation.Valid; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * ERP 销售订单 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpSaleOrderService { + + /** + * 创建销售订单 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSaleOrder(@Valid ErpSaleOrderSaveReqVO createReqVO); + + /** + * 更新销售订单 + * + * @param updateReqVO 更新信息 + */ + void updateSaleOrder(@Valid ErpSaleOrderSaveReqVO updateReqVO); + + /** + * 更新销售订单的状态 + * + * @param id 编号 + * @param status 状态 + */ + void updateSaleOrderStatus(Long id, Integer status); + + /** + * 更新销售订单的出库数量 + * + * @param id 编号 + * @param outCountMap 出库数量 Map:key 销售订单项编号;value 出库数量 + */ + void updateSaleOrderOutCount(Long id, Map outCountMap); + + /** + * 更新销售订单的退货数量 + * + * @param orderId 编号 + * @param returnCountMap 退货数量 Map:key 销售订单项编号;value 退货数量 + */ + void updateSaleOrderReturnCount(Long orderId, Map returnCountMap); + + /** + * 删除销售订单 + * + * @param ids 编号数组 + */ + void deleteSaleOrder(List ids); + + /** + * 获得销售订单 + * + * @param id 编号 + * @return 销售订单 + */ + ErpSaleOrderDO getSaleOrder(Long id); + + /** + * 校验销售订单,已经审核通过 + * + * @param id 编号 + * @return 销售订单 + */ + ErpSaleOrderDO validateSaleOrder(Long id); + + /** + * 获得销售订单分页 + * + * @param pageReqVO 分页查询 + * @return 销售订单分页 + */ + PageResult getSaleOrderPage(ErpSaleOrderPageReqVO pageReqVO); + + // ==================== 销售订单项 ==================== + + /** + * 获得销售订单项列表 + * + * @param orderId 销售订单编号 + * @return 销售订单项列表 + */ + List getSaleOrderItemListByOrderId(Long orderId); + + /** + * 获得销售订单项 List + * + * @param orderIds 销售订单编号数组 + * @return 销售订单项 List + */ + List getSaleOrderItemListByOrderIds(Collection orderIds); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java new file mode 100644 index 000000000..592fe5fe7 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java @@ -0,0 +1,307 @@ +package cn.iocoder.yudao.module.erp.service.sale; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO; +import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOrderItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOrderMapper; +import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +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.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +// TODO 芋艿:记录操作日志 + +/** + * ERP 销售订单 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpSaleOrderServiceImpl implements ErpSaleOrderService { + + @Resource + private ErpSaleOrderMapper saleOrderMapper; + @Resource + private ErpSaleOrderItemMapper saleOrderItemMapper; + + @Resource + private ErpNoRedisDAO noRedisDAO; + + @Resource + private ErpProductService productService; + @Resource + private ErpCustomerService customerService; + @Resource + private ErpAccountService accountService; + + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createSaleOrder(ErpSaleOrderSaveReqVO createReqVO) { + // 1.1 校验订单项的有效性 + List saleOrderItems = validateSaleOrderItems(createReqVO.getItems()); + // 1.2 校验客户 + customerService.validateCustomer(createReqVO.getCustomerId()); + // 1.3 校验结算账户 + if (createReqVO.getAccountId() != null) { + accountService.validateAccount(createReqVO.getAccountId()); + } + // 1.4 校验销售人员 + if (createReqVO.getSaleUserId() != null) { + adminUserApi.validateUser(createReqVO.getSaleUserId()); + } + // 1.5 生成订单号,并校验唯一性 + String no = noRedisDAO.generate(ErpNoRedisDAO.SALE_ORDER_NO_PREFIX); + if (saleOrderMapper.selectByNo(no) != null) { + throw exception(SALE_ORDER_NO_EXISTS); + } + + // 2.1 插入订单 + ErpSaleOrderDO saleOrder = BeanUtils.toBean(createReqVO, ErpSaleOrderDO.class, in -> in + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())); + calculateTotalPrice(saleOrder, saleOrderItems); + saleOrderMapper.insert(saleOrder); + // 2.2 插入订单项 + saleOrderItems.forEach(o -> o.setOrderId(saleOrder.getId())); + saleOrderItemMapper.insertBatch(saleOrderItems); + return saleOrder.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSaleOrder(ErpSaleOrderSaveReqVO updateReqVO) { + // 1.1 校验存在 + ErpSaleOrderDO saleOrder = validateSaleOrderExists(updateReqVO.getId()); + if (ErpAuditStatus.APPROVE.getStatus().equals(saleOrder.getStatus())) { + throw exception(SALE_ORDER_UPDATE_FAIL_APPROVE, saleOrder.getNo()); + } + // 1.2 校验客户 + customerService.validateCustomer(updateReqVO.getCustomerId()); + // 1.3 校验结算账户 + if (updateReqVO.getAccountId() != null) { + accountService.validateAccount(updateReqVO.getAccountId()); + } + // 1.4 校验销售人员 + if (updateReqVO.getSaleUserId() != null) { + adminUserApi.validateUser(updateReqVO.getSaleUserId()); + } + // 1.5 校验订单项的有效性 + List saleOrderItems = validateSaleOrderItems(updateReqVO.getItems()); + + // 2.1 更新订单 + ErpSaleOrderDO updateObj = BeanUtils.toBean(updateReqVO, ErpSaleOrderDO.class); + calculateTotalPrice(updateObj, saleOrderItems); + saleOrderMapper.updateById(updateObj); + // 2.2 更新订单项 + updateSaleOrderItemList(updateReqVO.getId(), saleOrderItems); + } + + private void calculateTotalPrice(ErpSaleOrderDO saleOrder, List saleOrderItems) { + saleOrder.setTotalCount(getSumValue(saleOrderItems, ErpSaleOrderItemDO::getCount, BigDecimal::add)); + saleOrder.setTotalProductPrice(getSumValue(saleOrderItems, ErpSaleOrderItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)); + saleOrder.setTotalTaxPrice(getSumValue(saleOrderItems, ErpSaleOrderItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO)); + saleOrder.setTotalPrice(saleOrder.getTotalProductPrice().add(saleOrder.getTotalTaxPrice())); + // 计算优惠价格 + if (saleOrder.getDiscountPercent() == null) { + saleOrder.setDiscountPercent(BigDecimal.ZERO); + } + saleOrder.setDiscountPrice(MoneyUtils.priceMultiplyPercent(saleOrder.getTotalPrice(), saleOrder.getDiscountPercent())); + saleOrder.setTotalPrice(saleOrder.getTotalPrice().subtract(saleOrder.getDiscountPrice())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSaleOrderStatus(Long id, Integer status) { + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); + // 1.1 校验存在 + ErpSaleOrderDO saleOrder = validateSaleOrderExists(id); + // 1.2 校验状态 + if (saleOrder.getStatus().equals(status)) { + throw exception(approve ? SALE_ORDER_APPROVE_FAIL : SALE_ORDER_PROCESS_FAIL); + } + // 1.3 存在销售出库单,无法反审核 + if (!approve && saleOrder.getOutCount().compareTo(BigDecimal.ZERO) > 0) { + throw exception(SALE_ORDER_PROCESS_FAIL_EXISTS_OUT); + } + // 1.4 存在销售退货单,无法反审核 + if (!approve && saleOrder.getReturnCount().compareTo(BigDecimal.ZERO) > 0) { + throw exception(SALE_ORDER_PROCESS_FAIL_EXISTS_RETURN); + } + + // 2. 更新状态 + int updateCount = saleOrderMapper.updateByIdAndStatus(id, saleOrder.getStatus(), + new ErpSaleOrderDO().setStatus(status)); + if (updateCount == 0) { + throw exception(approve ? SALE_ORDER_APPROVE_FAIL : SALE_ORDER_PROCESS_FAIL); + } + } + + private List validateSaleOrderItems(List list) { + // 1. 校验产品存在 + List productList = productService.validProductList( + convertSet(list, ErpSaleOrderSaveReqVO.Item::getProductId)); + Map productMap = convertMap(productList, ErpProductDO::getId); + // 2. 转化为 ErpSaleOrderItemDO 列表 + return convertList(list, o -> BeanUtils.toBean(o, ErpSaleOrderItemDO.class, item -> { + item.setProductUnitId(productMap.get(item.getProductId()).getUnitId()); + item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount())); + if (item.getTotalPrice() == null) { + return; + } + if (item.getTaxPercent() != null) { + item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent())); + } + })); + } + + private void updateSaleOrderItemList(Long id, List newList) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = saleOrderItemMapper.selectListByOrderId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setOrderId(id)); + saleOrderItemMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + saleOrderItemMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + saleOrderItemMapper.deleteBatchIds(convertList(diffList.get(2), ErpSaleOrderItemDO::getId)); + } + } + + @Override + public void updateSaleOrderOutCount(Long id, Map outCountMap) { + List orderItems = saleOrderItemMapper.selectListByOrderId(id); + // 1. 更新每个销售订单项 + orderItems.forEach(item -> { + BigDecimal outCount = outCountMap.getOrDefault(item.getId(), BigDecimal.ZERO); + if (item.getOutCount().equals(outCount)) { + return; + } + if (outCount.compareTo(item.getCount()) > 0) { + throw exception(SALE_ORDER_ITEM_OUT_FAIL_PRODUCT_EXCEED, + productService.getProduct(item.getProductId()).getName(), item.getCount()); + } + saleOrderItemMapper.updateById(new ErpSaleOrderItemDO().setId(item.getId()).setOutCount(outCount)); + }); + // 2. 更新销售订单 + BigDecimal totalOutCount = getSumValue(outCountMap.values(), value -> value, BigDecimal::add, BigDecimal.ZERO); + saleOrderMapper.updateById(new ErpSaleOrderDO().setId(id).setOutCount(totalOutCount)); + } + + @Override + public void updateSaleOrderReturnCount(Long orderId, Map returnCountMap) { + List orderItems = saleOrderItemMapper.selectListByOrderId(orderId); + // 1. 更新每个销售订单项 + orderItems.forEach(item -> { + BigDecimal returnCount = returnCountMap.getOrDefault(item.getId(), BigDecimal.ZERO); + if (item.getReturnCount().equals(returnCount)) { + return; + } + if (returnCount.compareTo(item.getOutCount()) > 0) { + throw exception(SALE_ORDER_ITEM_RETURN_FAIL_OUT_EXCEED, + productService.getProduct(item.getProductId()).getName(), item.getOutCount()); + } + saleOrderItemMapper.updateById(new ErpSaleOrderItemDO().setId(item.getId()).setReturnCount(returnCount)); + }); + // 2. 更新销售订单 + BigDecimal totalReturnCount = getSumValue(returnCountMap.values(), value -> value, BigDecimal::add, BigDecimal.ZERO); + saleOrderMapper.updateById(new ErpSaleOrderDO().setId(orderId).setReturnCount(totalReturnCount)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteSaleOrder(List ids) { + // 1. 校验不处于已审批 + List saleOrders = saleOrderMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(saleOrders)) { + return; + } + saleOrders.forEach(saleOrder -> { + if (ErpAuditStatus.APPROVE.getStatus().equals(saleOrder.getStatus())) { + throw exception(SALE_ORDER_DELETE_FAIL_APPROVE, saleOrder.getNo()); + } + }); + + // 2. 遍历删除,并记录操作日志 + saleOrders.forEach(saleOrder -> { + // 2.1 删除订单 + saleOrderMapper.deleteById(saleOrder.getId()); + // 2.2 删除订单项 + saleOrderItemMapper.deleteByOrderId(saleOrder.getId()); + }); + } + + private ErpSaleOrderDO validateSaleOrderExists(Long id) { + ErpSaleOrderDO saleOrder = saleOrderMapper.selectById(id); + if (saleOrder == null) { + throw exception(SALE_ORDER_NOT_EXISTS); + } + return saleOrder; + } + + @Override + public ErpSaleOrderDO getSaleOrder(Long id) { + return saleOrderMapper.selectById(id); + } + + @Override + public ErpSaleOrderDO validateSaleOrder(Long id) { + ErpSaleOrderDO saleOrder = validateSaleOrderExists(id); + if (ObjectUtil.notEqual(saleOrder.getStatus(), ErpAuditStatus.APPROVE.getStatus())) { + throw exception(SALE_ORDER_NOT_APPROVE); + } + return saleOrder; + } + + @Override + public PageResult getSaleOrderPage(ErpSaleOrderPageReqVO pageReqVO) { + return saleOrderMapper.selectPage(pageReqVO); + } + + // ==================== 订单项 ==================== + + @Override + public List getSaleOrderItemListByOrderId(Long orderId) { + return saleOrderItemMapper.selectListByOrderId(orderId); + } + + @Override + public List getSaleOrderItemListByOrderIds(Collection orderIds) { + if (CollUtil.isEmpty(orderIds)) { + return Collections.emptyList(); + } + return saleOrderItemMapper.selectListByOrderIds(orderIds); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutService.java new file mode 100644 index 000000000..616f77837 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutService.java @@ -0,0 +1,102 @@ +package cn.iocoder.yudao.module.erp.service.sale; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutItemDO; +import jakarta.validation.Valid; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; + +/** + * ERP 销售出库 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpSaleOutService { + + /** + * 创建销售出库 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSaleOut(@Valid ErpSaleOutSaveReqVO createReqVO); + + /** + * 更新销售出库 + * + * @param updateReqVO 更新信息 + */ + void updateSaleOut(@Valid ErpSaleOutSaveReqVO updateReqVO); + + /** + * 更新销售出库的状态 + * + * @param id 编号 + * @param status 状态 + */ + void updateSaleOutStatus(Long id, Integer status); + + /** + * 更新销售出库的收款金额 + * + * @param id 编号 + * @param receiptPrice 收款金额 + */ + void updateSaleInReceiptPrice(Long id, BigDecimal receiptPrice); + + /** + * 删除销售出库 + * + * @param ids 编号数组 + */ + void deleteSaleOut(List ids); + + /** + * 获得销售出库 + * + * @param id 编号 + * @return 销售出库 + */ + ErpSaleOutDO getSaleOut(Long id); + + /** + * 校验销售出库,已经审核通过 + * + * @param id 编号 + * @return 销售出库 + */ + ErpSaleOutDO validateSaleOut(Long id); + + /** + * 获得销售出库分页 + * + * @param pageReqVO 分页查询 + * @return 销售出库分页 + */ + PageResult getSaleOutPage(ErpSaleOutPageReqVO pageReqVO); + + // ==================== 销售出库项 ==================== + + /** + * 获得销售出库项列表 + * + * @param outId 销售出库编号 + * @return 销售出库项列表 + */ + List getSaleOutItemListByOutId(Long outId); + + /** + * 获得销售出库项 List + * + * @param outIds 销售出库编号数组 + * @return 销售出库项 List + */ + List getSaleOutItemListByOutIds(Collection outIds); + + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java new file mode 100644 index 000000000..d930c16bf --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOutServiceImpl.java @@ -0,0 +1,316 @@ +package cn.iocoder.yudao.module.erp.service.sale; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.out.ErpSaleOutSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOutItemDO; +import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOutItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOutMapper; +import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum; +import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockRecordService; +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +// TODO 芋艿:记录操作日志 + +/** + * ERP 销售出库 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpSaleOutServiceImpl implements ErpSaleOutService { + + @Resource + private ErpSaleOutMapper saleOutMapper; + @Resource + private ErpSaleOutItemMapper saleOutItemMapper; + + @Resource + private ErpNoRedisDAO noRedisDAO; + + @Resource + private ErpProductService productService; + @Resource + @Lazy // 延迟加载,避免循环依赖 + private ErpSaleOrderService saleOrderService; + @Resource + private ErpAccountService accountService; + @Resource + private ErpStockRecordService stockRecordService; + + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createSaleOut(ErpSaleOutSaveReqVO createReqVO) { + // 1.1 校验销售订单已审核 + ErpSaleOrderDO saleOrder = saleOrderService.validateSaleOrder(createReqVO.getOrderId()); + // 1.2 校验出库项的有效性 + List saleOutItems = validateSaleOutItems(createReqVO.getItems()); + // 1.3 校验结算账户 + accountService.validateAccount(createReqVO.getAccountId()); + // 1.4 校验销售人员 + if (createReqVO.getSaleUserId() != null) { + adminUserApi.validateUser(createReqVO.getSaleUserId()); + } + // 1.5 生成出库单号,并校验唯一性 + String no = noRedisDAO.generate(ErpNoRedisDAO.SALE_OUT_NO_PREFIX); + if (saleOutMapper.selectByNo(no) != null) { + throw exception(SALE_OUT_NO_EXISTS); + } + + // 2.1 插入出库 + ErpSaleOutDO saleOut = BeanUtils.toBean(createReqVO, ErpSaleOutDO.class, in -> in + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())) + .setOrderNo(saleOrder.getNo()).setCustomerId(saleOrder.getCustomerId()); + calculateTotalPrice(saleOut, saleOutItems); + saleOutMapper.insert(saleOut); + // 2.2 插入出库项 + saleOutItems.forEach(o -> o.setOutId(saleOut.getId())); + saleOutItemMapper.insertBatch(saleOutItems); + + // 3. 更新销售订单的出库数量 + updateSaleOrderOutCount(createReqVO.getOrderId()); + return saleOut.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSaleOut(ErpSaleOutSaveReqVO updateReqVO) { + // 1.1 校验存在 + ErpSaleOutDO saleOut = validateSaleOutExists(updateReqVO.getId()); + if (ErpAuditStatus.APPROVE.getStatus().equals(saleOut.getStatus())) { + throw exception(SALE_OUT_UPDATE_FAIL_APPROVE, saleOut.getNo()); + } + // 1.2 校验销售订单已审核 + ErpSaleOrderDO saleOrder = saleOrderService.validateSaleOrder(updateReqVO.getOrderId()); + // 1.3 校验结算账户 + accountService.validateAccount(updateReqVO.getAccountId()); + // 1.4 校验销售人员 + if (updateReqVO.getSaleUserId() != null) { + adminUserApi.validateUser(updateReqVO.getSaleUserId()); + } + // 1.5 校验订单项的有效性 + List saleOutItems = validateSaleOutItems(updateReqVO.getItems()); + + // 2.1 更新出库 + ErpSaleOutDO updateObj = BeanUtils.toBean(updateReqVO, ErpSaleOutDO.class) + .setOrderNo(saleOrder.getNo()).setCustomerId(saleOrder.getCustomerId()); + calculateTotalPrice(updateObj, saleOutItems); + saleOutMapper.updateById(updateObj); + // 2.2 更新出库项 + updateSaleOutItemList(updateReqVO.getId(), saleOutItems); + + // 3.1 更新销售订单的出库数量 + updateSaleOrderOutCount(updateObj.getOrderId()); + // 3.2 注意:如果销售订单编号变更了,需要更新“老”销售订单的出库数量 + if (ObjectUtil.notEqual(saleOut.getOrderId(), updateObj.getOrderId())) { + updateSaleOrderOutCount(saleOut.getOrderId()); + } + } + + private void calculateTotalPrice(ErpSaleOutDO saleOut, List saleOutItems) { + saleOut.setTotalCount(getSumValue(saleOutItems, ErpSaleOutItemDO::getCount, BigDecimal::add)); + saleOut.setTotalProductPrice(getSumValue(saleOutItems, ErpSaleOutItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)); + saleOut.setTotalTaxPrice(getSumValue(saleOutItems, ErpSaleOutItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO)); + saleOut.setTotalPrice(saleOut.getTotalProductPrice().add(saleOut.getTotalTaxPrice())); + // 计算优惠价格 + if (saleOut.getDiscountPercent() == null) { + saleOut.setDiscountPercent(BigDecimal.ZERO); + } + saleOut.setDiscountPrice(MoneyUtils.priceMultiplyPercent(saleOut.getTotalPrice(), saleOut.getDiscountPercent())); + saleOut.setTotalPrice(saleOut.getTotalPrice().subtract(saleOut.getDiscountPrice().add(saleOut.getOtherPrice()))); + } + + private void updateSaleOrderOutCount(Long orderId) { + // 1.1 查询销售订单对应的销售出库单列表 + List saleOuts = saleOutMapper.selectListByOrderId(orderId); + // 1.2 查询对应的销售订单项的出库数量 + Map returnCountMap = saleOutItemMapper.selectOrderItemCountSumMapByOutIds( + convertList(saleOuts, ErpSaleOutDO::getId)); + // 2. 更新销售订单的出库数量 + saleOrderService.updateSaleOrderOutCount(orderId, returnCountMap); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSaleOutStatus(Long id, Integer status) { + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); + // 1.1 校验存在 + ErpSaleOutDO saleOut = validateSaleOutExists(id); + // 1.2 校验状态 + if (saleOut.getStatus().equals(status)) { + throw exception(approve ? SALE_OUT_APPROVE_FAIL : SALE_OUT_PROCESS_FAIL); + } + // 1.3 校验已退款 + if (approve && saleOut.getReceiptPrice().compareTo(BigDecimal.ZERO) > 0) { + throw exception(SALE_OUT_PROCESS_FAIL_EXISTS_RECEIPT); + } + + // 2. 更新状态 + int updateCount = saleOutMapper.updateByIdAndStatus(id, saleOut.getStatus(), + new ErpSaleOutDO().setStatus(status)); + if (updateCount == 0) { + throw exception(approve ? SALE_OUT_APPROVE_FAIL : SALE_OUT_PROCESS_FAIL); + } + + // 3. 变更库存 + List saleOutItems = saleOutItemMapper.selectListByOutId(id); + Integer bizType = approve ? ErpStockRecordBizTypeEnum.SALE_OUT.getType() + : ErpStockRecordBizTypeEnum.SALE_OUT_CANCEL.getType(); + saleOutItems.forEach(saleOutItem -> { + BigDecimal count = approve ? saleOutItem.getCount().negate() : saleOutItem.getCount(); + stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO( + saleOutItem.getProductId(), saleOutItem.getWarehouseId(), count, + bizType, saleOutItem.getOutId(), saleOutItem.getId(), saleOut.getNo())); + }); + } + + @Override + public void updateSaleInReceiptPrice(Long id, BigDecimal receiptPrice) { + ErpSaleOutDO saleOut = saleOutMapper.selectById(id); + if (saleOut.getReceiptPrice().equals(receiptPrice)) { + return; + } + if (receiptPrice.compareTo(saleOut.getTotalPrice()) > 0) { + throw exception(SALE_OUT_FAIL_RECEIPT_PRICE_EXCEED, receiptPrice, saleOut.getTotalPrice()); + } + saleOutMapper.updateById(new ErpSaleOutDO().setId(id).setReceiptPrice(receiptPrice)); + } + + private List validateSaleOutItems(List list) { + // 1. 校验产品存在 + List productList = productService.validProductList( + convertSet(list, ErpSaleOutSaveReqVO.Item::getProductId)); + Map productMap = convertMap(productList, ErpProductDO::getId); + // 2. 转化为 ErpSaleOutItemDO 列表 + return convertList(list, o -> BeanUtils.toBean(o, ErpSaleOutItemDO.class, item -> { + item.setProductUnitId(productMap.get(item.getProductId()).getUnitId()); + item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount())); + if (item.getTotalPrice() == null) { + return; + } + if (item.getTaxPercent() != null) { + item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent())); + } + })); + } + + private void updateSaleOutItemList(Long id, List newList) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = saleOutItemMapper.selectListByOutId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setOutId(id)); + saleOutItemMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + saleOutItemMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + saleOutItemMapper.deleteBatchIds(convertList(diffList.get(2), ErpSaleOutItemDO::getId)); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteSaleOut(List ids) { + // 1. 校验不处于已审批 + List saleOuts = saleOutMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(saleOuts)) { + return; + } + saleOuts.forEach(saleOut -> { + if (ErpAuditStatus.APPROVE.getStatus().equals(saleOut.getStatus())) { + throw exception(SALE_OUT_DELETE_FAIL_APPROVE, saleOut.getNo()); + } + }); + + // 2. 遍历删除,并记录操作日志 + saleOuts.forEach(saleOut -> { + // 2.1 删除订单 + saleOutMapper.deleteById(saleOut.getId()); + // 2.2 删除订单项 + saleOutItemMapper.deleteByOutId(saleOut.getId()); + + // 2.3 更新销售订单的出库数量 + updateSaleOrderOutCount(saleOut.getOrderId()); + }); + + } + + private ErpSaleOutDO validateSaleOutExists(Long id) { + ErpSaleOutDO saleOut = saleOutMapper.selectById(id); + if (saleOut == null) { + throw exception(SALE_OUT_NOT_EXISTS); + } + return saleOut; + } + + @Override + public ErpSaleOutDO getSaleOut(Long id) { + return saleOutMapper.selectById(id); + } + + @Override + public ErpSaleOutDO validateSaleOut(Long id) { + ErpSaleOutDO saleOut = validateSaleOutExists(id); + if (ObjectUtil.notEqual(saleOut.getStatus(), ErpAuditStatus.APPROVE.getStatus())) { + throw exception(SALE_OUT_NOT_APPROVE); + } + return saleOut; + } + + @Override + public PageResult getSaleOutPage(ErpSaleOutPageReqVO pageReqVO) { + return saleOutMapper.selectPage(pageReqVO); + } + + // ==================== 销售出库项 ==================== + + @Override + public List getSaleOutItemListByOutId(Long outId) { + return saleOutItemMapper.selectListByOutId(outId); + } + + @Override + public List getSaleOutItemListByOutIds(Collection outIds) { + if (CollUtil.isEmpty(outIds)) { + return Collections.emptyList(); + } + return saleOutItemMapper.selectListByOutIds(outIds); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnService.java new file mode 100644 index 000000000..bc192a17c --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnService.java @@ -0,0 +1,101 @@ +package cn.iocoder.yudao.module.erp.service.sale; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.returns.ErpSaleReturnPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.returns.ErpSaleReturnSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleReturnDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleReturnItemDO; +import jakarta.validation.Valid; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.List; + +/** + * ERP 销售退货 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpSaleReturnService { + + /** + * 创建销售退货 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSaleReturn(@Valid ErpSaleReturnSaveReqVO createReqVO); + + /** + * 更新销售退货 + * + * @param updateReqVO 更新信息 + */ + void updateSaleReturn(@Valid ErpSaleReturnSaveReqVO updateReqVO); + + /** + * 更新销售退货的状态 + * + * @param id 编号 + * @param status 状态 + */ + void updateSaleReturnStatus(Long id, Integer status); + + /** + * 更新销售退货的退款金额 + * + * @param id 编号 + * @param refundPrice 退款金额 + */ + void updateSaleReturnRefundPrice(Long id, BigDecimal refundPrice); + + /** + * 删除销售退货 + * + * @param ids 编号数组 + */ + void deleteSaleReturn(List ids); + + /** + * 获得销售退货 + * + * @param id 编号 + * @return 销售退货 + */ + ErpSaleReturnDO getSaleReturn(Long id); + + /** + * 校验销售退货,已经审核通过 + * + * @param id 编号 + * @return 销售退货 + */ + ErpSaleReturnDO validateSaleReturn(Long id); + + /** + * 获得销售退货分页 + * + * @param pageReqVO 分页查询 + * @return 销售退货分页 + */ + PageResult getSaleReturnPage(ErpSaleReturnPageReqVO pageReqVO); + + // ==================== 销售退货项 ==================== + + /** + * 获得销售退货项列表 + * + * @param returnId 销售退货编号 + * @return 销售退货项列表 + */ + List getSaleReturnItemListByReturnId(Long returnId); + + /** + * 获得销售退货项 List + * + * @param returnIds 销售退货编号数组 + * @return 销售退货项 List + */ + List getSaleReturnItemListByReturnIds(Collection returnIds); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnServiceImpl.java new file mode 100644 index 000000000..85d798e72 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleReturnServiceImpl.java @@ -0,0 +1,316 @@ +package cn.iocoder.yudao.module.erp.service.sale; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.returns.ErpSaleReturnPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.returns.ErpSaleReturnSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleReturnDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleReturnItemDO; +import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleReturnItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleReturnMapper; +import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum; +import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.stock.ErpStockRecordService; +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +// TODO 芋艿:记录操作日志 + +/** + * ERP 销售退货 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpSaleReturnServiceImpl implements ErpSaleReturnService { + + @Resource + private ErpSaleReturnMapper saleReturnMapper; + @Resource + private ErpSaleReturnItemMapper saleReturnItemMapper; + + @Resource + private ErpNoRedisDAO noRedisDAO; + + @Resource + private ErpProductService productService; + @Resource + @Lazy // 延迟加载,避免循环依赖 + private ErpSaleOrderService saleOrderService; + @Resource + private ErpAccountService accountService; + @Resource + private ErpStockRecordService stockRecordService; + + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createSaleReturn(ErpSaleReturnSaveReqVO createReqVO) { + // 1.1 校验销售订单已审核 + ErpSaleOrderDO saleOrder = saleOrderService.validateSaleOrder(createReqVO.getOrderId()); + // 1.2 校验退货项的有效性 + List saleReturnItems = validateSaleReturnItems(createReqVO.getItems()); + // 1.3 校验结算账户 + accountService.validateAccount(createReqVO.getAccountId()); + // 1.4 校验销售人员 + if (createReqVO.getSaleUserId() != null) { + adminUserApi.validateUser(createReqVO.getSaleUserId()); + } + // 1.5 生成退货单号,并校验唯一性 + String no = noRedisDAO.generate(ErpNoRedisDAO.SALE_RETURN_NO_PREFIX); + if (saleReturnMapper.selectByNo(no) != null) { + throw exception(SALE_RETURN_NO_EXISTS); + } + + // 2.1 插入退货 + ErpSaleReturnDO saleReturn = BeanUtils.toBean(createReqVO, ErpSaleReturnDO.class, in -> in + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())) + .setOrderNo(saleOrder.getNo()).setCustomerId(saleOrder.getCustomerId()); + calculateTotalPrice(saleReturn, saleReturnItems); + saleReturnMapper.insert(saleReturn); + // 2.2 插入退货项 + saleReturnItems.forEach(o -> o.setReturnId(saleReturn.getId())); + saleReturnItemMapper.insertBatch(saleReturnItems); + + // 3. 更新销售订单的退货数量 + updateSaleOrderReturnCount(createReqVO.getOrderId()); + return saleReturn.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSaleReturn(ErpSaleReturnSaveReqVO updateReqVO) { + // 1.1 校验存在 + ErpSaleReturnDO saleReturn = validateSaleReturnExists(updateReqVO.getId()); + if (ErpAuditStatus.APPROVE.getStatus().equals(saleReturn.getStatus())) { + throw exception(SALE_RETURN_UPDATE_FAIL_APPROVE, saleReturn.getNo()); + } + // 1.2 校验销售订单已审核 + ErpSaleOrderDO saleOrder = saleOrderService.validateSaleOrder(updateReqVO.getOrderId()); + // 1.3 校验结算账户 + accountService.validateAccount(updateReqVO.getAccountId()); + // 1.4 校验销售人员 + if (updateReqVO.getSaleUserId() != null) { + adminUserApi.validateUser(updateReqVO.getSaleUserId()); + } + // 1.5 校验订单项的有效性 + List saleReturnItems = validateSaleReturnItems(updateReqVO.getItems()); + + // 2.1 更新退货 + ErpSaleReturnDO updateObj = BeanUtils.toBean(updateReqVO, ErpSaleReturnDO.class) + .setOrderNo(saleOrder.getNo()).setCustomerId(saleOrder.getCustomerId()); + calculateTotalPrice(updateObj, saleReturnItems); + saleReturnMapper.updateById(updateObj); + // 2.2 更新退货项 + updateSaleReturnItemList(updateReqVO.getId(), saleReturnItems); + + // 3.1 更新销售订单的出库数量 + updateSaleOrderReturnCount(updateObj.getOrderId()); + // 3.2 注意:如果销售订单编号变更了,需要更新“老”销售订单的出库数量 + if (ObjectUtil.notEqual(saleReturn.getOrderId(), updateObj.getOrderId())) { + updateSaleOrderReturnCount(saleReturn.getOrderId()); + } + } + + private void calculateTotalPrice(ErpSaleReturnDO saleReturn, List saleReturnItems) { + saleReturn.setTotalCount(getSumValue(saleReturnItems, ErpSaleReturnItemDO::getCount, BigDecimal::add)); + saleReturn.setTotalProductPrice(getSumValue(saleReturnItems, ErpSaleReturnItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)); + saleReturn.setTotalTaxPrice(getSumValue(saleReturnItems, ErpSaleReturnItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO)); + saleReturn.setTotalPrice(saleReturn.getTotalProductPrice().add(saleReturn.getTotalTaxPrice())); + // 计算优惠价格 + if (saleReturn.getDiscountPercent() == null) { + saleReturn.setDiscountPercent(BigDecimal.ZERO); + } + saleReturn.setDiscountPrice(MoneyUtils.priceMultiplyPercent(saleReturn.getTotalPrice(), saleReturn.getDiscountPercent())); + saleReturn.setTotalPrice(saleReturn.getTotalPrice().subtract(saleReturn.getDiscountPrice().add(saleReturn.getOtherPrice()))); + } + + private void updateSaleOrderReturnCount(Long orderId) { + // 1.1 查询销售订单对应的销售出库单列表 + List saleReturns = saleReturnMapper.selectListByOrderId(orderId); + // 1.2 查询对应的销售订单项的退货数量 + Map returnCountMap = saleReturnItemMapper.selectOrderItemCountSumMapByReturnIds( + convertList(saleReturns, ErpSaleReturnDO::getId)); + // 2. 更新销售订单的出库数量 + saleOrderService.updateSaleOrderReturnCount(orderId, returnCountMap); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSaleReturnStatus(Long id, Integer status) { + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); + // 1.1 校验存在 + ErpSaleReturnDO saleReturn = validateSaleReturnExists(id); + // 1.2 校验状态 + if (saleReturn.getStatus().equals(status)) { + throw exception(approve ? SALE_RETURN_APPROVE_FAIL : SALE_RETURN_PROCESS_FAIL); + } + // 1.3 校验已退款 + if (approve && saleReturn.getRefundPrice().compareTo(BigDecimal.ZERO) > 0) { + throw exception(SALE_RETURN_PROCESS_FAIL_EXISTS_REFUND); + } + + // 2. 更新状态 + int updateCount = saleReturnMapper.updateByIdAndStatus(id, saleReturn.getStatus(), + new ErpSaleReturnDO().setStatus(status)); + if (updateCount == 0) { + throw exception(approve ? SALE_RETURN_APPROVE_FAIL : SALE_RETURN_PROCESS_FAIL); + } + + // 3. 变更库存 + List saleReturnItems = saleReturnItemMapper.selectListByReturnId(id); + Integer bizType = approve ? ErpStockRecordBizTypeEnum.SALE_RETURN.getType() + : ErpStockRecordBizTypeEnum.SALE_RETURN_CANCEL.getType(); + saleReturnItems.forEach(saleReturnItem -> { + BigDecimal count = approve ? saleReturnItem.getCount() : saleReturnItem.getCount().negate(); + stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO( + saleReturnItem.getProductId(), saleReturnItem.getWarehouseId(), count, + bizType, saleReturnItem.getReturnId(), saleReturnItem.getId(), saleReturn.getNo())); + }); + } + + @Override + public void updateSaleReturnRefundPrice(Long id, BigDecimal refundPrice) { + ErpSaleReturnDO saleReturn = saleReturnMapper.selectById(id); + if (saleReturn.getRefundPrice().equals(refundPrice)) { + return; + } + if (refundPrice.compareTo(saleReturn.getTotalPrice()) > 0) { + throw exception(SALE_RETURN_FAIL_REFUND_PRICE_EXCEED, refundPrice, saleReturn.getTotalPrice()); + } + saleReturnMapper.updateById(new ErpSaleReturnDO().setId(id).setRefundPrice(refundPrice)); + } + + private List validateSaleReturnItems(List list) { + // 1. 校验产品存在 + List productList = productService.validProductList( + convertSet(list, ErpSaleReturnSaveReqVO.Item::getProductId)); + Map productMap = convertMap(productList, ErpProductDO::getId); + // 2. 转化为 ErpSaleReturnItemDO 列表 + return convertList(list, o -> BeanUtils.toBean(o, ErpSaleReturnItemDO.class, item -> { + item.setProductUnitId(productMap.get(item.getProductId()).getUnitId()); + item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount())); + if (item.getTotalPrice() == null) { + return; + } + if (item.getTaxPercent() != null) { + item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent())); + } + })); + } + + private void updateSaleReturnItemList(Long id, List newList) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = saleReturnItemMapper.selectListByReturnId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setReturnId(id)); + saleReturnItemMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + saleReturnItemMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + saleReturnItemMapper.deleteBatchIds(convertList(diffList.get(2), ErpSaleReturnItemDO::getId)); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteSaleReturn(List ids) { + // 1. 校验不处于已审批 + List saleReturns = saleReturnMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(saleReturns)) { + return; + } + saleReturns.forEach(saleReturn -> { + if (ErpAuditStatus.APPROVE.getStatus().equals(saleReturn.getStatus())) { + throw exception(SALE_RETURN_DELETE_FAIL_APPROVE, saleReturn.getNo()); + } + }); + + // 2. 遍历删除,并记录操作日志 + saleReturns.forEach(saleReturn -> { + // 2.1 删除订单 + saleReturnMapper.deleteById(saleReturn.getId()); + // 2.2 删除订单项 + saleReturnItemMapper.deleteByReturnId(saleReturn.getId()); + + // 2.3 更新销售订单的出库数量 + updateSaleOrderReturnCount(saleReturn.getOrderId()); + }); + + } + + private ErpSaleReturnDO validateSaleReturnExists(Long id) { + ErpSaleReturnDO saleReturn = saleReturnMapper.selectById(id); + if (saleReturn == null) { + throw exception(SALE_RETURN_NOT_EXISTS); + } + return saleReturn; + } + + @Override + public ErpSaleReturnDO getSaleReturn(Long id) { + return saleReturnMapper.selectById(id); + } + + @Override + public ErpSaleReturnDO validateSaleReturn(Long id) { + ErpSaleReturnDO saleReturn = validateSaleReturnExists(id); + if (ObjectUtil.notEqual(saleReturn.getStatus(), ErpAuditStatus.APPROVE.getStatus())) { + throw exception(SALE_RETURN_NOT_APPROVE); + } + return saleReturn; + } + + @Override + public PageResult getSaleReturnPage(ErpSaleReturnPageReqVO pageReqVO) { + return saleReturnMapper.selectPage(pageReqVO); + } + + // ==================== 销售退货项 ==================== + + @Override + public List getSaleReturnItemListByReturnId(Long returnId) { + return saleReturnItemMapper.selectListByReturnId(returnId); + } + + @Override + public List getSaleReturnItemListByReturnIds(Collection returnIds) { + if (CollUtil.isEmpty(returnIds)) { + return Collections.emptyList(); + } + return saleReturnItemMapper.selectListByReturnIds(returnIds); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckService.java new file mode 100644 index 000000000..2f8a0f9ee --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckService.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.check.ErpStockCheckPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.check.ErpStockCheckSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockCheckDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockCheckItemDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * ERP 库存盘点单 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpStockCheckService { + + /** + * 创建库存盘点单 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createStockCheck(@Valid ErpStockCheckSaveReqVO createReqVO); + + /** + * 更新库存盘点单 + * + * @param updateReqVO 更新信息 + */ + void updateStockCheck(@Valid ErpStockCheckSaveReqVO updateReqVO); + + /** + * 更新库存盘点单的状态 + * + * @param id 编号 + * @param status 状态 + */ + void updateStockCheckStatus(Long id, Integer status); + + /** + * 删除库存盘点单 + * + * @param ids 编号数组 + */ + void deleteStockCheck(List ids); + + /** + * 获得库存盘点单 + * + * @param id 编号 + * @return 库存盘点单 + */ + ErpStockCheckDO getStockCheck(Long id); + + /** + * 获得库存盘点单分页 + * + * @param pageReqVO 分页查询 + * @return 库存盘点单分页 + */ + PageResult getStockCheckPage(ErpStockCheckPageReqVO pageReqVO); + + // ==================== 盘点项 ==================== + + /** + * 获得库存盘点单项列表 + * + * @param checkId 盘点编号 + * @return 库存盘点单项列表 + */ + List getStockCheckItemListByCheckId(Long checkId); + + /** + * 获得库存盘点单项 List + * + * @param checkIds 盘点编号数组 + * @return 库存盘点单项 List + */ + List getStockCheckItemListByCheckIds(Collection checkIds); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckServiceImpl.java new file mode 100644 index 000000000..49aca94d3 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockCheckServiceImpl.java @@ -0,0 +1,232 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.check.ErpStockCheckPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.check.ErpStockCheckSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockCheckDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockCheckItemDO; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockCheckItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockCheckMapper; +import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO; +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.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +// TODO 芋艿:记录操作日志 + +/** + * ERP 库存盘点单 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpStockCheckServiceImpl implements ErpStockCheckService { + + @Resource + private ErpStockCheckMapper stockCheckMapper; + @Resource + private ErpStockCheckItemMapper stockCheckItemMapper; + + @Resource + private ErpNoRedisDAO noRedisDAO; + + @Resource + private ErpProductService productService; + @Resource + private ErpWarehouseService warehouseService; + @Resource + private ErpStockRecordService stockRecordService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createStockCheck(ErpStockCheckSaveReqVO createReqVO) { + // 1.1 校验盘点项的有效性 + List stockCheckItems = validateStockCheckItems(createReqVO.getItems()); + // 1.2 生成盘点单号,并校验唯一性 + String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_CHECK_NO_PREFIX); + if (stockCheckMapper.selectByNo(no) != null) { + throw exception(STOCK_CHECK_NO_EXISTS); + } + + // 2.1 插入盘点单 + ErpStockCheckDO stockCheck = BeanUtils.toBean(createReqVO, ErpStockCheckDO.class, in -> in + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()) + .setTotalCount(getSumValue(stockCheckItems, ErpStockCheckItemDO::getCount, BigDecimal::add)) + .setTotalPrice(getSumValue(stockCheckItems, ErpStockCheckItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO))); + stockCheckMapper.insert(stockCheck); + // 2.2 插入盘点单项 + stockCheckItems.forEach(o -> o.setCheckId(stockCheck.getId())); + stockCheckItemMapper.insertBatch(stockCheckItems); + return stockCheck.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateStockCheck(ErpStockCheckSaveReqVO updateReqVO) { + // 1.1 校验存在 + ErpStockCheckDO stockCheck = validateStockCheckExists(updateReqVO.getId()); + if (ErpAuditStatus.APPROVE.getStatus().equals(stockCheck.getStatus())) { + throw exception(STOCK_CHECK_UPDATE_FAIL_APPROVE, stockCheck.getNo()); + } + // 1.2 校验盘点项的有效性 + List stockCheckItems = validateStockCheckItems(updateReqVO.getItems()); + + // 2.1 更新盘点单 + ErpStockCheckDO updateObj = BeanUtils.toBean(updateReqVO, ErpStockCheckDO.class, in -> in + .setTotalCount(getSumValue(stockCheckItems, ErpStockCheckItemDO::getCount, BigDecimal::add)) + .setTotalPrice(getSumValue(stockCheckItems, ErpStockCheckItemDO::getTotalPrice, BigDecimal::add))); + stockCheckMapper.updateById(updateObj); + // 2.2 更新盘点单项 + updateStockCheckItemList(updateReqVO.getId(), stockCheckItems); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateStockCheckStatus(Long id, Integer status) { + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); + // 1.1 校验存在 + ErpStockCheckDO stockCheck = validateStockCheckExists(id); + // 1.2 校验状态 + if (stockCheck.getStatus().equals(status)) { + throw exception(approve ? STOCK_CHECK_APPROVE_FAIL : STOCK_CHECK_PROCESS_FAIL); + } + + // 2. 更新状态 + int updateCount = stockCheckMapper.updateByIdAndStatus(id, stockCheck.getStatus(), + new ErpStockCheckDO().setStatus(status)); + if (updateCount == 0) { + throw exception(approve ? STOCK_CHECK_APPROVE_FAIL : STOCK_CHECK_PROCESS_FAIL); + } + + // 3. 变更库存 + List stockCheckItems = stockCheckItemMapper.selectListByCheckId(id); + stockCheckItems.forEach(stockCheckItem -> { + // 没有盈亏,不用出入库 + if (stockCheckItem.getCount().compareTo(BigDecimal.ZERO) == 0) { + return; + } + // 10;12;-2() + BigDecimal count = approve ? stockCheckItem.getCount(): stockCheckItem.getCount().negate(); + Integer bizType; + if (approve) { + bizType = count.compareTo(BigDecimal.ZERO) > 0 ? ErpStockRecordBizTypeEnum.CHECK_MORE_IN.getType() + : ErpStockRecordBizTypeEnum.CHECK_LESS_OUT.getType(); + } else { + bizType = count.compareTo(BigDecimal.ZERO) > 0 ? ErpStockRecordBizTypeEnum.CHECK_MORE_IN_CANCEL.getType() + : ErpStockRecordBizTypeEnum.CHECK_LESS_OUT_CANCEL.getType(); + } + stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO( + stockCheckItem.getProductId(), stockCheckItem.getWarehouseId(), count, + bizType, stockCheckItem.getCheckId(), stockCheckItem.getId(), stockCheck.getNo())); + }); + } + + private List validateStockCheckItems(List list) { + // 1.1 校验产品存在 + List productList = productService.validProductList( + convertSet(list, ErpStockCheckSaveReqVO.Item::getProductId)); + Map productMap = convertMap(productList, ErpProductDO::getId); + // 1.2 校验仓库存在 + warehouseService.validWarehouseList(convertSet(list, ErpStockCheckSaveReqVO.Item::getWarehouseId)); + // 2. 转化为 ErpStockCheckItemDO 列表 + return convertList(list, o -> BeanUtils.toBean(o, ErpStockCheckItemDO.class, item -> item + .setProductUnitId(productMap.get(item.getProductId()).getUnitId()) + .setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount())))); + } + + private void updateStockCheckItemList(Long id, List newList) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = stockCheckItemMapper.selectListByCheckId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setCheckId(id)); + stockCheckItemMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + stockCheckItemMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + stockCheckItemMapper.deleteBatchIds(convertList(diffList.get(2), ErpStockCheckItemDO::getId)); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteStockCheck(List ids) { + // 1. 校验不处于已审批 + List stockChecks = stockCheckMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(stockChecks)) { + return; + } + stockChecks.forEach(stockCheck -> { + if (ErpAuditStatus.APPROVE.getStatus().equals(stockCheck.getStatus())) { + throw exception(STOCK_CHECK_DELETE_FAIL_APPROVE, stockCheck.getNo()); + } + }); + + // 2. 遍历删除,并记录操作日志 + stockChecks.forEach(stockCheck -> { + // 2.1 删除盘点单 + stockCheckMapper.deleteById(stockCheck.getId()); + // 2.2 删除盘点单项 + stockCheckItemMapper.deleteByCheckId(stockCheck.getId()); + }); + } + + private ErpStockCheckDO validateStockCheckExists(Long id) { + ErpStockCheckDO stockCheck = stockCheckMapper.selectById(id); + if (stockCheck == null) { + throw exception(STOCK_CHECK_NOT_EXISTS); + } + return stockCheck; + } + + @Override + public ErpStockCheckDO getStockCheck(Long id) { + return stockCheckMapper.selectById(id); + } + + @Override + public PageResult getStockCheckPage(ErpStockCheckPageReqVO pageReqVO) { + return stockCheckMapper.selectPage(pageReqVO); + } + + // ==================== 盘点项 ==================== + + @Override + public List getStockCheckItemListByCheckId(Long checkId) { + return stockCheckItemMapper.selectListByCheckId(checkId); + } + + @Override + public List getStockCheckItemListByCheckIds(Collection checkIds) { + if (CollUtil.isEmpty(checkIds)) { + return Collections.emptyList(); + } + return stockCheckItemMapper.selectListByCheckIds(checkIds); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInService.java new file mode 100644 index 000000000..4e51545e4 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInService.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInItemDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * ERP 其它入库单 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpStockInService { + + /** + * 创建其它入库单 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createStockIn(@Valid ErpStockInSaveReqVO createReqVO); + + /** + * 更新其它入库单 + * + * @param updateReqVO 更新信息 + */ + void updateStockIn(@Valid ErpStockInSaveReqVO updateReqVO); + + /** + * 更新其它入库单的状态 + * + * @param id 编号 + * @param status 状态 + */ + void updateStockInStatus(Long id, Integer status); + + /** + * 删除其它入库单 + * + * @param ids 编号数组 + */ + void deleteStockIn(List ids); + + /** + * 获得其它入库单 + * + * @param id 编号 + * @return 其它入库单 + */ + ErpStockInDO getStockIn(Long id); + + /** + * 获得其它入库单分页 + * + * @param pageReqVO 分页查询 + * @return 其它入库单分页 + */ + PageResult getStockInPage(ErpStockInPageReqVO pageReqVO); + + // ==================== 入库项 ==================== + + /** + * 获得其它入库单项列表 + * + * @param inId 入库编号 + * @return 其它入库单项列表 + */ + List getStockInItemListByInId(Long inId); + + /** + * 获得其它入库单项 List + * + * @param inIds 入库编号数组 + * @return 其它入库单项 List + */ + List getStockInItemListByInIds(Collection inIds); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java new file mode 100644 index 000000000..213fca8b8 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockInServiceImpl.java @@ -0,0 +1,228 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.in.ErpStockInSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockInItemDO; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockInItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockInMapper; +import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService; +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO; +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.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +// TODO 芋艿:记录操作日志 +/** + * ERP 其它入库单 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpStockInServiceImpl implements ErpStockInService { + + @Resource + private ErpStockInMapper stockInMapper; + @Resource + private ErpStockInItemMapper stockInItemMapper; + + @Resource + private ErpNoRedisDAO noRedisDAO; + + @Resource + private ErpProductService productService; + @Resource + private ErpWarehouseService warehouseService; + @Resource + private ErpSupplierService supplierService; + @Resource + private ErpStockRecordService stockRecordService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createStockIn(ErpStockInSaveReqVO createReqVO) { + // 1.1 校验入库项的有效性 + List stockInItems = validateStockInItems(createReqVO.getItems()); + // 1.2 校验供应商 + supplierService.validateSupplier(createReqVO.getSupplierId()); + // 1.3 生成入库单号,并校验唯一性 + String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_IN_NO_PREFIX); + if (stockInMapper.selectByNo(no) != null) { + throw exception(STOCK_IN_NO_EXISTS); + } + + // 2.1 插入入库单 + ErpStockInDO stockIn = BeanUtils.toBean(createReqVO, ErpStockInDO.class, in -> in + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()) + .setTotalCount(getSumValue(stockInItems, ErpStockInItemDO::getCount, BigDecimal::add)) + .setTotalPrice(getSumValue(stockInItems, ErpStockInItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO))); + stockInMapper.insert(stockIn); + // 2.2 插入入库单项 + stockInItems.forEach(o -> o.setInId(stockIn.getId())); + stockInItemMapper.insertBatch(stockInItems); + return stockIn.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateStockIn(ErpStockInSaveReqVO updateReqVO) { + // 1.1 校验存在 + ErpStockInDO stockIn = validateStockInExists(updateReqVO.getId()); + if (ErpAuditStatus.APPROVE.getStatus().equals(stockIn.getStatus())) { + throw exception(STOCK_IN_UPDATE_FAIL_APPROVE, stockIn.getNo()); + } + // 1.2 校验供应商 + supplierService.validateSupplier(updateReqVO.getSupplierId()); + // 1.3 校验入库项的有效性 + List stockInItems = validateStockInItems(updateReqVO.getItems()); + + // 2.1 更新入库单 + ErpStockInDO updateObj = BeanUtils.toBean(updateReqVO, ErpStockInDO.class, in -> in + .setTotalCount(getSumValue(stockInItems, ErpStockInItemDO::getCount, BigDecimal::add)) + .setTotalPrice(getSumValue(stockInItems, ErpStockInItemDO::getTotalPrice, BigDecimal::add))); + stockInMapper.updateById(updateObj); + // 2.2 更新入库单项 + updateStockInItemList(updateReqVO.getId(), stockInItems); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateStockInStatus(Long id, Integer status) { + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); + // 1.1 校验存在 + ErpStockInDO stockIn = validateStockInExists(id); + // 1.2 校验状态 + if (stockIn.getStatus().equals(status)) { + throw exception(approve ? STOCK_IN_APPROVE_FAIL : STOCK_IN_PROCESS_FAIL); + } + + // 2. 更新状态 + int updateCount = stockInMapper.updateByIdAndStatus(id, stockIn.getStatus(), + new ErpStockInDO().setStatus(status)); + if (updateCount == 0) { + throw exception(approve ? STOCK_IN_APPROVE_FAIL : STOCK_IN_PROCESS_FAIL); + } + + // 3. 变更库存 + List stockInItems = stockInItemMapper.selectListByInId(id); + Integer bizType = approve ? ErpStockRecordBizTypeEnum.OTHER_IN.getType() + : ErpStockRecordBizTypeEnum.OTHER_IN_CANCEL.getType(); + stockInItems.forEach(stockInItem -> { + BigDecimal count = approve ? stockInItem.getCount() : stockInItem.getCount().negate(); + stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO( + stockInItem.getProductId(), stockInItem.getWarehouseId(), count, + bizType, stockInItem.getInId(), stockInItem.getId(), stockIn.getNo())); + }); + } + + private List validateStockInItems(List list) { + // 1.1 校验产品存在 + List productList = productService.validProductList( + convertSet(list, ErpStockInSaveReqVO.Item::getProductId)); + Map productMap = convertMap(productList, ErpProductDO::getId); + // 1.2 校验仓库存在 + warehouseService.validWarehouseList(convertSet( + list, ErpStockInSaveReqVO.Item::getWarehouseId)); + // 2. 转化为 ErpStockInItemDO 列表 + return convertList(list, o -> BeanUtils.toBean(o, ErpStockInItemDO.class, item -> item + .setProductUnitId(productMap.get(item.getProductId()).getUnitId()) + .setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount())))); + } + + private void updateStockInItemList(Long id, List newList) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = stockInItemMapper.selectListByInId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setInId(id)); + stockInItemMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + stockInItemMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + stockInItemMapper.deleteBatchIds(convertList(diffList.get(2), ErpStockInItemDO::getId)); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteStockIn(List ids) { + // 1. 校验不处于已审批 + List stockIns = stockInMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(stockIns)) { + return; + } + stockIns.forEach(stockIn -> { + if (ErpAuditStatus.APPROVE.getStatus().equals(stockIn.getStatus())) { + throw exception(STOCK_IN_DELETE_FAIL_APPROVE, stockIn.getNo()); + } + }); + + // 2. 遍历删除,并记录操作日志 + stockIns.forEach(stockIn -> { + // 2.1 删除入库单 + stockInMapper.deleteById(stockIn.getId()); + // 2.2 删除入库单项 + stockInItemMapper.deleteByInId(stockIn.getId()); + }); + } + + private ErpStockInDO validateStockInExists(Long id) { + ErpStockInDO stockIn = stockInMapper.selectById(id); + if (stockIn == null) { + throw exception(STOCK_IN_NOT_EXISTS); + } + return stockIn; + } + + @Override + public ErpStockInDO getStockIn(Long id) { + return stockInMapper.selectById(id); + } + + @Override + public PageResult getStockInPage(ErpStockInPageReqVO pageReqVO) { + return stockInMapper.selectPage(pageReqVO); + } + + // ==================== 入库项 ==================== + + @Override + public List getStockInItemListByInId(Long inId) { + return stockInItemMapper.selectListByInId(inId); + } + + @Override + public List getStockInItemListByInIds(Collection inIds) { + if (CollUtil.isEmpty(inIds)) { + return Collections.emptyList(); + } + return stockInItemMapper.selectListByInIds(inIds); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveService.java new file mode 100644 index 000000000..8af13a639 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveService.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMovePageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveItemDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * ERP 库存调拨单 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpStockMoveService { + + /** + * 创建库存调拨单 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createStockMove(@Valid ErpStockMoveSaveReqVO createReqVO); + + /** + * 更新库存调拨单 + * + * @param updateReqVO 更新信息 + */ + void updateStockMove(@Valid ErpStockMoveSaveReqVO updateReqVO); + + /** + * 更新库存调拨单的状态 + * + * @param id 编号 + * @param status 状态 + */ + void updateStockMoveStatus(Long id, Integer status); + + /** + * 删除库存调拨单 + * + * @param ids 编号数组 + */ + void deleteStockMove(List ids); + + /** + * 获得库存调拨单 + * + * @param id 编号 + * @return 库存调拨单 + */ + ErpStockMoveDO getStockMove(Long id); + + /** + * 获得库存调拨单分页 + * + * @param pageReqVO 分页查询 + * @return 库存调拨单分页 + */ + PageResult getStockMovePage(ErpStockMovePageReqVO pageReqVO); + + // ==================== 调拨项 ==================== + + /** + * 获得库存调拨单项列表 + * + * @param moveId 调拨编号 + * @return 库存调拨单项列表 + */ + List getStockMoveItemListByMoveId(Long moveId); + + /** + * 获得库存调拨单项 List + * + * @param moveIds 调拨编号数组 + * @return 库存调拨单项 List + */ + List getStockMoveItemListByMoveIds(Collection moveIds); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java new file mode 100644 index 000000000..a0d8f79d4 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockMoveServiceImpl.java @@ -0,0 +1,229 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMovePageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.move.ErpStockMoveSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockMoveItemDO; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockMoveItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockMoveMapper; +import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO; +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.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +// TODO 芋艿:记录操作日志 + +/** + * ERP 库存调拨单 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpStockMoveServiceImpl implements ErpStockMoveService { + + @Resource + private ErpStockMoveMapper stockMoveMapper; + @Resource + private ErpStockMoveItemMapper stockMoveItemMapper; + + @Resource + private ErpNoRedisDAO noRedisDAO; + + @Resource + private ErpProductService productService; + @Resource + private ErpWarehouseService warehouseService; + @Resource + private ErpStockRecordService stockRecordService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createStockMove(ErpStockMoveSaveReqVO createReqVO) { + // 1.1 校验出库项的有效性 + List stockMoveItems = validateStockMoveItems(createReqVO.getItems()); + // 1.2 生成调拨单号,并校验唯一性 + String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_MOVE_NO_PREFIX); + if (stockMoveMapper.selectByNo(no) != null) { + throw exception(STOCK_MOVE_NO_EXISTS); + } + + // 2.1 插入出库单 + ErpStockMoveDO stockMove = BeanUtils.toBean(createReqVO, ErpStockMoveDO.class, in -> in + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()) + .setTotalCount(getSumValue(stockMoveItems, ErpStockMoveItemDO::getCount, BigDecimal::add)) + .setTotalPrice(getSumValue(stockMoveItems, ErpStockMoveItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO))); + stockMoveMapper.insert(stockMove); + // 2.2 插入出库单项 + stockMoveItems.forEach(o -> o.setMoveId(stockMove.getId())); + stockMoveItemMapper.insertBatch(stockMoveItems); + return stockMove.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateStockMove(ErpStockMoveSaveReqVO updateReqVO) { + // 1.1 校验存在 + ErpStockMoveDO stockMove = validateStockMoveExists(updateReqVO.getId()); + if (ErpAuditStatus.APPROVE.getStatus().equals(stockMove.getStatus())) { + throw exception(STOCK_MOVE_UPDATE_FAIL_APPROVE, stockMove.getNo()); + } + // 1.2 校验出库项的有效性 + List stockMoveItems = validateStockMoveItems(updateReqVO.getItems()); + + // 2.1 更新出库单 + ErpStockMoveDO updateObj = BeanUtils.toBean(updateReqVO, ErpStockMoveDO.class, in -> in + .setTotalCount(getSumValue(stockMoveItems, ErpStockMoveItemDO::getCount, BigDecimal::add)) + .setTotalPrice(getSumValue(stockMoveItems, ErpStockMoveItemDO::getTotalPrice, BigDecimal::add))); + stockMoveMapper.updateById(updateObj); + // 2.2 更新出库单项 + updateStockMoveItemList(updateReqVO.getId(), stockMoveItems); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateStockMoveStatus(Long id, Integer status) { + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); + // 1.1 校验存在 + ErpStockMoveDO stockMove = validateStockMoveExists(id); + // 1.2 校验状态 + if (stockMove.getStatus().equals(status)) { + throw exception(approve ? STOCK_MOVE_APPROVE_FAIL : STOCK_MOVE_PROCESS_FAIL); + } + + // 2. 更新状态 + int updateCount = stockMoveMapper.updateByIdAndStatus(id, stockMove.getStatus(), + new ErpStockMoveDO().setStatus(status)); + if (updateCount == 0) { + throw exception(approve ? STOCK_MOVE_APPROVE_FAIL : STOCK_MOVE_PROCESS_FAIL); + } + + // 3. 变更库存 + List stockMoveItems = stockMoveItemMapper.selectListByMoveId(id); + Integer fromBizType = approve ? ErpStockRecordBizTypeEnum.MOVE_OUT.getType() + : ErpStockRecordBizTypeEnum.MOVE_OUT_CANCEL.getType(); + Integer toBizType = approve ? ErpStockRecordBizTypeEnum.MOVE_IN.getType() + : ErpStockRecordBizTypeEnum.MOVE_IN_CANCEL.getType(); + stockMoveItems.forEach(stockMoveItem -> { + BigDecimal fromCount = approve ? stockMoveItem.getCount().negate() : stockMoveItem.getCount(); + BigDecimal toCount = approve ? stockMoveItem.getCount() : stockMoveItem.getCount().negate(); + stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO( + stockMoveItem.getProductId(), stockMoveItem.getFromWarehouseId(), fromCount, + fromBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo())); + stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO( + stockMoveItem.getProductId(), stockMoveItem.getToWarehouseId(), toCount, + toBizType, stockMoveItem.getMoveId(), stockMoveItem.getId(), stockMove.getNo())); + }); + } + + private List validateStockMoveItems(List list) { + // 1.1 校验产品存在 + List productList = productService.validProductList( + convertSet(list, ErpStockMoveSaveReqVO.Item::getProductId)); + Map productMap = convertMap(productList, ErpProductDO::getId); + // 1.2 校验仓库存在 + warehouseService.validWarehouseList(convertSetByFlatMap(list, + item -> Stream.of(item.getFromWarehouseId(), item.getToWarehouseId()))); + // 2. 转化为 ErpStockMoveItemDO 列表 + return convertList(list, o -> BeanUtils.toBean(o, ErpStockMoveItemDO.class, item -> item + .setProductUnitId(productMap.get(item.getProductId()).getUnitId()) + .setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount())))); + } + + private void updateStockMoveItemList(Long id, List newList) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = stockMoveItemMapper.selectListByMoveId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setMoveId(id)); + stockMoveItemMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + stockMoveItemMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + stockMoveItemMapper.deleteBatchIds(convertList(diffList.get(2), ErpStockMoveItemDO::getId)); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteStockMove(List ids) { + // 1. 校验不处于已审批 + List stockMoves = stockMoveMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(stockMoves)) { + return; + } + stockMoves.forEach(stockMove -> { + if (ErpAuditStatus.APPROVE.getStatus().equals(stockMove.getStatus())) { + throw exception(STOCK_MOVE_DELETE_FAIL_APPROVE, stockMove.getNo()); + } + }); + + // 2. 遍历删除,并记录操作日志 + stockMoves.forEach(stockMove -> { + // 2.1 删除出库单 + stockMoveMapper.deleteById(stockMove.getId()); + // 2.2 删除出库单项 + stockMoveItemMapper.deleteByMoveId(stockMove.getId()); + }); + } + + private ErpStockMoveDO validateStockMoveExists(Long id) { + ErpStockMoveDO stockMove = stockMoveMapper.selectById(id); + if (stockMove == null) { + throw exception(STOCK_MOVE_NOT_EXISTS); + } + return stockMove; + } + + @Override + public ErpStockMoveDO getStockMove(Long id) { + return stockMoveMapper.selectById(id); + } + + @Override + public PageResult getStockMovePage(ErpStockMovePageReqVO pageReqVO) { + return stockMoveMapper.selectPage(pageReqVO); + } + + // ==================== 出库项 ==================== + + @Override + public List getStockMoveItemListByMoveId(Long moveId) { + return stockMoveItemMapper.selectListByMoveId(moveId); + } + + @Override + public List getStockMoveItemListByMoveIds(Collection moveIds) { + if (CollUtil.isEmpty(moveIds)) { + return Collections.emptyList(); + } + return stockMoveItemMapper.selectListByMoveIds(moveIds); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutService.java new file mode 100644 index 000000000..558fb6948 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutService.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutItemDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * ERP 其它出库单 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpStockOutService { + + /** + * 创建其它出库单 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createStockOut(@Valid ErpStockOutSaveReqVO createReqVO); + + /** + * 更新其它出库单 + * + * @param updateReqVO 更新信息 + */ + void updateStockOut(@Valid ErpStockOutSaveReqVO updateReqVO); + + /** + * 更新其它出库单的状态 + * + * @param id 编号 + * @param status 状态 + */ + void updateStockOutStatus(Long id, Integer status); + + /** + * 删除其它出库单 + * + * @param ids 编号数组 + */ + void deleteStockOut(List ids); + + /** + * 获得其它出库单 + * + * @param id 编号 + * @return 其它出库单 + */ + ErpStockOutDO getStockOut(Long id); + + /** + * 获得其它出库单分页 + * + * @param pageReqVO 分页查询 + * @return 其它出库单分页 + */ + PageResult getStockOutPage(ErpStockOutPageReqVO pageReqVO); + + // ==================== 出库项 ==================== + + /** + * 获得其它出库单项列表 + * + * @param outId 出库编号 + * @return 其它出库单项列表 + */ + List getStockOutItemListByOutId(Long outId); + + /** + * 获得其它出库单项 List + * + * @param outIds 出库编号数组 + * @return 其它出库单项 List + */ + List getStockOutItemListByOutIds(Collection outIds); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java new file mode 100644 index 000000000..9fa192465 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockOutServiceImpl.java @@ -0,0 +1,228 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutPageReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.out.ErpStockOutSaveReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutItemDO; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockOutItemMapper; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockOutMapper; +import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO; +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus; +import cn.iocoder.yudao.module.erp.enums.stock.ErpStockRecordBizTypeEnum; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService; +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO; +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.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +// TODO 芋艿:记录操作日志 + +/** + * ERP 其它出库单 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpStockOutServiceImpl implements ErpStockOutService { + + @Resource + private ErpStockOutMapper stockOutMapper; + @Resource + private ErpStockOutItemMapper stockOutItemMapper; + + @Resource + private ErpNoRedisDAO noRedisDAO; + + @Resource + private ErpProductService productService; + @Resource + private ErpWarehouseService warehouseService; + @Resource + private ErpCustomerService customerService; + @Resource + private ErpStockRecordService stockRecordService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createStockOut(ErpStockOutSaveReqVO createReqVO) { + // 1.1 校验出库项的有效性 + List stockOutItems = validateStockOutItems(createReqVO.getItems()); + // 1.2 校验客户 + customerService.validateCustomer(createReqVO.getCustomerId()); + // 1.3 生成出库单号,并校验唯一性 + String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_OUT_NO_PREFIX); + if (stockOutMapper.selectByNo(no) != null) { + throw exception(STOCK_OUT_NO_EXISTS); + } + + // 2.1 插入出库单 + ErpStockOutDO stockOut = BeanUtils.toBean(createReqVO, ErpStockOutDO.class, in -> in + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus()) + .setTotalCount(getSumValue(stockOutItems, ErpStockOutItemDO::getCount, BigDecimal::add)) + .setTotalPrice(getSumValue(stockOutItems, ErpStockOutItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO))); + stockOutMapper.insert(stockOut); + // 2.2 插入出库单项 + stockOutItems.forEach(o -> o.setOutId(stockOut.getId())); + stockOutItemMapper.insertBatch(stockOutItems); + return stockOut.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateStockOut(ErpStockOutSaveReqVO updateReqVO) { + // 1.1 校验存在 + ErpStockOutDO stockOut = validateStockOutExists(updateReqVO.getId()); + if (ErpAuditStatus.APPROVE.getStatus().equals(stockOut.getStatus())) { + throw exception(STOCK_OUT_UPDATE_FAIL_APPROVE, stockOut.getNo()); + } + // 1.2 校验客户 + customerService.validateCustomer(updateReqVO.getCustomerId()); + // 1.3 校验出库项的有效性 + List stockOutItems = validateStockOutItems(updateReqVO.getItems()); + + // 2.1 更新出库单 + ErpStockOutDO updateObj = BeanUtils.toBean(updateReqVO, ErpStockOutDO.class, in -> in + .setTotalCount(getSumValue(stockOutItems, ErpStockOutItemDO::getCount, BigDecimal::add)) + .setTotalPrice(getSumValue(stockOutItems, ErpStockOutItemDO::getTotalPrice, BigDecimal::add))); + stockOutMapper.updateById(updateObj); + // 2.2 更新出库单项 + updateStockOutItemList(updateReqVO.getId(), stockOutItems); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateStockOutStatus(Long id, Integer status) { + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status); + // 1.1 校验存在 + ErpStockOutDO stockOut = validateStockOutExists(id); + // 1.2 校验状态 + if (stockOut.getStatus().equals(status)) { + throw exception(approve ? STOCK_OUT_APPROVE_FAIL : STOCK_OUT_PROCESS_FAIL); + } + + // 2. 更新状态 + int updateCount = stockOutMapper.updateByIdAndStatus(id, stockOut.getStatus(), + new ErpStockOutDO().setStatus(status)); + if (updateCount == 0) { + throw exception(approve ? STOCK_OUT_APPROVE_FAIL : STOCK_OUT_PROCESS_FAIL); + } + + // 3. 变更库存 + List stockOutItems = stockOutItemMapper.selectListByOutId(id); + Integer bizType = approve ? ErpStockRecordBizTypeEnum.OTHER_OUT.getType() + : ErpStockRecordBizTypeEnum.OTHER_OUT_CANCEL.getType(); + stockOutItems.forEach(stockOutItem -> { + BigDecimal count = approve ? stockOutItem.getCount().negate() : stockOutItem.getCount(); + stockRecordService.createStockRecord(new ErpStockRecordCreateReqBO( + stockOutItem.getProductId(), stockOutItem.getWarehouseId(), count, + bizType, stockOutItem.getOutId(), stockOutItem.getId(), stockOut.getNo())); + }); + } + + private List validateStockOutItems(List list) { + // 1.1 校验产品存在 + List productList = productService.validProductList( + convertSet(list, ErpStockOutSaveReqVO.Item::getProductId)); + Map productMap = convertMap(productList, ErpProductDO::getId); + // 1.2 校验仓库存在 + warehouseService.validWarehouseList(convertSet(list, ErpStockOutSaveReqVO.Item::getWarehouseId)); + // 2. 转化为 ErpStockOutItemDO 列表 + return convertList(list, o -> BeanUtils.toBean(o, ErpStockOutItemDO.class, item -> item + .setProductUnitId(productMap.get(item.getProductId()).getUnitId()) + .setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount())))); + } + + private void updateStockOutItemList(Long id, List newList) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = stockOutItemMapper.selectListByOutId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setOutId(id)); + stockOutItemMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + stockOutItemMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + stockOutItemMapper.deleteBatchIds(convertList(diffList.get(2), ErpStockOutItemDO::getId)); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteStockOut(List ids) { + // 1. 校验不处于已审批 + List stockOuts = stockOutMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(stockOuts)) { + return; + } + stockOuts.forEach(stockOut -> { + if (ErpAuditStatus.APPROVE.getStatus().equals(stockOut.getStatus())) { + throw exception(STOCK_OUT_DELETE_FAIL_APPROVE, stockOut.getNo()); + } + }); + + // 2. 遍历删除,并记录操作日志 + stockOuts.forEach(stockOut -> { + // 2.1 删除出库单 + stockOutMapper.deleteById(stockOut.getId()); + // 2.2 删除出库单项 + stockOutItemMapper.deleteByOutId(stockOut.getId()); + }); + } + + private ErpStockOutDO validateStockOutExists(Long id) { + ErpStockOutDO stockOut = stockOutMapper.selectById(id); + if (stockOut == null) { + throw exception(STOCK_OUT_NOT_EXISTS); + } + return stockOut; + } + + @Override + public ErpStockOutDO getStockOut(Long id) { + return stockOutMapper.selectById(id); + } + + @Override + public PageResult getStockOutPage(ErpStockOutPageReqVO pageReqVO) { + return stockOutMapper.selectPage(pageReqVO); + } + + // ==================== 出库项 ==================== + + @Override + public List getStockOutItemListByOutId(Long outId) { + return stockOutItemMapper.selectListByOutId(outId); + } + + @Override + public List getStockOutItemListByOutIds(Collection outIds) { + if (CollUtil.isEmpty(outIds)) { + return Collections.emptyList(); + } + return stockOutItemMapper.selectListByOutIds(outIds); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordService.java new file mode 100644 index 000000000..506b992a4 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordService.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record.ErpStockRecordPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockRecordDO; +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO; +import jakarta.validation.Valid; + +/** + * ERP 产品库存明细 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpStockRecordService { + + /** + * 获得产品库存明细 + * + * @param id 编号 + * @return 产品库存明细 + */ + ErpStockRecordDO getStockRecord(Long id); + + /** + * 获得产品库存明细分页 + * + * @param pageReqVO 分页查询 + * @return 产品库存明细分页 + */ + PageResult getStockRecordPage(ErpStockRecordPageReqVO pageReqVO); + + /** + * 创建库存明细 + * + * @param createReqBO 创建库存明细 BO + */ + void createStockRecord(@Valid ErpStockRecordCreateReqBO createReqBO); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordServiceImpl.java new file mode 100644 index 000000000..d84951c28 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockRecordServiceImpl.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.record.ErpStockRecordPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockRecordDO; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockRecordMapper; +import cn.iocoder.yudao.module.erp.service.stock.bo.ErpStockRecordCreateReqBO; +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; + +/** + * ERP 产品库存明细 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpStockRecordServiceImpl implements ErpStockRecordService { + + @Resource + private ErpStockRecordMapper stockRecordMapper; + + @Resource + private ErpStockService stockService; + + @Override + public ErpStockRecordDO getStockRecord(Long id) { + return stockRecordMapper.selectById(id); + } + + @Override + public PageResult getStockRecordPage(ErpStockRecordPageReqVO pageReqVO) { + return stockRecordMapper.selectPage(pageReqVO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void createStockRecord(ErpStockRecordCreateReqBO createReqBO) { + // 1. 更新库存 + BigDecimal totalCount = stockService.updateStockCountIncrement( + createReqBO.getProductId(), createReqBO.getWarehouseId(), createReqBO.getCount()); + // 2. 创建库存明细 + ErpStockRecordDO stockRecord = BeanUtils.toBean(createReqBO, ErpStockRecordDO.class) + .setTotalCount(totalCount); + stockRecordMapper.insert(stockRecord); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockService.java new file mode 100644 index 000000000..63ad5fefa --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockService.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; + +import java.math.BigDecimal; + +/** + * ERP 产品库存 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpStockService { + + /** + * 获得产品库存 + * + * @param id 编号 + * @return 库存 + */ + ErpStockDO getStock(Long id); + + /** + * 基于产品 + 仓库,获得产品库存 + * + * @param productId 产品编号 + * @param warehouseId 仓库编号 + * @return 产品库存 + */ + ErpStockDO getStock(Long productId, Long warehouseId); + + /** + * 获得产品库存数量 + * + * 如果不存在库存记录,则返回 0 + * + * @param productId 产品编号 + * @return 产品库存数量 + */ + BigDecimal getStockCount(Long productId); + + /** + * 获得产品库存分页 + * + * @param pageReqVO 分页查询 + * @return 库存分页 + */ + PageResult getStockPage(ErpStockPageReqVO pageReqVO); + + /** + * 增量更新产品库存数量 + * + * @param productId 产品编号 + * @param warehouseId 仓库编号 + * @param count 增量数量:正数,表示增加;负数,表示减少 + * @return 更新后的库存 + */ + BigDecimal updateStockCountIncrement(Long productId, Long warehouseId, BigDecimal count); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java new file mode 100644 index 000000000..654117047 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java @@ -0,0 +1,89 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpStockMapper; +import cn.iocoder.yudao.module.erp.service.product.ErpProductService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.math.BigDecimal; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.STOCK_COUNT_NEGATIVE2; + +/** + * ERP 产品库存 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpStockServiceImpl implements ErpStockService { + + /** + * 允许库存为负数 + * + * TODO 芋艿:后续做成 db 配置 + */ + private static final Boolean NEGATIVE_STOCK_COUNT_ENABLE = false; + + @Resource + private ErpProductService productService; + @Resource + private ErpWarehouseService warehouseService; + + @Resource + private ErpStockMapper stockMapper; + + @Override + public ErpStockDO getStock(Long id) { + return stockMapper.selectById(id); + } + + @Override + public ErpStockDO getStock(Long productId, Long warehouseId) { + return stockMapper.selectByProductIdAndWarehouseId(productId, warehouseId); + } + + @Override + public BigDecimal getStockCount(Long productId) { + BigDecimal count = stockMapper.selectSumByProductId(productId); + return count != null ? count : BigDecimal.ZERO; + } + + @Override + public PageResult getStockPage(ErpStockPageReqVO pageReqVO) { + return stockMapper.selectPage(pageReqVO); + } + + @Override + public BigDecimal updateStockCountIncrement(Long productId, Long warehouseId, BigDecimal count) { + // 1.1 查询当前库存 + ErpStockDO stock = stockMapper.selectByProductIdAndWarehouseId(productId, warehouseId); + if (stock == null) { + stock = new ErpStockDO().setProductId(productId).setWarehouseId(warehouseId).setCount(BigDecimal.ZERO); + stockMapper.insert(stock); + } + // 1.2 校验库存是否充足 + if (!NEGATIVE_STOCK_COUNT_ENABLE && stock.getCount().add(count).compareTo(BigDecimal.ZERO) < 0) { + throw exception(STOCK_COUNT_NEGATIVE, productService.getProduct(productId).getName(), + warehouseService.getWarehouse(warehouseId).getName(), stock.getCount(), count); + } + + // 2. 库存变更 + int updateCount = stockMapper.updateCountIncrement(stock.getId(), count, NEGATIVE_STOCK_COUNT_ENABLE); + if (updateCount == 0) { + // 此时不好去查询最新库存,所以直接抛出该提示,不提供具体库存数字 + throw exception(STOCK_COUNT_NEGATIVE2, productService.getProduct(productId).getName(), + warehouseService.getWarehouse(warehouseId).getName()); + } + + // 3. 返回最新库存 + return stock.getCount().add(count); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpWarehouseService.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpWarehouseService.java new file mode 100644 index 000000000..872a698de --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpWarehouseService.java @@ -0,0 +1,102 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.ErpWarehouseSaveReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.warehouse.ErpWarehousePageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * ERP 仓库 Service 接口 + * + * @author 芋道源码 + */ +public interface ErpWarehouseService { + + /** + * 创建仓库 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createWarehouse(@Valid ErpWarehouseSaveReqVO createReqVO); + + /** + * 更新ERP 仓库 + * + * @param updateReqVO 更新信息 + */ + void updateWarehouse(@Valid ErpWarehouseSaveReqVO updateReqVO); + + /** + * 更新仓库默认状态 + * + * @param id 编号 + * @param defaultStatus 默认状态 + */ + void updateWarehouseDefaultStatus(Long id, Boolean defaultStatus); + + /** + * 删除仓库 + * + * @param id 编号 + */ + void deleteWarehouse(Long id); + + /** + * 获得仓库 + * + * @param id 编号 + * @return 仓库 + */ + ErpWarehouseDO getWarehouse(Long id); + + /** + * 校验仓库列表的有效性 + * + * @param ids 编号数组 + * @return 仓库列表 + */ + List validWarehouseList(Collection ids); + + /** + * 获得指定状态的仓库列表 + * + * @param status 状态 + * @return 仓库列表 + */ + List getWarehouseListByStatus(Integer status); + + /** + * 获得仓库列表 + * + * @param ids 编号数组 + * @return 仓库列表 + */ + List getWarehouseList(Collection ids); + + /** + * 获得仓库 Map + * + * @param ids 编号数组 + * @return 仓库 Map + */ + default Map getWarehouseMap(Collection ids) { + return convertMap(getWarehouseList(ids), ErpWarehouseDO::getId); + } + + /** + * 获得仓库分页 + * + * @param pageReqVO 分页查询 + * @return 仓库分页 + */ + PageResult getWarehousePage(ErpWarehousePageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpWarehouseServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpWarehouseServiceImpl.java new file mode 100644 index 000000000..fb4829146 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpWarehouseServiceImpl.java @@ -0,0 +1,126 @@ +package cn.iocoder.yudao.module.erp.service.stock; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.ErpWarehouseSaveReqVO; +import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.warehouse.ErpWarehousePageReqVO; +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpWarehouseDO; +import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpWarehouseMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*; + +/** + * ERP 仓库 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class ErpWarehouseServiceImpl implements ErpWarehouseService { + + @Resource + private ErpWarehouseMapper warehouseMapper; + + @Override + public Long createWarehouse(ErpWarehouseSaveReqVO createReqVO) { + // 插入 + ErpWarehouseDO warehouse = BeanUtils.toBean(createReqVO, ErpWarehouseDO.class); + warehouseMapper.insert(warehouse); + // 返回 + return warehouse.getId(); + } + + @Override + public void updateWarehouse(ErpWarehouseSaveReqVO updateReqVO) { + // 校验存在 + validateWarehouseExists(updateReqVO.getId()); + // 更新 + ErpWarehouseDO updateObj = BeanUtils.toBean(updateReqVO, ErpWarehouseDO.class); + warehouseMapper.updateById(updateObj); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateWarehouseDefaultStatus(Long id, Boolean defaultStatus) { + // 1. 校验存在 + validateWarehouseExists(id); + + // 2.1 如果开启,则需要关闭所有其它的默认 + if (defaultStatus) { + ErpWarehouseDO warehouse = warehouseMapper.selectByDefaultStatus(); + if (warehouse != null) { + warehouseMapper.updateById(new ErpWarehouseDO().setId(warehouse.getId()).setDefaultStatus(false)); + } + } + // 2.2 更新对应的默认状态 + warehouseMapper.updateById(new ErpWarehouseDO().setId(id).setDefaultStatus(defaultStatus)); + } + + @Override + public void deleteWarehouse(Long id) { + // 校验存在 + validateWarehouseExists(id); + // 删除 + warehouseMapper.deleteById(id); + } + + private void validateWarehouseExists(Long id) { + if (warehouseMapper.selectById(id) == null) { + throw exception(WAREHOUSE_NOT_EXISTS); + } + } + + @Override + public ErpWarehouseDO getWarehouse(Long id) { + return warehouseMapper.selectById(id); + } + + @Override + public List validWarehouseList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + List list = warehouseMapper.selectBatchIds(ids); + Map warehouseMap = convertMap(list, ErpWarehouseDO::getId); + for (Long id : ids) { + ErpWarehouseDO warehouse = warehouseMap.get(id); + if (warehouseMap.get(id) == null) { + throw exception(WAREHOUSE_NOT_EXISTS); + } + if (CommonStatusEnum.isDisable(warehouse.getStatus())) { + throw exception(WAREHOUSE_NOT_ENABLE, warehouse.getName()); + } + } + return list; + } + + @Override + public List getWarehouseListByStatus(Integer status) { + return warehouseMapper.selectListByStatus(status); + } + + @Override + public List getWarehouseList(Collection ids) { + return warehouseMapper.selectBatchIds(ids); + } + + @Override + public PageResult getWarehousePage(ErpWarehousePageReqVO pageReqVO) { + return warehouseMapper.selectPage(pageReqVO); + } + +} \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/bo/ErpStockRecordCreateReqBO.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/bo/ErpStockRecordCreateReqBO.java new file mode 100644 index 000000000..7006f10b5 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/bo/ErpStockRecordCreateReqBO.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.erp.service.stock.bo; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +/** + * 库存明细的创建 Request BO + * + * @author 芋道源码 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ErpStockRecordCreateReqBO { + + /** + * 产品编号 + */ + @NotNull(message = "产品编号不能为空") + private Long productId; + /** + * 仓库编号 + */ + @NotNull(message = "仓库编号不能为空") + private Long warehouseId; + /** + * 出入库数量 + * + * 正数,表示入库;负数,表示出库 + */ + @NotNull(message = "出入库数量不能为空") + private BigDecimal count; + + /** + * 业务类型 + */ + @NotNull(message = "业务类型不能为空") + private Integer bizType; + /** + * 业务编号 + */ + @NotNull(message = "业务编号不能为空") + private Long bizId; + /** + * 业务项编号 + */ + @NotNull(message = "业务项编号不能为空") + private Long bizItemId; + /** + * 业务单号 + */ + @NotNull(message = "业务单号不能为空") + private String bizNo; + +} diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/application-dev.yaml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/application-dev.yaml new file mode 100644 index 000000000..affbaf3c2 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/application-dev.yaml @@ -0,0 +1,104 @@ +--- #################### 数据库相关配置 #################### +spring: + # 数据源配置项 + autoconfigure: + exclude: + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 5 # 初始连接数 + min-idle: 10 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: master + datasource: + master: + name: ruoyi-vue-pro + url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&nullCatalogMeansCurrent=true + driver-class-name: com.mysql.jdbc.Driver + username: root + password: 3WLiVUBEwTbvAfsh + slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 + name: ruoyi-vue-pro + url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&nullCatalogMeansCurrent=true + driver-class-name: com.mysql.jdbc.Driver + username: root + password: 3WLiVUBEwTbvAfsh + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 400-infra.server.iocoder.cn # 地址 + port: 6379 # 端口 + database: 1 # 数据库索引 +# password: 123456 # 密码,建议生产环境开启 + +--- #################### MQ 消息队列相关配置 #################### + +--- #################### 定时任务相关配置 #################### + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator + exposure: + include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + # Spring Boot Admin Client 客户端的相关配置 + client: + instance: + service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + xss: + enable: false + exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 + - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求 + - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 + access-log: # 访问日志的配置项 + enable: false + error-code: # 错误码相关配置项 + enable: false + demo: false # 关闭演示模式 diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/application-local.yaml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/application-local.yaml new file mode 100644 index 000000000..2efe5472f --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/application-local.yaml @@ -0,0 +1,130 @@ +--- #################### 数据库相关配置 #################### +spring: + # 数据源配置项 + autoconfigure: + exclude: + - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置 + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 1 # 初始连接数 + min-idle: 1 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: master + datasource: + master: + name: ruoyi-vue-pro + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 + # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 + # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例 + # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 + # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例 + username: root + password: 123456 + # username: sa + # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W + slave: # 模拟从库,可根据自己需要修改 + name: ruoyi-vue-pro + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 + # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 + # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 + # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 + # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例 + username: root + password: 123456 + # username: sa + # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 127.0.0.1 # 地址 + port: 6379 # 端口 + database: 0 # 数据库索引 +# password: 123456 # 密码,建议生产环境开启 + +--- #################### MQ 消息队列相关配置 #################### + +--- #################### 定时任务相关配置 #################### +xxl: + job: + enabled: false # 是否开启调度中心,默认为 true 开启 + admin: + addresses: http://127.0.0.1:8000/xxl-job-admin # 调度中心部署跟地址 + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator + exposure: + include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + # Spring Boot Admin Client 客户端的相关配置 + client: + instance: + service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + +# 日志文件配置 +logging: + level: + # 配置自己写的 MyBatis Mapper 打印日志 + cn.iocoder.yudao.module.erp.dal.mysql: debug + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + env: # 多环境的配置项 + tag: ${HOSTNAME} + security: + mock-enable: true + xss: + enable: false + exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 + - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求 + - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 + access-log: # 访问日志的配置项 + enable: false + error-code: # 错误码相关配置项 + enable: false + demo: false # 关闭演示模式 diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/application.yaml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/application.yaml new file mode 100644 index 000000000..dcbd00cda --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/application.yaml @@ -0,0 +1,105 @@ +spring: + main: + allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。 + allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务 + + # Servlet 配置 + servlet: + # 文件上传相关配置项 + multipart: + max-file-size: 16MB # 单个文件大小 + max-request-size: 32MB # 设置总上传的文件大小 + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER # 解决 SpringFox 与 SpringBoot 2.6.x 不兼容的问题,参见 SpringFoxHandlerProviderBeanPostProcessor 类 + + # Jackson 配置项 + jackson: + serialization: + write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳 + write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401 + write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳 + fail-on-empty-beans: false # 允许序列化无属性的 Bean + + # Cache 配置项 + cache: + type: REDIS + redis: + time-to-live: 1h # 设置过期时间为 1 小时 + +--- #################### 接口文档配置 #################### + +springdoc: + api-docs: + enabled: true # 1. 是否开启 Swagger 接文档的元数据 + path: /v3/api-docs + swagger-ui: + enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面 + path: /swagger-ui.html + default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档 + +knife4j: + enable: true # 2.2 是否开启 Swagger 文档的 Knife4j UI 界面 + setting: + language: zh_cn + +# MyBatis Plus 的配置项 +mybatis-plus: + configuration: + map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。 + global-config: + db-config: + id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。 + # id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库 + # id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 + # id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解 + logic-delete-value: 1 # 逻辑已删除值(默认为 1) + logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) + banner: false # 关闭控制台的 Banner 打印 + type-aliases-package: ${yudao.info.base-package}.dal.dataobject + encryptor: + password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成 + +mybatis-plus-join: + banner: false # 关闭控制台的 Banner 打印 + +# Spring Data Redis 配置 +spring: + data: + redis: + repositories: + enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度 + +--- #################### MQ 消息队列相关配置 #################### + +--- #################### 定时任务相关配置 #################### + +xxl: + job: + executor: + appname: ${spring.application.name} # 执行器 AppName + logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径 + accessToken: default_token # 执行器通讯TOKEN + +--- #################### 芋道相关配置 #################### + +yudao: + info: + version: 1.0.0 + base-package: cn.iocoder.yudao.module.erp + web: + admin-ui: + url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址 + swagger: + title: 管理后台 + description: 提供管理员管理的所有功能 + version: ${yudao.info.version} + base-package: ${yudao.info.base-package} + error-code: # 错误码相关配置项 + constants-class-list: + - cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants + tenant: # 多租户相关配置项 + enable: true + ignore-urls: + +debug: false diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/bootstrap-local.yaml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/bootstrap-local.yaml new file mode 100644 index 000000000..2de0efbf7 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/bootstrap-local.yaml @@ -0,0 +1,23 @@ +--- #################### 注册中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: 127.0.0.1:8848 + discovery: + namespace: dev # 命名空间。这里使用 dev 开发环境 + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + +--- #################### 配置中心相关配置 #################### + +spring: + cloud: + nacos: + # Nacos Config 配置项,对应 NacosConfigProperties 配置属性类 + config: + server-addr: 127.0.0.1:8848 # Nacos 服务器地址 + namespace: dev # 命名空间 dev 的ID,不能直接使用 dev 名称。创建命名空间的时候需要指定ID为 dev,这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + name: ${spring.application.name} # 使用的 Nacos 配置集的 dataId,默认为 spring.application.name + file-extension: yaml # 使用的 Nacos 配置集的 dataId 的文件拓展名,同时也是 Nacos 配置集的配置格式,默认为 properties diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/bootstrap.yaml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/bootstrap.yaml new file mode 100644 index 000000000..70de21db1 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/bootstrap.yaml @@ -0,0 +1,14 @@ +spring: + application: + name: erp-server + + profiles: + active: local + +server: + port: 48088 + +# 日志文件配置。注意,如果 logging.file.name 不放在 bootstrap.yaml 配置文件,而是放在 application.yaml 中,会导致出现 LOG_FILE_IS_UNDEFINED 文件 +logging: + file: + name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径 diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/logback-spring.xml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..b1b9f3faf --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/logback-spring.xml @@ -0,0 +1,76 @@ + + + + + + + + + +       + + + ${PATTERN_DEFAULT} + + + + + + + + + + ${PATTERN_DEFAULT} + + + + ${LOG_FILE} + + + ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} + + ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} + + ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} + + ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} + + ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} + + + + + + 0 + + 256 + + + + + + + + ${PATTERN_DEFAULT} + + + + + + + + + + + + + + + + + + + + + + diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/finance/ErpAccountMapper.xml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/finance/ErpAccountMapper.xml new file mode 100644 index 000000000..a3cf6877b --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/finance/ErpAccountMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/product/ErpProductUnitMapper.xml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/product/ErpProductUnitMapper.xml new file mode 100644 index 000000000..4666e48d4 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/product/ErpProductUnitMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/sale/ErpCustomerMapper.xml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/sale/ErpCustomerMapper.xml new file mode 100644 index 000000000..c012f4467 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/sale/ErpCustomerMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/stock/ErpWarehouseMapper.xml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/stock/ErpWarehouseMapper.xml new file mode 100644 index 000000000..be2bc61cb --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/stock/ErpWarehouseMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/supplier/ErpSupplierMapper.xml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/supplier/ErpSupplierMapper.xml new file mode 100644 index 000000000..54849af3f --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/supplier/ErpSupplierMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/resources/application-dev.yaml b/yudao-module-pay/yudao-module-pay-biz/src/main/resources/application-dev.yaml index f03c61606..e7f672391 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/resources/application-dev.yaml +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/resources/application-dev.yaml @@ -51,11 +51,12 @@ spring: password: 3WLiVUBEwTbvAfsh # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 - redis: - host: 400-infra.server.iocoder.cn # 地址 - port: 6379 # 端口 - database: 1 # 数据库索引 -# password: 123456 # 密码,建议生产环境开启 + data: + redis: + host: 400-infra.server.iocoder.cn # 地址 + port: 6379 # 端口 + database: 1 # 数据库索引 +# password: 123456 # 密码,建议生产环境开启 --- #################### MQ 消息队列相关配置 #################### diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/resources/application-local.yaml b/yudao-module-pay/yudao-module-pay-biz/src/main/resources/application-local.yaml index f36dbced7..61e6f21e4 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/resources/application-local.yaml +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/resources/application-local.yaml @@ -62,11 +62,12 @@ spring: # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 - redis: - host: 127.0.0.1 # 地址 - port: 6379 # 端口 - database: 0 # 数据库索引 -# password: 123456 # 密码,建议生产环境开启 + data: + redis: + host: 127.0.0.1 # 地址 + port: 6379 # 端口 + database: 0 # 数据库索引 + # password: 123456 # 密码,建议生产环境开启 --- #################### MQ 消息队列相关配置 #################### diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test-integration/resources/application-integration-test.yaml b/yudao-module-pay/yudao-module-pay-biz/src/test-integration/resources/application-integration-test.yaml index 4fca0a52c..fa369b9d5 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/test-integration/resources/application-integration-test.yaml +++ b/yudao-module-pay/yudao-module-pay-biz/src/test-integration/resources/application-integration-test.yaml @@ -42,10 +42,11 @@ spring: password: 123456 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 - redis: - host: 127.0.0.1 # 地址 - port: 6379 # 端口 - database: 0 # 数据库索引 + data: + redis: + host: 127.0.0.1 # 地址 + port: 6379 # 端口 + database: 0 # 数据库索引 mybatis: lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java index 96e3f110a..113b482e4 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java @@ -11,10 +11,7 @@ import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; @FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = @Tag(name = "RPC 服务 - 管理员用户") @@ -53,6 +50,17 @@ public interface AdminUserApi { return CollectionUtils.convertMap(users, AdminUserRespDTO::getId); } + /** + * 校验用户是否有效。如下情况,视为无效: + * 1. 用户编号不存在 + * 2. 用户被禁用 + * + * @param id 用户编号 + */ + default void validateUser(Long id) { + validateUserList(Collections.singleton(id)); + } + @GetMapping(PREFIX + "/valid") @Operation(summary = "校验用户们是否有效") @Parameter(name = "ids", description = "用户编号数组", example = "3,5", required = true) diff --git a/yudao-module-system/yudao-module-system-biz/src/test-integration/resources/application-integration-test.yaml b/yudao-module-system/yudao-module-system-biz/src/test-integration/resources/application-integration-test.yaml index 9c7a46e25..e5d653d6e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test-integration/resources/application-integration-test.yaml +++ b/yudao-module-system/yudao-module-system-biz/src/test-integration/resources/application-integration-test.yaml @@ -57,10 +57,11 @@ spring: password: 123456 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 - redis: - host: 127.0.0.1 # 地址 - port: 6379 # 端口 - database: 0 # 数据库索引 + data: + redis: + host: 127.0.0.1 # 地址 + port: 6379 # 端口 + database: 0 # 数据库索引 mybatis: lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 From 0e55c4da6d4b8cb8490f41b1b42c781916e35664 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 17 Feb 2024 23:25:41 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9C=A8=20=E5=A2=9E=E5=8A=A0=20CRM=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 1 + .../src/main/resources/application.yaml | 10 + yudao-module-crm/pom.xml | 25 + yudao-module-crm/yudao-module-crm-api/pom.xml | 33 + .../yudao/module/crm/api/package-info.java | 4 + .../yudao/module/crm/enums/ApiConstants.java | 23 + .../module/crm/enums/DictTypeConstants.java | 18 + .../module/crm/enums/ErrorCodeConstants.java | 97 +++ .../module/crm/enums/LogRecordConstants.java | 139 +++++ .../crm/enums/business/CrmBizEndStatus.java | 55 ++ .../crm/enums/common/CrmAuditStatusEnum.java | 34 ++ .../crm/enums/common/CrmBizTypeEnum.java | 52 ++ .../crm/enums/common/CrmSceneTypeEnum.java | 51 ++ .../enums/customer/CrmCustomerLevelEnum.java | 38 ++ .../CrmCustomerLimitConfigTypeEnum.java | 52 ++ .../permission/CrmPermissionLevelEnum.java | 51 ++ .../permission/CrmPermissionRoleCodeEnum.java | 27 + .../enums/product/CrmProductStatusEnum.java | 38 ++ yudao-module-crm/yudao-module-crm-biz/pom.xml | 141 +++++ .../module/crm/CrmServerApplication.java | 30 + .../yudao/module/crm/api/package-info.java | 4 + .../admin/backlog/CrmBacklogController.java | 41 ++ .../backlog/vo/CrmTodayCustomerPageReqVO.java | 37 ++ .../admin/bi/CrmBiRankController.http | 9 + .../admin/bi/CrmBiRankController.java | 87 +++ .../admin/bi/vo/CrmBiRanKRespVO.java | 29 + .../admin/bi/vo/CrmBiRankReqVO.java | 35 ++ .../admin/business/CrmBusinessController.http | 32 + .../admin/business/CrmBusinessController.java | 177 ++++++ .../CrmBusinessStatusTypeController.java | 141 +++++ .../vo/business/CrmBusinessExcelVO.java | 75 +++ .../vo/business/CrmBusinessPageReqVO.java | 30 + .../vo/business/CrmBusinessRespVO.java | 69 +++ .../vo/business/CrmBusinessSaveReqVO.java | 103 ++++ .../vo/business/CrmBusinessTransferReqVO.java | 31 + .../vo/status/CrmBusinessStatusPageReqVO.java | 15 + .../vo/status/CrmBusinessStatusQueryVO.java | 19 + .../vo/status/CrmBusinessStatusRespVO.java | 33 + .../vo/status/CrmBusinessStatusSaveReqVO.java | 32 + .../type/CrmBusinessStatusTypePageReqVO.java | 15 + .../vo/type/CrmBusinessStatusTypeQueryVO.java | 19 + .../vo/type/CrmBusinessStatusTypeRespVO.java | 44 ++ .../type/CrmBusinessStatusTypeSaveReqVO.java | 29 + .../admin/clue/CrmClueController.java | 107 ++++ .../admin/clue/vo/CrmCluePageReqVO.java | 42 ++ .../admin/clue/vo/CrmClueRespVO.java | 115 ++++ .../admin/clue/vo/CrmClueSaveReqVO.java | 105 ++++ .../admin/clue/vo/CrmClueTransferReqVO.java | 25 + .../admin/clue/vo/CrmClueTranslateReqVO.java | 17 + .../admin/contact/CrmContactController.java | 204 +++++++ .../contact/vo/CrmContactBusinessReqVO.java | 22 + .../admin/contact/vo/CrmContactPageReqVO.java | 42 ++ .../admin/contact/vo/CrmContactRespVO.java | 112 ++++ .../admin/contact/vo/CrmContactSaveReqVO.java | 103 ++++ .../contact/vo/CrmContactTransferReqVO.java | 32 + .../admin/contract/CrmContractController.java | 187 ++++++ .../contract/vo/CrmContractPageReqVO.java | 50 ++ .../admin/contract/vo/CrmContractRespVO.java | 165 +++++ .../contract/vo/CrmContractSaveReqVO.java | 115 ++++ .../contract/vo/CrmContractTransferReqVO.java | 26 + .../admin/customer/CrmCustomerController.http | 16 + .../admin/customer/CrmCustomerController.java | 272 +++++++++ .../CrmCustomerLimitConfigController.java | 97 +++ .../CrmCustomerPoolConfigController.java | 44 ++ .../vo/CrmCustomerDistributeReqVO.java | 22 + .../customer/vo/CrmCustomerImportExcelVO.java | 71 +++ .../customer/vo/CrmCustomerImportReqVO.java | 25 + .../customer/vo/CrmCustomerImportRespVO.java | 24 + .../customer/vo/CrmCustomerLockReqVO.java | 16 + .../customer/vo/CrmCustomerPageReqVO.java | 39 ++ .../admin/customer/vo/CrmCustomerRespVO.java | 138 +++++ .../customer/vo/CrmCustomerSaveReqVO.java | 106 ++++ .../customer/vo/CrmCustomerTransferReqVO.java | 31 + .../CrmCustomerLimitConfigPageReqVO.java | 18 + .../CrmCustomerLimitConfigRespVO.java | 42 ++ .../CrmCustomerLimitConfigSaveReqVO.java | 41 ++ .../CrmCustomerPoolConfigRespVO.java | 27 + .../CrmCustomerPoolConfigSaveReqVO.java | 65 ++ .../followup/CrmFollowUpRecordController.java | 92 +++ .../vo/CrmFollowUpRecordPageReqVO.java | 21 + .../followup/vo/CrmFollowUpRecordRespVO.java | 55 ++ .../vo/CrmFollowUpRecordSaveReqVO.java | 48 ++ .../operatelog/CrmOperateLogController.java | 64 ++ .../operatelog/vo/CrmOperateLogPageReqVO.java | 27 + .../operatelog/vo/CrmOperateLogV2RespVO.java | 44 ++ .../permission/CrmPermissionController.http | 32 + .../permission/CrmPermissionController.java | 116 ++++ .../permission/vo/CrmPermissionBaseVO.java | 38 ++ .../vo/CrmPermissionCreateReqVO.java | 14 + .../permission/vo/CrmPermissionRespVO.java | 28 + .../vo/CrmPermissionUpdateReqVO.java | 34 ++ .../product/CrmProductCategoryController.java | 73 +++ .../admin/product/CrmProductController.java | 126 ++++ .../CrmProductCategoryCreateReqVO.java | 25 + .../category/CrmProductCategoryListReqVO.java | 22 + .../vo/category/CrmProductCategoryRespVO.java | 24 + .../vo/product/CrmProductPageReqVO.java | 21 + .../product/vo/product/CrmProductRespVO.java | 74 +++ .../vo/product/CrmProductSaveReqVO.java | 54 ++ .../receivable/CrmReceivableController.java | 147 +++++ .../CrmReceivablePlanController.java | 156 +++++ .../vo/plan/CrmReceivablePlanBaseVO.java | 54 ++ .../vo/plan/CrmReceivablePlanCreateReqVO.java | 12 + .../vo/plan/CrmReceivablePlanPageReqVO.java | 44 ++ .../vo/plan/CrmReceivablePlanRespVO.java | 40 ++ .../vo/plan/CrmReceivablePlanUpdateReqVO.java | 18 + .../vo/receivable/CrmReceivableBaseVO.java | 61 ++ .../receivable/CrmReceivableCreateReqVO.java | 12 + .../vo/receivable/CrmReceivablePageReqVO.java | 35 ++ .../vo/receivable/CrmReceivableRespVO.java | 38 ++ .../receivable/CrmReceivableUpdateReqVO.java | 18 + .../crm/controller/app/package-info.java | 4 + .../module/crm/controller/package-info.java | 6 + .../convert/business/CrmBusinessConvert.java | 57 ++ .../business/CrmBusinessStatusConvert.java | 25 + .../CrmBusinessStatusTypeConvert.java | 44 ++ .../crm/convert/clue/CrmClueConvert.java | 22 + .../convert/contact/CrmContactConvert.java | 77 +++ .../convert/contract/CrmContractConvert.java | 70 +++ .../convert/customer/CrmCustomerConvert.java | 66 ++ .../CrmCustomerLimitConfigConvert.java | 42 ++ .../module/crm/convert/package-info.java | 6 + .../permission/CrmPermissionConvert.java | 56 ++ .../convert/product/CrmProductConvert.java | 46 ++ .../receivable/CrmReceivableConvert.java | 52 ++ .../receivable/CrmReceivablePlanConvert.java | 56 ++ ...道 Spring Boot 对象转换 MapStruct 入门》.md | 1 + .../dataobject/business/CrmBusinessDO.java | 108 ++++ .../business/CrmBusinessProductDO.java | 63 ++ .../business/CrmBusinessStatusDO.java | 46 ++ .../business/CrmBusinessStatusTypeDO.java | 51 ++ .../crm/dal/dataobject/clue/CrmClueDO.java | 116 ++++ .../crm/dal/dataobject/clue/package-info.java | 4 + .../contact/CrmContactBusinessDO.java | 43 ++ .../dal/dataobject/contact/CrmContactDO.java | 117 ++++ .../dal/dataobject/contact/package-info.java | 4 + .../dataobject/contract/CrmContractDO.java | 121 ++++ .../contract/CrmContractProductDO.java | 65 ++ .../dataobject/customer/CrmCustomerDO.java | 135 +++++ .../customer/CrmCustomerLimitConfigDO.java | 61 ++ .../customer/CrmCustomerPoolConfigDO.java | 52 ++ .../followup/CrmFollowUpRecordDO.java | 97 +++ .../permission/CrmPermissionDO.java | 59 ++ .../product/CrmProductCategoryDO.java | 49 ++ .../dal/dataobject/product/CrmProductDO.java | 72 +++ .../dal/dataobject/product/package-info.java | 4 + .../receivable/CrmReceivableDO.java | 96 +++ .../receivable/CrmReceivablePlanDO.java | 88 +++ .../dataobject/receivable/package-info.java | 4 + .../crm/dal/mysql/bi/CrmBiRankingMapper.java | 81 +++ .../dal/mysql/business/CrmBusinessMapper.java | 66 ++ .../business/CrmBusinessProductMapper.java | 33 + .../business/CrmBusinessStatusMapper.java | 37 ++ .../business/CrmBusinessStatusTypeMapper.java | 44 ++ .../crm/dal/mysql/business/package-info.java | 4 + .../crm/dal/mysql/clue/CrmClueMapper.java | 56 ++ .../crm/dal/mysql/clue/package-info.java | 4 + .../dal/mysql/contact/CrmContactMapper.java | 75 +++ .../crm/dal/mysql/contact/package-info.java | 4 + .../CrmContactBusinessMapper.java | 34 ++ .../dal/mysql/contract/CrmContractMapper.java | 90 +++ .../contract/CrmContractProductMapper.java | 32 + .../CrmCustomerLimitConfigMapper.java | 37 ++ .../dal/mysql/customer/CrmCustomerMapper.java | 150 +++++ .../customer/CrmCustomerPoolConfigMapper.java | 20 + .../followup/CrmFollowUpRecordMapper.java | 40 ++ .../mysql/permission/CrmPermissionMapper.java | 74 +++ .../dal/mysql/permission/package-info.java | 1 + .../product/CrmProductCategoryMapper.java | 34 ++ .../dal/mysql/product/CrmProductMapper.java | 37 ++ .../mysql/receivable/CrmReceivableMapper.java | 62 ++ .../receivable/CrmReceivablePlanMapper.java | 81 +++ .../core/CrmBusinessParseFunction.java | 44 ++ .../core/CrmContactParseFunction.java | 44 ++ .../core/CrmContractParseFunction.java | 44 ++ .../CrmCustomerIndustryParseFunction.java | 40 ++ .../core/CrmCustomerLevelParseFunction.java | 40 ++ .../core/CrmCustomerParseFunction.java | 44 ++ .../core/CrmCustomerSourceParseFunction.java | 40 ++ .../core/CrmProductStatusParseFunction.java | 39 ++ .../core/CrmProductUnitParseFunction.java | 39 ++ .../core/SysAdminUserParseFunction.java | 50 ++ .../operatelog/core/SysAreaParseFunction.java | 38 ++ .../core/SysBooleanParseFunction.java | 39 ++ .../operatelog/core/SysDeptParseFunction.java | 45 ++ .../operatelog/core/SysSexParseFunction.java | 39 ++ .../framework/operatelog/package-info.java | 1 + .../module/crm/framework/package-info.java | 6 + .../core/annotations/CrmPermission.java | 46 ++ .../core/aop/CrmPermissionAspect.java | 130 ++++ .../permission/core/package-info.java | 1 + .../core/util/CrmPermissionUtils.java | 40 ++ .../framework/permission/package-info.java | 1 + .../rpc/config/RpcConfiguration.java | 14 + .../crm/framework/rpc/package-info.java | 4 + .../config/SecurityConfiguration.java | 37 ++ .../framework/security/core/package-info.java | 4 + .../customer/CrmCustomerAutoPutPoolJob.java | 27 + .../yudao/module/crm/job/package-info.java | 4 + .../yudao/module/crm/package-info.java | 10 + .../crm/service/bi/CrmBiRankingService.java | 80 +++ .../service/bi/CrmBiRankingServiceImpl.java | 134 ++++ .../service/business/CrmBusinessService.java | 132 ++++ .../business/CrmBusinessServiceImpl.java | 298 +++++++++ .../business/CrmBusinessStatusService.java | 77 +++ .../CrmBusinessStatusServiceImpl.java | 86 +++ .../CrmBusinessStatusTypeService.java | 75 +++ .../CrmBusinessStatusTypeServiceImpl.java | 132 ++++ .../bo/CrmBusinessUpdateProductReqBO.java | 49 ++ .../crm/service/clue/CrmClueService.java | 93 +++ .../crm/service/clue/CrmClueServiceImpl.java | 279 +++++++++ .../contact/CrmContactBusinessService.java | 52 ++ .../CrmContactBusinessServiceImpl.java | 99 +++ .../service/contact/CrmContactService.java | 137 +++++ .../contact/CrmContactServiceImpl.java | 256 ++++++++ .../service/contract/CrmContractService.java | 146 +++++ .../contract/CrmContractServiceImpl.java | 363 +++++++++++ .../CrmCustomerLimitConfigService.java | 64 ++ .../CrmCustomerLimitConfigServiceImpl.java | 124 ++++ .../CrmCustomerPoolConfigService.java | 29 + .../CrmCustomerPoolConfigServiceImpl.java | 65 ++ .../service/customer/CrmCustomerService.java | 146 +++++ .../customer/CrmCustomerServiceImpl.java | 572 ++++++++++++++++++ .../customer/bo/CrmCustomerCreateReqBO.java | 125 ++++ .../followup/CrmFollowUpRecordService.java | 76 +++ .../CrmFollowUpRecordServiceImpl.java | 158 +++++ .../followup/bo/CrmFollowUpCreateReqBO.java | 78 +++ .../followup/bo/CrmUpdateFollowUpReqBO.java | 34 ++ .../service/message/CrmBacklogService.java | 23 + .../message/CrmBacklogServiceImpl.java | 28 + .../permission/CrmPermissionService.java | 122 ++++ .../permission/CrmPermissionServiceImpl.java | 222 +++++++ .../bo/CrmPermissionCreateReqBO.java | 43 ++ .../bo/CrmPermissionTransferReqBO.java | 49 ++ .../product/CrmProductCategoryService.java | 64 ++ .../CrmProductCategoryServiceImpl.java | 138 +++++ .../service/product/CrmProductService.java | 81 +++ .../product/CrmProductServiceImpl.java | 166 +++++ .../receivable/CrmReceivablePlanService.java | 80 +++ .../CrmReceivablePlanServiceImpl.java | 166 +++++ .../receivable/CrmReceivableService.java | 81 +++ .../receivable/CrmReceivableServiceImpl.java | 186 ++++++ .../module/crm/util/CrmQueryWrapperUtils.java | 113 ++++ .../src/main/resources/application-dev.yaml | 104 ++++ .../src/main/resources/application-local.yaml | 130 ++++ .../src/main/resources/application.yaml | 105 ++++ .../src/main/resources/bootstrap-local.yaml | 23 + .../src/main/resources/bootstrap.yaml | 14 + .../src/main/resources/logback-spring.xml | 76 +++ .../mapper/bi/CrmBiRankingMapper.xml | 126 ++++ .../BusinessStatusTypeServiceImplTest.java | 119 ++++ .../business/CrmBusinessServiceImplTest.java | 181 ++++++ .../service/clue/CrmClueServiceImplTest.java | 207 +++++++ .../contract/ContractServiceImplTest.java | 135 +++++ .../customer/CrmCustomerServiceImplTest.java | 141 +++++ ...CrmCustomerLimitConfigServiceImplTest.java | 119 ++++ .../CrmCrmReceivablePlanServiceImplTest.java | 137 +++++ .../CrmCrmReceivableServiceImplTest.java | 143 +++++ .../test/resources/application-unit-test.yaml | 51 ++ .../src/test/resources/logback.xml | 4 + .../src/test/resources/sql/clean.sql | 11 + .../src/test/resources/sql/create_tables.sql | 162 +++++ .../config/SecurityConfiguration.java | 6 +- .../yudao/module/system/api/dept/DeptApi.java | 7 +- .../yudao/module/system/api/dept/PostApi.java | 20 + .../system/api/dept/dto/PostRespDTO.java | 30 + .../module/system/api/user/AdminUserApi.java | 7 +- .../module/system/api/dept/DeptApiImpl.java | 7 + .../module/system/api/dept/PostApiImpl.java | 10 + .../system/api/user/AdminUserApiImpl.java | 42 +- 270 files changed, 17975 insertions(+), 9 deletions(-) create mode 100644 yudao-module-crm/pom.xml create mode 100644 yudao-module-crm/yudao-module-crm-api/pom.xml create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ApiConstants.java create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBizEndStatus.java create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmAuditStatusEnum.java create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneTypeEnum.java create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLevelEnum.java create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLimitConfigTypeEnum.java create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionLevelEnum.java create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java create mode 100644 yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/product/CrmProductStatusEnum.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/pom.xml create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/CrmServerApplication.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/api/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/CrmBacklogController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/vo/CrmTodayCustomerPageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.http create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRanKRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRankReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.http create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessExcelVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessTransferReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusPageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusQueryVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypePageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeQueryVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTransferReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTranslateReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactTransferReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractTransferReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerDistributeReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerPageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigPageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigSaveReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigSaveReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordPageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogPageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.http create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionBaseVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionCreateReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionUpdateReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductCategoryController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryCreateReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryListReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductPageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanBaseVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanCreateReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanUpdateReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableBaseVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableCreateReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableUpdateReqVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/app/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/clue/CrmClueConvert.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerLimitConfigConvert.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/product/CrmProductConvert.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerLimitConfigDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerPoolConfigDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/permission/CrmPermissionDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductCategoryDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/bi/CrmBiRankingMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerPoolConfigMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/followup/CrmFollowUpRecordMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductCategoryMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmBusinessParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContactParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContractParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerIndustryParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerLevelParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerSourceParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductStatusParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductUnitParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAdminUserParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAreaParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysBooleanParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysDeptParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysSexParseFunction.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/annotations/CrmPermission.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/rpc/config/RpcConfiguration.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/rpc/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/security/config/SecurityConfiguration.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/security/core/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/package-info.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateProductReqBO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/bo/CrmCustomerCreateReqBO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmFollowUpCreateReqBO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionCreateReqBO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionTransferReqBO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryWrapperUtils.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/resources/application-dev.yaml create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/resources/application-local.yaml create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/resources/application.yaml create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/resources/bootstrap-local.yaml create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/resources/bootstrap.yaml create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/resources/logback-spring.xml create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/BusinessStatusTypeServiceImplTest.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImplTest.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImplTest.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/contract/ContractServiceImplTest.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImplTest.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customerlimitconfig/CrmCustomerLimitConfigServiceImplTest.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivablePlanServiceImplTest.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivableServiceImplTest.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/test/resources/application-unit-test.yaml create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/test/resources/logback.xml create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/test/resources/sql/clean.sql create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/test/resources/sql/create_tables.sql create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/dto/PostRespDTO.java diff --git a/pom.xml b/pom.xml index 3f234c29d..6106623ac 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ yudao-module-report yudao-module-mp yudao-module-mall + yudao-module-crm yudao-module-erp diff --git a/yudao-gateway/src/main/resources/application.yaml b/yudao-gateway/src/main/resources/application.yaml index 87d9a4a9b..d20d17176 100644 --- a/yudao-gateway/src/main/resources/application.yaml +++ b/yudao-gateway/src/main/resources/application.yaml @@ -145,6 +145,13 @@ spring: - Path=/admin-api/erp/** filters: - RewritePath=/admin-api/erp/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs + ## crm-server 服务 + - id: crm-admin-api # 路由的编号 + uri: grayLb://crm-server + predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组 + - Path=/admin-api/crm/** + filters: + - RewritePath=/admin-api/crm/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs x-forwarded: prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀 @@ -186,6 +193,9 @@ knife4j: - name: erp-server service-name: erp-server url: /admin-api/erp/v3/api-docs + - name: crm-server + service-name: crm-server + url: /admin-api/crm/v3/api-docs --- #################### 芋道相关配置 #################### diff --git a/yudao-module-crm/pom.xml b/yudao-module-crm/pom.xml new file mode 100644 index 000000000..56921c6fb --- /dev/null +++ b/yudao-module-crm/pom.xml @@ -0,0 +1,25 @@ + + + + cn.iocoder.cloud + yudao + ${revision} + + + yudao-module-crm-api + yudao-module-crm-biz + + 4.0.0 + yudao-module-crm + pom + + ${project.artifactId} + + crm 包下,客户关系管理(Customer Relationship Management)。 + 例如说:客户、联系人、商机、合同、回款等等 + 商业智能 BI 模块,包括:报表、图表、数据大屏等等 + + + diff --git a/yudao-module-crm/yudao-module-crm-api/pom.xml b/yudao-module-crm/yudao-module-crm-api/pom.xml new file mode 100644 index 000000000..fc97b6759 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/pom.xml @@ -0,0 +1,33 @@ + + + + cn.iocoder.cloud + yudao-module-crm + ${revision} + + 4.0.0 + yudao-module-crm-api + jar + + ${project.artifactId} + + crm 模块 API,暴露给其它模块调用 + + + + + cn.iocoder.cloud + yudao-common + + + + + org.springframework.boot + spring-boot-starter-validation + true + + + + diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/package-info.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/package-info.java new file mode 100644 index 000000000..c38bde7f5 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/package-info.java @@ -0,0 +1,4 @@ +/** + * crm API 包,定义暴露给其它模块的 API + */ +package cn.iocoder.yudao.module.crm.api; diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ApiConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ApiConstants.java new file mode 100644 index 000000000..544ebf9f4 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ApiConstants.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.crm.enums; + +import cn.iocoder.yudao.framework.common.enums.RpcConstants; + +/** + * API 相关的枚举 + * + * @author 芋道源码 + */ +public class ApiConstants { + + /** + * 服务名 + * + * 注意,需要保证和 spring.application.name 保持一致 + */ + public static final String NAME = "crm-server"; + + public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/crm"; + + public static final String VERSION = "1.0.0"; + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java new file mode 100644 index 000000000..22bb0b426 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.crm.enums; + +/** + * CRM 字典类型的枚举类 + * + * @author 芋道源码 + */ +public interface DictTypeConstants { + + String CRM_CUSTOMER_INDUSTRY = "crm_customer_industry"; // CRM 客户所属行业 + String CRM_CUSTOMER_LEVEL = "crm_customer_level"; // CRM 客户等级 + String CRM_CUSTOMER_SOURCE = "crm_customer_source"; // CRM 客户来源 + String CRM_AUDIT_STATUS = "crm_audit_status"; // CRM 审批状态 + String CRM_PRODUCT_UNIT = "crm_product_unit"; // CRM 产品单位 + String CRM_PRODUCT_STATUS = "crm_product_status"; // CRM 产品状态 + String CRM_FOLLOW_UP_TYPE = "crm_follow_up_type"; // CRM 跟进方式 + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java new file mode 100644 index 000000000..d536d8a40 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java @@ -0,0 +1,97 @@ +package cn.iocoder.yudao.module.crm.enums; + +import cn.iocoder.yudao.framework.common.exception.ErrorCode; + +/** + * CRM 错误码枚举类 + *

+ * crm 系统,使用 1-020-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== 合同管理 1-020-000-000 ========== + ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在"); + ErrorCode CONTRACT_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_020_000_001, "更新合同失败,原因:禁止编辑"); + ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态"); + + // ========== 线索管理 1-020-001-000 ========== + ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在"); + ErrorCode CLUE_ANY_CLUE_NOT_EXISTS = new ErrorCode(1_020_001_001, "线索【{}】不存在"); + ErrorCode CLUE_ANY_CLUE_ALREADY_TRANSLATED = new ErrorCode(1_020_001_002, "线索【{}】已经转化过了,请勿重复转化"); + + // ========== 商机管理 1-020-002-000 ========== + ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_002_000, "商机不存在"); + ErrorCode BUSINESS_CONTRACT_EXISTS = new ErrorCode(1_020_002_001, "商机已关联合同,不能删除"); + + // TODO @lilleo:商机状态、商机类型,都单独错误码段 + + + // ========== 联系人管理 1-020-003-000 ========== + ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在"); + ErrorCode CONTACT_BUSINESS_LINK_NOT_EXISTS = new ErrorCode(1_020_003_001, "联系人商机关联不存在"); + ErrorCode CONTACT_DELETE_FAIL_CONTRACT_LINK_EXISTS = new ErrorCode(1_020_003_002, "联系人已关联合同,不能删除"); + + // ========== 回款 1-020-004-000 ========== + ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在"); + + // ========== 合同管理 1-020-005-000 ========== + ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在"); + + // ========== 客户管理 1_020_006_000 ========== + ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_020_006_000, "客户不存在"); + ErrorCode CUSTOMER_OWNER_EXISTS = new ErrorCode(1_020_006_001, "客户【{}】已存在所属负责人"); + ErrorCode CUSTOMER_LOCKED = new ErrorCode(1_020_006_002, "客户【{}】状态已锁定"); + ErrorCode CUSTOMER_ALREADY_DEAL = new ErrorCode(1_020_006_003, "客户已交易"); + ErrorCode CUSTOMER_IN_POOL = new ErrorCode(1_020_006_004, "客户【{}】放入公海失败,原因:已经是公海客户"); + ErrorCode CUSTOMER_LOCKED_PUT_POOL_FAIL = new ErrorCode(1_020_006_005, "客户【{}】放入公海失败,原因:客户已锁定"); + ErrorCode CUSTOMER_UPDATE_OWNER_USER_FAIL = new ErrorCode(1_020_006_006, "更新客户【{}】负责人失败, 原因:系统异常"); + ErrorCode CUSTOMER_LOCK_FAIL_IS_LOCK = new ErrorCode(1_020_006_007, "锁定客户失败,它已经处于锁定状态"); + ErrorCode CUSTOMER_UNLOCK_FAIL_IS_UNLOCK = new ErrorCode(1_020_006_008, "解锁客户失败,它已经处于未锁定状态"); + ErrorCode CUSTOMER_LOCK_EXCEED_LIMIT = new ErrorCode(1_020_006_009, "锁定客户失败,超出锁定规则上限"); + ErrorCode CUSTOMER_OWNER_EXCEED_LIMIT = new ErrorCode(1_020_006_010, "操作失败,超出客户数拥有上限"); + ErrorCode CUSTOMER_DELETE_FAIL_HAVE_REFERENCE = new ErrorCode(1_020_006_011, "删除客户失败,有关联{}"); + ErrorCode CUSTOMER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_020_006_012, "导入客户数据不能为空!"); + ErrorCode CUSTOMER_CREATE_NAME_NOT_NULL = new ErrorCode(1_020_006_013, "客户名称不能为空!"); + ErrorCode CUSTOMER_NAME_EXISTS = new ErrorCode(1_020_006_014, "已存在名为【{}】的客户!"); + + // ========== 权限管理 1_020_007_000 ========== + ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在"); + ErrorCode CRM_PERMISSION_DENIED = new ErrorCode(1_020_007_001, "{}操作失败,原因:没有权限"); + ErrorCode CRM_PERMISSION_MODEL_NOT_EXISTS = new ErrorCode(1_020_007_002, "{}不存在"); + ErrorCode CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS = new ErrorCode(1_020_007_003, "{}操作失败,原因:转移对象已经是该负责人"); + ErrorCode CRM_PERMISSION_DELETE_FAIL = new ErrorCode(1_020_007_004, "删除数据权限失败,原因:批量删除权限的时候,只能属于同一个 bizId 下"); + ErrorCode CRM_PERMISSION_DELETE_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_005, "删除数据权限失败,原因:存在负责人"); + ErrorCode CRM_PERMISSION_DELETE_DENIED = new ErrorCode(1_020_007_006, "删除数据权限失败,原因:没有权限"); + ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人"); + ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限"); + + // ========== 产品 1_020_008_000 ========== + ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在"); + ErrorCode PRODUCT_NO_EXISTS = new ErrorCode(1_020_008_001, "产品编号已存在"); + + // ========== 产品分类 1_020_009_000 ========== + ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_020_009_000, "产品分类不存在"); + ErrorCode PRODUCT_CATEGORY_EXISTS = new ErrorCode(1_020_009_001, "产品分类已存在"); + ErrorCode PRODUCT_CATEGORY_USED = new ErrorCode(1_020_009_002, "产品分类已关联产品"); + ErrorCode PRODUCT_CATEGORY_PARENT_NOT_EXISTS = new ErrorCode(1_020_009_003, "父分类不存在"); + ErrorCode PRODUCT_CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1_020_009_004, "父分类不能是二级分类"); + ErrorCode product_CATEGORY_EXISTS_CHILDREN = new ErrorCode(1_020_009_005, "存在子分类,无法删除"); + + // ========== 商机状态类型 1_020_010_000 ========== + ErrorCode BUSINESS_STATUS_TYPE_NOT_EXISTS = new ErrorCode(1_020_010_000, "商机状态类型不存在"); + ErrorCode BUSINESS_STATUS_TYPE_NAME_EXISTS = new ErrorCode(1_020_010_001, "商机状态类型名称已存在"); + + // ========== 商机状态 1_020_011_000 ========== + ErrorCode BUSINESS_STATUS_NOT_EXISTS = new ErrorCode(1_020_011_000, "商机状态不存在"); + + // ========== 客户公海规则设置 1_020_012_000 ========== + ErrorCode CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_020_012_000, "客户公海配置不存在或未启用"); + ErrorCode CUSTOMER_LIMIT_CONFIG_NOT_EXISTS = new ErrorCode(1_020_012_001, "客户限制配置不存在"); + + // ========== 跟进记录 1_020_013_000 ========== + ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在"); + ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限"); + + // ========== 待办消息 1_020_014_000 ========== + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java new file mode 100644 index 000000000..98a66d2c9 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java @@ -0,0 +1,139 @@ +package cn.iocoder.yudao.module.crm.enums; + +/** + * CRM 操作日志枚举 + * 目的:统一管理,也减少 Service 里各种“复杂”字符串 + * + * @author HUIHUI + */ +public interface LogRecordConstants { + + // ======================= CRM_LEADS 线索 ======================= + + String CRM_LEADS_TYPE = "CRM 线索"; + String CRM_LEADS_CREATE_SUB_TYPE = "创建线索"; + String CRM_LEADS_CREATE_SUCCESS = "创建了线索{{#clue.name}}"; + String CRM_LEADS_UPDATE_SUB_TYPE = "更新线索"; + String CRM_LEADS_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}"; + String CRM_LEADS_DELETE_SUB_TYPE = "删除线索"; + String CRM_LEADS_DELETE_SUCCESS = "删除了线索【{{#clueName}}】"; + String CRM_LEADS_TRANSFER_SUB_TYPE = "转移线索"; + String CRM_LEADS_TRANSFER_SUCCESS = "将线索【{{#clue.name}}】的负责人从【{getAdminUserById{#clue.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; + String CRM_LEADS_TRANSLATE_SUB_TYPE = "线索转化为客户"; + String CRM_LEADS_TRANSLATE_SUCCESS = "将线索【{{#clue.name}}】转化为客户"; + + // ======================= CRM_CUSTOMER 客户 ======================= + + String CRM_CUSTOMER_TYPE = "CRM 客户"; + String CRM_CUSTOMER_CREATE_SUB_TYPE = "创建客户"; + String CRM_CUSTOMER_CREATE_SUCCESS = "创建了客户{{#customer.name}}"; + String CRM_CUSTOMER_UPDATE_SUB_TYPE = "更新客户"; + String CRM_CUSTOMER_UPDATE_SUCCESS = "更新了客户【{{#customerName}}】: {_DIFF{#updateReqVO}}"; + String CRM_CUSTOMER_DELETE_SUB_TYPE = "删除客户"; + String CRM_CUSTOMER_DELETE_SUCCESS = "删除了客户【{{#customerName}}】"; + String CRM_CUSTOMER_TRANSFER_SUB_TYPE = "转移客户"; + String CRM_CUSTOMER_TRANSFER_SUCCESS = "将客户【{{#customer.name}}】的负责人从【{getAdminUserById{#customer.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; + String CRM_CUSTOMER_LOCK_SUB_TYPE = "{{#customer.lockStatus ? '解锁客户' : '锁定客户'}}"; + String CRM_CUSTOMER_LOCK_SUCCESS = "{{#customer.lockStatus ? '将客户【' + #customer.name + '】解锁' : '将客户【' + #customer.name + '】锁定'}}"; + String CRM_CUSTOMER_POOL_SUB_TYPE = "客户放入公海"; + String CRM_CUSTOMER_POOL_SUCCESS = "将客户【{{#customerName}}】放入了公海"; + String CRM_CUSTOMER_RECEIVE_SUB_TYPE = "{{#ownerUserName != null ? '分配客户' : '领取客户'}}"; + String CRM_CUSTOMER_RECEIVE_SUCCESS = "{{#ownerUserName != null ? '将客户【' + #customer.name + '】分配给【' + #ownerUserName + '】' : '领取客户【' + #customer.name + '】'}}"; + String CRM_CUSTOMER_IMPORT_SUB_TYPE = "{{#isUpdate ? '导入并更新客户' : '导入客户'}}"; + String CRM_CUSTOMER_IMPORT_SUCCESS = "{{#isUpdate ? '导入并更新了客户【'+ #customer.name +'】' : '导入了客户【'+ #customer.name +'】'}}"; + + // ======================= CRM_CUSTOMER_LIMIT_CONFIG 客户限制配置 ======================= + + String CRM_CUSTOMER_LIMIT_CONFIG_TYPE = "CRM 客户限制配置"; + String CRM_CUSTOMER_LIMIT_CONFIG_CREATE_SUB_TYPE = "创建客户限制配置"; + String CRM_CUSTOMER_LIMIT_CONFIG_CREATE_SUCCESS = "创建了【{{#limitType}}】类型的客户限制配置"; + String CRM_CUSTOMER_LIMIT_CONFIG_UPDATE_SUB_TYPE = "更新客户限制配置"; + String CRM_CUSTOMER_LIMIT_CONFIG_UPDATE_SUCCESS = "更新了客户限制配置: {_DIFF{#updateReqVO}}"; + String CRM_CUSTOMER_LIMIT_CONFIG_DELETE_SUB_TYPE = "删除客户限制配置"; + String CRM_CUSTOMER_LIMIT_CONFIG_DELETE_SUCCESS = "删除了【{{#limitType}}】类型的客户限制配置"; + + // ======================= CRM_CUSTOMER_POOL_CONFIG 客户公海规则 ======================= + + String CRM_CUSTOMER_POOL_CONFIG_TYPE = "CRM 客户公海规则"; + String CRM_CUSTOMER_POOL_CONFIG_SUB_TYPE = "{{#isPoolConfigUpdate ? '更新客户公海规则' : '创建客户公海规则'}}"; + String CRM_CUSTOMER_POOL_CONFIG_SUCCESS = "{{#isPoolConfigUpdate ? '更新了客户公海规则' : '创建了客户公海规则'}}"; + + // ======================= CRM_CONTACT 联系人 ======================= + + String CRM_CONTACT_TYPE = "CRM 联系人"; + String CRM_CONTACT_CREATE_SUB_TYPE = "创建联系人"; + String CRM_CONTACT_CREATE_SUCCESS = "创建了联系人{{#contact.name}}"; + String CRM_CONTACT_UPDATE_SUB_TYPE = "更新联系人"; + String CRM_CONTACT_UPDATE_SUCCESS = "更新了联系人【{{#contactName}}】: {_DIFF{#updateReqVO}}"; + String CRM_CONTACT_DELETE_SUB_TYPE = "删除联系人"; + String CRM_CONTACT_DELETE_SUCCESS = "删除了联系人【{{#contactName}}】"; + String CRM_CONTACT_TRANSFER_SUB_TYPE = "转移联系人"; + String CRM_CONTACT_TRANSFER_SUCCESS = "将联系人【{{#contact.name}}】的负责人从【{getAdminUserById{#contact.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; + + // ======================= CRM_BUSINESS 商机 ======================= + + String CRM_BUSINESS_TYPE = "CRM 商机"; + String CRM_BUSINESS_CREATE_SUB_TYPE = "创建商机"; + String CRM_BUSINESS_CREATE_SUCCESS = "创建了商机{{#business.name}}"; + String CRM_BUSINESS_UPDATE_SUB_TYPE = "更新商机"; + String CRM_BUSINESS_UPDATE_SUCCESS = "更新了商机【{{#businessName}}】: {_DIFF{#updateReqVO}}"; + String CRM_BUSINESS_DELETE_SUB_TYPE = "删除商机"; + String CRM_BUSINESS_DELETE_SUCCESS = "删除了商机【{{#businessName}}】"; + String CRM_BUSINESS_TRANSFER_SUB_TYPE = "转移商机"; + String CRM_BUSINESS_TRANSFER_SUCCESS = "将商机【{{#business.name}}】的负责人从【{getAdminUserById{#business.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; + + // ======================= CRM_CONTRACT 合同 ======================= + + String CRM_CONTRACT_TYPE = "CRM 合同"; + String CRM_CONTRACT_CREATE_SUB_TYPE = "创建合同"; + String CRM_CONTRACT_CREATE_SUCCESS = "创建了合同{{#contract.name}}"; + String CRM_CONTRACT_UPDATE_SUB_TYPE = "更新合同"; + String CRM_CONTRACT_UPDATE_SUCCESS = "更新了合同【{{#contractName}}】: {_DIFF{#updateReqVO}}"; + String CRM_CONTRACT_DELETE_SUB_TYPE = "删除合同"; + String CRM_CONTRACT_DELETE_SUCCESS = "删除了合同【{{#contractName}}】"; + String CRM_CONTRACT_TRANSFER_SUB_TYPE = "转移合同"; + String CRM_CONTRACT_TRANSFER_SUCCESS = "将合同【{{#contract.name}}】的负责人从【{getAdminUserById{#contract.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; + String CRM_CONTRACT_SUBMIT_SUB_TYPE = "提交合同审批"; + String CRM_CONTRACT_SUBMIT_SUCCESS = "提交合同【{{#contractName}}】审批成功"; + + // ======================= CRM_PRODUCT 产品 ======================= + + String CRM_PRODUCT_TYPE = "CRM 产品"; + String CRM_PRODUCT_CREATE_SUB_TYPE = "创建产品"; + String CRM_PRODUCT_CREATE_SUCCESS = "创建了产品【{{#createReqVO.name}}】"; + String CRM_PRODUCT_UPDATE_SUB_TYPE = "更新产品"; + String CRM_PRODUCT_UPDATE_SUCCESS = "更新了产品【{{#updateReqVO.name}}】: {_DIFF{#updateReqVO}}"; + String CRM_PRODUCT_DELETE_SUB_TYPE = "删除产品"; + String CRM_PRODUCT_DELETE_SUCCESS = "删除了产品【{{#product.name}}】"; + + // ======================= CRM_PRODUCT_CATEGORY 产品分类 ======================= + + String CRM_PRODUCT_CATEGORY_TYPE = "CRM 产品分类"; + String CRM_PRODUCT_CATEGORY_CREATE_SUB_TYPE = "创建产品分类"; + String CRM_PRODUCT_CATEGORY_CREATE_SUCCESS = "创建了产品分类【{{#createReqVO.name}}】"; + String CRM_PRODUCT_CATEGORY_UPDATE_SUB_TYPE = "更新产品分类"; + String CRM_PRODUCT_CATEGORY_UPDATE_SUCCESS = "更新了产品分类【{{#updateReqVO.name}}】: {_DIFF{#updateReqVO}}"; + String CRM_PRODUCT_CATEGORY_DELETE_SUB_TYPE = "删除产品分类"; + String CRM_PRODUCT_CATEGORY_DELETE_SUCCESS = "删除了产品分类【{{#productCategory.name}}】"; + + // ======================= CRM_RECEIVABLE 回款 ======================= + + String CRM_RECEIVABLE_TYPE = "CRM 回款"; + String CRM_RECEIVABLE_CREATE_SUB_TYPE = "创建回款"; + String CRM_RECEIVABLE_CREATE_SUCCESS = "创建了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款"; + String CRM_RECEIVABLE_UPDATE_SUB_TYPE = "更新回款"; + String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}"; + String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款"; + String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款"; + + // ======================= CRM_RECEIVABLE_PLAN 回款计划 ======================= + + String CRM_RECEIVABLE_PLAN_TYPE = "CRM 回款计划"; + String CRM_RECEIVABLE_PLAN_CREATE_SUB_TYPE = "创建回款计划"; + String CRM_RECEIVABLE_PLAN_CREATE_SUCCESS = "创建了合同【{getContractById{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划"; + String CRM_RECEIVABLE_PLAN_UPDATE_SUB_TYPE = "更新回款计划"; + String CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划: {_DIFF{#updateReqVO}}"; + String CRM_RECEIVABLE_PLAN_DELETE_SUB_TYPE = "删除回款计划"; + String CRM_RECEIVABLE_PLAN_DELETE_SUCCESS = "删除了合同【{getContractById{#receivablePlan.contractId}}】的第【{{#receivablePlan.period}}】期回款计划"; + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBizEndStatus.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBizEndStatus.java new file mode 100644 index 000000000..55548dbff --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBizEndStatus.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.crm.enums.business; + +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +// TODO @lzxhqs:1)title、description、create 可以删除,非标准的 javadoc 注释哈,然后可以在类上加下这个类的注释;2)CrmBizEndStatus 改成 CrmBusinessEndStatus,非必要不缩写哈,可阅读比较重要 +/** + * @author lzxhqs + * @version 1.0 + * @title CrmBizEndStatus + * @description + * @create 2024/1/12 + */ +@RequiredArgsConstructor +@Getter +public enum CrmBizEndStatus implements IntArrayValuable { + + WIN(1, "赢单"), + LOSE(2, "输单"), + INVALID(3, "无效"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmBizEndStatus::getStatus).toArray(); + + // TODO @lzxhqs:这里的方法,建议放到 49 行之后;一般类里是,静态变量,普通变量;静态方法;普通方法 + public static boolean isWin(Integer status) { + return ObjectUtil.equal(WIN.getStatus(), status); + } + + public static boolean isLose(Integer status) { + return ObjectUtil.equal(LOSE.getStatus(), status); + } + + public static boolean isInvalid(Integer status) { + return ObjectUtil.equal(INVALID.getStatus(), status); + } + + /** + * 场景类型 + */ + private final Integer status; + /** + * 场景名称 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmAuditStatusEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmAuditStatusEnum.java new file mode 100644 index 000000000..67709e95b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmAuditStatusEnum.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.crm.enums.common; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * CRM 的审批状态 + * + * @author 赤焰 + */ +@RequiredArgsConstructor +@Getter +public enum CrmAuditStatusEnum implements IntArrayValuable { + + DRAFT(0, "未提交"), + PROCESS(10, "审批中"), + APPROVE(20, "审核通过"), + REJECT(30, "审核不通过"), + CANCEL(40, "已取消"); + + private final Integer status; + private final String name; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmAuditStatusEnum::getStatus).toArray(); + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java new file mode 100644 index 000000000..f0784cab2 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.crm.enums.common; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * CRM 业务类型枚举 + * + * @author HUIHUI + */ +@RequiredArgsConstructor +@Getter +public enum CrmBizTypeEnum implements IntArrayValuable { + + CRM_LEADS(1, "线索"), + CRM_CUSTOMER(2, "客户"), + CRM_CONTACT(3, "联系人"), + CRM_BUSINESS(4, "商机"), + CRM_CONTRACT(5, "合同"), + CRM_PRODUCT(6, "产品"), + CRM_RECEIVABLE(7, "回款"), + CRM_RECEIVABLE_PLAN(8, "回款计划") + ; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmBizTypeEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + /** + * 名称 + */ + private final String name; + + public static String getNameByType(Integer type) { + CrmBizTypeEnum typeEnum = CollUtil.findOne(CollUtil.newArrayList(CrmBizTypeEnum.values()), + item -> ObjUtil.equal(item.type, type)); + return typeEnum == null ? null : typeEnum.getName(); + } + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneTypeEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneTypeEnum.java new file mode 100644 index 000000000..945d7c6a3 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneTypeEnum.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.crm.enums.common; + +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * CRM 列表检索场景 + * + * @author HUIHUI + */ +@Getter +@AllArgsConstructor +public enum CrmSceneTypeEnum implements IntArrayValuable { + + OWNER(1, "我负责的"), + INVOLVED(2, "我参与的"), + SUBORDINATE(3, "下属负责的"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmSceneTypeEnum::getType).toArray(); + + /** + * 场景类型 + */ + private final Integer type; + /** + * 场景名称 + */ + private final String name; + + public static boolean isOwner(Integer type) { + return ObjUtil.equal(OWNER.getType(), type); + } + + public static boolean isInvolved(Integer type) { + return ObjUtil.equal(INVOLVED.getType(), type); + } + + public static boolean isSubordinate(Integer type) { + return ObjUtil.equal(SUBORDINATE.getType(), type); + } + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLevelEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLevelEnum.java new file mode 100644 index 000000000..aa06b05eb --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLevelEnum.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.crm.enums.customer; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * CRM 客户等级 + * + * @author Wanwan + */ +@Getter +@AllArgsConstructor +public enum CrmCustomerLevelEnum implements IntArrayValuable { + + IMPORTANT(1, "A(重点客户)"), + GENERAL(2, "B(普通客户)"), + LOW_PRIORITY(3, "C(非优先客户)"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerLevelEnum::getLevel).toArray(); + + /** + * 状态 + */ + private final Integer level; + /** + * 状态名 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLimitConfigTypeEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLimitConfigTypeEnum.java new file mode 100644 index 000000000..2cf8d7811 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/customer/CrmCustomerLimitConfigTypeEnum.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.crm.enums.customer; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * CRM 客户限制配置规则类型 + * + * @author Wanwan + */ +@Getter +@AllArgsConstructor +public enum CrmCustomerLimitConfigTypeEnum implements IntArrayValuable { + + /** + * 拥有客户数限制 + */ + CUSTOMER_OWNER_LIMIT(1, "拥有客户数限制"), + /** + * 锁定客户数限制 + */ + CUSTOMER_LOCK_LIMIT(2, "锁定客户数限制"), + ; + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmCustomerLimitConfigTypeEnum::getType).toArray(); + + /** + * 状态 + */ + private final Integer type; + /** + * 状态名 + */ + private final String name; + + public static String getNameByType(Integer type) { + CrmCustomerLimitConfigTypeEnum typeEnum = CollUtil.findOne(CollUtil.newArrayList(CrmCustomerLimitConfigTypeEnum.values()), + item -> ObjUtil.equal(item.type, type)); + return typeEnum == null ? null : typeEnum.getName(); + } + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionLevelEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionLevelEnum.java new file mode 100644 index 000000000..56b0366aa --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionLevelEnum.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.crm.enums.permission; + +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * CRM 数据权限级别枚举 + * + * @author HUIHUI + */ +@Getter +@AllArgsConstructor +public enum CrmPermissionLevelEnum implements IntArrayValuable { + + OWNER(1, "负责人"), + READ(2, "读"), + WRITE(3, "写"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmPermissionLevelEnum::getLevel).toArray(); + + /** + * 级别 + */ + private final Integer level; + /** + * 级别名称 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + + public static boolean isOwner(Integer level) { + return ObjUtil.equal(OWNER.level, level); + } + + public static boolean isRead(Integer level) { + return ObjUtil.equal(READ.level, level); + } + + public static boolean isWrite(Integer level) { + return ObjUtil.equal(WRITE.level, level); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java new file mode 100644 index 000000000..c9a51057b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.crm.enums.permission; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Crm 数据权限角色枚举 + * + * @author HUIHUI + */ +@Getter +@AllArgsConstructor +public enum CrmPermissionRoleCodeEnum { + + CRM_ADMIN("crm_admin", "CRM 管理员"); + + /** + * 角色标识 + */ + private String code; + /** + * 角色名称 + */ + private String name; + +} + diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/product/CrmProductStatusEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/product/CrmProductStatusEnum.java new file mode 100644 index 000000000..e82d5b5b8 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/product/CrmProductStatusEnum.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.crm.enums.product; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * CRM 商品状态 + * + * @author ZanGe丶 + * @since 2023-11-30 21:53 + */ +@Getter +@AllArgsConstructor +public enum CrmProductStatusEnum implements IntArrayValuable { + + DISABLE(0, "下架"), + ENABLE(1, "上架"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmProductStatusEnum::getStatus).toArray(); + + /** + * 状态 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/pom.xml b/yudao-module-crm/yudao-module-crm-biz/pom.xml new file mode 100644 index 000000000..323e873d9 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/pom.xml @@ -0,0 +1,141 @@ + + + + cn.iocoder.cloud + yudao-module-crm + ${revision} + + 4.0.0 + yudao-module-crm-biz + + ${project.artifactId} + + crm 包下,客户关系管理(Customer Relationship Management)。 + 例如说:客户、联系人、商机、合同、回款等等 + + + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + + cn.iocoder.cloud + yudao-spring-boot-starter-env + + + + + cn.iocoder.cloud + yudao-module-system-api + ${revision} + + + cn.iocoder.cloud + yudao-module-crm-api + ${revision} + + + cn.iocoder.cloud + yudao-module-bpm-api + ${revision} + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-operatelog + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-ip + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-tenant + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-security + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-mybatis + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-rpc + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-job + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-excel + + + cn.iocoder.cloud + yudao-spring-boot-starter-biz-dict + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-monitor + + + + + cn.iocoder.cloud + yudao-spring-boot-starter-test + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + + diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/CrmServerApplication.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/CrmServerApplication.java new file mode 100644 index 000000000..36bc7e8e3 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/CrmServerApplication.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.crm; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 项目的启动类 + *

+ * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * + * @author 芋道源码 + */ +@SpringBootApplication +public class CrmServerApplication { + + public static void main(String[] args) { + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + + SpringApplication.run(CrmServerApplication.class, args); + + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/api/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/api/package-info.java new file mode 100644 index 000000000..5c4e2493e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/api/package-info.java @@ -0,0 +1,4 @@ +/** + * crm API 实现类,定义暴露给其它模块的 API + */ +package cn.iocoder.yudao.module.crm.api; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/CrmBacklogController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/CrmBacklogController.java new file mode 100644 index 000000000..9b8841e2e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/CrmBacklogController.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.crm.controller.admin.backlog; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +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.customer.vo.CrmCustomerRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.backlog.vo.CrmTodayCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.service.message.CrmBacklogService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - CRM待办消息") +@RestController +@RequestMapping("/crm/backlog") +@Validated +public class CrmBacklogController { + + @Resource + private CrmBacklogService crmMessageService; + + // TODO 芋艿:未来可能合并到 CrmCustomerController + @GetMapping("/today-customer-page") + @Operation(summary = "今日需联系客户") + @PreAuthorize("@ss.hasPermission('crm:customer:query')") + public CommonResult> getTodayCustomerPage(@Valid CrmTodayCustomerPageReqVO pageReqVO) { + PageResult pageResult = crmMessageService.getTodayCustomerPage(pageReqVO, getLoginUserId()); + return success(BeanUtils.toBean(pageResult, CrmCustomerRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/vo/CrmTodayCustomerPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/vo/CrmTodayCustomerPageReqVO.java new file mode 100644 index 000000000..21fd88c0b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/vo/CrmTodayCustomerPageReqVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.crm.controller.admin.backlog.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 今日需联系客户 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmTodayCustomerPageReqVO extends PageParam { + + /** + * 联系状态 - 今日需联系 + */ + public static final int CONTACT_TODAY = 1; + /** + * 联系状态 - 已逾期 + */ + public static final int CONTACT_EXPIRED = 2; + /** + * 联系状态 - 已联系 + */ + public static final int CONTACT_ALREADY = 3; + + @Schema(description = "联系状态", example = "1") + private Integer contactStatus; + + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.http b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.http new file mode 100644 index 000000000..b9e9a4edf --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.http @@ -0,0 +1,9 @@ +### 合同金额排行榜 +GET {{baseUrl}}/crm/bi-rank/get-contract-price-rank?deptId=100×[0]=2022-12-12 00:00:00×[1]=2024-12-12 23:59:59 +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} + +### 回款金额排行榜 +GET {{baseUrl}}/crm/bi-rank/get-receivable-price-rank?deptId=100×[0]=2022-12-12 00:00:00×[1]=2024-12-12 23:59:59 +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.java new file mode 100644 index 000000000..21463aed0 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.java @@ -0,0 +1,87 @@ +package cn.iocoder.yudao.module.crm.controller.admin.bi; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO; +import cn.iocoder.yudao.module.crm.service.bi.CrmBiRankingService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + + +@Tag(name = "管理后台 - CRM BI 排行榜") +@RestController +@RequestMapping("/crm/bi-rank") +@Validated +public class CrmBiRankController { + + @Resource + private CrmBiRankingService rankingService; + + @GetMapping("/get-contract-price-rank") + @Operation(summary = "获得合同金额排行榜") + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") + public CommonResult> getContractPriceRank(@Valid CrmBiRankReqVO rankingReqVO) { + return success(rankingService.getContractPriceRank(rankingReqVO)); + } + + @GetMapping("/get-receivable-price-rank") + @Operation(summary = "获得回款金额排行榜") + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") + public CommonResult> getReceivablePriceRank(@Valid CrmBiRankReqVO rankingReqVO) { + return success(rankingService.getReceivablePriceRank(rankingReqVO)); + } + + @GetMapping("/get-contract-count-rank") + @Operation(summary = "获得签约合同数量排行榜") + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") + public CommonResult> getContractCountRank(@Valid CrmBiRankReqVO rankingReqVO) { + return success(rankingService.getContractCountRank(rankingReqVO)); + } + + @GetMapping("/get-product-sales-rank") + @Operation(summary = "获得产品销量排行榜") + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") + public CommonResult> getProductSalesRank(@Valid CrmBiRankReqVO rankingReqVO) { + return success(rankingService.getProductSalesRank(rankingReqVO)); + } + + @GetMapping("/get-customer-count-rank") + @Operation(summary = "获得新增客户数排行榜") + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") + public CommonResult> getCustomerCountRank(@Valid CrmBiRankReqVO rankingReqVO) { + return success(rankingService.getCustomerCountRank(rankingReqVO)); + } + + @GetMapping("/get-contacts-count-rank") + @Operation(summary = "获得新增联系人数排行榜") + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") + public CommonResult> getContactsCountRank(@Valid CrmBiRankReqVO rankingReqVO) { + return success(rankingService.getContactsCountRank(rankingReqVO)); + } + + @GetMapping("/get-follow-count-rank") + @Operation(summary = "获得跟进次数排行榜") + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") + public CommonResult> getFollowCountRank(@Valid CrmBiRankReqVO rankingReqVO) { + return success(rankingService.getFollowCountRank(rankingReqVO)); + } + + @GetMapping("/get-follow-customer-count-rank") + @Operation(summary = "获得跟进客户数排行榜") + @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") + public CommonResult> getFollowCustomerCountRank(@Valid CrmBiRankReqVO rankingReqVO) { + return success(rankingService.getFollowCustomerCountRank(rankingReqVO)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRanKRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRanKRespVO.java new file mode 100644 index 000000000..404ee3352 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRanKRespVO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.crm.controller.admin.bi.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + + +@Schema(description = "管理后台 - CRM BI 排行榜 Response VO") +@Data +public class CrmBiRanKRespVO { + + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long ownerUserId; + + @Schema(description = "姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private String nickname; + + @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private String deptName; + + /** + * 数量是个特别“抽象”的概念,在不同排行下,代表不同含义 + * + * 1. 金额:合同金额排行、回款金额排行 + * 2. 个数:签约合同排行、产品销量排行、产品销量排行、新增客户数排行、新增联系人排行、跟进次数排行、跟进客户数排行 + */ + @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer count; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRankReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRankReqVO.java new file mode 100644 index 000000000..6d36f6d6f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRankReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.crm.controller.admin.bi.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - CRM BI 排行榜 Request VO") +@Data +public class CrmBiRankReqVO { + + @Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "部门 id 不能为空") + private Long deptId; + + /** + * userIds 目前不用前端传递,目前是方便后端通过 deptId 读取编号后,设置回来 + * + * 后续,可能会支持选择部分用户进行查询 + */ + @Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2") + private List userIds; + + @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.REQUIRED) + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @NotEmpty(message = "时间范围不能为空") + private LocalDateTime[] times; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.http b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.http new file mode 100644 index 000000000..55adb4bd5 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.http @@ -0,0 +1,32 @@ +### 请求 /transfer +PUT {{baseUrl}}/crm/business/transfer +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} + +{ + "id": 1, + "ownerUserId": 2, + "transferType": 2, + "permissionType": 2 +} + +### 请求 /update +PUT {{baseUrl}}/crm/business/update +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} + +{ + "id": 1, + "name": "2", + "statusTypeId": 2, + "statusId": 2, + "customerId": 1 +} + +### 请求 /get +GET {{baseUrl}}/crm/business/get?id=1024 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java new file mode 100644 index 000000000..c85c151f5 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java @@ -0,0 +1,177 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; +import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusTypeService; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +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.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +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.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS; + +@Tag(name = "管理后台 - CRM 商机") +@RestController +@RequestMapping("/crm/business") +@Validated +public class CrmBusinessController { + + @Resource + private CrmBusinessService businessService; + @Resource + private CrmCustomerService customerService; + @Resource + private CrmBusinessStatusTypeService businessStatusTypeService; + @Resource + private CrmBusinessStatusService businessStatusService; + + @PostMapping("/create") + @Operation(summary = "创建商机") + @PreAuthorize("@ss.hasPermission('crm:business:create')") + public CommonResult createBusiness(@Valid @RequestBody CrmBusinessSaveReqVO createReqVO) { + return success(businessService.createBusiness(createReqVO, getLoginUserId())); + } + + @PutMapping("/update") + @Operation(summary = "更新商机") + @PreAuthorize("@ss.hasPermission('crm:business:update')") + public CommonResult updateBusiness(@Valid @RequestBody CrmBusinessSaveReqVO updateReqVO) { + businessService.updateBusiness(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除商机") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:business:delete')") + public CommonResult deleteBusiness(@RequestParam("id") Long id) { + businessService.deleteBusiness(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得商机") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:business:query')") + public CommonResult getBusiness(@RequestParam("id") Long id) { + CrmBusinessDO business = businessService.getBusiness(id); + return success(BeanUtils.toBean(business, CrmBusinessRespVO.class)); + } + + @GetMapping("/list-by-ids") + @Operation(summary = "获得商机列表") + @Parameter(name = "ids", description = "编号", required = true, example = "[1024]") + @PreAuthorize("@ss.hasPermission('crm:business:query')") + public CommonResult> getContactListByIds(@RequestParam("ids") List ids) { + return success(BeanUtils.toBean(businessService.getBusinessList(ids, getLoginUserId()), CrmBusinessRespVO.class)); + } + + @GetMapping("/simple-all-list") + @Operation(summary = "获得联系人的精简列表") + @PreAuthorize("@ss.hasPermission('crm:contact:query')") + public CommonResult> getSimpleContactList() { + CrmBusinessPageReqVO reqVO = new CrmBusinessPageReqVO(); + reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页 + PageResult pageResult = businessService.getBusinessPage(reqVO, getLoginUserId()); + return success(convertList(pageResult.getList(), business -> // 只返回 id、name 字段 + new CrmBusinessRespVO().setId(business.getId()).setName(business.getName()))); + } + + @GetMapping("/page") + @Operation(summary = "获得商机分页") + @PreAuthorize("@ss.hasPermission('crm:business:query')") + public CommonResult> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) { + PageResult pageResult = businessService.getBusinessPage(pageVO, getLoginUserId()); + return success(buildBusinessDetailPageResult(pageResult)); + } + + @GetMapping("/page-by-customer") + @Operation(summary = "获得商机分页,基于指定客户") + public CommonResult> getBusinessPageByCustomer(@Valid CrmBusinessPageReqVO pageReqVO) { + if (pageReqVO.getCustomerId() == null) { + throw exception(CUSTOMER_NOT_EXISTS); + } + PageResult pageResult = businessService.getBusinessPageByCustomerId(pageReqVO); + return success(buildBusinessDetailPageResult(pageResult)); + } + + @GetMapping("/page-by-contact") + @Operation(summary = "获得联系人的商机分页") + @PreAuthorize("@ss.hasPermission('crm:business:query')") + public CommonResult> getBusinessContactPage(@Valid CrmBusinessPageReqVO pageReqVO) { + PageResult pageResult = businessService.getBusinessPageByContact(pageReqVO); + return success(buildBusinessDetailPageResult(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出商机 Excel") + @PreAuthorize("@ss.hasPermission('crm:business:export')") + @OperateLog(type = EXPORT) + public void exportBusinessExcel(@Valid CrmBusinessPageReqVO exportReqVO, + HttpServletResponse response) throws IOException { + exportReqVO.setPageSize(PAGE_SIZE_NONE); + PageResult pageResult = businessService.getBusinessPage(exportReqVO, getLoginUserId()); + // 导出 Excel + ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessRespVO.class, + buildBusinessDetailPageResult(pageResult).getList()); + } + + /** + * 构建详细的商机分页结果 + * + * @param pageResult 简单的商机分页结果 + * @return 详细的商机分页结果 + */ + private PageResult buildBusinessDetailPageResult(PageResult pageResult) { + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + List statusTypeList = businessStatusTypeService.getBusinessStatusTypeList( + convertSet(pageResult.getList(), CrmBusinessDO::getStatusTypeId)); + List statusList = businessStatusService.getBusinessStatusList( + convertSet(pageResult.getList(), CrmBusinessDO::getStatusId)); + List customerList = customerService.getCustomerList( + convertSet(pageResult.getList(), CrmBusinessDO::getCustomerId)); + return CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList); + } + + @PutMapping("/transfer") + @Operation(summary = "商机转移") + @PreAuthorize("@ss.hasPermission('crm:business:update')") + public CommonResult transferBusiness(@Valid @RequestBody CrmBusinessTransferReqVO reqVO) { + businessService.transferBusiness(reqVO, getLoginUserId()); + return success(true); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java new file mode 100644 index 000000000..86a15dcaf --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java @@ -0,0 +1,141 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO; +import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusConvert; +import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusTypeConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusTypeService; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +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.Collection; +import java.util.List; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Tag(name = "管理后台 - CRM 商机状态类型") +@RestController +@RequestMapping("/crm/business-status-type") +@Validated +public class CrmBusinessStatusTypeController { + + @Resource + private CrmBusinessStatusTypeService businessStatusTypeService; + + @Resource + private CrmBusinessStatusService businessStatusService; + + @Resource + private DeptApi deptApi; + + @PostMapping("/create") + @Operation(summary = "创建商机状态类型") + @PreAuthorize("@ss.hasPermission('crm:business-status-type:create')") + public CommonResult createBusinessStatusType(@Valid @RequestBody CrmBusinessStatusTypeSaveReqVO createReqVO) { + return success(businessStatusTypeService.createBusinessStatusType(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新商机状态类型") + @PreAuthorize("@ss.hasPermission('crm:business-status-type:update')") + public CommonResult updateBusinessStatusType(@Valid @RequestBody CrmBusinessStatusTypeSaveReqVO updateReqVO) { + businessStatusTypeService.updateBusinessStatusType(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除商机状态类型") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:business-status-type:delete')") + public CommonResult deleteBusinessStatusType(@RequestParam("id") Long id) { + businessStatusTypeService.deleteBusinessStatusType(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得商机状态类型") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')") + public CommonResult getBusinessStatusType(@RequestParam("id") Long id) { + CrmBusinessStatusTypeDO statusType = businessStatusTypeService.getBusinessStatusType(id); + // 处理状态回显 + // TODO @lzxhqs:可以在 businessStatusService 加个 getBusinessStatusListByTypeId 方法,直接返回 List 哈,常用的,尽量封装个简单易懂的方法,不用追求绝对通用哈; + CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO(); + queryVO.setTypeId(id); + List statusList = businessStatusService.selectList(queryVO); + return success(CrmBusinessStatusTypeConvert.INSTANCE.convert(statusType, statusList)); + } + + @GetMapping("/page") + @Operation(summary = "获得商机状态类型分页") + @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')") + public CommonResult> getBusinessStatusTypePage(@Valid CrmBusinessStatusTypePageReqVO pageReqVO) { + PageResult pageResult = businessStatusTypeService.getBusinessStatusTypePage(pageReqVO); + // 处理部门回显 + Set deptIds = CollectionUtils.convertSetByFlatMap(pageResult.getList(), CrmBusinessStatusTypeDO::getDeptIds,Collection::stream); + List deptList = deptApi.getDeptList(deptIds).getCheckedData(); + return success(CrmBusinessStatusTypeConvert.INSTANCE.convertPage(pageResult, deptList)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出商机状态类型 Excel") + @PreAuthorize("@ss.hasPermission('crm:business-status-type:export')") + @OperateLog(type = EXPORT) + public void exportBusinessStatusTypeExcel(@Valid CrmBusinessStatusTypePageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = businessStatusTypeService.getBusinessStatusTypePage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "商机状态类型.xls", "数据", CrmBusinessStatusTypeRespVO.class, + BeanUtils.toBean(list, CrmBusinessStatusTypeRespVO.class)); + } + + @GetMapping("/get-simple-list") + @Operation(summary = "获得商机状态类型列表") + @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')") + public CommonResult> getBusinessStatusTypeList() { + CrmBusinessStatusTypeQueryVO queryVO = new CrmBusinessStatusTypeQueryVO(); + queryVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + List list = businessStatusTypeService.selectList(queryVO); + return success(BeanUtils.toBean(list, CrmBusinessStatusTypeRespVO.class)); + } + + // TODO @ljlleo 这个接口,是不是可以和 getBusinessStatusTypeList 合并成一个? + @GetMapping("/get-status-list") + @Operation(summary = "获得商机状态列表") + @PreAuthorize("@ss.hasPermission('crm:business-status:query')") + public CommonResult> getBusinessStatusListByTypeId(@RequestParam("typeId") Long typeId) { + CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO(); + queryVO.setTypeId(typeId); + List list = businessStatusService.selectList(queryVO); + return success(CrmBusinessStatusConvert.INSTANCE.convertList(list)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessExcelVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessExcelVO.java new file mode 100644 index 000000000..a11949ecd --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessExcelVO.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; + +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Set; + +/** + * 商机 Excel VO + * + * @author ljlleo + */ +@Data +public class CrmBusinessExcelVO { + + @ExcelProperty("主键") + private Long id; + + @ExcelProperty("商机名称") + private String name; + + @ExcelProperty("商机状态类型编号") + private Long statusTypeId; + + @ExcelProperty("商机状态编号") + private Long statusId; + + @ExcelProperty("下次联系时间") + private LocalDateTime contactNextTime; + + @ExcelProperty("客户编号") + private Long customerId; + + @ExcelProperty("预计成交日期") + private LocalDateTime dealTime; + + @ExcelProperty("商机金额") + private BigDecimal price; + + @ExcelProperty("整单折扣") + private BigDecimal discountPercent; + + @ExcelProperty("产品总金额") + private BigDecimal productPrice; + + @ExcelProperty("备注") + private String remark; + + @ExcelProperty("负责人的用户编号") + private Long ownerUserId; + + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @ExcelProperty("只读权限的用户编号数组") + private Set roUserIds; + + @ExcelProperty("读写权限的用户编号数组") + private Set rwUserIds; + + @ExcelProperty("1赢单2输单3无效") + private Integer endStatus; + + @ExcelProperty("结束时的备注") + private String endRemark; + + @ExcelProperty("最后跟进时间") + private LocalDateTime contactLastTime; + + @ExcelProperty("跟进状态") + private Integer followUpStatus; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java new file mode 100644 index 000000000..0e47bf5be --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessPageReqVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 商机分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmBusinessPageReqVO extends PageParam { + + @Schema(description = "商机名称", example = "李四") + private String name; + + @Schema(description = "客户编号", example = "10795") + private Long customerId; + + @Schema(description = "联系人编号", example = "10795") + private Long contactId; + + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java new file mode 100644 index 000000000..d3b6ab2fb --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java @@ -0,0 +1,69 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 商机 Response VO") +@Data +public class CrmBusinessRespVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129") + private Long id; + + @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @NotNull(message = "商机名称不能为空") + private String name; + + @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714") + @NotNull(message = "商机状态类型不能为空") + private Long statusTypeId; + + @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") + @NotNull(message = "商机状态不能为空") + private Long statusId; + + @Schema(description = "下次联系时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime contactNextTime; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299") + @NotNull(message = "客户不能为空") + private Long customerId; + + @Schema(description = "预计成交日期") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime dealTime; + + @Schema(description = "商机金额", example = "12371") + private Integer price; + + // TODO @ljileo:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题 + @Schema(description = "整单折扣") + private Integer discountPercent; + + @Schema(description = "产品总金额", example = "12025") + private BigDecimal productPrice; + + @Schema(description = "备注", example = "随便") + private String remark; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + private String customerName; + + @Schema(description = "状态类型名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中") + private String statusTypeName; + + @Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "跟进中") + private String statusName; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java new file mode 100644 index 000000000..0be6264eb --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java @@ -0,0 +1,103 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.business.CrmBizEndStatus; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction; +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - CRM 商机创建/更新 Request VO") +@Data +public class CrmBusinessSaveReqVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129") + private Long id; + + @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @DiffLogField(name = "商机名称") + @NotNull(message = "商机名称不能为空") + private String name; + + @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714") + @DiffLogField(name = "商机状态") + @NotNull(message = "商机状态类型不能为空") + private Long statusTypeId; + + @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") + @DiffLogField(name = "商机状态") + @NotNull(message = "商机状态不能为空") + private Long statusId; + + @Schema(description = "下次联系时间") + @DiffLogField(name = "下次联系时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime contactNextTime; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299") + @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME) + @NotNull(message = "客户不能为空") + private Long customerId; + + @Schema(description = "预计成交日期") + @DiffLogField(name = "预计成交日期") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime dealTime; + + @Schema(description = "商机金额", example = "12371") + @DiffLogField(name = "商机金额") + private Integer price; + + @Schema(description = "整单折扣") + @DiffLogField(name = "整单折扣") + private Integer discountPercent; + + @Schema(description = "产品总金额", example = "12025") + @DiffLogField(name = "产品总金额") + private BigDecimal productPrice; + + @Schema(description = "备注", example = "随便") + @DiffLogField(name = "备注") + private String remark; + + @Schema(description = "结束状态", example = "1") + @InEnum(CrmBizEndStatus.class) + private Integer endStatus; + + @Schema(description = "联系人编号", example = "110") + private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段 + + // TODO @puhui999:传递 items 就行啦; + @Schema(description = "产品列表") + private List productItems; + + @Schema(description = "产品列表") + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class CrmBusinessProductItem { + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") + @NotNull(message = "产品编号不能为空") + private Long id; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") + @NotNull(message = "产品数量不能为空") + private Integer count; + + @Schema(description = "产品折扣") + private Integer discountPercent; + + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessTransferReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessTransferReqVO.java new file mode 100644 index 000000000..a76c48cae --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessTransferReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; + +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 商机转移 Request VO") +@Data +public class CrmBusinessTransferReqVO { + + @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + @NotNull(message = "商机编号不能为空") + private Long id; + + /** + * 新负责人的用户编号 + */ + @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + @NotNull(message = "新负责人的用户编号不能为空") + private Long newOwnerUserId; + + /** + * 老负责人加入团队后的权限级别。如果 null 说明移除 + * + * 关联 {@link CrmPermissionLevelEnum} + */ + @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer oldOwnerPermissionLevel; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusPageReqVO.java new file mode 100644 index 000000000..b91a954e0 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusPageReqVO.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status; + +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 = "管理后台 - 商机状态分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmBusinessStatusPageReqVO extends PageParam { + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusQueryVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusQueryVO.java new file mode 100644 index 000000000..fbf4d06e1 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusQueryVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +import java.util.Collection; + +@Schema(description = "管理后台 - 商机状态 Query VO") +@Data +@ToString(callSuper = true) +public class CrmBusinessStatusQueryVO { + + @Schema(description = "主键集合") + private Collection idList; + + @Schema(description = "状态类型编号") + private Long typeId; +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusRespVO.java new file mode 100644 index 000000000..405a832a5 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusRespVO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 商机状态 Response VO") +@Data +@ExcelIgnoreUnannotated +public class CrmBusinessStatusRespVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "23899") + @ExcelProperty("主键") + private Long id; + + @Schema(description = "状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7139") + @ExcelProperty("状态类型编号") + private Long typeId; + + @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @ExcelProperty("状态名") + private String name; + + @Schema(description = "赢单率") + @ExcelProperty("赢单率") + private String percent; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("排序") + private Integer sort; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java new file mode 100644 index 000000000..3327b09f7 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - 商机状态新增/修改 Request VO") +@Data +public class CrmBusinessStatusSaveReqVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "23899") + private Long id; + + @Schema(description = "状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7139") + @NotNull(message = "状态类型编号不能为空") + private Long typeId; + + @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @NotEmpty(message = "状态名不能为空") + private String name; + + // TODO @lzxhqs::percent 应该是 Integer; + @Schema(description = "赢单率") + private String percent; + + // TODO @lzxhqs:这个是不是不用前端新增和修改的时候传递,交给顺序计算出来,存储起来就好了; + @Schema(description = "排序") + private Integer sort; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypePageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypePageReqVO.java new file mode 100644 index 000000000..03b113cc7 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypePageReqVO.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type; + +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 = "管理后台 - 商机状态类型分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmBusinessStatusTypePageReqVO extends PageParam { + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeQueryVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeQueryVO.java new file mode 100644 index 000000000..9c78f1afc --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeQueryVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +import java.util.Collection; + +@Schema(description = "管理后台 - 商机状态类型 Query VO") +@Data +@ToString(callSuper = true) +public class CrmBusinessStatusTypeQueryVO { + + @Schema(description = "主键集合") + private Collection idList; + + @Schema(description = "状态") + private Integer status; +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeRespVO.java new file mode 100644 index 000000000..9d13d5dc3 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeRespVO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type; + +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 商机状态类型 Response VO") +@Data +@ExcelIgnoreUnannotated +public class CrmBusinessStatusTypeRespVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934") + @ExcelProperty("主键") + private Long id; + + @Schema(description = "状态类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @ExcelProperty("状态类型名") + private String name; + + @Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("使用的部门编号") + private List deptIds; + @Schema(description = "使用的部门名称", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("使用的部门名称") + private List deptNames; + + @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建人") + private String creator; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + // TODO @ljlleo 字段后缀改成 statuses,保持和 deptIds 风格一致;CrmBusinessStatusDO 改成 VO 哈;一般不使用 do 直接返回 + @Schema(description = "状态集合", requiredMode = Schema.RequiredMode.REQUIRED) + private List statusList; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java new file mode 100644 index 000000000..23dc7742d --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type; + +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO; +import com.google.common.collect.Lists; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; +import java.util.List; + +@Schema(description = "管理后台 - 商机状态类型新增/修改 Request VO") +@Data +public class CrmBusinessStatusTypeSaveReqVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934") + private Long id; + + @Schema(description = "状态类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @NotEmpty(message = "状态类型名不能为空") + private String name; + + // TODO @lzxhqs: VO 里面,我们不使用默认值哈。这里 Lists.newArrayList() 看看怎么去掉。上面 deptIds 也是类似噢 + @Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED) + private List deptIds = Lists.newArrayList(); + + @Schema(description = "商机状态集合", requiredMode = Schema.RequiredMode.REQUIRED) + private List statusList; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java new file mode 100644 index 000000000..8be62ae26 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java @@ -0,0 +1,107 @@ +package cn.iocoder.yudao.module.crm.controller.admin.clue; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.*; +import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; +import cn.iocoder.yudao.module.crm.service.clue.CrmClueService; +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.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - 线索") +@RestController +@RequestMapping("/crm/clue") +@Validated +public class CrmClueController { + + @Resource + private CrmClueService clueService; + + @PostMapping("/create") + @Operation(summary = "创建线索") + @PreAuthorize("@ss.hasPermission('crm:clue:create')") + public CommonResult createClue(@Valid @RequestBody CrmClueSaveReqVO createReqVO) { + return success(clueService.createClue(createReqVO, getLoginUserId())); + } + + @PutMapping("/update") + @Operation(summary = "更新线索") + @PreAuthorize("@ss.hasPermission('crm:clue:update')") + public CommonResult updateClue(@Valid @RequestBody CrmClueSaveReqVO updateReqVO) { + clueService.updateClue(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除线索") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:clue:delete')") + public CommonResult deleteClue(@RequestParam("id") Long id) { + clueService.deleteClue(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得线索") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:clue:query')") + public CommonResult getClue(@RequestParam("id") Long id) { + CrmClueDO clue = clueService.getClue(id); + return success(BeanUtils.toBean(clue, CrmClueRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得线索分页") + @PreAuthorize("@ss.hasPermission('crm:clue:query')") + public CommonResult> getCluePage(@Valid CrmCluePageReqVO pageVO) { + PageResult pageResult = clueService.getCluePage(pageVO, getLoginUserId()); + return success(BeanUtils.toBean(pageResult, CrmClueRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出线索 Excel") + @PreAuthorize("@ss.hasPermission('crm:clue:export')") + @OperateLog(type = EXPORT) + public void exportClueExcel(@Valid CrmCluePageReqVO pageReqVO, HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PAGE_SIZE_NONE); + List list = clueService.getCluePage(pageReqVO, getLoginUserId()).getList(); + // 导出 Excel + List datas = BeanUtils.toBean(list, CrmClueRespVO.class); + ExcelUtils.write(response, "线索.xls", "数据", CrmClueRespVO.class, datas); + } + + @PutMapping("/transfer") + @Operation(summary = "线索转移") + @PreAuthorize("@ss.hasPermission('crm:clue:update')") + public CommonResult transferClue(@Valid @RequestBody CrmClueTransferReqVO reqVO) { + clueService.transferClue(reqVO, getLoginUserId()); + return success(true); + } + + @PostMapping("/transform") + @Operation(summary = "线索转化为客户") + @PreAuthorize("@ss.hasPermission('crm:clue:update')") + public CommonResult translateCustomer(@Valid @RequestBody CrmClueTranslateReqVO reqVO) { + clueService.translateCustomer(reqVO, getLoginUserId()); + return success(Boolean.TRUE); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java new file mode 100644 index 000000000..3ba823d54 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - 线索分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmCluePageReqVO extends PageParam { + + @Schema(description = "线索名称", example = "线索xxx") + private String name; + + @Schema(description = "电话", example = "18000000000") + private String telephone; + + @Schema(description = "手机号", example = "18000000000") + private String mobile; + + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + + @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + private Boolean pool; // null 则表示为不是公海数据 + + @Schema(description = "所属行业", example = "1") + private Integer industryId; + + @Schema(description = "客户等级", example = "1") + private Integer level; + + @Schema(description = "客户来源", example = "1") + private Integer source; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java new file mode 100644 index 000000000..35d30956e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java @@ -0,0 +1,115 @@ +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 线索 Response VO") +@Data +@ToString(callSuper = true) +@ExcelIgnoreUnannotated +public class CrmClueRespVO { + + @Schema(description = "编号,主键自增", requiredMode = Schema.RequiredMode.REQUIRED, example = "10969") + @ExcelProperty("编号") + private Long id; + + @Schema(description = "转化状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @ExcelProperty(value = "转化状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) + private Boolean transformStatus; + + @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @ExcelProperty(value = "跟进状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) + private Boolean followUpStatus; + + @Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx") + @ExcelProperty("线索名称") + private String name; + + @Schema(description = "客户 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "520") + // TODO 这里需要导出成客户名称 + @ExcelProperty("客户id") + private Long customerId; + + @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ExcelProperty("下次联系时间") + private LocalDateTime contactNextTime; + + @Schema(description = "电话", example = "18000000000") + @ExcelProperty("电话") + private String telephone; + + @Schema(description = "手机号", example = "18000000000") + @ExcelProperty("手机号") + private String mobile; + + @Schema(description = "地址", example = "北京市海淀区") + @ExcelProperty("地址") + private String address; + + @Schema(description = "负责人编号") + @ExcelProperty("负责人的用户编号") + private Long ownerUserId; + + @Schema(description = "最后跟进时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ExcelProperty("最后跟进时间") + private LocalDateTime contactLastTime; + + @Schema(description = "备注", example = "随便") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "所属行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "所属行业", converter = DictConvert.class) + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY) + private Integer industryId; + + @Schema(description = "客户等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "客户等级", converter = DictConvert.class) + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL) + private Integer level; + + @Schema(description = "客户来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "客户来源", converter = DictConvert.class) + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE) + private Integer source; + + @Schema(description = "网址", example = "25682") + @ExcelProperty("网址") + private String website; + + @Schema(description = "QQ", example = "25682") + @ExcelProperty("QQ") + private String qq; + + @Schema(description = "wechat", example = "25682") + @ExcelProperty("wechat") + private String wechat; + + @Schema(description = "email", example = "25682") + @ExcelProperty("email") + private String email; + + @Schema(description = "客户描述", example = "25682") + @ExcelProperty("客户描述") + private String description; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java new file mode 100644 index 000000000..4ca004a59 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java @@ -0,0 +1,105 @@ +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.framework.common.validation.Mobile; +import cn.iocoder.yudao.framework.common.validation.Telephone; +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLevelEnum; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerIndustryParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerLevelParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerSourceParseFunction; +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY; + +@Schema(description = "管理后台 - CRM 线索 创建/更新 Request VO") +@Data +public class CrmClueSaveReqVO { + + @Schema(description = "编号", example = "10969") + private Long id; + + @Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx") + @DiffLogField(name = "线索名称") + @NotEmpty(message = "线索名称不能为空") + private String name; + + @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00") + @DiffLogField(name = "下次联系时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime contactNextTime; + + @Schema(description = "电话", example = "18000000000") + @DiffLogField(name = "电话") + @Telephone + private String telephone; + + @Schema(description = "手机号", example = "18000000000") + @DiffLogField(name = "手机号") + @Mobile + private String mobile; + + @Schema(description = "地址", example = "北京市海淀区") + @DiffLogField(name = "地址") + private String address; + + @Schema(description = "最后跟进时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @DiffLogField(name = "最后跟进时间") + private LocalDateTime contactLastTime; + + @Schema(description = "负责人编号", example = "2048") + private Long ownerUserId; + + @Schema(description = "备注", example = "随便") + @DiffLogField(name = "备注") + private String remark; + + @Schema(description = "所属行业", example = "1") + @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME) + @DictFormat(CRM_CUSTOMER_INDUSTRY) + private Integer industryId; + + @Schema(description = "客户等级", example = "2") + @DiffLogField(name = "客户等级", function = CrmCustomerLevelParseFunction.NAME) + @InEnum(CrmCustomerLevelEnum.class) + private Integer level; + + @Schema(description = "客户来源", example = "3") + @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME) + private Integer source; + + @Schema(description = "网址", example = "https://www.baidu.com") + @DiffLogField(name = "网址") + private String website; + + @Schema(description = "QQ", example = "123456789") + @DiffLogField(name = "QQ") + @Size(max = 20, message = "QQ长度不能超过 20 个字符") + private String qq; + + @Schema(description = "微信", example = "123456789") + @DiffLogField(name = "微信") + @Size(max = 255, message = "微信长度不能超过 255 个字符") + private String wechat; + + @Schema(description = "邮箱", example = "123456789@qq.com") + @DiffLogField(name = "邮箱") + @Email(message = "邮箱格式不正确") + @Size(max = 255, message = "邮箱长度不能超过 255 个字符") + private String email; + + @Schema(description = "客户描述", example = "任意文字") + @DiffLogField(name = "客户描述") + @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符") + private String description; +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTransferReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTransferReqVO.java new file mode 100644 index 000000000..63bdc1838 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTransferReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 线索转移 Request VO") +@Data +public class CrmClueTransferReqVO { + + @Schema(description = "线索编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + @NotNull(message = "线索编号不能为空") + private Long id; + + @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + @NotNull(message = "新负责人的用户编号不能为空") + private Long newOwnerUserId; + + @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @InEnum(value = CrmPermissionLevelEnum.class) + private Integer oldOwnerPermissionLevel; // 老负责人加入团队后的权限级别。如果 null 说明移除 + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTranslateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTranslateReqVO.java new file mode 100644 index 000000000..03a4d78f1 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTranslateReqVO.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.Set; + +@Schema(description = "管理后台 - 线索转化为客户 Request VO") +@Data +public class CrmClueTranslateReqVO { + + @Schema(description = "线索编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1024, 1025]") + @NotEmpty(message = "线索编号不能为空") + private Set ids; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java new file mode 100644 index 000000000..766adb02d --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java @@ -0,0 +1,204 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contact; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.NumberUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*; +import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import com.google.common.collect.Lists; +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 lombok.extern.slf4j.Slf4j; +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.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - CRM 联系人") +@RestController +@RequestMapping("/crm/contact") +@Validated +@Slf4j +public class CrmContactController { + + @Resource + private CrmContactService contactService; + @Resource + private CrmCustomerService customerService; + @Resource + private CrmContactBusinessService contactBusinessLinkService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建联系人") + @PreAuthorize("@ss.hasPermission('crm:contact:create')") + public CommonResult createContact(@Valid @RequestBody CrmContactSaveReqVO createReqVO) { + return success(contactService.createContact(createReqVO, getLoginUserId())); + } + + @PutMapping("/update") + @Operation(summary = "更新联系人") + @OperateLog(enable = false) + @PreAuthorize("@ss.hasPermission('crm:contact:update')") + public CommonResult updateContact(@Valid @RequestBody CrmContactSaveReqVO updateReqVO) { + contactService.updateContact(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除联系人") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:contact:delete')") + public CommonResult deleteContact(@RequestParam("id") Long id) { + contactService.deleteContact(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得联系人") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:contact:query')") + public CommonResult getContact(@RequestParam("id") Long id) { + CrmContactDO contact = contactService.getContact(id); + if (contact == null) { + throw exception(ErrorCodeConstants.CONTACT_NOT_EXISTS); + } + // 1. 获取用户名 + Map userMap = adminUserApi.getUserMap(CollUtil.removeNull(Lists.newArrayList( + NumberUtil.parseLong(contact.getCreator()), contact.getOwnerUserId()))); + // 2. 获取客户信息 + List customerList = customerService.getCustomerList( + Collections.singletonList(contact.getCustomerId())); + // 3. 直属上级 + List parentContactList = contactService.getContactListByIds( + Collections.singletonList(contact.getParentId()), getLoginUserId()); + return success(CrmContactConvert.INSTANCE.convert(contact, userMap, customerList, parentContactList)); + } + + @GetMapping("/list-by-ids") + @Operation(summary = "获得联系人列表") + @Parameter(name = "ids", description = "编号", required = true, example = "[1024]") + @PreAuthorize("@ss.hasPermission('crm:contact:query')") + public CommonResult> getContactListByIds(@RequestParam("ids") List ids) { + return success(BeanUtils.toBean(contactService.getContactListByIds(ids, getLoginUserId()), CrmContactRespVO.class)); + } + + @GetMapping("/simple-all-list") + @Operation(summary = "获得联系人的精简列表") + @PreAuthorize("@ss.hasPermission('crm:contact:query')") + public CommonResult> getSimpleContactList() { + List list = contactService.getSimpleContactList(getLoginUserId()); + return success(convertList(list, contact -> // 只返回 id、name 字段 + new CrmContactRespVO().setId(contact.getId()).setName(contact.getName()))); + } + + @GetMapping("/page") + @Operation(summary = "获得联系人分页") + @PreAuthorize("@ss.hasPermission('crm:contact:query')") + public CommonResult> getContactPage(@Valid CrmContactPageReqVO pageVO) { + PageResult pageResult = contactService.getContactPage(pageVO, getLoginUserId()); + return success(buildContactDetailPage(pageResult)); + } + + @GetMapping("/page-by-customer") + @Operation(summary = "获得联系人分页,基于指定客户") + public CommonResult> getContactPageByCustomer(@Valid CrmContactPageReqVO pageVO) { + Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空"); + PageResult pageResult = contactService.getContactPageByCustomerId(pageVO); + return success(buildContactDetailPage(pageResult)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出联系人 Excel") + @PreAuthorize("@ss.hasPermission('crm:contact:export')") + @OperateLog(type = EXPORT) + public void exportContactExcel(@Valid CrmContactPageReqVO exportReqVO, + HttpServletResponse response) throws IOException { + exportReqVO.setPageNo(PAGE_SIZE_NONE); + PageResult pageResult = contactService.getContactPage(exportReqVO, getLoginUserId()); + ExcelUtils.write(response, "联系人.xls", "数据", CrmContactRespVO.class, + buildContactDetailPage(pageResult).getList()); + } + + /** + * 构建详细的联系人分页结果 + * + * @param pageResult 简单的联系人分页结果 + * @return 详细的联系人分页结果 + */ + private PageResult buildContactDetailPage(PageResult pageResult) { + List contactList = pageResult.getList(); + if (CollUtil.isEmpty(contactList)) { + return PageResult.empty(pageResult.getTotal()); + } + // 1. 获取客户列表 + List crmCustomerDOList = customerService.getCustomerList( + convertSet(contactList, CrmContactDO::getCustomerId)); + // 2. 获取创建人、负责人列表 + Map userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList, + contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); + // 3. 直属上级 + List parentContactList = contactService.getContactListByIds( + convertSet(contactList, CrmContactDO::getParentId), getLoginUserId()); + return CrmContactConvert.INSTANCE.convertPage(pageResult, userMap, crmCustomerDOList, parentContactList); + } + + @PutMapping("/transfer") + @Operation(summary = "联系人转移") + @PreAuthorize("@ss.hasPermission('crm:contact:update')") + public CommonResult transferContact(@Valid @RequestBody CrmContactTransferReqVO reqVO) { + contactService.transferContact(reqVO, getLoginUserId()); + return success(true); + } + + // ================== 关联/取关联系人 =================== + + @PostMapping("/create-business-list") + @Operation(summary = "创建联系人与商机的关联") + @PreAuthorize("@ss.hasPermission('crm:contact:create-business')") + public CommonResult createContactBusinessList(@Valid @RequestBody CrmContactBusinessReqVO createReqVO) { + contactBusinessLinkService.createContactBusinessList(createReqVO); + return success(true); + } + + @DeleteMapping("/delete-business-list") + @Operation(summary = "删除联系人与联系人的关联") + @PreAuthorize("@ss.hasPermission('crm:contact:delete-business')") + public CommonResult deleteContactBusinessList(@Valid @RequestBody CrmContactBusinessReqVO deleteReqVO) { + contactBusinessLinkService.deleteContactBusinessList(deleteReqVO); + return success(true); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java new file mode 100644 index 000000000..9b360f84b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 用于关联,取消关联的操作 +@Data +public class CrmContactBusinessReqVO { + + @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878") + @NotNull(message="联系人不能为空") + private Long contactId; + + @Schema(description = "商机编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638") + @NotEmpty(message="商机不能为空") + private List businessIds; + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java new file mode 100644 index 000000000..75294a1bd --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +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 CrmContactPageReqVO extends PageParam { + + @Schema(description = "姓名", example = "芋艿") + private String name; + + @Schema(description = "客户编号", example = "10795") + private Long customerId; + + @Schema(description = "手机号", example = "13898273941") + private String mobile; + + @Schema(description = "电话", example = "021-383773") + private String telephone; + + @Schema(description = "电子邮箱", example = "111@22.com") + private String email; + + @Schema(description = "QQ", example = "3882872") + private Long qq; + + @Schema(description = "微信", example = "zzZ98373") + private String wechat; + + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java new file mode 100644 index 000000000..d99ea703c --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java @@ -0,0 +1,112 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.ToString; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - CRM 联系人 Response VO") +@Data +@ToString(callSuper = true) +@ExcelIgnoreUnannotated +public class CrmContactRespVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167") + private Long id; + + @Schema(description = "姓名", example = "芋艿") + @ExcelProperty(value = "姓名", order = 1) + private String name; + + @Schema(description = "客户编号", example = "10795") + private Long customerId; + + @Schema(description = "性别") + @ExcelProperty(value = "性别", converter = DictConvert.class, order = 3) + @DictFormat(cn.iocoder.yudao.module.system.enums.DictTypeConstants.USER_SEX) + private Integer sex; + + @Schema(description = "职位") + @ExcelProperty(value = "职位", order = 3) + private String post; + + @Schema(description = "是否关键决策人") + @ExcelProperty(value = "是否关键决策人", converter = DictConvert.class, order = 3) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) + private Boolean master; + + @Schema(description = "直属上级", example = "23457") + private Long parentId; + + @Schema(description = "手机号", example = "1387171766") + @ExcelProperty(value = "手机号", order = 4) + private String mobile; + + @Schema(description = "电话", example = "021-0029922") + @ExcelProperty(value = "电话", order = 4) + private String telephone; + + @Schema(description = "QQ", example = "197272662") + @ExcelProperty(value = "QQ", order = 4) + private Long qq; + + @Schema(description = "微信", example = "zzz3883") + @ExcelProperty(value = "微信", order = 4) + private String wechat; + + @Schema(description = "电子邮箱", example = "1111@22.com") + @ExcelProperty(value = "邮箱", order = 4) + private String email; + + @Schema(description = "地区编号", example = "20158") + private Integer areaId; + + @Schema(description = "地址") + @ExcelProperty(value = "地址", order = 5) + private String detailAddress; + + @Schema(description = "备注", example = "你说的对") + @ExcelProperty(value = "备注", order = 6) + private String remark; + + @Schema(description = "负责人用户编号", example = "14334") + private Long ownerUserId; + + @Schema(description = "最后跟进时间") + @ExcelProperty(value = "最后跟进时间", order = 6) + private LocalDateTime contactLastTime; + + @Schema(description = "下次联系时间") + @ExcelProperty(value = "下次联系时间", order = 6) + private LocalDateTime contactNextTime; + + @Schema(description = "创建人", example = "25682") + private String creator; + + @Schema(description = "创建人名字", example = "test") + @ExcelProperty(value = "创建人", order = 8) + private String creatorName; + + @ExcelProperty(value = "客户名称", order = 2) + @Schema(description = "客户名字", example = "test") + private String customerName; + + @Schema(description = "负责人", example = "test") + @ExcelProperty(value = "负责人", order = 7) + private String ownerUserName; + + @Schema(description = "直属上级名", example = "芋头") + @ExcelProperty(value = "直属上级", order = 4) + private String parentName; + + @Schema(description = "地区名", example = "上海上海市浦东新区") + @ExcelProperty(value = "地区", order = 5) + private String areaName; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java new file mode 100644 index 000000000..299b1fbbb --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java @@ -0,0 +1,103 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; + +import cn.iocoder.yudao.framework.common.validation.Mobile; +import cn.iocoder.yudao.framework.common.validation.Telephone; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.*; +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - CRM 联系人创建/更新 Request VO") +@Data +public class CrmContactSaveReqVO { + + @Schema(description = "主键", example = "3167") + private Long id; + + @Schema(description = "姓名", example = "芋艿") + @NotNull(message = "姓名不能为空") + @DiffLogField(name = "姓名") + private String name; + + @Schema(description = "客户编号", example = "10795") + @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME) + private Long customerId; + + @Schema(description = "性别") + @DiffLogField(name = "性别", function = SysSexParseFunction.NAME) + private Integer sex; + + @Schema(description = "职位") + @DiffLogField(name = "职位") + private String post; + + @Schema(description = "是否关键决策人") + @DiffLogField(name = "关键决策人", function = SysBooleanParseFunction.NAME) + private Boolean master; + + @Schema(description = "直属上级", example = "23457") + @DiffLogField(name = "直属上级", function = CrmContactParseFunction.NAME) + private Long parentId; + + @Schema(description = "手机号", example = "1387171766") + @Mobile + @DiffLogField(name = "手机号") + private String mobile; + + @Schema(description = "电话", example = "021-0029922") + @Telephone + @DiffLogField(name = "电话") + private String telephone; + + @Schema(description = "QQ", example = "197272662") + @DiffLogField(name = "QQ") + private Long qq; + + @Schema(description = "微信", example = "zzz3883") + @DiffLogField(name = "微信") + private String wechat; + + @Schema(description = "电子邮箱", example = "1111@22.com") + @DiffLogField(name = "邮箱") + @Email + private String email; + + @Schema(description = "地区编号", example = "20158") + @DiffLogField(name = "所在地", function = SysAreaParseFunction.NAME) + private Integer areaId; + + @Schema(description = "地址") + @DiffLogField(name = "地址") + private String detailAddress; + + @Schema(description = "备注", example = "你说的对") + @DiffLogField(name = "备注") + private String remark; + + @Schema(description = "负责人用户编号", example = "14334") + @NotNull(message = "负责人不能为空") + @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME) + private Long ownerUserId; + + @Schema(description = "最后跟进时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @DiffLogField(name = "最后跟进时间") + private LocalDateTime contactLastTime; + + @Schema(description = "下次联系时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + @DiffLogField(name = "下次联系时间") + private LocalDateTime contactNextTime; + + @Schema(description = "关联商机 ID", example = "122233") + private Long businessId; // 注意:该字段用于在【商机】详情界面「新建联系人」时,自动进行关联 + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactTransferReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactTransferReqVO.java new file mode 100644 index 000000000..c65b205c8 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactTransferReqVO.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; + +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - CRM 联系人转移 Request VO") +@Data +public class CrmContactTransferReqVO { + + @Schema(description = "联系人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + @NotNull(message = "联系人编号不能为空") + private Long id; + + /** + * 新负责人的用户编号 + */ + @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + @NotNull(message = "新负责人的用户编号不能为空") + private Long newOwnerUserId; + + /** + * 老负责人加入团队后的权限级别。如果 null 说明移除 + * + * 关联 {@link CrmPermissionLevelEnum} + */ + @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer oldOwnerPermissionLevel; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java new file mode 100644 index 000000000..ace5d1817 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java @@ -0,0 +1,187 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contract; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; +import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.crm.service.product.CrmProductService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static java.util.Collections.singletonList; + +@Tag(name = "管理后台 - CRM 合同") +@RestController +@RequestMapping("/crm/contract") +@Validated +public class CrmContractController { + + @Resource + private CrmContractService contractService; + @Resource + private CrmCustomerService customerService; + @Resource + private CrmContactService contactService; + @Resource + private CrmBusinessService businessService; + @Resource + private CrmProductService productService; + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建合同") + @PreAuthorize("@ss.hasPermission('crm:contract:create')") + public CommonResult createContract(@Valid @RequestBody CrmContractSaveReqVO createReqVO) { + return success(contractService.createContract(createReqVO, getLoginUserId())); + } + + @PutMapping("/update") + @Operation(summary = "更新合同") + @PreAuthorize("@ss.hasPermission('crm:contract:update')") + public CommonResult updateContract(@Valid @RequestBody CrmContractSaveReqVO updateReqVO) { + contractService.updateContract(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除合同") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:contract:delete')") + public CommonResult deleteContract(@RequestParam("id") Long id) { + contractService.deleteContract(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得合同") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:contract:query')") + public CommonResult getContract(@RequestParam("id") Long id) { + // 1. 查询合同 + CrmContractDO contract = contractService.getContract(id); + if (contract == null) { + return success(null); + } + + // 2. 拼接合同信息 + List respVOList = buildContractDetailList(singletonList(contract)); + return success(respVOList.get(0)); + } + + @GetMapping("/page") + @Operation(summary = "获得合同分页") + @PreAuthorize("@ss.hasPermission('crm:contract:query')") + public CommonResult> getContractPage(@Valid CrmContractPageReqVO pageVO) { + PageResult pageResult = contractService.getContractPage(pageVO, getLoginUserId()); + return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetailList(pageResult.getList()))); + } + + @GetMapping("/page-by-customer") + @Operation(summary = "获得合同分页,基于指定客户") + public CommonResult> getContractPageByCustomer(@Valid CrmContractPageReqVO pageVO) { + Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空"); + PageResult pageResult = contractService.getContractPageByCustomerId(pageVO); + return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetailList(pageResult.getList()))); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出合同 Excel") + @PreAuthorize("@ss.hasPermission('crm:contract:export')") + @OperateLog(type = EXPORT) + public void exportContractExcel(@Valid CrmContractPageReqVO exportReqVO, + HttpServletResponse response) throws IOException { + PageResult pageResult = contractService.getContractPage(exportReqVO, getLoginUserId()); + // 导出 Excel + ExcelUtils.write(response, "合同.xls", "数据", CrmContractRespVO.class, + BeanUtils.toBean(pageResult.getList(), CrmContractRespVO.class)); + } + + @PutMapping("/transfer") + @Operation(summary = "合同转移") + @PreAuthorize("@ss.hasPermission('crm:contract:update')") + public CommonResult transferContract(@Valid @RequestBody CrmContractTransferReqVO reqVO) { + contractService.transferContract(reqVO, getLoginUserId()); + return success(true); + } + + @PutMapping("/submit") + @Operation(summary = "提交合同审批") + @PreAuthorize("@ss.hasPermission('crm:contract:update')") + public CommonResult submitContract(@RequestParam("id") Long id) { + contractService.submitContract(id, getLoginUserId()); + return success(true); + } + + /** + * 构建详细的合同结果 + * + * @param contractList 原始合同信息 + * @return 细的合同结果 + */ + private List buildContractDetailList(List contractList) { + if (CollUtil.isEmpty(contractList)) { + return Collections.emptyList(); + } + // 1. 获取客户列表 + List customerList = customerService.getCustomerList( + convertSet(contractList, CrmContractDO::getCustomerId)); + // 2. 获取创建人、负责人列表 + Map userMap = adminUserApi.getUserMap(convertListByFlatMap(contractList, + contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); + // 3. 获取联系人 + Map contactMap = convertMap(contactService.getContactListByIds(convertSet(contractList, + CrmContractDO::getContactId)), CrmContactDO::getId); + // 4. 获取商机 + Map businessMap = convertMap(businessService.getBusinessList(convertSet(contractList, + CrmContractDO::getBusinessId)), CrmBusinessDO::getId); + // 5. 获取合同关联的商品 + Map contractProductMap = null; + List productList = null; + if (contractList.size() == 1) { + List contractProductList = contractService.getContractProductListByContractId(contractList.get(0).getId()); + contractProductMap = convertMap(contractProductList, CrmContractProductDO::getProductId); + productList = productService.getProductListByIds(convertSet(contractProductList, CrmContractProductDO::getProductId)); + } + return CrmContractConvert.INSTANCE.convertList(contractList, userMap, customerList, contactMap, businessMap, contractProductMap, productList); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java new file mode 100644 index 000000000..c61a64ccf --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +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 CrmContractPageReqVO extends PageParam { + + /** + * 过期类型 - 即将过期 + */ + public static final Integer EXPIRY_TYPE_ABOUT_TO_EXPIRE = 1; + /** + * 过期类型 - 已过期 + */ + public static final Integer EXPIRY_TYPE_EXPIRED = 2; + + @Schema(description = "合同编号", example = "XYZ008") + private String no; + + @Schema(description = "合同名称", example = "王五") + private String name; + + @Schema(description = "客户编号", example = "18336") + private Long customerId; + + @Schema(description = "商机编号", example = "10864") + private Long businessId; + + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + + @Schema(description = "审批状态", example = "20") + @InEnum(CrmAuditStatusEnum.class) + private Integer auditStatus; + + @Schema(description = "过期类型", example = "1") + private Integer expiryType; // 过期类型,为 null 时则表示全部 + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java new file mode 100644 index 000000000..da6239414 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java @@ -0,0 +1,165 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - CRM 合同 Response VO") +@Data +@ExcelIgnoreUnannotated +public class CrmContractRespVO { + + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + @ExcelProperty("合同编号") + private Long id; + + @Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @ExcelProperty("合同名称") + private String name; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336") + @ExcelProperty("客户编号") + private Long customerId; + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336") + @ExcelProperty("客户名称") + private String customerName; + + @Schema(description = "商机编号", example = "10864") + @ExcelProperty("商机编号") + private Long businessId; + @Schema(description = "商机名称", example = "10864") + @ExcelProperty("商机名称") + private String businessName; + + @Schema(description = "工作流编号", example = "1043") + @ExcelProperty("工作流编号") + private Long processInstanceId; + + @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("下单日期") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime orderDate; + + @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17144") + @ExcelProperty("负责人的用户编号") + private Long ownerUserId; + + // TODO @芋艿:未来应该支持自动生成; + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20230101") + @ExcelProperty("合同编号") + private String no; + + @Schema(description = "开始时间") + @ExcelProperty("开始时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime startTime; + + @Schema(description = "结束时间") + @ExcelProperty("结束时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime endTime; + + @Schema(description = "合同金额", example = "5617") + @ExcelProperty("合同金额") + private Integer price; + + @Schema(description = "整单折扣") + @ExcelProperty("整单折扣") + private Integer discountPercent; + + @Schema(description = "产品总金额", example = "19510") + @ExcelProperty("产品总金额") + private Integer productPrice; + + @Schema(description = "联系人编号", example = "18546") + @ExcelProperty("联系人编号") + private Long contactId; + @Schema(description = "联系人编号", example = "18546") + @ExcelProperty("联系人编号") + private String contactName; + + @Schema(description = "公司签约人", example = "14036") + @ExcelProperty("公司签约人") + private Long signUserId; + @Schema(description = "公司签约人", example = "14036") + @ExcelProperty("公司签约人") + private String signUserName; + + @Schema(description = "最后跟进时间") + @ExcelProperty("最后跟进时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime contactLastTime; + + @Schema(description = "备注", example = "你猜") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime createTime; + + @Schema(description = "创建人", example = "25682") + @ExcelProperty("创建人") + private String creator; + + @Schema(description = "创建人名字", example = "test") + @ExcelProperty("创建人名字") + private String creatorName; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("更新时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime updateTime; + + @Schema(description = "负责人", example = "test") + @ExcelProperty("负责人") + private String ownerUserName; + + @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @ExcelProperty("审批状态") + private Integer auditStatus; + + @Schema(description = "产品列表") + private List productItems; + + // TODO @puhui999:可以直接叫 Item + @Schema(description = "产品列表") + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class CrmContractProductItemRespVO { + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") + private Long id; + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是产品") + private String name; + + @Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "N881") + private String no; + + @Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + private Integer unit; + + @Schema(description = "价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") + private Integer price; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") + private Integer count; + + @Schema(description = "产品折扣", example = "99") + private Integer discountPercent; + + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java new file mode 100644 index 000000000..20b20580e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java @@ -0,0 +1,115 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; + +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmBusinessParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmContactParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAdminUserParseFunction; +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - CRM 合同创建/更新 Request VO") +@Data +public class CrmContractSaveReqVO { + + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + private Long id; + + @Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @DiffLogField(name = "合同名称") + @NotNull(message = "合同名称不能为空") + private String name; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336") + @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME) + @NotNull(message = "客户编号不能为空") + private Long customerId; + + @Schema(description = "商机编号", example = "10864") + @DiffLogField(name = "商机", function = CrmBusinessParseFunction.NAME) + private Long businessId; + + @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED) + @DiffLogField(name = "下单日期") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @NotNull(message = "下单日期不能为空") + private LocalDateTime orderDate; + + @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17144") + @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME) + @NotNull(message = "负责人不能为空") + private Long ownerUserId; + + // TODO @芋艿:未来应该支持自动生成; + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20230101") + @DiffLogField(name = "合同编号") + @NotNull(message = "合同编号不能为空") + private String no; + + @Schema(description = "开始时间") + @DiffLogField(name = "开始时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime startTime; + + @Schema(description = "结束时间") + @DiffLogField(name = "结束时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime endTime; + + @Schema(description = "合同金额", example = "5617") + @DiffLogField(name = "合同金额") + private Integer price; + + @Schema(description = "整单折扣") + @DiffLogField(name = "整单折扣") + private Integer discountPercent; + + @Schema(description = "产品总金额", example = "19510") + @DiffLogField(name = "产品总金额") + private Integer productPrice; + + @Schema(description = "联系人编号", example = "18546") + @DiffLogField(name = "联系人", function = CrmContactParseFunction.NAME) + private Long contactId; + + @Schema(description = "公司签约人", example = "14036") + @DiffLogField(name = "公司签约人", function = SysAdminUserParseFunction.NAME) + private Long signUserId; + + @Schema(description = "备注", example = "你猜") + @DiffLogField(name = "备注") + private String remark; + + + @Schema(description = "产品列表") + private List productItems; + + @Schema(description = "产品列表") + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class CrmContractProductItem { + + @Schema(description = "产品编号", example = "20529") + @NotNull(message = "产品编号不能为空") + private Long id; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") + @NotNull(message = "产品数量不能为空") + private Integer count; + + @Schema(description = "产品折扣") + private Integer discountPercent; + + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractTransferReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractTransferReqVO.java new file mode 100644 index 000000000..88227c560 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractTransferReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - CRM 合同转移 Request VO") +@Data +public class CrmContractTransferReqVO { + + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + @NotNull(message = "联系人编号不能为空") + private Long id; + + @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + @NotNull(message = "新负责人的用户编号不能为空") + private Long newOwnerUserId; + + @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @InEnum(value = CrmPermissionLevelEnum.class) + private Integer oldOwnerPermissionLevel; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http new file mode 100644 index 000000000..9a6cb93a8 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http @@ -0,0 +1,16 @@ +### 请求 /transfer +PUT {{baseUrl}}/crm/customer/transfer +Content-Type: application/-id: {{adminTenentId}}json +Authorization: Bearer {{token}} +tenant + +{ + "id": 10, + "newOwnerUserId": 127 +} + +### 自定义日志记录结果 +### 操作日志 ===> OperateLogV2CreateReqBO(traceId=, userId=1, userType=2, module=CRM-客户, name=客户转移, bizId=10, content=把客户【张三】的负责人从【芋道源码(15612345678)】变更为了【tttt】, requestMethod=PUT, requestUrl=/admin-api/crm/customer/transfer, userIp=127.0.0.1, userAgent=Apache-HttpClient/4.5.14 (Java/17.0.9)) + +### diff 日志 +### | 操作日志 ===> OperateLogV2CreateReqBO(traceId=, userId=1, userType=2, module=CRM-客户, name=更新客户, bizId=11, content=更新了客户【所属行业】从【H 住宿和餐饮业】修改为【D 电力、热力、燃气及水生产和供应业】;【客户等级】从【C (非优先客户)】修改为【A (重点客户)】;【客户来源】从【线上咨询】修改为【预约上门】, requestMethod=PUT, requestUrl=/admin-api/crm/customer/update, userIp=0:0:0:0:0:0:0:1, userAgent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36) diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java new file mode 100644 index 000000000..da23f2b47 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java @@ -0,0 +1,272 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*; +import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +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 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.mapstruct.ap.internal.util.Collections; +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.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED; + +@Tag(name = "管理后台 - CRM 客户") +@RestController +@RequestMapping("/crm/customer") +@Validated +public class CrmCustomerController { + + @Resource + private CrmCustomerService customerService; + @Resource + private CrmCustomerPoolConfigService customerPoolConfigService; + @Resource + private DeptApi deptApi; + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建客户") + @PreAuthorize("@ss.hasPermission('crm:customer:create')") + public CommonResult createCustomer(@Valid @RequestBody CrmCustomerSaveReqVO createReqVO) { + return success(customerService.createCustomer(createReqVO, getLoginUserId())); + } + + @PutMapping("/update") + @Operation(summary = "更新客户") + @PreAuthorize("@ss.hasPermission('crm:customer:update')") + public CommonResult updateCustomer(@Valid @RequestBody CrmCustomerSaveReqVO updateReqVO) { + customerService.updateCustomer(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除客户") + @Parameter(name = "id", description = "客户编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:customer:delete')") + public CommonResult deleteCustomer(@RequestParam("id") Long id) { + customerService.deleteCustomer(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得客户") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:customer:query')") + public CommonResult getCustomer(@RequestParam("id") Long id) { + // 1. 获取客户 + CrmCustomerDO customer = customerService.getCustomer(id); + if (customer == null) { + return success(null); + } + // 2. 拼接数据 + Map userMap = adminUserApi.getUserMap( + Collections.asSet(Long.valueOf(customer.getCreator()), customer.getOwnerUserId())); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); + return success(CrmCustomerConvert.INSTANCE.convert(customer, userMap, deptMap)); + } + + @GetMapping("/page") + @Operation(summary = "获得客户分页") + @PreAuthorize("@ss.hasPermission('crm:customer:query')") + public CommonResult> getCustomerPage(@Valid CrmCustomerPageReqVO pageVO) { + // 1. 查询客户分页 + PageResult pageResult = customerService.getCustomerPage(pageVO, getLoginUserId()); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + + // 2. 拼接数据 + Map poolDayMap = Boolean.TRUE.equals(pageVO.getPool()) ? null : + getPoolDayMap(pageResult.getList()); // 客户界面,需要查看距离进入公海的时间 + Map userMap = adminUserApi.getUserMap( + convertSetByFlatMap(pageResult.getList(), user -> Stream.of(Long.parseLong(user.getCreator()), user.getOwnerUserId()))); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); + return success(CrmCustomerConvert.INSTANCE.convertPage(pageResult, userMap, deptMap, poolDayMap)); + } + + @GetMapping("/put-in-pool-remind-page") + @Operation(summary = "获得待进入公海客户分页") + @PreAuthorize("@ss.hasPermission('crm:customer:query')") + public CommonResult> getPutInPoolRemindCustomerPage(@Valid CrmCustomerPageReqVO pageVO) { + // 获取公海配置 TODO @dbh52:合并到 getPutInPoolRemindCustomerPage 会更合适哈; + CrmCustomerPoolConfigDO poolConfigDO = customerPoolConfigService.getCustomerPoolConfig(); + if (ObjUtil.isNull(poolConfigDO) + || Boolean.FALSE.equals(poolConfigDO.getEnabled()) + || Boolean.FALSE.equals(poolConfigDO.getNotifyEnabled()) + ) { // TODO @dbh52:这个括号,一般不换行,在 java 这里; + throw exception(CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED); + } + + // 1. 查询客户分页 + PageResult pageResult = customerService.getPutInPoolRemindCustomerPage(pageVO, poolConfigDO, getLoginUserId()); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + + // 2. 拼接数据 + // TODO @芋艿:合并 getCustomerPage 和 getPutInPoolRemindCustomerPage 的后置处理; + Map poolDayMap = getPoolDayMap(pageResult.getList()); // 客户界面,需要查看距离进入公海的时间 + Map userMap = adminUserApi.getUserMap( + convertSetByFlatMap(pageResult.getList(), user -> Stream.of(Long.parseLong(user.getCreator()), user.getOwnerUserId()))); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); + return success(CrmCustomerConvert.INSTANCE.convertPage(pageResult, userMap, deptMap, poolDayMap)); + } + + /** + * 获取距离进入公海的时间 + * + * @param customerList 客户列表 + * @return Map + */ + private Map getPoolDayMap(List customerList) { + CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig(); + if (poolConfig == null || !poolConfig.getEnabled()) { + return MapUtil.empty(); + } + return convertMap(customerList, CrmCustomerDO::getId, customer -> { + // 1.1 未成交放入公海天数 + long dealExpireDay = 0; + if (!customer.getDealStatus()) { + dealExpireDay = poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime()); + } + // 1.2 未跟进放入公海天数 + LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime()); + long contactExpireDay = poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime); + if (contactExpireDay < 0) { + contactExpireDay = 0; + } + // 2. 返回最小的天数 + return Math.min(dealExpireDay, contactExpireDay); + }); + } + + @GetMapping(value = "/list-all-simple") + @Operation(summary = "获取客户精简信息列表", description = "只包含有读权限的客户,主要用于前端的下拉选项") + public CommonResult> getSimpleDeptList() { + CrmCustomerPageReqVO reqVO = new CrmCustomerPageReqVO(); + reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页 + List list = customerService.getCustomerPage(reqVO, getLoginUserId()).getList(); + return success(convertList(list, customer -> // 只返回 id、name 精简字段 + new CrmCustomerRespVO().setId(customer.getId()).setName(customer.getName()))); + } + + // TODO @puhui999:公海的导出,前端可以接下 + @GetMapping("/export-excel") + @Operation(summary = "导出客户 Excel") + @PreAuthorize("@ss.hasPermission('crm:customer:export')") + @OperateLog(type = EXPORT) + public void exportCustomerExcel(@Valid CrmCustomerPageReqVO pageVO, + HttpServletResponse response) throws IOException { + pageVO.setPageSize(PAGE_SIZE_NONE); // 不分页 + List list = customerService.getCustomerPage(pageVO, getLoginUserId()).getList(); + // 导出 Excel + ExcelUtils.write(response, "客户.xls", "数据", CrmCustomerRespVO.class, + BeanUtils.toBean(list, CrmCustomerRespVO.class)); + } + + @GetMapping("/get-import-template") + @Operation(summary = "获得导入客户模板") + public void importTemplate(HttpServletResponse response) throws IOException { + // 手动创建导出 demo + List list = Arrays.asList( + CrmCustomerImportExcelVO.builder().name("芋道").industryId(1).level(1).source(1).mobile("15601691300").telephone("") + .website("https://doc.iocoder.cn/").qq("").wechat("").email("yunai@iocoder.cn").description("").remark("") + .areaId(null).detailAddress("").build(), + CrmCustomerImportExcelVO.builder().name("源码").industryId(1).level(1).source(1).mobile("15601691300").telephone("") + .website("https://doc.iocoder.cn/").qq("").wechat("").email("yunai@iocoder.cn").description("").remark("") + .areaId(null).detailAddress("").build() + ); + // 输出 + ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list); + } + + @PostMapping("/import") + @Operation(summary = "导入客户") + @PreAuthorize("@ss.hasPermission('system:customer:import')") + public CommonResult importExcel(@Valid @RequestBody CrmCustomerImportReqVO importReqVO) + throws Exception { + List list = ExcelUtils.read(importReqVO.getFile(), CrmCustomerImportExcelVO.class); + return success(customerService.importCustomerList(list, importReqVO)); + } + + @PutMapping("/transfer") + @Operation(summary = "转移客户") + @PreAuthorize("@ss.hasPermission('crm:customer:update')") + public CommonResult transferCustomer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) { + customerService.transferCustomer(reqVO, getLoginUserId()); + return success(true); + } + + @PutMapping("/lock") + @Operation(summary = "锁定/解锁客户") + @PreAuthorize("@ss.hasPermission('crm:customer:update')") + public CommonResult lockCustomer(@Valid @RequestBody CrmCustomerLockReqVO lockReqVO) { + customerService.lockCustomer(lockReqVO, getLoginUserId()); + return success(true); + } + + // ==================== 公海相关操作 ==================== + + @PutMapping("/put-pool") + @Operation(summary = "数据放入公海") + @Parameter(name = "id", description = "客户编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:customer:update')") + public CommonResult putCustomerPool(@RequestParam("id") Long id) { + customerService.putCustomerPool(id); + return success(true); + } + + @PutMapping("/receive") + @Operation(summary = "领取公海客户") + @Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3") + @PreAuthorize("@ss.hasPermission('crm:customer:receive')") + public CommonResult receiveCustomer(@RequestParam(value = "ids") List ids) { + customerService.receiveCustomer(ids, getLoginUserId(), Boolean.TRUE); + return success(true); + } + + @PutMapping("/distribute") + @Operation(summary = "分配公海给对应负责人") + @PreAuthorize("@ss.hasPermission('crm:customer:distribute')") + public CommonResult distributeCustomer(@Valid @RequestBody CrmCustomerDistributeReqVO distributeReqVO) { + customerService.receiveCustomer(distributeReqVO.getIds(), distributeReqVO.getOwnerUserId(), Boolean.FALSE); + return success(true); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java new file mode 100644 index 000000000..95f4ccd8f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java @@ -0,0 +1,97 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigSaveReqVO; +import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerLimitConfigConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerLimitConfigService; +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 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.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Collection; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; + +@Tag(name = "管理后台 - CRM 客户限制配置") +@RestController +@RequestMapping("/crm/customer-limit-config") +@Validated +public class CrmCustomerLimitConfigController { + + @Resource + private CrmCustomerLimitConfigService customerLimitConfigService; + + @Resource + private DeptApi deptApi; + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建客户限制配置") + @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:create')") + public CommonResult createCustomerLimitConfig(@Valid @RequestBody CrmCustomerLimitConfigSaveReqVO createReqVO) { + return success(customerLimitConfigService.createCustomerLimitConfig(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新客户限制配置") + @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:update')") + public CommonResult updateCustomerLimitConfig(@Valid @RequestBody CrmCustomerLimitConfigSaveReqVO updateReqVO) { + customerLimitConfigService.updateCustomerLimitConfig(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除客户限制配置") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:delete')") + public CommonResult deleteCustomerLimitConfig(@RequestParam("id") Long id) { + customerLimitConfigService.deleteCustomerLimitConfig(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得客户限制配置") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:query')") + public CommonResult getCustomerLimitConfig(@RequestParam("id") Long id) { + CrmCustomerLimitConfigDO customerLimitConfig = customerLimitConfigService.getCustomerLimitConfig(id); + // 拼接数据 + Map userMap = adminUserApi.getUserMap(customerLimitConfig.getUserIds()); + Map deptMap = deptApi.getDeptMap(customerLimitConfig.getDeptIds()); + return success(CrmCustomerLimitConfigConvert.INSTANCE.convert(customerLimitConfig, userMap, deptMap)); + } + + @GetMapping("/page") + @Operation(summary = "获得客户限制配置分页") + @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:query')") + public CommonResult> getCustomerLimitConfigPage(@Valid CrmCustomerLimitConfigPageReqVO pageVO) { + PageResult pageResult = customerLimitConfigService.getCustomerLimitConfigPage(pageVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + // 拼接数据 + Map userMap = adminUserApi.getUserMap( + convertSetByFlatMap(pageResult.getList(), CrmCustomerLimitConfigDO::getUserIds, Collection::stream)); + Map deptMap = deptApi.getDeptMap( + convertSetByFlatMap(pageResult.getList(), CrmCustomerLimitConfigDO::getDeptIds, Collection::stream)); + return success(CrmCustomerLimitConfigConvert.INSTANCE.convertPage(pageResult, userMap, deptMap)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java new file mode 100644 index 000000000..ca3b1ac62 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerPoolConfigController.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig.CrmCustomerPoolConfigRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig.CrmCustomerPoolConfigSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - CRM 客户公海配置") +@RestController +@RequestMapping("/crm/customer-pool-config") +@Validated +public class CrmCustomerPoolConfigController { + + @Resource + private CrmCustomerPoolConfigService customerPoolConfigService; + + @GetMapping("/get") + @Operation(summary = "获取客户公海规则设置") + @PreAuthorize("@ss.hasPermission('crm:customer-pool-config:query')") + public CommonResult getCustomerPoolConfig() { + CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig(); + return success(BeanUtils.toBean(poolConfig, CrmCustomerPoolConfigRespVO.class)); + } + + @PutMapping("/save") + @Operation(summary = "更新客户公海规则设置") + @PreAuthorize("@ss.hasPermission('crm:customer-pool-config:update')") + public CommonResult saveCustomerPoolConfig(@Valid @RequestBody CrmCustomerPoolConfigSaveReqVO updateReqVO) { + customerPoolConfigService.saveCustomerPoolConfig(updateReqVO); + return success(true); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerDistributeReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerDistributeReqVO.java new file mode 100644 index 000000000..24113ed12 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerDistributeReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - CRM 客户分配公海给对应负责人 Request VO") +@Data +public class CrmCustomerDistributeReqVO { + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1024]") + @NotEmpty(message = "客户编号不能为空") + private List ids; + + @Schema(description = "负责人", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "负责人不能为空") + private Long ownerUserId; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java new file mode 100644 index 000000000..4f57564dd --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java @@ -0,0 +1,71 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*; + +/** + * 客户 Excel 导入 VO + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = false) // 设置 chain = false,避免用户导入有问题 +public class CrmCustomerImportExcelVO { + + @ExcelProperty("客户名称") + private String name; + + // TODO @puhui999:industryId、level、source 字段,可以研究下怎么搞下拉框 + @ExcelProperty(value = "所属行业", converter = DictConvert.class) + @DictFormat(CRM_CUSTOMER_INDUSTRY) + private Integer industryId; + + @ExcelProperty(value = "客户等级", converter = DictConvert.class) + @DictFormat(CRM_CUSTOMER_LEVEL) + private Integer level; + + @ExcelProperty(value = "客户来源", converter = DictConvert.class) + @DictFormat(CRM_CUSTOMER_SOURCE) + private Integer source; + + @ExcelProperty("手机") + private String mobile; + + @ExcelProperty("电话") + private String telephone; + + @ExcelProperty("网址") + private String website; + + @ExcelProperty("QQ") + private String qq; + + @ExcelProperty("微信") + private String wechat; + + @ExcelProperty("邮箱") + private String email; + + @ExcelProperty("客户描述") + private String description; + + @ExcelProperty("备注") + private String remark; + + // TODO @puhui999:需要选择省市区,需要研究下,怎么搞合理点; + @ExcelProperty("地区编号") + private Integer areaId; + + @ExcelProperty("详细地址") + private String detailAddress; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportReqVO.java new file mode 100644 index 000000000..a396dc50b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +@Schema(description = "管理后台 - 客户导入 Request VO") +@Data +@Builder +public class CrmCustomerImportReqVO { + + @Schema(description = "Excel 文件", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "Excel 文件不能为空") + private MultipartFile file; + + @Schema(description = "是否支持更新", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "是否支持更新不能为空") + private Boolean updateSupport; + + @Schema(description = "负责人", example = "1") + private Long ownerUserId; // 为 null 则客户进入公海 + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java new file mode 100644 index 000000000..de35b7b92 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Schema(description = "管理后台 - 客户导入 Response VO") +@Data +@Builder +public class CrmCustomerImportRespVO { + + @Schema(description = "创建成功的客户名数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List createCustomerNames; + + @Schema(description = "更新成功的客户名数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List updateCustomerNames; + + @Schema(description = "导入失败的客户集合,key 为客户名,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED) + private Map failureCustomerNames; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java new file mode 100644 index 000000000..1cf9ff382 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - CRM 客户锁定/解锁 Request VO") +@Data +public class CrmCustomerLockReqVO { + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private Long id; + + @Schema(description = "客户锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Boolean lockStatus; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerPageReqVO.java new file mode 100644 index 000000000..bded50473 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerPageReqVO.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +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 CrmCustomerPageReqVO extends PageParam { + + @Schema(description = "客户名称", example = "赵六") + private String name; + + @Schema(description = "手机", example = "18000000000") + private String mobile; + + @Schema(description = "所属行业", example = "1") + private Integer industryId; + + @Schema(description = "客户等级", example = "1") + private Integer level; + + @Schema(description = "客户来源", example = "1") + private Integer source; + + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + + @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + private Boolean pool; // null 则表示为不是公海数据 + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerRespVO.java new file mode 100644 index 000000000..69c75856f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerRespVO.java @@ -0,0 +1,138 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - CRM 客户 Response VO") +@Data +@ExcelIgnoreUnannotated +public class CrmCustomerRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty("编号") + private Long id; + + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty("客户名称") + private String name; + + @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "跟进状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) + private Boolean followUpStatus; + + @Schema(description = "锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "锁定状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) + private Boolean lockStatus; + + @Schema(description = "成交状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "成交状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) + private Boolean dealStatus; + + @Schema(description = "所属行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "所属行业", converter = DictConvert.class) + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY) + private Integer industryId; + + @Schema(description = "客户等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "客户等级", converter = DictConvert.class) + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL) + private Integer level; + + @Schema(description = "客户来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @ExcelProperty(value = "客户来源", converter = DictConvert.class) + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE) + private Integer source; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("手机") + private String mobile; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("电话") + private String telephone; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("网址") + private String website; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("QQ") + private String qq; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("wechat") + private String wechat; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("email") + private String email; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("客户描述") + private String description; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("负责人的用户编号") + private Long ownerUserId; + @Schema(description = "负责人名字", example = "25682") + @ExcelProperty("负责人名字") + private String ownerUserName; + @Schema(description = "负责人部门") + @ExcelProperty("负责人部门") + private String ownerUserDeptName; + + @Schema(description = "地区编号", example = "1024") + @ExcelProperty("地区编号") + private Integer areaId; + @Schema(description = "地区名称", example = "北京市") + @ExcelProperty("地区名称") + private String areaName; + @Schema(description = "详细地址", example = "北京市成华大道") + @ExcelProperty("详细地址") + private String detailAddress; + + @Schema(description = "最后跟进时间") + @ExcelProperty("最后跟进时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime contactLastTime; + + @Schema(description = "下次联系时间") + @ExcelProperty("下次联系时间") + private LocalDateTime contactNextTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("更新时间") + private LocalDateTime updateTime; + + @Schema(description = "创建人", example = "1024") + @ExcelProperty("创建人") + private String creator; + @Schema(description = "创建人名字", example = "芋道源码") + @ExcelProperty("创建人名字") + private String creatorName; + + @Schema(description = "距离加入公海时间", example = "1") + private Long poolDay; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java new file mode 100644 index 000000000..d6d73b142 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java @@ -0,0 +1,106 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.framework.common.validation.Mobile; +import cn.iocoder.yudao.framework.common.validation.Telephone; +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLevelEnum; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerIndustryParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerLevelParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerSourceParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAreaParseFunction; +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY; + +@Schema(description = "管理后台 - CRM 客户新增/修改 Request VO") +@Data +public class CrmCustomerSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private Long id; + + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + @DiffLogField(name = "客户名称") + @NotEmpty(message = "客户名称不能为空") + private String name; + + @Schema(description = "所属行业", example = "1") + @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME) + @DictFormat(CRM_CUSTOMER_INDUSTRY) + private Integer industryId; + + @Schema(description = "客户等级", example = "2") + @DiffLogField(name = "客户等级", function = CrmCustomerLevelParseFunction.NAME) + @InEnum(CrmCustomerLevelEnum.class) + private Integer level; + + @Schema(description = "客户来源", example = "3") + @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME) + private Integer source; + + @Schema(description = "手机", example = "18000000000") + @DiffLogField(name = "手机") + @Mobile + private String mobile; + + @Schema(description = "电话", example = "18000000000") + @DiffLogField(name = "电话") + @Telephone + private String telephone; + + @Schema(description = "网址", example = "https://www.baidu.com") + @DiffLogField(name = "网址") + private String website; + + @Schema(description = "QQ", example = "123456789") + @DiffLogField(name = "QQ") + @Size(max = 20, message = "QQ长度不能超过 20 个字符") + private String qq; + + @Schema(description = "微信", example = "123456789") + @DiffLogField(name = "微信") + @Size(max = 255, message = "微信长度不能超过 255 个字符") + private String wechat; + + @Schema(description = "邮箱", example = "123456789@qq.com") + @DiffLogField(name = "邮箱") + @Email(message = "邮箱格式不正确") + @Size(max = 255, message = "邮箱长度不能超过 255 个字符") + private String email; + + @Schema(description = "客户描述", example = "任意文字") + @DiffLogField(name = "客户描述") + @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符") + private String description; + + @Schema(description = "备注", example = "随便") + @DiffLogField(name = "备注") + private String remark; + + @Schema(description = "地区编号", example = "20158") + @DiffLogField(name = "地区编号", function = SysAreaParseFunction.NAME) + private Integer areaId; + + @Schema(description = "详细地址", example = "北京市海淀区") + @DiffLogField(name = "详细地址") + private String detailAddress; + + @Schema(description = "下次联系时间") + @DiffLogField(name = "下次联系时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime contactNextTime; + + @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private Long ownerUserId; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java new file mode 100644 index 000000000..9bdc43532 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - CRM 客户转移 Request VO") +@Data +public class CrmCustomerTransferReqVO { + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + @NotNull(message = "客户编号不能为空") + private Long id; + + /** + * 新负责人的用户编号 + */ + @Schema(description = "新负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10430") + @NotNull(message = "新负责人的用户编号不能为空") + private Long newOwnerUserId; + + /** + * 老负责人加入团队后的权限级别。如果 null 说明移除 + * + * 关联 {@link CrmPermissionLevelEnum} + */ + @Schema(description = "老负责人加入团队后的权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer oldOwnerPermissionLevel; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigPageReqVO.java new file mode 100644 index 000000000..37ce11009 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigPageReqVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig; + +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 = "管理后台 - 客户限制配置分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmCustomerLimitConfigPageReqVO extends PageParam { + + @Schema(description = "规则类型", example = "1") + private Integer type; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigRespVO.java new file mode 100644 index 000000000..8ff03ad66 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigRespVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig; + +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 客户限制配置 Response VO") +@Data +public class CrmCustomerLimitConfigRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "27930") + private Long id; + + @Schema(description = "规则类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer type; + + @Schema(description = "规则适用人群") + private List userIds; + + @Schema(description = "规则适用部门") + private List deptIds; + + @Schema(description = "数量上限", requiredMode = Schema.RequiredMode.REQUIRED, example = "28384") + private Integer maxCount; + + @Schema(description = "成交客户是否占有拥有客户数") + private Boolean dealCountEnabled; + + @Schema(description = "规则适用人群名称") + private List users; + + @Schema(description = "规则适用部门名称") + private List depts; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigSaveReqVO.java new file mode 100644 index 000000000..a0e88e3f6 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/limitconfig/CrmCustomerLimitConfigSaveReqVO.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig; + +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAdminUserParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysDeptParseFunction; +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 客户限制配置创建/更新 Request VO") +@Data +public class CrmCustomerLimitConfigSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "27930") + private Long id; + + @Schema(description = "规则类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "规则类型不能为空") + @DiffLogField(name = "规则类型") + private Integer type; + + @Schema(description = "规则适用人群") + @DiffLogField(name = "规则适用人群", function = SysAdminUserParseFunction.NAME) + private List userIds; + + @Schema(description = "规则适用部门") + @DiffLogField(name = "规则适用部门", function = SysDeptParseFunction.NAME) + private List deptIds; + + @Schema(description = "数量上限", requiredMode = Schema.RequiredMode.REQUIRED, example = "28384") + @NotNull(message = "数量上限不能为空") + @DiffLogField(name = "数量上限") + private Integer maxCount; + + @Schema(description = "成交客户是否占有拥有客户数(当 type = 1 时)") + @DiffLogField(name = "成交客户是否占有拥有客户数") + private Boolean dealCountEnabled; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigRespVO.java new file mode 100644 index 000000000..2aeb3402e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigRespVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - CRM 客户公海规则 Response VO") +@Data +public class CrmCustomerPoolConfigRespVO { + + @Schema(description = "是否启用客户公海", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @NotNull(message = "是否启用客户公海不能为空") + private Boolean enabled; + + @Schema(description = "未跟进放入公海天数", example = "2") + private Integer contactExpireDays; + + @Schema(description = "未成交放入公海天数", example = "2") + private Integer dealExpireDays; + + @Schema(description = "是否开启提前提醒", example = "true") + private Boolean notifyEnabled; + + @Schema(description = "提前提醒天数", example = "2") + private Integer notifyDays; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigSaveReqVO.java new file mode 100644 index 000000000..3215f8645 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/poolconfig/CrmCustomerPoolConfigSaveReqVO.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig; + +import cn.hutool.core.util.BooleanUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Objects; + +@Schema(description = "管理后台 - CRM 客户公海配置的创建/更新 Request VO") +@Data +public class CrmCustomerPoolConfigSaveReqVO { + + @Schema(description = "是否启用客户公海", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @DiffLogField(name = "是否启用客户公海") + @NotNull(message = "是否启用客户公海不能为空") + private Boolean enabled; + + @Schema(description = "未跟进放入公海天数", example = "2") + @DiffLogField(name = "未跟进放入公海天数") + private Integer contactExpireDays; + + @Schema(description = "未成交放入公海天数", example = "2") + @DiffLogField(name = "未成交放入公海天数") + private Integer dealExpireDays; + + @Schema(description = "是否开启提前提醒", example = "true") + @DiffLogField(name = "是否开启提前提醒") + private Boolean notifyEnabled; + + @Schema(description = "提前提醒天数", example = "2") + @DiffLogField(name = "提前提醒天数") + private Integer notifyDays; + + @AssertTrue(message = "未成交放入公海天数不能为空") + @JsonIgnore + public boolean isDealExpireDaysValid() { + if (!BooleanUtil.isTrue(getEnabled())) { + return true; + } + return Objects.nonNull(getDealExpireDays()); + } + + @AssertTrue(message = "未跟进放入公海天数不能为空") + @JsonIgnore + public boolean isContactExpireDaysValid() { + if (!BooleanUtil.isTrue(getEnabled())) { + return true; + } + return Objects.nonNull(getContactExpireDays()); + } + + @AssertTrue(message = "提前提醒天数不能为空") + @JsonIgnore + public boolean isNotifyDaysValid() { + if (!BooleanUtil.isTrue(getNotifyEnabled())) { + return true; + } + return Objects.nonNull(getNotifyDays()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java new file mode 100644 index 000000000..f0b726353 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java @@ -0,0 +1,92 @@ +package cn.iocoder.yudao.module.crm.controller.admin.followup; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; +import cn.iocoder.yudao.module.crm.service.followup.CrmFollowUpRecordService; +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.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + + +@Tag(name = "管理后台 - 跟进记录") +@RestController +@RequestMapping("/crm/follow-up-record") +@Validated +public class CrmFollowUpRecordController { + + @Resource + private CrmFollowUpRecordService followUpRecordService; + @Resource + private CrmContactService contactService; + @Resource + private CrmBusinessService businessService; + + @PostMapping("/create") + @Operation(summary = "创建跟进记录") + @PreAuthorize("@ss.hasPermission('crm:follow-up-record:create')") + public CommonResult createFollowUpRecord(@Valid @RequestBody CrmFollowUpRecordSaveReqVO createReqVO) { + return success(followUpRecordService.createFollowUpRecord(createReqVO)); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除跟进记录") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:follow-up-record:delete')") + public CommonResult deleteFollowUpRecord(@RequestParam("id") Long id) { + followUpRecordService.deleteFollowUpRecord(id, getLoginUserId()); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得跟进记录") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:follow-up-record:query')") + public CommonResult getFollowUpRecord(@RequestParam("id") Long id) { + CrmFollowUpRecordDO followUpRecord = followUpRecordService.getFollowUpRecord(id); + return success(BeanUtils.toBean(followUpRecord, CrmFollowUpRecordRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得跟进记录分页") + @PreAuthorize("@ss.hasPermission('crm:follow-up-record:query')") + public CommonResult> getFollowUpRecordPage(@Valid CrmFollowUpRecordPageReqVO pageReqVO) { + PageResult pageResult = followUpRecordService.getFollowUpRecordPage(pageReqVO); + /// 拼接数据 + Map contactMap = convertMap(contactService.getContactListByIds( + convertSetByFlatMap(pageResult.getList(), item -> item.getContactIds().stream())), CrmContactDO::getId); + Map businessMap = convertMap(businessService.getBusinessList( + convertSetByFlatMap(pageResult.getList(), item -> item.getBusinessIds().stream())), CrmBusinessDO::getId); + PageResult voPageResult = BeanUtils.toBean(pageResult, CrmFollowUpRecordRespVO.class, record -> { + record.setContactNames(new ArrayList<>()).setBusinessNames(new ArrayList<>()); + record.getContactIds().forEach(id -> MapUtils.findAndThen(contactMap, id, + contact -> record.getContactNames().add(contact.getName()))); + record.getContactIds().forEach(id -> MapUtils.findAndThen(businessMap, id, + business -> record.getBusinessNames().add(business.getName()))); + }); + return success(voPageResult); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordPageReqVO.java new file mode 100644 index 000000000..78c28a08f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordPageReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.crm.controller.admin.followup.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 = "管理后台 - 跟进记录分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmFollowUpRecordPageReqVO extends PageParam { + + @Schema(description = "数据类型", example = "2") + private Integer bizType; + + @Schema(description = "数据编号", example = "5564") + private Long bizId; + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java new file mode 100644 index 000000000..83bfd9edc --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.crm.controller.admin.followup.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_FOLLOW_UP_TYPE; + +@Schema(description = "管理后台 - 跟进记录 Response VO") +@Data +@ExcelIgnoreUnannotated +public class CrmFollowUpRecordRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28800") + private Long id; + + @Schema(description = "数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer bizType; + + @Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5564") + private Long bizId; + + @Schema(description = "跟进类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @DictFormat(CRM_FOLLOW_UP_TYPE) + private Integer type; + + @Schema(description = "跟进内容", requiredMode = Schema.RequiredMode.REQUIRED) + private String content; + + @Schema(description = "下次联系时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime nextTime; + + @Schema(description = "关联的商机编号数组") + private List businessIds; + @Schema(description = "关联的商机名称数组") + private List businessNames; + + @Schema(description = "关联的联系人编号数组") + private List contactIds; + @Schema(description = "关联的联系人名称数组") + private List contactNames; + + @Schema(description = "图片") + private List picUrls; + @Schema(description = "附件") + private List fileUrls; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java new file mode 100644 index 000000000..c4d53859b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.module.crm.controller.admin.followup.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 跟进记录新增/修改 Request VO") +@Data +public class CrmFollowUpRecordSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28800") + private Long id; + + @Schema(description = "数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "数据类型不能为空") + private Integer bizType; + + @Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5564") + @NotNull(message = "数据编号不能为空") + private Long bizId; + + @Schema(description = "跟进类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "跟进类型不能为空") + private Integer type; + + @Schema(description = "跟进内容", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "跟进内容不能为空") + private String content; + + @Schema(description = "下次联系时间", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "下次联系时间不能为空") + private LocalDateTime nextTime; + + @Schema(description = "关联的商机编号数组") + private List businessIds; + @Schema(description = "关联的联系人编号数组") + private List contactIds; + + @Schema(description = "图片") + private List picUrls; + @Schema(description = "附件") + private List fileUrls; + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java new file mode 100644 index 000000000..1793f66d2 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.crm.controller.admin.operatelog; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +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.operatelog.vo.CrmOperateLogPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogV2RespVO; +import cn.iocoder.yudao.module.crm.enums.LogRecordConstants; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.system.api.logger.OperateLogApi; +import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; + +@Tag(name = "管理后台 - CRM 操作日志") +@RestController +@RequestMapping("/crm/operate-log") +@Validated +public class CrmOperateLogController { + + @Resource + private OperateLogApi operateLogApi; + + /** + * {@link CrmBizTypeEnum} 与 {@link LogRecordConstants} 的映射关系 + */ + private static final Map BIZ_TYPE_MAP = new HashMap<>(); + + static { + BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_LEADS.getType(), CRM_LEADS_TYPE); + BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CUSTOMER.getType(), CRM_CUSTOMER_TYPE); + BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CONTACT.getType(), CRM_CONTACT_TYPE); + BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_BUSINESS.getType(), CRM_BUSINESS_TYPE); + BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CONTRACT.getType(), CRM_CONTRACT_TYPE); + BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_PRODUCT.getType(), CRM_PRODUCT_TYPE); + BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), CRM_RECEIVABLE_TYPE); + BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), CRM_RECEIVABLE_PLAN_TYPE); + } + + @GetMapping("/page") + @Operation(summary = "获得操作日志") + @PreAuthorize("@ss.hasPermission('crm:operate-log:query')") + public CommonResult> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) { + OperateLogV2PageReqDTO reqDTO = new OperateLogV2PageReqDTO(); + reqDTO.setPageSize(PAGE_SIZE_NONE); // 默认不分页,需要分页需注释 + reqDTO.setBizType(BIZ_TYPE_MAP.get(pageReqVO.getBizType())).setBizId(pageReqVO.getBizId()); + return success(BeanUtils.toBean(operateLogApi.getOperateLogPage(reqDTO).getCheckedData(), CrmOperateLogV2RespVO.class)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogPageReqVO.java new file mode 100644 index 000000000..f49ccb38b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogPageReqVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "管理后台 - CRM 操作日志 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmOperateLogPageReqVO extends PageParam { + + @Schema(description = "数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @InEnum(CrmBizTypeEnum.class) + @NotNull(message = "数据类型不能为空") + private Integer bizType; + + @Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "数据编号不能为空") + private Long bizId; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java new file mode 100644 index 000000000..b3405428f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - CRM 跟进 Response VO") +@Data +@ExcelIgnoreUnannotated +public class CrmOperateLogV2RespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private Long id; + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long userId; + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + private String userName; + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer userType; + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private String type; + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "修改客户") + private String subType; + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private Long bizId; + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "将什么从什么改为了什么") + private String action; + + @Schema(description = "编号", example = "{orderId: 1}") + private String extra; + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-01-01") + private LocalDateTime createTime; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.http b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.http new file mode 100644 index 000000000..1ef2bc1a1 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.http @@ -0,0 +1,32 @@ +### 请求 /add +POST {{baseUrl}}/crm/permission/create +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} + +{ + "userId": 1, + "bizType": 2, + "bizId": 2, + "level": 1 +} + +### 请求 /update +PUT {{baseUrl}}/crm/permission/update +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} + +{ + "userId": 1, + "bizType": 2, + "bizId": 2, + "level": 1, + "id": 1 +} + +### 请求 /delete +DELETE {{baseUrl}}/crm/permission/delete?bizType=2&bizId=1&id=1 +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} + diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java new file mode 100644 index 000000000..63085cab0 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java @@ -0,0 +1,116 @@ +package cn.iocoder.yudao.module.crm.controller.admin.permission; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; +import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.PostApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +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.util.*; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - CRM 数据权限") +@RestController +@RequestMapping("/crm/permission") +@Validated +public class CrmPermissionController { + + @Resource + private CrmPermissionService permissionService; + + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; + @Resource + private PostApi postApi; + + @PostMapping("/create") + @Operation(summary = "创建数据权限") + @PreAuthorize("@ss.hasPermission('crm:permission:create')") + @CrmPermission(bizTypeValue = "#reqVO.bizType", bizId = "#reqVO.bizId", level = CrmPermissionLevelEnum.OWNER) + public CommonResult addPermission(@Valid @RequestBody CrmPermissionCreateReqVO reqVO) { + permissionService.createPermission(BeanUtils.toBean(reqVO, CrmPermissionCreateReqBO.class)); + return success(true); + } + + @PutMapping("/update") + @Operation(summary = "编辑数据权限") + @PreAuthorize("@ss.hasPermission('crm:permission:update')") + @CrmPermission(bizTypeValue = "#updateReqVO.bizType", bizId = "#updateReqVO.bizId" + , level = CrmPermissionLevelEnum.OWNER) + public CommonResult updatePermission(@Valid @RequestBody CrmPermissionUpdateReqVO updateReqVO) { + permissionService.updatePermission(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除数据权限") + @Parameter(name = "ids", description = "数据权限编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:permission:delete')") + public CommonResult deletePermission(@RequestParam("ids") Collection ids) { + permissionService.deletePermissionBatch(ids, getLoginUserId()); + return success(true); + } + + @DeleteMapping("/delete-self") + @Operation(summary = "删除自己的数据权限") + @Parameter(name = "id", description = "数据权限编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:permission:delete')") + public CommonResult deleteSelfPermission(@RequestParam("id") Long id) { + permissionService.deleteSelfPermission(id, getLoginUserId()); + return success(true); + } + + @GetMapping("/list") + @Operation(summary = "获得数据权限列表") + @Parameters({ + @Parameter(name = "bizType", description = "CRM 类型", required = true, example = "2"), + @Parameter(name = "bizId", description = "CRM 类型数据编号", required = true, example = "1024") + }) + @PreAuthorize("@ss.hasPermission('crm:permission:query')") + public CommonResult> getPermissionList(@RequestParam("bizType") Integer bizType, + @RequestParam("bizId") Long bizId) { + List permission = permissionService.getPermissionListByBiz(bizType, bizId); + if (CollUtil.isEmpty(permission)) { + return success(Collections.emptyList()); + } + + // 拼接数据 + List userList = adminUserApi.getUserList(convertSet(permission, CrmPermissionDO::getUserId)) + .getCheckedData(); + Map deptMap = deptApi.getDeptMap(convertSet(userList, AdminUserRespDTO::getDeptId)); + Set postIds = CollectionUtils.convertSetByFlatMap(userList, AdminUserRespDTO::getPostIds, + item -> item != null ? item.stream() : Stream.empty()); + Map postMap = postApi.getPostMap(postIds); + return success(CrmPermissionConvert.INSTANCE.convert(permission, userList, deptMap, postMap)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionBaseVO.java new file mode 100644 index 000000000..796b3cd46 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionBaseVO.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.crm.controller.admin.permission.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +/** + * 数据权限 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + * + * @author HUIHUI + */ +@Data +public class CrmPermissionBaseVO { + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") + @NotNull(message = "用户编号不能为空") + private Long userId; + + @Schema(description = "CRM 类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @InEnum(CrmBizTypeEnum.class) + @NotNull(message = "CRM 类型不能为空") + private Integer bizType; + + @Schema(description = "CRM 类型数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "CRM 类型数据编号不能为空") + private Long bizId; + + @Schema(description = "权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @InEnum(CrmPermissionLevelEnum.class) + @NotNull(message = "权限级别不能为空") + private Integer level; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionCreateReqVO.java new file mode 100644 index 000000000..99793389b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionCreateReqVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.crm.controller.admin.permission.vo; + +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 CrmPermissionCreateReqVO extends CrmPermissionBaseVO { + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionRespVO.java new file mode 100644 index 000000000..10f1ce198 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionRespVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.crm.controller.admin.permission.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Set; + +@Schema(description = "管理后台 - CRM 数据权限 Response VO") +@Data +public class CrmPermissionRespVO extends CrmPermissionBaseVO { + + @Schema(description = "数据权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private Long id; + + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + private String nickname; + + @Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部") + private String deptName; + + @Schema(description = "岗位名称数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[BOOS,经理]") + private Set postNames; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2023-01-01 00:00:00") + private LocalDateTime createTime; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionUpdateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionUpdateReqVO.java new file mode 100644 index 000000000..26c94728a --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/vo/CrmPermissionUpdateReqVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.crm.controller.admin.permission.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; +import java.util.List; + +@Schema(description = "管理后台 - CRM 数据权限更新 Request VO") +@Data +public class CrmPermissionUpdateReqVO { + + @Schema(description = "数据权限编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2]") + @NotNull(message = "数据权限编号列表不能为空") + private List ids; + + @Schema(description = "Crm 类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @InEnum(CrmBizTypeEnum.class) + @NotNull(message = "Crm 类型不能为空") + private Integer bizType; + + @Schema(description = "Crm 类型数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "Crm 类型数据编号不能为空") + private Long bizId; + + @Schema(description = "权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @InEnum(CrmPermissionLevelEnum.class) + @NotNull(message = "权限级别不能为空") + private Integer level; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductCategoryController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductCategoryController.java new file mode 100644 index 000000000..2c840d595 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductCategoryController.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.module.crm.controller.admin.product; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryListReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryRespVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; +import cn.iocoder.yudao.module.crm.service.product.CrmProductCategoryService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - CRM 产品分类") +@RestController +@RequestMapping("/crm/product-category") +@Validated +public class CrmProductCategoryController { + + @Resource + private CrmProductCategoryService productCategoryService; + + @PostMapping("/create") + @Operation(summary = "创建产品分类") + @PreAuthorize("@ss.hasPermission('crm:product-category:create')") + public CommonResult createProductCategory(@Valid @RequestBody CrmProductCategoryCreateReqVO createReqVO) { + return success(productCategoryService.createProductCategory(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新产品分类") + @PreAuthorize("@ss.hasPermission('crm:product-category:update')") + public CommonResult updateProductCategory(@Valid @RequestBody CrmProductCategoryCreateReqVO updateReqVO) { + productCategoryService.updateProductCategory(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除产品分类") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:product-category:delete')") + public CommonResult deleteProductCategory(@RequestParam("id") Long id) { + productCategoryService.deleteProductCategory(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得产品分类") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:product-category:query')") + public CommonResult getProductCategory(@RequestParam("id") Long id) { + CrmProductCategoryDO category = productCategoryService.getProductCategory(id); + return success(BeanUtils.toBean(category, CrmProductCategoryRespVO.class)); + } + + @GetMapping("/list") + @Operation(summary = "获得产品分类列表") + @PreAuthorize("@ss.hasPermission('crm:product-category:query')") + public CommonResult> getProductCategoryList(@Valid CrmProductCategoryListReqVO listReqVO) { + List list = productCategoryService.getProductCategoryList(listReqVO); + return success(BeanUtils.toBean(list, CrmProductCategoryRespVO.class)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java new file mode 100644 index 000000000..94774373d --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java @@ -0,0 +1,126 @@ +package cn.iocoder.yudao.module.crm.controller.admin.product; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO; +import cn.iocoder.yudao.module.crm.convert.product.CrmProductConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import cn.iocoder.yudao.module.crm.service.product.CrmProductCategoryService; +import cn.iocoder.yudao.module.crm.service.product.CrmProductService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - CRM 产品") +@RestController +@RequestMapping("/crm/product") +@Validated +public class CrmProductController { + + @Resource + private CrmProductService productService; + @Resource + private CrmProductCategoryService productCategoryService; + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建产品") + @PreAuthorize("@ss.hasPermission('crm:product:create')") + public CommonResult createProduct(@Valid @RequestBody CrmProductSaveReqVO createReqVO) { + return success(productService.createProduct(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新产品") + @PreAuthorize("@ss.hasPermission('crm:product:update')") + public CommonResult updateProduct(@Valid @RequestBody CrmProductSaveReqVO updateReqVO) { + productService.updateProduct(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除产品") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:product:delete')") + public CommonResult deleteProduct(@RequestParam("id") Long id) { + productService.deleteProduct(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得产品") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:product:query')") + public CommonResult getProduct(@RequestParam("id") Long id) { + CrmProductDO product = productService.getProduct(id); + if (product == null) { + return success(null); + } + Map userMap = adminUserApi.getUserMap( + SetUtils.asSet(Long.valueOf(product.getCreator()), product.getOwnerUserId())); + CrmProductCategoryDO category = productCategoryService.getProductCategory(product.getCategoryId()); + return success(CrmProductConvert.INSTANCE.convert(product, userMap, category)); + } + + @GetMapping("/page") + @Operation(summary = "获得产品分页") + @PreAuthorize("@ss.hasPermission('crm:product:query')") + public CommonResult> getProductPage(@Valid CrmProductPageReqVO pageVO) { + PageResult pageResult = productService.getProductPage(pageVO, getLoginUserId()); + return success(new PageResult<>(getProductDetailList(pageResult.getList()), pageResult.getTotal())); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出产品 Excel") + @PreAuthorize("@ss.hasPermission('crm:product:export')") + @OperateLog(type = EXPORT) + public void exportProductExcel(@Valid CrmProductPageReqVO exportReqVO, + HttpServletResponse response) throws IOException { + exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = productService.getProductPage(exportReqVO, getLoginUserId()).getList(); + // 导出 Excel + ExcelUtils.write(response, "产品.xls", "数据", CrmProductRespVO.class, + getProductDetailList(list)); + } + + private List getProductDetailList(List list) { + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); + } + Map userMap = adminUserApi.getUserMap( + convertSetByFlatMap(list, user -> Stream.of(Long.valueOf(user.getCreator()), user.getOwnerUserId()))); + List productCategoryList = productCategoryService.getProductCategoryList( + convertSet(list, CrmProductDO::getCategoryId)); + return CrmProductConvert.INSTANCE.convertList(list, userMap, productCategoryList); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryCreateReqVO.java new file mode 100644 index 000000000..bb17806a8 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryCreateReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.crm.controller.admin.product.vo.category; + +import com.mzt.logapi.starter.annotation.DiffLogField; +import lombok.*; +import io.swagger.v3.oas.annotations.media.Schema; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "管理后台 - CRM 产品分类创建/更新 Request VO") +@Data +public class CrmProductCategoryCreateReqVO{ + + @Schema(description = "分类编号", example = "23902") + private Long id; + + @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + @NotNull(message = "分类名称不能为空") + @DiffLogField(name = "分类名称") + private String name; + + @Schema(description = "父级编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4680") + @NotNull(message = "父级编号不能为空") + private Long parentId; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryListReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryListReqVO.java new file mode 100644 index 000000000..6144c95c4 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryListReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.crm.controller.admin.product.vo.category; + +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - CRM 产品分类列表 Request VO") +@Data +public class CrmProductCategoryListReqVO { + + @ExcelProperty("名称") + private String name; + + @ExcelProperty("父级 id") + private Long parentId; + + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryRespVO.java new file mode 100644 index 000000000..4cea8e464 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryRespVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.crm.controller.admin.product.vo.category; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - CRM 产品分类 Response VO") +@Data +public class CrmProductCategoryRespVO { + + @Schema(description = "分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23902") + private Long id; + + @Schema(description = "分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + private String name; + + @Schema(description = "父级编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4680") + private Long parentId; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductPageReqVO.java new file mode 100644 index 000000000..39b1090f8 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductPageReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.crm.controller.admin.product.vo.product; + +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 CrmProductPageReqVO extends PageParam { + + @Schema(description = "产品名称", example = "李四") + private String name; + + @Schema(description = "状态", example = "1") + private Integer status; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java new file mode 100644 index 000000000..ceca3e5a0 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.crm.controller.admin.product.vo.product; + +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 com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - CRM 产品 Response VO") +@Data +@ExcelIgnoreUnannotated +public class CrmProductRespVO { + + @Schema(description = "产品编号", example = "20529") + @ExcelProperty("产品编号") + private Long id; + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "好产品") + @ExcelProperty("产品名称") + private String name; + + @Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "12306") + @ExcelProperty("产品编码") + private String no; + + @Schema(description = "单位", example = "2") + @ExcelProperty(value = "单位", converter = DictConvert.class) + @DictFormat(DictTypeConstants.CRM_PRODUCT_UNIT) + private Integer unit; + + @Schema(description = "价格, 单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") + @ExcelProperty("价格,单位:分") + private Long price; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架") + @ExcelProperty(value = "单位", converter = DictConvert.class) + @DictFormat(DictTypeConstants.CRM_PRODUCT_STATUS) + private Integer status; + + @Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long categoryId; + @Schema(description = "产品分类名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "衣服") + @ExcelProperty("产品分类") + private String categoryName; + + @Schema(description = "产品描述", example = "你说的对") + @ExcelProperty("产品描述") + private String description; + + @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31926") + private Long ownerUserId; + @Schema(description = "负责人的用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") + @ExcelProperty("负责人") + private String ownerUserName; + + @Schema(description = "创建人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private String creator; + @Schema(description = "创建人名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") + @ExcelProperty("创建人") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("更新时间") + private LocalDateTime updateTime; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java new file mode 100644 index 000000000..01b2ae443 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.crm.controller.admin.product.vo.product; + +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmProductStatusParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmProductUnitParseFunction; +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - CRM 产品创建/修改 Request VO") +@Data +public class CrmProductSaveReqVO { + + @Schema(description = "产品编号", example = "20529") + private Long id; + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "好产品") + @NotNull(message = "产品名称不能为空") + @DiffLogField(name = "产品名称") + private String name; + + @Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "12306") + @NotNull(message = "产品编码不能为空") + @DiffLogField(name = "产品编码") + private String no; + + @Schema(description = "单位", example = "2") + @DiffLogField(name = "单位", function = CrmProductUnitParseFunction.NAME) + private Integer unit; + + @Schema(description = "价格, 单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") + @NotNull(message = "价格不能为空") + @DiffLogField(name = "价格") + private Long price; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架") + @NotNull(message = "状态不能为空") + @DiffLogField(name = "状态", function = CrmProductStatusParseFunction.NAME) + private Integer status; + + @Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "产品分类编号不能为空") + @DiffLogField(name = "产品分类编号") + private Long categoryId; + + @Schema(description = "产品描述", example = "你说的对") + @DiffLogField(name = "产品描述") + private String description; + + @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31926") + @NotNull(message = "负责人的用户编号不能为空") + private Long ownerUserId; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java new file mode 100644 index 000000000..8516ebd66 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java @@ -0,0 +1,147 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; +import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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 java.util.Map; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - CRM 回款") +@RestController +@RequestMapping("/crm/receivable") +@Validated +public class CrmReceivableController { + + @Resource + private CrmReceivableService receivableService; + @Resource + private CrmContractService contractService; + @Resource + private CrmCustomerService customerService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建回款") + @PreAuthorize("@ss.hasPermission('crm:receivable:create')") + public CommonResult createReceivable(@Valid @RequestBody CrmReceivableCreateReqVO createReqVO) { + return success(receivableService.createReceivable(createReqVO, getLoginUserId())); + } + + @PutMapping("/update") + @Operation(summary = "更新回款") + @PreAuthorize("@ss.hasPermission('crm:receivable:update')") + public CommonResult updateReceivable(@Valid @RequestBody CrmReceivableUpdateReqVO updateReqVO) { + receivableService.updateReceivable(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除回款") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:receivable:delete')") + public CommonResult deleteReceivable(@RequestParam("id") Long id) { + receivableService.deleteReceivable(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得回款") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:receivable:query')") + public CommonResult getReceivable(@RequestParam("id") Long id) { + CrmReceivableDO receivable = receivableService.getReceivable(id); + return success(CrmReceivableConvert.INSTANCE.convert(receivable)); + } + + @GetMapping("/page") + @Operation(summary = "获得回款分页") + @PreAuthorize("@ss.hasPermission('crm:receivable:query')") + public CommonResult> getReceivablePage(@Valid CrmReceivablePageReqVO pageReqVO) { + PageResult pageResult = receivableService.getReceivablePage(pageReqVO, getLoginUserId()); + return success(buildReceivableDetailPage(pageResult)); + } + + @GetMapping("/page-by-customer") + @Operation(summary = "获得回款分页,基于指定客户") + public CommonResult> getReceivablePageByCustomer(@Valid CrmReceivablePageReqVO pageReqVO) { + Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空"); + PageResult pageResult = receivableService.getReceivablePageByCustomerId(pageReqVO); + return success(buildReceivableDetailPage(pageResult)); + } + + // TODO 芋艿:后面在优化导出 + @GetMapping("/export-excel") + @Operation(summary = "导出回款 Excel") + @PreAuthorize("@ss.hasPermission('crm:receivable:export')") + @OperateLog(type = EXPORT) + public void exportReceivableExcel(@Valid CrmReceivablePageReqVO exportReqVO, + HttpServletResponse response) throws IOException { + exportReqVO.setPageSize(PAGE_SIZE_NONE); + PageResult pageResult = receivableService.getReceivablePage(exportReqVO, getLoginUserId()); + // 导出 Excel + ExcelUtils.write(response, "回款.xls", "数据", CrmReceivableRespVO.class, + buildReceivableDetailPage(pageResult).getList()); + } + + /** + * 构建详细的回款分页结果 + * + * @param pageResult 简单的回款分页结果 + * @return 详细的回款分页结果 + */ + private PageResult buildReceivableDetailPage(PageResult pageResult) { + List receivableList = pageResult.getList(); + if (CollUtil.isEmpty(receivableList)) { + return PageResult.empty(pageResult.getTotal()); + } + // 1. 获取客户列表 + List customerList = customerService.getCustomerList( + convertSet(receivableList, CrmReceivableDO::getCustomerId)); + // 2. 获取创建人、负责人列表 + Map userMap = adminUserApi.getUserMap(convertListByFlatMap(receivableList, + contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); + // 3. 获得合同列表 + List contractList = contractService.getContractList( + convertSet(receivableList, CrmReceivableDO::getContractId)); + return CrmReceivableConvert.INSTANCE.convertPage(pageResult, userMap, customerList, contractList); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java new file mode 100644 index 000000000..252d714f4 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java @@ -0,0 +1,156 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; +import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivablePlanService; +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +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.context.annotation.Lazy; +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 java.util.Map; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - CRM 回款计划") +@RestController +@RequestMapping("/crm/receivable-plan") +@Validated +public class CrmReceivablePlanController { + + @Resource + private CrmReceivablePlanService receivablePlanService; + @Resource + private CrmReceivableService receivableService; + @Resource + @Lazy + private CrmContractService contractService; + @Resource + private CrmCustomerService customerService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建回款计划") + @PreAuthorize("@ss.hasPermission('crm:receivable-plan:create')") + public CommonResult createReceivablePlan(@Valid @RequestBody CrmReceivablePlanCreateReqVO createReqVO) { + return success(receivablePlanService.createReceivablePlan(createReqVO, getLoginUserId())); + } + + @PutMapping("/update") + @Operation(summary = "更新回款计划") + @PreAuthorize("@ss.hasPermission('crm:receivable-plan:update')") + public CommonResult updateReceivablePlan(@Valid @RequestBody CrmReceivablePlanUpdateReqVO updateReqVO) { + receivablePlanService.updateReceivablePlan(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除回款计划") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:receivable-plan:delete')") + public CommonResult deleteReceivablePlan(@RequestParam("id") Long id) { + receivablePlanService.deleteReceivablePlan(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得回款计划") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')") + public CommonResult getReceivablePlan(@RequestParam("id") Long id) { + CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(id); + return success(CrmReceivablePlanConvert.INSTANCE.convert(receivablePlan)); + } + + @GetMapping("/page") + @Operation(summary = "获得回款计划分页") + @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')") + public CommonResult> getReceivablePlanPage(@Valid CrmReceivablePlanPageReqVO pageReqVO) { + PageResult pageResult = receivablePlanService.getReceivablePlanPage(pageReqVO, getLoginUserId()); + return success(convertDetailReceivablePlanPage(pageResult)); + } + + @GetMapping("/page-by-customer") + @Operation(summary = "获得回款计划分页,基于指定客户") + public CommonResult> getReceivablePlanPageByCustomer(@Valid CrmReceivablePlanPageReqVO pageReqVO) { + Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空"); + PageResult pageResult = receivablePlanService.getReceivablePlanPageByCustomerId(pageReqVO); + return success(convertDetailReceivablePlanPage(pageResult)); + } + + // TODO 芋艿:后面在优化导出 + @GetMapping("/export-excel") + @Operation(summary = "导出回款计划 Excel") + @PreAuthorize("@ss.hasPermission('crm:receivable-plan:export')") + @OperateLog(type = EXPORT) + public void exportReceivablePlanExcel(@Valid CrmReceivablePlanPageReqVO exportReqVO, + HttpServletResponse response) throws IOException { + exportReqVO.setPageSize(PAGE_SIZE_NONE); + PageResult pageResult = receivablePlanService.getReceivablePlanPage(exportReqVO, getLoginUserId()); + // 导出 Excel + ExcelUtils.write(response, "回款计划.xls", "数据", CrmReceivablePlanRespVO.class, + convertDetailReceivablePlanPage(pageResult).getList()); + } + + /** + * 构建详细的回款计划分页结果 + * + * @param pageResult 简单的回款计划分页结果 + * @return 详细的回款计划分页结果 + */ + private PageResult convertDetailReceivablePlanPage(PageResult pageResult) { + List receivablePlanList = pageResult.getList(); + if (CollUtil.isEmpty(receivablePlanList)) { + return PageResult.empty(pageResult.getTotal()); + } + // 1. 获取客户列表 + List customerList = customerService.getCustomerList( + convertSet(receivablePlanList, CrmReceivablePlanDO::getCustomerId)); + // 2. 获取创建人、负责人列表 + Map userMap = adminUserApi.getUserMap(convertListByFlatMap(receivablePlanList, + contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); + // 3. 获得合同列表 + List contractList = contractService.getContractList( + convertSet(receivablePlanList, CrmReceivablePlanDO::getContractId)); + // 4. 获得还款列表 + List receivableList = receivableService.getReceivableList( + convertSet(receivablePlanList, CrmReceivablePlanDO::getReceivableId)); + return CrmReceivablePlanConvert.INSTANCE.convertPage(pageResult, userMap, customerList, contractList, receivableList); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanBaseVO.java new file mode 100644 index 000000000..70272b8e8 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanBaseVO.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 回款计划 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class CrmReceivablePlanBaseVO { + + @Schema(description = "期数", example = "1") + private Integer period; + + @Schema(description = "回款计划编号", example = "19852") + private Long receivableId; + + @Schema(description = "计划回款金额", example = "29675") + private Integer price; + + @Schema(description = "计划回款日期") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime returnTime; + + @Schema(description = "提前几天提醒") + private Integer remindDays; + + @Schema(description = "提醒日期") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime remindTime; + + @Schema(description = "客户名称", example = "18026") + private Long customerId; + + @Schema(description = "合同编号", example = "3473") + private Long contractId; + + // TODO @liuhongfeng:负责人编号 + @Schema(description = "负责人编号", example = "17828") + private Long ownerUserId; + + @Schema(description = "显示顺序") + private Integer sort; + + @Schema(description = "备注", example = "备注") + private String remark; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanCreateReqVO.java new file mode 100644 index 000000000..193a44bf4 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanCreateReqVO.java @@ -0,0 +1,12 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; + +import lombok.*; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "管理后台 - CRM 回款计划创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmReceivablePlanCreateReqVO extends CrmReceivablePlanBaseVO { + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java new file mode 100644 index 000000000..3675fba1f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +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 CrmReceivablePlanPageReqVO extends PageParam { + + /** + * 提醒类型 - 待回款 + */ + public final static Integer REMIND_TYPE_NEEDED = 1; + /** + * 提醒类型 - 已逾期 + */ + public final static Integer REMIND_TYPE_EXPIRED = 2; + /** + * 提醒类型 - 已回款 + */ + public final static Integer REMIND_TYPE_RECEIVED = 3; + + @Schema(description = "客户编号", example = "18026") + private Long customerId; + + // TODO @芋艿:这个搜的应该是合同编号 no + @Schema(description = "合同名称", example = "3473") + private Long contractId; + + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + + @Schema(description = "提醒类型", example = "1") + private Integer remindType; // 提醒类型,为 null 时则表示全部 + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java new file mode 100644 index 000000000..d5e9de187 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - CRM 回款计划 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmReceivablePlanRespVO extends CrmReceivablePlanBaseVO { + + @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25153") + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test") + private String customerName; + + @Schema(description = "合同编号", example = "Q110") + private String contractNo; + + @Schema(description = "负责人", example = "test") + private String ownerUserName; + + @Schema(description = "创建人", example = "25682") + private String creator; + + @Schema(description = "创建人名字", example = "test") + private String creatorName; + + @Schema(description = "完成状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Boolean finishStatus; + + @Schema(description = "回款方式", example = "1") // 来自 Receivable 的 returnType 字段 + private Integer returnType; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanUpdateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanUpdateReqVO.java new file mode 100644 index 000000000..2e83a1cbb --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanUpdateReqVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import jakarta.validation.constraints.*; + +@Schema(description = "管理后台 - CRM 回款计划更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmReceivablePlanUpdateReqVO extends CrmReceivablePlanBaseVO { + + @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25153") + @NotNull(message = "ID不能为空") + private Long id; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableBaseVO.java new file mode 100644 index 000000000..c32bc9ad5 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableBaseVO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 回款 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class CrmReceivableBaseVO { + + @Schema(description = "回款编号",requiredMode = Schema.RequiredMode.REQUIRED, example = "31177") + private String no; + + // TODO @liuhongfeng:回款计划编号 + @Schema(description = "回款计划", example = "31177") + private Long planId; + + // TODO @liuhongfeng:客户编号 + @Schema(description = "客户名称", example = "4963") + private Long customerId; + + // TODO @liuhongfeng:客户编号 + @Schema(description = "合同名称", example = "30305") + private Long contractId; + + // TODO @liuhongfeng:这个字段,应该不是前端传递的噢,而是后端自己生成的 + @Schema(description = "审批状态", example = "1") + @InEnum(CrmAuditStatusEnum.class) + private Integer checkStatus; + + @Schema(description = "回款日期") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime returnTime; + + @Schema(description = "回款方式", example = "2") + private Integer returnType; + + @Schema(description = "回款金额,单位:分", example = "31859") + private Integer price; + + // TODO @liuhongfeng:负责人编号 + @Schema(description = "负责人", example = "22202") + private Long ownerUserId; + + @Schema(description = "显示顺序") + private Integer sort; + + @Schema(description = "备注", example = "备注") + private String remark; + + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableCreateReqVO.java new file mode 100644 index 000000000..4471b780a --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableCreateReqVO.java @@ -0,0 +1,12 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; + +import lombok.*; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "管理后台 - CRM 回款创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmReceivableCreateReqVO extends CrmReceivableBaseVO { + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java new file mode 100644 index 000000000..e1fe83087 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +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 CrmReceivablePageReqVO extends PageParam { + + @Schema(description = "回款编号") + private String no; + + @Schema(description = "回款计划编号", example = "31177") + private Long planId; + + @Schema(description = "客户编号", example = "4963") + private Long customerId; + + @Schema(description = "场景类型", example = "1") + @InEnum(CrmSceneTypeEnum.class) + private Integer sceneType; // 场景类型,为 null 时则表示全部 + + @Schema(description = "审批状态", example = "20") + @InEnum(CrmAuditStatusEnum.class) + private Integer auditStatus; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java new file mode 100644 index 000000000..7c536bd51 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.time.LocalDateTime; + +// TODO 芋艿:导出的 VO,可以考虑使用 @Excel 注解,实现导出功能 +@Schema(description = "管理后台 - CRM 回款 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmReceivableRespVO extends CrmReceivableBaseVO { + + @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25787") + private Long id; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test") + private String customerName; + + @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer auditStatus; + + @Schema(description = "合同编号", example = "Q110") + private String contractNo; + + @Schema(description = "负责人", example = "test") + private String ownerUserName; + + @Schema(description = "创建人", example = "25682") + private String creator; + + @Schema(description = "创建人名字", example = "test") + private String creatorName; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableUpdateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableUpdateReqVO.java new file mode 100644 index 000000000..0f63978c8 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableUpdateReqVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import jakarta.validation.constraints.*; + +@Schema(description = "管理后台 - CRM 回款更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CrmReceivableUpdateReqVO extends CrmReceivableBaseVO { + + @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25787") + @NotNull(message = "ID不能为空") + private Long id; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/app/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/app/package-info.java new file mode 100644 index 000000000..78d85635c --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/app/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package cn.iocoder.yudao.module.crm.controller.app; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/package-info.java new file mode 100644 index 000000000..8354b3176 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 RESTful API 给前端: + * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 + * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 + */ +package cn.iocoder.yudao.module.crm.controller; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java new file mode 100644 index 000000000..d7f990043 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.crm.convert.business; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * 商机 Convert + * + * @author ljlleo + */ +@Mapper +public interface CrmBusinessConvert { + + CrmBusinessConvert INSTANCE = Mappers.getMapper(CrmBusinessConvert.class); + + @Mapping(target = "bizId", source = "reqVO.id") + CrmPermissionTransferReqBO convert(CrmBusinessTransferReqVO reqVO, Long userId); + + default PageResult convertPage(PageResult pageResult, List customerList, + List statusTypeList, List statusList) { + PageResult voPageResult = BeanUtils.toBean(pageResult, CrmBusinessRespVO.class); + // 拼接关联字段 + Map customerMap = convertMap(customerList, CrmCustomerDO::getId, CrmCustomerDO::getName); + Map statusTypeMap = convertMap(statusTypeList, CrmBusinessStatusTypeDO::getId, CrmBusinessStatusTypeDO::getName); + Map statusMap = convertMap(statusList, CrmBusinessStatusDO::getId, CrmBusinessStatusDO::getName); + voPageResult.getList().forEach(type -> type + .setCustomerName(customerMap.get(type.getCustomerId())) + .setStatusTypeName(statusTypeMap.get(type.getStatusTypeId())) + .setStatusName(statusMap.get(type.getStatusId()))); + return voPageResult; + } + + @Mapping(target = "id", source = "reqBO.bizId") + CrmBusinessDO convert(CrmUpdateFollowUpReqBO reqBO); + + default List convertList(List list) { + return CollectionUtils.convertList(list, INSTANCE::convert); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java new file mode 100644 index 000000000..52186e3d9 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.crm.convert.business; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +/** + * 商机状态 Convert + * + * @author ljlleo + */ +@Mapper +public interface CrmBusinessStatusConvert { + + CrmBusinessStatusConvert INSTANCE = Mappers.getMapper(CrmBusinessStatusConvert.class); + + List convertList(List list); + + PageResult convertPage(PageResult page); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java new file mode 100644 index 000000000..4876fb537 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.convert.business; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +// TODO @lzxhqs:看看是不是用 BeanUtils 替代了 +/** + * 商机状态类型 Convert + * + * @author ljlleo + */ +@Mapper +public interface CrmBusinessStatusTypeConvert { + + CrmBusinessStatusTypeConvert INSTANCE = Mappers.getMapper(CrmBusinessStatusTypeConvert.class); + + CrmBusinessStatusTypeRespVO convert(CrmBusinessStatusTypeDO bean); + + PageResult convertPage(PageResult page); + + default PageResult convertPage(PageResult page, List deptList) { + PageResult pageResult = convertPage(page); + // 拼接关联字段 + Map deptMap = convertMap(deptList, DeptRespDTO::getId, DeptRespDTO::getName); + pageResult.getList().forEach(type -> type.setDeptNames(convertList(type.getDeptIds(), deptMap::get))); + return pageResult; + } + + default CrmBusinessStatusTypeRespVO convert(CrmBusinessStatusTypeDO bean, List statusList) { + return convert(bean).setStatusList(statusList); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/clue/CrmClueConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/clue/CrmClueConvert.java new file mode 100644 index 000000000..39e607bcb --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/clue/CrmClueConvert.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.crm.convert.clue; + +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * 线索 Convert + * + * @author Wanwan + */ +@Mapper +public interface CrmClueConvert { + + CrmClueConvert INSTANCE = Mappers.getMapper(CrmClueConvert.class); + + @Mapping(target = "bizId", source = "reqVO.id") + CrmPermissionTransferReqBO convert(CrmClueTransferReqVO reqVO, Long userId); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java new file mode 100644 index 000000000..5fd2afa67 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java @@ -0,0 +1,77 @@ +package cn.iocoder.yudao.module.crm.convert.contact; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; + +/** + * CRM 联系人 Convert + * + * @author 芋道源码 + */ +@Mapper +public interface CrmContactConvert { + + CrmContactConvert INSTANCE = Mappers.getMapper(CrmContactConvert.class); + + CrmContactRespVO convert(CrmContactDO bean); + + @Mapping(target = "bizId", source = "reqVO.id") + CrmPermissionTransferReqBO convert(CrmContactTransferReqVO reqVO, Long userId); + + default PageResult convertPage(PageResult pageResult, Map userMap, + List customerList, List parentContactList) { + PageResult voPageResult = BeanUtils.toBean(pageResult, CrmContactRespVO.class); + // 拼接关联字段 + Map parentContactMap = convertMap(parentContactList, CrmContactDO::getId); + Map customerMap = convertMap(customerList, CrmCustomerDO::getId); + voPageResult.getList().forEach(item -> { + setUserInfo(item, userMap); + findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName())); + findAndThen(parentContactMap, item.getParentId(), contactDO -> item.setParentName(contactDO.getName())); + }); + return voPageResult; + } + + default CrmContactRespVO convert(CrmContactDO contactDO, Map userMap, + List customerList, List parentContactList) { + CrmContactRespVO contactVO = convert(contactDO); + setUserInfo(contactVO, userMap); + Map customerMap = CollectionUtils.convertMap(customerList, CrmCustomerDO::getId); + Map contactMap = CollectionUtils.convertMap(parentContactList, CrmContactDO::getId); + findAndThen(customerMap, contactDO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName())); + findAndThen(contactMap, contactDO.getParentId(), contact -> contactVO.setParentName(contact.getName())); + return contactVO; + } + + static void setUserInfo(CrmContactRespVO contactRespVO, Map userMap) { + contactRespVO.setAreaName(AreaUtils.format(contactRespVO.getAreaId())); + findAndThen(userMap, contactRespVO.getOwnerUserId(), user -> contactRespVO.setOwnerUserName(user.getNickname())); + findAndThen(userMap, Long.parseLong(contactRespVO.getCreator()), user -> contactRespVO.setCreatorName(user.getNickname())); + } + + @Mapping(target = "id", source = "reqBO.bizId") + CrmContactDO convert(CrmUpdateFollowUpReqBO reqBO); + + default List convertList(List list) { + return CollectionUtils.convertList(list, INSTANCE::convert); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java new file mode 100644 index 000000000..0d2e49934 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.crm.convert.contract; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; + +/** + * 合同 Convert + * + * @author dhb52 + */ +@Mapper +public interface CrmContractConvert { + + CrmContractConvert INSTANCE = Mappers.getMapper(CrmContractConvert.class); + + @Mapping(target = "bizId", source = "reqVO.id") + CrmPermissionTransferReqBO convert(CrmContractTransferReqVO reqVO, Long userId); + + default List convertList(List contractList, Map userMap, + List customerList, Map contactMap, + Map businessMap, Map contractProductMap, + List productList) { + List respVOList = BeanUtils.toBean(contractList, CrmContractRespVO.class); + // 拼接关联字段 + Map customerMap = convertMap(customerList, CrmCustomerDO::getId); + respVOList.forEach(contract -> { + findAndThen(userMap, contract.getOwnerUserId(), user -> contract.setOwnerUserName(user.getNickname())); + findAndThen(userMap, Long.parseLong(contract.getCreator()), user -> contract.setCreatorName(user.getNickname())); + findAndThen(userMap, contract.getSignUserId(), user -> contract.setSignUserName(user.getNickname())); + findAndThen(customerMap, contract.getCustomerId(), customer -> contract.setCustomerName(customer.getName())); + findAndThen(contactMap, contract.getContactId(), contact -> contract.setContactName(contact.getName())); + findAndThen(businessMap, contract.getBusinessId(), business -> contract.setBusinessName(business.getName())); + }); + if (CollUtil.isNotEmpty(respVOList) && respVOList.size() == 1) { + setContractRespVOProductItems(respVOList.get(0), contractProductMap, productList); + } + return respVOList; + } + + default void setContractRespVOProductItems(CrmContractRespVO respVO, Map contractProductMap, + List productList) { + respVO.setProductItems(CollectionUtils.convertList(productList, product -> { + CrmContractRespVO.CrmContractProductItemRespVO productItemRespVO = BeanUtils.toBean(product, CrmContractRespVO.CrmContractProductItemRespVO.class); + findAndThen(contractProductMap, product.getId(), contractProduct -> + productItemRespVO.setCount(contractProduct.getCount()).setDiscountPercent(contractProduct.getDiscountPercent())); + return productItemRespVO; + })); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java new file mode 100644 index 000000000..6a6642968 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java @@ -0,0 +1,66 @@ +package cn.iocoder.yudao.module.crm.convert.customer; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; + +/** + * 客户 Convert + * + * @author Wanwan + */ +@Mapper +public interface CrmCustomerConvert { + + CrmCustomerConvert INSTANCE = Mappers.getMapper(CrmCustomerConvert.class); + + default CrmCustomerRespVO convert(CrmCustomerDO customer, Map userMap, + Map deptMap) { + CrmCustomerRespVO customerResp = BeanUtils.toBean(customer, CrmCustomerRespVO.class); + setUserInfo(customerResp, userMap, deptMap); + return customerResp; + } + + default PageResult convertPage(PageResult pageResult, Map userMap, + Map deptMap, Map poolDayMap) { + PageResult result = BeanUtils.toBean(pageResult, CrmCustomerRespVO.class); + result.getList().forEach(item -> { + setUserInfo(item, userMap, deptMap); + findAndThen(poolDayMap, item.getId(), item::setPoolDay); + }); + return result; + } + + /** + * 设置用户信息 + * + * @param customer CRM 客户 Response VO + * @param userMap 用户信息 map + * @param deptMap 用户部门信息 map + */ + static void setUserInfo(CrmCustomerRespVO customer, Map userMap, Map deptMap) { + customer.setAreaName(AreaUtils.format(customer.getAreaId())); + findAndThen(userMap, customer.getOwnerUserId(), user -> { + customer.setOwnerUserName(user.getNickname()); + findAndThen(deptMap, user.getDeptId(), dept -> customer.setOwnerUserDeptName(dept.getName())); + }); + findAndThen(userMap, Long.parseLong(customer.getCreator()), user -> customer.setCreatorName(user.getNickname())); + } + + @Mapping(target = "bizId", source = "reqVO.id") + CrmPermissionTransferReqBO convert(CrmCustomerTransferReqVO reqVO, Long userId); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerLimitConfigConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerLimitConfigConvert.java new file mode 100644 index 000000000..81550b378 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerLimitConfigConvert.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.crm.convert.customer; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigRespVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +/** + * 客户限制配置 Convert + * + * @author Wanwan + */ +@Mapper +public interface CrmCustomerLimitConfigConvert { + + CrmCustomerLimitConfigConvert INSTANCE = Mappers.getMapper(CrmCustomerLimitConfigConvert.class); + + default PageResult convertPage( + PageResult pageResult, + Map userMap, Map deptMap) { + List list = CollectionUtils.convertList(pageResult.getList(), + limitConfig -> convert(limitConfig, userMap, deptMap)); + return new PageResult<>(list, pageResult.getTotal()); + } + + default CrmCustomerLimitConfigRespVO convert(CrmCustomerLimitConfigDO limitConfig, + Map userMap, Map deptMap) { + CrmCustomerLimitConfigRespVO limitConfigVO = BeanUtils.toBean(limitConfig, CrmCustomerLimitConfigRespVO.class); + limitConfigVO.setUsers(CollectionUtils.convertList(limitConfigVO.getUserIds(), userMap::get)); + limitConfigVO.setDepts(CollectionUtils.convertList(limitConfigVO.getDeptIds(), deptMap::get)); + return limitConfigVO; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/package-info.java new file mode 100644 index 000000000..6fbc52508 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 POJO 类的实体转换 + * + * 目前使用 MapStruct 框架 + */ +package cn.iocoder.yudao.module.crm.convert; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java new file mode 100644 index 000000000..f51544cae --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.crm.convert.permission; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import com.google.common.collect.Multimaps; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; + +/** + * Crm 数据权限 Convert + * + * @author HUIHUI + */ +@Mapper +public interface CrmPermissionConvert { + + CrmPermissionConvert INSTANCE = Mappers.getMapper(CrmPermissionConvert.class); + + default List convert(List permissions, List userList, + Map deptMap, Map postMap) { + Map userMap = CollectionUtils.convertMap(userList, AdminUserRespDTO::getId); + return CollectionUtils.convertList(BeanUtils.toBean(permissions, CrmPermissionRespVO.class), item -> { + findAndThen(userMap, item.getUserId(), user -> { + item.setNickname(user.getNickname()); + findAndThen(deptMap, user.getDeptId(), deptRespDTO -> item.setDeptName(deptRespDTO.getName())); + if (CollUtil.isEmpty(user.getPostIds())) { + item.setPostNames(Collections.emptySet()); + return; + } + List postList = MapUtils.getList(Multimaps.forMap(postMap), user.getPostIds()); + item.setPostNames(CollectionUtils.convertSet(postList, PostRespDTO::getName)); + }); + return item; + }); + } + + default List convertList(CrmPermissionUpdateReqVO updateReqVO) { + return CollectionUtils.convertList(updateReqVO.getIds(), + id -> new CrmPermissionDO().setId(id).setLevel(updateReqVO.getLevel())); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/product/CrmProductConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/product/CrmProductConvert.java new file mode 100644 index 000000000..2165a208f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/product/CrmProductConvert.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.crm.convert.product; + +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductRespVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; + +/** + * 产品 Convert + * + * @author ZanGe丶 + */ +@Mapper +public interface CrmProductConvert { + + CrmProductConvert INSTANCE = Mappers.getMapper(CrmProductConvert.class); + + default List convertList(List list, + Map userMap, + List categoryList) { + Map categoryMap = convertMap(categoryList, CrmProductCategoryDO::getId); + return CollectionUtils.convertList(list, + product -> convert(product, userMap, categoryMap.get(product.getCategoryId()))); + } + + default CrmProductRespVO convert(CrmProductDO product, + Map userMap, CrmProductCategoryDO category) { + CrmProductRespVO productVO = BeanUtils.toBean(product, CrmProductRespVO.class); + Optional.ofNullable(category).ifPresent(c -> productVO.setCategoryName(c.getName())); + MapUtils.findAndThen(userMap, productVO.getOwnerUserId(), user -> productVO.setOwnerUserName(user.getNickname())); + MapUtils.findAndThen(userMap, Long.valueOf(productVO.getCreator()), user -> productVO.setCreatorName(user.getNickname())); + return productVO; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java new file mode 100644 index 000000000..3b0c23aae --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.crm.convert.receivable; + +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.receivable.vo.receivable.CrmReceivableCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; + +/** + * 回款 Convert + * + * @author 赤焰 + */ +@Mapper +public interface CrmReceivableConvert { + + CrmReceivableConvert INSTANCE = Mappers.getMapper(CrmReceivableConvert.class); + + CrmReceivableDO convert(CrmReceivableCreateReqVO bean); + + CrmReceivableDO convert(CrmReceivableUpdateReqVO bean); + + CrmReceivableRespVO convert(CrmReceivableDO bean); + + default PageResult convertPage(PageResult pageResult, Map userMap, + List customerList, List contractList) { + PageResult voPageResult = BeanUtils.toBean(pageResult, CrmReceivableRespVO.class); + // 拼接关联字段 + Map customerMap = convertMap(customerList, CrmCustomerDO::getId); + Map contractMap = convertMap(contractList, CrmContractDO::getId); + voPageResult.getList().forEach(receivable -> { + findAndThen(userMap, receivable.getOwnerUserId(), user -> receivable.setOwnerUserName(user.getNickname())); + findAndThen(userMap, Long.parseLong(receivable.getCreator()), user -> receivable.setCreatorName(user.getNickname())); + findAndThen(customerMap, receivable.getCustomerId(), customer -> receivable.setCustomerName(customer.getName())); + findAndThen(contractMap, receivable.getContractId(), contract -> receivable.setContractNo(contract.getNo())); + }); + return voPageResult; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java new file mode 100644 index 000000000..9b6bb3e82 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.crm.convert.receivable; + +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.receivable.vo.plan.CrmReceivablePlanCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; + +/** + * 回款计划 Convert + * + * @author 芋道源码 + */ +@Mapper +public interface CrmReceivablePlanConvert { + + CrmReceivablePlanConvert INSTANCE = Mappers.getMapper(CrmReceivablePlanConvert.class); + + CrmReceivablePlanDO convert(CrmReceivablePlanCreateReqVO bean); + + CrmReceivablePlanDO convert(CrmReceivablePlanUpdateReqVO bean); + + CrmReceivablePlanRespVO convert(CrmReceivablePlanDO bean); + + default PageResult convertPage(PageResult pageResult, Map userMap, + List customerList, List contractList, + List receivableList) { + PageResult voPageResult = BeanUtils.toBean(pageResult, CrmReceivablePlanRespVO.class); + // 拼接关联字段 + Map customerMap = convertMap(customerList, CrmCustomerDO::getId); + Map contractMap = convertMap(contractList, CrmContractDO::getId); + Map receivableMap = convertMap(receivableList, CrmReceivableDO::getId); + voPageResult.getList().forEach(receivablePlan -> { + findAndThen(userMap, receivablePlan.getOwnerUserId(), user -> receivablePlan.setOwnerUserName(user.getNickname())); + findAndThen(userMap, Long.parseLong(receivablePlan.getCreator()), user -> receivablePlan.setCreatorName(user.getNickname())); + findAndThen(customerMap, receivablePlan.getCustomerId(), customer -> receivablePlan.setCustomerName(customer.getName())); + findAndThen(contractMap, receivablePlan.getContractId(), contract -> receivablePlan.setContractNo(contract.getNo())); + findAndThen(receivableMap, receivablePlan.getReceivableId(), receivable -> receivablePlan.setReturnType(receivable.getReturnType())); + }); + return voPageResult; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md new file mode 100644 index 000000000..8153487b7 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md @@ -0,0 +1 @@ + diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java new file mode 100644 index 000000000..3a543712b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java @@ -0,0 +1,108 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.business; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.enums.business.CrmBizEndStatus; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 商机 DO + * + * @author ljlleo + */ +@TableName("crm_business") +@KeySequence("crm_business_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmBusinessDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 商机名称 + */ + private String name; + /** + * 商机状态类型编号 + * + * 关联 {@link CrmBusinessStatusTypeDO#getId()} + */ + private Long statusTypeId; + /** + * 商机状态编号 + * + * 关联 {@link CrmBusinessStatusDO#getId()} + */ + private Long statusId; + /** + * 下次联系时间 + */ + private LocalDateTime contactNextTime; + /** + * 客户编号 + * + * TODO @ljileo:这个字段,后续要写下关联的实体哈 + * 关联 {@link CrmCustomerDO#getId()} + */ + private Long customerId; + /** + * 预计成交日期 + */ + private LocalDateTime dealTime; + /** + * 商机金额 + * + */ + private Integer price; + /** + * 整单折扣 + * + */ + private Integer discountPercent; + /** + * 产品总金额,单位:分 + */ + private Integer productPrice; + /** + * 备注 + */ + private String remark; + /** + * 结束状态 + * + * 枚举 {@link CrmBizEndStatus} + */ + private Integer endStatus; + /** + * 结束时的备注 + */ + private String endRemark; + /** + * 最后跟进时间 + */ + private LocalDateTime contactLastTime; + /** + * 跟进状态 + */ + private Boolean followUpStatus; + + /** + * 负责人的用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java new file mode 100644 index 000000000..79d6a2a7b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.business; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 商机产品关联表 DO + * + * @author lzxhqs + */ +@TableName("crm_business_product") +@KeySequence("crm_business_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmBusinessProductDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 商机编号 + * + * 关联 {@link CrmBusinessDO#getId()} + */ + private Long businessId; + /** + * 产品编号 + * + * 关联 {@link CrmProductDO#getId()} + */ + private Long productId; + /** + * 产品单价 + */ + private Integer price; + /** + * 销售价格, 单位:分 + */ + private Integer salesPrice; + /** + * 数量 + */ + private Integer count; + /** + * 折扣 + */ + private Integer discountPercent; + /** + * 总计价格(折扣后价格) + */ + private Integer totalPrice; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java new file mode 100644 index 000000000..17ce4f88c --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.business; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 商机状态 DO + * + * @author ljlleo + */ +@TableName("crm_business_status") +@KeySequence("crm_business_status_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmBusinessStatusDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 状态类型编号 + * + * 关联 {@link CrmBusinessStatusTypeDO#getId()} + */ + private Long typeId; + /** + * 状态名 + */ + private String name; + /** + * 赢单率,百分比 + */ + private Integer percent; + /** + * 排序 + */ + private Integer sort; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java new file mode 100644 index 000000000..aa9f86251 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.business; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.util.List; + +/** + * 商机状态类型 DO + * + * @author ljlleo + */ +@TableName(value = "crm_business_status_type", autoResultMap = true) +@KeySequence("crm_business_status_type_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmBusinessStatusTypeDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 状态类型名 + */ + private String name; + /** + * 使用的部门编号 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List deptIds; + /** + * 开启状态 + * + * TODO 改成 Integer,关联 CommonStatus + * 枚举 {@link CommonStatusEnum} + */ + private Boolean status; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java new file mode 100644 index 000000000..5ea1dea28 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java @@ -0,0 +1,116 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.clue; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +// TODO 芋艿:字段的顺序,需要整理下; +/** + * 线索 DO + * + * @author Wanwan + */ +@TableName("crm_clue") +@KeySequence("crm_clue_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmClueDO extends BaseDO { + + /** + * 编号,主键自增 + */ + @TableId + private Long id; + /** + * 转化状态 + */ + private Boolean transformStatus; + /** + * 跟进状态 + */ + private Boolean followUpStatus; + /** + * 线索名称 + */ + private String name; + /** + * 客户 id + * + * 关联 {@link CrmCustomerDO#getId()} + */ + private Long customerId; + /** + * 下次联系时间 + */ + private LocalDateTime contactNextTime; + /** + * 电话 + */ + private String telephone; + /** + * 手机号 + */ + private String mobile; + /** + * 地址 + */ + private String address; + /** + * 最后跟进时间 TODO 添加跟进记录时更新该值 + */ + private LocalDateTime contactLastTime; + /** + * 备注 + */ + private String remark; + /** + * 负责人的用户编号 + * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + /** + * 所属行业 + * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_INDUSTRY} + */ + private Integer industryId; + /** + * 客户等级 + * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_LEVEL} + */ + private Integer level; + /** + * 客户来源 + * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_SOURCE} + */ + private Integer source; + /** + * 网址 + */ + private String website; + /** + * QQ + */ + private String qq; + /** + * wechat + */ + private String wechat; + /** + * email + */ + private String email; + /** + * 客户描述 + */ + private String description; +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/package-info.java new file mode 100644 index 000000000..929b9b6fe --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/package-info.java @@ -0,0 +1,4 @@ +/** + * 线索 + */ +package cn.iocoder.yudao.module.crm.dal.dataobject.clue; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessDO.java new file mode 100644 index 000000000..46185a5ac --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactBusinessDO.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.contact; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * CRM 联系人与商机的关联 DO + * + * @author 芋道源码 + */ +@TableName("crm_contact_business") +@KeySequence("crm_contact_business_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmContactBusinessDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 联系人编号 + * + * 关联 {@link CrmContactDO#getId()} 字段 + */ + private Long contactId; + /** + * 商机编号 + * + * 关联 {@link CrmBusinessDO#getId()} 字段 + */ + private Long businessId; + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java new file mode 100644 index 000000000..c2dec247d --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java @@ -0,0 +1,117 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.contact; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * CRM 联系人 DO + * + * @author 芋道源码 + */ +@TableName("crm_contact") +@KeySequence("crm_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmContactDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 客户编号 + * + * 关联 {@link CrmCustomerDO#getId()} + */ + private Long customerId; + /** + * 手机号 + */ + private String mobile; + /** + * 电话 + */ + private String telephone; + /** + * 电子邮箱 + */ + private String email; + /** + * 所在地 + * + * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段 + */ + private Integer areaId; + /** + * 详细地址 + */ + private String detailAddress; + /** + * 备注 + */ + private String remark; + + /** + * 直属上级 + * + * 关联 {@link CrmContactDO#id} + */ + private Long parentId; + /** + * 姓名 + */ + private String name; + /** + * 职位 + */ + private String post; + /** + * QQ + */ + private Long qq; + /** + * 微信 + */ + private String wechat; + /** + * 性别 + * + * 枚举 {@link cn.iocoder.yudao.module.system.enums.common.SexEnum} + */ + private Integer sex; + /** + * 是否关键决策人 + */ + private Boolean master; + /** + * 负责人用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + + /** + * 最后跟进时间 + */ + private LocalDateTime contactLastTime; + /** + * 最后跟进内容 + */ + private String contactLastContent; + /** + * 下次联系时间 + */ + private LocalDateTime contactNextTime; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/package-info.java new file mode 100644 index 000000000..dfe0898e3 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/package-info.java @@ -0,0 +1,4 @@ +/** + * 联系人 + */ +package cn.iocoder.yudao.module.crm.dal.dataobject.contact; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java new file mode 100644 index 000000000..0c01a3394 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java @@ -0,0 +1,121 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.contract; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +// TODO 芋艿:实体的梳理 +/** + * CRM 合同 DO + * + * @author dhb52 + */ +@TableName("crm_contract") +@KeySequence("crm_contract_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmContractDO extends BaseDO { + + /** + * 合同编号 + */ + @TableId + private Long id; + /** + * 合同编号 + */ + private String no; + /** + * 合同名称 + */ + private String name; + /** + * 客户编号 + * + * 关联 {@link CrmCustomerDO#getId()} + */ + private Long customerId; + /** + * 商机编号 + * + * 关联 {@link CrmBusinessDO#getId()} + */ + private Long businessId; + /** + * 工作流编号 + * + * 关联 ProcessInstance 的 id 属性 + */ + private String processInstanceId; + /** + * 下单日期 + */ + private LocalDateTime orderDate; + /** + * 开始时间 + */ + private LocalDateTime startTime; + /** + * 结束时间 + */ + private LocalDateTime endTime; + /** + * 合同金额,单位:分 + */ + private Integer price; + /** + * 整单折扣 + */ + private Integer discountPercent; + /** + * 产品总金额,单位:分 + */ + private Integer productPrice; + /** + * 客户签约人 + * + * 关联 {@link CrmContactDO#getId()} + */ + private Long contactId; + /** + * 公司签约人 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long signUserId; + /** + * 最后跟进时间 + */ + private LocalDateTime contactLastTime; + /** + * 备注 + */ + private String remark; + + /** + * 负责人的用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + + /** + * 审批状态 + * + * 枚举 {@link CrmAuditStatusEnum} + */ + private Integer auditStatus; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java new file mode 100644 index 000000000..bc977c78f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.contract; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 合同产品关联表 DO + * + * @author HUIHUI + */ +@TableName("crm_contract_product") +@KeySequence("crm_contract_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmContractProductDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 产品编号 + * + * 关联 {@link CrmProductDO#getId()} + */ + private Long productId; + /** + * 合同编号 + * + * 关联 {@link CrmContractDO#getId()} + */ + private Long contractId; + /** + * 产品单价 + */ + private Integer price; + /** + * 销售价格, 单位:分 + */ + private Integer salesPrice; + /** + * 数量 + */ + private Integer count; + /** + * 折扣 + */ + private Integer discountPercent; + /** + * 总计价格(折扣后价格) + * + * TODO @puhui999:可以写下计算公式哈; + */ + private Integer totalPrice; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java new file mode 100644 index 000000000..2bd614f57 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java @@ -0,0 +1,135 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.customer; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +// TODO 芋艿:调整下字段 + +/** + * CRM 客户 DO + * + * @author Wanwan + */ +@TableName(value = "crm_customer") +@KeySequence("crm_customer_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmCustomerDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 客户名称 + */ + private String name; + /** + * 跟进状态 + */ + private Boolean followUpStatus; + /** + * 锁定状态 + */ + private Boolean lockStatus; + /** + * 成交状态 + */ + private Boolean dealStatus; + /** + * 所属行业 + * + * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_INDUSTRY} + */ + private Integer industryId; + /** + * 客户等级 + * + * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_LEVEL} + */ + private Integer level; + /** + * 客户来源 + * + * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_SOURCE} + */ + private Integer source; + /** + * 手机 + */ + private String mobile; + /** + * 电话 + */ + private String telephone; + /** + * 网址 + */ + private String website; + /** + * QQ + */ + private String qq; + /** + * wechat + */ + private String wechat; + /** + * email + */ + private String email; + /** + * 客户描述 + */ + private String description; + /** + * 备注 + */ + private String remark; + /** + * 负责人的用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + /** + * 所在地 + * + * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段 + */ + private Integer areaId; + /** + * 详细地址 + */ + private String detailAddress; + + /** + * 最后接收时间 + */ + private LocalDateTime receiveTime; + /** + * 最后跟进时间 + */ + private LocalDateTime contactLastTime; + + /** + * 最后跟进内容 + */ + private String contactLastContent; + /** + * 下次联系时间 + */ + private LocalDateTime contactNextTime; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerLimitConfigDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerLimitConfigDO.java new file mode 100644 index 000000000..df3d3be6f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerLimitConfigDO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.customer; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; +import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.util.List; + +/** + * 客户限制配置 DO + * + * @author Wanwan + */ +@TableName(value = "crm_customer_limit_config", autoResultMap = true) +@KeySequence("crm_customer_limit_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmCustomerLimitConfigDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 规则类型 + *

+ * 枚举 {@link CrmCustomerLimitConfigTypeEnum} + */ + private Integer type; + /** + * 规则适用人群 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List userIds; + /** + * 规则适用部门 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List deptIds; + /** + * 数量上限 + */ + private Integer maxCount; + /** + * 成交客户是否占有拥有客户数 + * + * 当且仅当 {@link #type} 为 1 时,进行使用 + */ + private Boolean dealCountEnabled; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerPoolConfigDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerPoolConfigDO.java new file mode 100644 index 000000000..76f20dc3b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerPoolConfigDO.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.customer; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.*; +import lombok.*; + +/** + * 客户公海配置 DO + * + * @author Wanwan + */ +@TableName(value = "crm_customer_pool_config") +@KeySequence("crm_customer_pool_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmCustomerPoolConfigDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 是否启用客户公海 + */ + private Boolean enabled; + /** + * 未跟进放入公海天数 + */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private Integer contactExpireDays; + /** + * 未成交放入公海天数 + */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private Integer dealExpireDays; + /** + * 是否开启提前提醒 + */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private Boolean notifyEnabled; + /** + * 提前提醒天数 + */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private Integer notifyDays; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java new file mode 100644 index 000000000..a01d47af9 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java @@ -0,0 +1,97 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.followup; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; +import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 跟进记录 DO + * + * 用于记录客户、联系人的每一次跟进 + * + * @author 芋道源码 + */ +@TableName(value = "crm_follow_up_record", autoResultMap = true) +@KeySequence("crm_follow_up_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmFollowUpRecordDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 数据类型 + * + * 枚举 {@link CrmBizTypeEnum} + */ + private Integer bizType; + /** + * 数据编号 + * + * 关联 {@link CrmBizTypeEnum} 对应模块 DO 的 id 字段 + */ + private Long bizId; + + /** + * 跟进类型 + * + * 关联 {@link DictTypeConstants#CRM_FOLLOW_UP_TYPE} 字典 + */ + private Integer type; + /** + * 跟进内容 + */ + private String content; + /** + * 下次联系时间 + */ + private LocalDateTime nextTime; + + /** + * 图片 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List picUrls; + /** + * 附件 + */ + @TableField(typeHandler = StringListTypeHandler.class) + private List fileUrls; + + /** + * 关联的商机编号数组 + * + * 关联 {@link CrmBusinessDO#getId()} + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List businessIds; + /** + * 关联的联系人编号数组 + * + * 关联 {@link CrmContactDO#getId()} + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List contactIds; + + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/permission/CrmPermissionDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/permission/CrmPermissionDO.java new file mode 100644 index 000000000..59e47a5a8 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/permission/CrmPermissionDO.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.permission; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * CRM 数据权限 DO + * + * @author HUIHUI + */ +@TableName("crm_permission") +@KeySequence("crm_permission_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmPermissionDO extends BaseDO { + + /** + * 编号,主键自增 + */ + @TableId + private Long id; + + /** + * 数据类型 + * + * 枚举 {@link CrmBizTypeEnum} + */ + private Integer bizType; + /** + * 数据编号 + * + * 关联 {@link CrmBizTypeEnum} 对应模块 DO 的 id 字段 + */ + private Long bizId; + + /** + * 用户编号 + * + * 关联 AdminUser 的 id 字段 + */ + private Long userId; + + /** + * 权限级别 + * + * 关联 {@link CrmPermissionLevelEnum} + */ + private Integer level; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductCategoryDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductCategoryDO.java new file mode 100644 index 000000000..e0f4d6245 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductCategoryDO.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.product; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 产品分类 DO + * + * @author ZanGe丶 + */ +@TableName("crm_product_category") +@KeySequence("crm_product_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmProductCategoryDO extends BaseDO { + + /** + * 父分类编号 - 根分类 + */ + public static final Long PARENT_ID_NULL = 0L; + /** + * 限定分类层级 + */ + public static final int CATEGORY_LEVEL = 2; + + /** + * 分类编号 + */ + @TableId + private Long id; + /** + * 分类名称 + */ + private String name; + /** + * 父级编号 + * + * 关联 {@link CrmProductCategoryDO#getId()} + */ + private Long parentId; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java new file mode 100644 index 000000000..a3c56ccc9 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java @@ -0,0 +1,72 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.product; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; +import cn.iocoder.yudao.module.crm.enums.product.CrmProductStatusEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * CRM 产品 DO + * + * @author ZanGe丶 + */ +@TableName("crm_product") +@KeySequence("crm_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmProductDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 产品名称 + */ + private String name; + /** + * 产品编码 + */ + private String no; + /** + * 单位 + * + * 字典 {@link DictTypeConstants#CRM_PRODUCT_UNIT} + */ + private Integer unit; + /** + * 价格,单位:分 + */ + private Integer price; + /** + * 状态 + * + * 关联 {@link CrmProductStatusEnum} + */ + private Integer status; + /** + * 产品分类 ID + * + * 关联 {@link CrmProductCategoryDO#getId()} 字段 + */ + private Long categoryId; + /** + * 产品描述 + */ + private String description; + /** + * 负责人的用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/package-info.java new file mode 100644 index 000000000..4c7282d73 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/package-info.java @@ -0,0 +1,4 @@ +/** + * 产品表 + */ +package cn.iocoder.yudao.module.crm.dal.dataobject.product; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java new file mode 100644 index 000000000..842fc96b4 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java @@ -0,0 +1,96 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.receivable; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 回款 DO + * + * @author 赤焰 + */ +@TableName("crm_receivable") +@KeySequence("crm_receivable_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmReceivableDO extends BaseDO { + + /** + * ID + */ + @TableId + private Long id; + /** + * 回款编号 + */ + private String no; + // TODO @liuhongfeng:“对应实体”,参考别的模块,关联 {@link TableField.MetaInfo#getJdbcType()} + /** + * 回款计划 + * + * TODO @liuhongfeng:这个字段什么时候更新,也可以写下 + * + * 对应实体 {@link CrmReceivablePlanDO} + */ + private Long planId; + /** + * 客户 ID + * + * 对应实体 {@link cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO} + */ + private Long customerId; + /** + * 合同 ID + * + * 对应实体 {@link CrmContractDO} + */ + private Long contractId; + /** + * 工作流编号 + * + * TODO @liuhongfeng:这个字段,后续要写下关联的实体哈 + */ + private Long processInstanceId; + /** + * 回款日期 + */ + private LocalDateTime returnTime; + // TODO @liuhongfeng:少个枚举 + /** + * 回款方式 + */ + private Integer returnType; + /** + * 回款金额 + */ + private Integer price; + // TODO @liuhongfeng:少关联实体; + /** + * 负责人 + */ + private Long ownerUserId; + /** + * 显示顺序 + */ + private Integer sort; + /** + * 审核状态 + * + * 枚举 {@link cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum} + */ + private Integer auditStatus; + /** + * 备注 + */ + private String remark; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java new file mode 100644 index 000000000..78658e609 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java @@ -0,0 +1,88 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.receivable; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 回款计划 DO + * + * @author 芋道源码 + */ +@TableName("crm_receivable_plan") +@KeySequence("crm_receivable_plan_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmReceivablePlanDO extends BaseDO { + + /** + * ID + */ + @TableId + private Long id; + /** + * 期数 + */ + private Integer period; + /** + * 回款ID + * + * TODO @liuhongfeng:少关联实体; + */ + private Long receivableId; + /** + * 完成状态 + */ + private Boolean finishStatus; + /** + * 计划回款金额,单位:分 + */ + private Integer price; + /** + * 计划回款日期 + */ + private LocalDateTime returnTime; + /** + * 提前几天提醒 + */ + private Integer remindDays; + /** + * 提醒日期 + */ + private LocalDateTime remindTime; + /** + * 客户 ID + * + * TODO @liuhongfeng:少关联实体; + */ + private Long customerId; + /** + * 合同 ID + * + * TODO @liuhongfeng:少关联实体; + */ + private Long contractId; + /** + * 负责人 ID + * + * TODO @liuhongfeng:少关联实体; + */ + private Long ownerUserId; + /** + * 显示顺序 + */ + private Integer sort; + /** + * 备注 + */ + private String remark; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/package-info.java new file mode 100644 index 000000000..f27442cdf --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/package-info.java @@ -0,0 +1,4 @@ +/** + * 回款 + */ +package cn.iocoder.yudao.module.crm.dal.dataobject.receivable; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/bi/CrmBiRankingMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/bi/CrmBiRankingMapper.java new file mode 100644 index 000000000..9b71df7b6 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/bi/CrmBiRankingMapper.java @@ -0,0 +1,81 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.bi; + +import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * CRM BI 排行榜 Mapper + * + * @author anhaohao + */ +@Mapper +public interface CrmBiRankingMapper { + + /** + * 查询合同金额排行榜 + * + * @param rankReqVO 参数 + * @return 合同金额排行榜 + */ + List selectContractPriceRank(CrmBiRankReqVO rankReqVO); + + /** + * 查询回款金额排行榜 + * + * @param rankReqVO 参数 + * @return 回款金额排行榜 + */ + List selectReceivablePriceRank(CrmBiRankReqVO rankReqVO); + + /** + * 查询签约合同数量排行榜 + * + * @param rankReqVO 参数 + * @return 签约合同数量排行榜 + */ + List selectContractCountRank(CrmBiRankReqVO rankReqVO); + + /** + * 查询产品销量排行榜 + * + * @param rankReqVO 参数 + * @return 产品销量排行榜 + */ + List selectProductSalesRank(CrmBiRankReqVO rankReqVO); + + /** + * 查询新增客户数排行榜 + * + * @param rankReqVO 参数 + * @return 新增客户数排行榜 + */ + List selectCustomerCountRank(CrmBiRankReqVO rankReqVO); + + /** + * 查询联系人数量排行榜 + * + * @param rankReqVO 参数 + * @return 联系人数量排行榜 + */ + List selectContactsCountRank(CrmBiRankReqVO rankReqVO); + + /** + * 查询跟进次数排行榜 + * + * @param rankReqVO 参数 + * @return 跟进次数排行榜 + */ + List selectFollowCountRank(CrmBiRankReqVO rankReqVO); + + /** + * 查询跟进客户数排行榜 + * + * @param rankReqVO 参数 + * @return 跟进客户数排行榜 + */ + List selectFollowCustomerCountRank(CrmBiRankReqVO rankReqVO); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java new file mode 100644 index 000000000..d7438484f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java @@ -0,0 +1,66 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.business; + +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.framework.mybatis.core.query.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * 商机 Mapper + * + * @author ljlleo + */ +@Mapper +public interface CrmBusinessMapper extends BaseMapperX { + + default int updateOwnerUserIdById(Long id, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmBusinessDO::getId, id) + .set(CrmBusinessDO::getOwnerUserId, ownerUserId)); + } + + default PageResult selectPageByCustomerId(CrmBusinessPageReqVO pageReqVO) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eq(CrmBusinessDO::getCustomerId, pageReqVO.getCustomerId()) // 指定客户编号 + .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName()) + .orderByDesc(CrmBusinessDO::getId)); + } + + default PageResult selectPageByContactId(CrmBusinessPageReqVO pageReqVO, Collection businessIds) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .in(CrmBusinessDO::getId, businessIds) // 指定商机编号 + .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName()) + .orderByDesc(CrmBusinessDO::getId)); + } + + default PageResult selectPage(CrmBusinessPageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(), + CrmBusinessDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); + // 拼接自身的查询条件 + query.selectAll(CrmBusinessDO.class) + .likeIfPresent(CrmBusinessDO::getName, pageReqVO.getName()) + .orderByDesc(CrmBusinessDO::getId); + return selectJoinPage(pageReqVO, CrmBusinessDO.class, query); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(), ids, userId); + // 拼接自身的查询条件 + query.selectAll(CrmBusinessDO.class).in(CrmBusinessDO::getId, ids).orderByDesc(CrmBusinessDO::getId); + return selectJoinList(CrmBusinessDO.class, query); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java new file mode 100644 index 000000000..2d1471c73 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.business; + + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 商机产品 Mapper + * + * @author lzxhqs + */ +@Mapper +public interface CrmBusinessProductMapper extends BaseMapperX { + + // TODO @puhui999:用不到的方法,看看是不是删除哈 + default void deleteByBusinessId(Long getBusinessId) { // TODO @lzxhqs:第一个方法,和类之间最好空一行; + delete(CrmBusinessProductDO::getBusinessId, getBusinessId); + } + + default CrmBusinessProductDO selectByBusinessId(Long getBusinessId) { + return selectOne(CrmBusinessProductDO::getBusinessId, getBusinessId); + } + + default List selectListByBusinessId(Long businessId) { + // TODO @puhui999:可以简化,selectList(CrmBusinessProductDO::getBusinessId, businessId) + return selectList(new LambdaQueryWrapperX().eq(CrmBusinessProductDO::getBusinessId, businessId)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusMapper.java new file mode 100644 index 000000000..be847ab9d --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusMapper.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.business; + +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.business.vo.status.CrmBusinessStatusPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 商机状态 Mapper + * + * @author ljlleo + */ +@Mapper +public interface CrmBusinessStatusMapper extends BaseMapperX { + + default PageResult selectPage(CrmBusinessStatusPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .orderByDesc(CrmBusinessStatusDO::getId)); + } + + default List selectList(CrmBusinessStatusQueryVO queryVO) { + return selectList(new LambdaQueryWrapperX() + .eqIfPresent(CrmBusinessStatusDO::getTypeId, queryVO.getTypeId()) + .inIfPresent(CrmBusinessStatusDO::getId, queryVO.getIdList()) + .orderByDesc(CrmBusinessStatusDO::getId)); + } + + default int delete(Long typeId) { + return delete(CrmBusinessStatusDO::getTypeId, typeId); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java new file mode 100644 index 000000000..410ebf050 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.business; + +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.business.vo.type.CrmBusinessStatusTypePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 商机状态类型 Mapper + * + * @author ljlleo + */ +@Mapper +public interface CrmBusinessStatusTypeMapper extends BaseMapperX { + + default PageResult selectPage(CrmBusinessStatusTypePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .orderByDesc(CrmBusinessStatusTypeDO::getId)); + } + + default List selectList(CrmBusinessStatusTypeQueryVO queryVO) { + return selectList(new LambdaQueryWrapperX() + .eqIfPresent(CrmBusinessStatusTypeDO::getStatus, queryVO.getStatus()) + .inIfPresent(CrmBusinessStatusTypeDO::getId, queryVO.getIdList())); + } + + // TODO @lzxhqs:这个可以改成 selectByName。业务上基于在判断 id 匹配;这样更通用一些;mapper 尽量通用,不关注或者特别关联业务; + /** + * 根据ID和name查询 + * + * @param id 商机状态类型id + * @param name 状态类型名 + * @return result + */ + default CrmBusinessStatusTypeDO selectByIdAndName(Long id, String name) { + return selectOne(CrmBusinessStatusTypeDO::getId, id, CrmBusinessStatusTypeDO::getName, name); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/package-info.java new file mode 100644 index 000000000..72863e1f4 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/package-info.java @@ -0,0 +1,4 @@ +/** + * 商机(销售机会) + */ +package cn.iocoder.yudao.module.crm.dal.mysql.business; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java new file mode 100644 index 000000000..cab4c4662 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.clue; + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * 线索 Mapper + * + * @author Wanwan + */ +@Mapper +public interface CrmClueMapper extends BaseMapperX { + + default int updateOwnerUserIdById(Long id, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmClueDO::getId, id) + .set(CrmClueDO::getOwnerUserId, ownerUserId)); + } + + default PageResult selectPage(CrmCluePageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(), + CrmClueDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool()); + // 拼接自身的查询条件 + query.selectAll(CrmClueDO.class) + .likeIfPresent(CrmClueDO::getName, pageReqVO.getName()) + .likeIfPresent(CrmClueDO::getTelephone, pageReqVO.getTelephone()) + .likeIfPresent(CrmClueDO::getMobile, pageReqVO.getMobile()) + .eqIfPresent(CrmClueDO::getIndustryId, pageReqVO.getIndustryId()) + .eqIfPresent(CrmClueDO::getLevel, pageReqVO.getLevel()) + .eqIfPresent(CrmClueDO::getSource, pageReqVO.getSource()) + .orderByDesc(CrmClueDO::getId); + return selectJoinPage(pageReqVO, CrmClueDO.class, query); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(), ids, userId); + query.selectAll(CrmClueDO.class).in(CrmClueDO::getId, ids).orderByDesc(CrmClueDO::getId); + // 拼接自身的查询条件 + return selectJoinList(CrmClueDO.class, query); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/package-info.java new file mode 100644 index 000000000..f9978e868 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/package-info.java @@ -0,0 +1,4 @@ +/** + * 线索 + */ +package cn.iocoder.yudao.module.crm.dal.mysql.clue; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java new file mode 100644 index 000000000..c35df47d3 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.contact; + +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.framework.mybatis.core.query.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * CRM 联系人 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface CrmContactMapper extends BaseMapperX { + + default int updateOwnerUserIdById(Long id, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmContactDO::getId, id) + .set(CrmContactDO::getOwnerUserId, ownerUserId)); + } + + default int updateOwnerUserIdByCustomerId(Long customerId, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmContactDO::getCustomerId, customerId) + .set(CrmContactDO::getOwnerUserId, ownerUserId)); + } + + default PageResult selectPageByCustomerId(CrmContactPageReqVO pageVO) { + return selectPage(pageVO, new LambdaQueryWrapperX() + .eq(CrmContactDO::getCustomerId, pageVO.getCustomerId()) // 指定客户编号 + .likeIfPresent(CrmContactDO::getName, pageVO.getName()) + .eqIfPresent(CrmContactDO::getMobile, pageVO.getMobile()) + .eqIfPresent(CrmContactDO::getTelephone, pageVO.getTelephone()) + .eqIfPresent(CrmContactDO::getEmail, pageVO.getEmail()) + .eqIfPresent(CrmContactDO::getQq, pageVO.getQq()) + .eqIfPresent(CrmContactDO::getWechat, pageVO.getWechat()) + .orderByDesc(CrmContactDO::getId)); + } + + default PageResult selectPage(CrmContactPageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(), + CrmContactDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); + // 拼接自身的查询条件 + query.selectAll(CrmContactDO.class) + .likeIfPresent(CrmContactDO::getName, pageReqVO.getName()) + .eqIfPresent(CrmContactDO::getMobile, pageReqVO.getMobile()) + .eqIfPresent(CrmContactDO::getTelephone, pageReqVO.getTelephone()) + .eqIfPresent(CrmContactDO::getEmail, pageReqVO.getEmail()) + .eqIfPresent(CrmContactDO::getQq, pageReqVO.getQq()) + .eqIfPresent(CrmContactDO::getWechat, pageReqVO.getWechat()) + .orderByDesc(CrmContactDO::getId); + return selectJoinPage(pageReqVO, CrmContactDO.class, query); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(), ids, userId); + // 拼接自身的查询条件 + query.selectAll(CrmContactDO.class).in(CrmContactDO::getId, ids).orderByDesc(CrmContactDO::getId); + return selectJoinList(CrmContactDO.class, query); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/package-info.java new file mode 100644 index 000000000..6cb7d4be2 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/package-info.java @@ -0,0 +1,4 @@ +/** + * 联系人 + */ +package cn.iocoder.yudao.module.crm.dal.mysql.contact; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java new file mode 100644 index 000000000..3eae483bc --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * CRM 联系人与商机的关联 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface CrmContactBusinessMapper extends BaseMapperX { + + default CrmContactBusinessDO selectByContactIdAndBusinessId(Long contactId, Long businessId) { + return selectOne(CrmContactBusinessDO::getContactId, contactId, + CrmContactBusinessDO::getBusinessId, businessId); + } + + default void deleteByContactIdAndBusinessId(Long contactId, Collection businessIds) { + delete(new LambdaQueryWrapper() + .eq(CrmContactBusinessDO::getContactId, contactId) + .in(CrmContactBusinessDO::getBusinessId, businessIds)); + } + + default List selectListByContactId(Long contactId) { + return selectList(CrmContactBusinessDO::getContactId, contactId); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java new file mode 100644 index 000000000..3ecd93fcb --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java @@ -0,0 +1,90 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.contract; + +import cn.hutool.core.date.LocalDateTimeUtil; +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.framework.mybatis.core.query.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * CRM 合同 Mapper + * + * @author dhb52 + */ +@Mapper +public interface CrmContractMapper extends BaseMapperX { + + default int updateOwnerUserIdById(Long id, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmContractDO::getId, id) + .set(CrmContractDO::getOwnerUserId, ownerUserId)); + } + + default PageResult selectPageByCustomerId(CrmContractPageReqVO pageReqVO) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eq(CrmContractDO::getCustomerId, pageReqVO.getCustomerId()) + .likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo()) + .likeIfPresent(CrmContractDO::getName, pageReqVO.getName()) + .eqIfPresent(CrmContractDO::getCustomerId, pageReqVO.getCustomerId()) + .eqIfPresent(CrmContractDO::getBusinessId, pageReqVO.getBusinessId()) + .orderByDesc(CrmContractDO::getId)); + } + + default PageResult selectPage(CrmContractPageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(), + CrmContractDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); + // 拼接自身的查询条件 + query.selectAll(CrmContractDO.class) + .likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo()) + .likeIfPresent(CrmContractDO::getName, pageReqVO.getName()) + .eqIfPresent(CrmContractDO::getCustomerId, pageReqVO.getCustomerId()) + .eqIfPresent(CrmContractDO::getBusinessId, pageReqVO.getBusinessId()) + .eqIfPresent(CrmContractDO::getAuditStatus, pageReqVO.getAuditStatus()) + .orderByDesc(CrmContractDO::getId); + + // Backlog: 即将到期的合同 + LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); + LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now()); + if (CrmContractPageReqVO.EXPIRY_TYPE_ABOUT_TO_EXPIRE.equals(pageReqVO.getExpiryType())) { // 即将到期 + // TODO: @芋艿 需要配置 提前提醒天数 + int REMIND_DAYS = 20; + query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus()) + .between(CrmContractDO::getEndTime, beginOfToday, endOfToday.plusDays(REMIND_DAYS)); + } else if (CrmContractPageReqVO.EXPIRY_TYPE_EXPIRED.equals(pageReqVO.getExpiryType())) { // 已到期 + query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus()) + .lt(CrmContractDO::getEndTime, endOfToday); + } + return selectJoinPage(pageReqVO, CrmContractDO.class, query); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 构建数据权限连表条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(), ids, userId); + // 拼接自身的查询条件 + query.selectAll(CrmContractDO.class).in(CrmContractDO::getId, ids).orderByDesc(CrmContractDO::getId); + return selectJoinList(CrmContractDO.class, query); + } + + default Long selectCountByContactId(Long contactId) { + return selectCount(CrmContractDO::getContactId, contactId); + } + + default Long selectCountByBusinessId(Long businessId) { + return selectCount(CrmContractDO::getBusinessId, businessId); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java new file mode 100644 index 000000000..814024125 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.contract; + + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 合同产品 Mapper + * + * @author HUIHUI + */ +@Mapper +public interface CrmContractProductMapper extends BaseMapperX { + + // TODO @puhui999:用不到的方法,看看是不是删除哈 + default void deleteByContractId(Long contractId) { // TODO @lzxhqs:第一个方法,和类之间最好空一行; + delete(CrmContractProductDO::getContractId, contractId); + } + + default CrmContractProductDO selectByContractId(Long contractId) { + return selectOne(CrmContractProductDO::getContractId, contractId); + } + + default List selectListByContractId(Long contractId) { + return selectList(new LambdaQueryWrapperX().eq(CrmContractProductDO::getContractId, contractId)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java new file mode 100644 index 000000000..08beaf808 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerLimitConfigMapper.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.customer; + +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.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 客户限制配置 Mapper + * + * @author Wanwan + */ +@Mapper +public interface CrmCustomerLimitConfigMapper extends BaseMapperX { + + default PageResult selectPage(CrmCustomerLimitConfigPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(CrmCustomerLimitConfigDO::getType, reqVO.getType()) + .orderByDesc(CrmCustomerLimitConfigDO::getId)); + } + + default List selectListByTypeAndUserIdAndDeptId( + Integer type, Long userId, Long deptId) { + LambdaQueryWrapperX query = new LambdaQueryWrapperX() + .eq(CrmCustomerLimitConfigDO::getType, type); + query.apply("FIND_IN_SET({0}, user_ids) > 0", userId); + if (deptId != null) { + query.apply("FIND_IN_SET({0}, dept_ids) > 0", deptId); + } + return selectList(query); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java new file mode 100644 index 000000000..29cd47c66 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java @@ -0,0 +1,150 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.customer; + +import cn.hutool.core.date.LocalDateTimeUtil; +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.framework.mybatis.core.query.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.crm.controller.admin.backlog.vo.CrmTodayCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.lang.Nullable; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 客户 Mapper + * + * @author Wanwan + */ +@Mapper +public interface CrmCustomerMapper extends BaseMapperX { + + default Long selectCountByLockStatusAndOwnerUserId(Boolean lockStatus, Long ownerUserId) { + return selectCount(new LambdaUpdateWrapper() + .eq(CrmCustomerDO::getLockStatus, lockStatus) + .eq(CrmCustomerDO::getOwnerUserId, ownerUserId)); + } + + default Long selectCountByDealStatusAndOwnerUserId(@Nullable Boolean dealStatus, Long ownerUserId) { + return selectCount(new LambdaQueryWrapperX() + .eqIfPresent(CrmCustomerDO::getDealStatus, dealStatus) + .eq(CrmCustomerDO::getOwnerUserId, ownerUserId)); + } + + default int updateOwnerUserIdById(Long id, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmCustomerDO::getId, id) + .set(CrmCustomerDO::getOwnerUserId, ownerUserId)); + } + + default PageResult selectPage(CrmCustomerPageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), + CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool()); + // 拼接自身的查询条件 + query.selectAll(CrmCustomerDO.class) + .likeIfPresent(CrmCustomerDO::getName, pageReqVO.getName()) + .eqIfPresent(CrmCustomerDO::getMobile, pageReqVO.getMobile()) + .eqIfPresent(CrmCustomerDO::getIndustryId, pageReqVO.getIndustryId()) + .eqIfPresent(CrmCustomerDO::getLevel, pageReqVO.getLevel()) + .eqIfPresent(CrmCustomerDO::getSource, pageReqVO.getSource()); + return selectJoinPage(pageReqVO, CrmCustomerDO.class, query); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), ids, userId); + // 拼接自身的查询条件 + query.selectAll(CrmCustomerDO.class).in(CrmCustomerDO::getId, ids).orderByDesc(CrmCustomerDO::getId); + return selectJoinList(CrmCustomerDO.class, query); + } + + /** + * 待办事项 - 今日需联系客户 + * + * @param pageReqVO 分页请求参数 + * @param userId 当前用户ID + * @return 分页结果 + */ + default PageResult selectTodayCustomerPage(CrmTodayCustomerPageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), + CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), null); + + // 拼接自身的查询条件 + query.selectAll(CrmCustomerDO.class); + if (pageReqVO.getContactStatus() != null) { + LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); + LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now()); + if (pageReqVO.getContactStatus().equals(CrmTodayCustomerPageReqVO.CONTACT_TODAY)) { // 今天需联系 + query.between(CrmCustomerDO::getContactNextTime, beginOfToday, endOfToday); + } else if (pageReqVO.getContactStatus().equals(CrmTodayCustomerPageReqVO.CONTACT_EXPIRED)) { // 已逾期 + query.lt(CrmCustomerDO::getContactNextTime, beginOfToday); + } else if (pageReqVO.getContactStatus().equals(CrmTodayCustomerPageReqVO.CONTACT_ALREADY)) { // 已联系 + query.between(CrmCustomerDO::getContactLastTime, beginOfToday, endOfToday); + } else { + throw new IllegalArgumentException("未知联系状态:" + pageReqVO.getContactStatus()); + } + } + return selectJoinPage(pageReqVO, CrmCustomerDO.class, query); + } + + default List selectListByLockAndNotPool(Boolean lockStatus) { + return selectList(new LambdaQueryWrapper() + .eq(CrmCustomerDO::getLockStatus, lockStatus) + .gt(CrmCustomerDO::getOwnerUserId, 0)); + } + + default CrmCustomerDO selectByCustomerName(String name) { + return selectOne(CrmCustomerDO::getName, name); + } + + default PageResult selectPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageReqVO, + CrmCustomerPoolConfigDO poolConfigDO, + Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), + CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), null); + // TODO @dhb52:lock 的情况,不需要提醒哈; + + // 拼接自身的查询条件 + query.selectAll(CrmCustomerDO.class); + // 情况一:未成交提醒日期区间 + Integer dealExpireDays = poolConfigDO.getDealExpireDays(); + LocalDateTime startDealRemindDate = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()) + .minusDays(dealExpireDays); + LocalDateTime endDealRemindDate = LocalDateTimeUtil.endOfDay(LocalDateTime.now()) + .minusDays(Math.max(dealExpireDays - poolConfigDO.getNotifyDays(), 0)); + // 情况二:未跟进提醒日期区间 + Integer contactExpireDays = poolConfigDO.getContactExpireDays(); + LocalDateTime startContactRemindDate = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()) + .minusDays(contactExpireDays); + LocalDateTime endContactRemindDate = LocalDateTimeUtil.endOfDay(LocalDateTime.now()) + .minusDays(Math.max(contactExpireDays - poolConfigDO.getNotifyDays(), 0)); + query + // 情况一:1. 未成交放入公海提醒 + .eq(CrmCustomerDO::getDealStatus, false) + .between(CrmCustomerDO::getCreateTime, startDealRemindDate, endDealRemindDate) + // 情况二:未跟进放入公海提醒 + .or() // 2.1 contactLastTime 为空 TODO 芋艿:这个要不要搞个默认值; + .isNull(CrmCustomerDO::getContactLastTime) + .between(CrmCustomerDO::getCreateTime, startContactRemindDate, endContactRemindDate) + .or() // 2.2 ContactLastTime 不为空 + .between(CrmCustomerDO::getContactLastTime, startContactRemindDate, endContactRemindDate); + return selectJoinPage(pageReqVO, CrmCustomerDO.class, query); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerPoolConfigMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerPoolConfigMapper.java new file mode 100644 index 000000000..06cf44e4f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerPoolConfigMapper.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.customer; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 客户公海配置 Mapper + * + * @author Wanwan + */ +@Mapper +public interface CrmCustomerPoolConfigMapper extends BaseMapperX { + + default CrmCustomerPoolConfigDO selectOne() { + return selectOne(new LambdaQueryWrapperX().last("LIMIT 1")); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/followup/CrmFollowUpRecordMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/followup/CrmFollowUpRecordMapper.java new file mode 100644 index 000000000..45e2b412e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/followup/CrmFollowUpRecordMapper.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.followup; + +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.followup.vo.CrmFollowUpRecordPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * 跟进记录 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface CrmFollowUpRecordMapper extends BaseMapperX { + + default PageResult selectPage(CrmFollowUpRecordPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(CrmFollowUpRecordDO::getBizType, reqVO.getBizType()) + .eqIfPresent(CrmFollowUpRecordDO::getBizId, reqVO.getBizId()) + .orderByDesc(CrmFollowUpRecordDO::getId)); + } + + default void deleteByBiz(Integer bizType, Long bizId) { + delete(new LambdaQueryWrapperX() + .eq(CrmFollowUpRecordDO::getBizType, bizType) + .eq(CrmFollowUpRecordDO::getBizId, bizId)); + } + + default List selectListByBiz(Integer bizType, Collection bizIds) { + return selectList(new LambdaQueryWrapperX() + .eq(CrmFollowUpRecordDO::getBizType, bizType) + .in(CrmFollowUpRecordDO::getBizId, bizIds)); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java new file mode 100644 index 000000000..26f212e5e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/CrmPermissionMapper.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.permission; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * crm 数据权限 mapper + * + * @author HUIHUI + */ +@Mapper +public interface CrmPermissionMapper extends BaseMapperX { + + default CrmPermissionDO selectByBizTypeAndBizIdByUserId(Integer bizType, Long bizId, Long userId) { + return selectOne(new LambdaQueryWrapperX() + .eq(CrmPermissionDO::getBizType, bizType) + .eq(CrmPermissionDO::getBizId, bizId) + .eq(CrmPermissionDO::getUserId, userId)); + } + + default List selectByBizTypeAndBizId(Integer bizType, Long bizId) { + return selectList(new LambdaQueryWrapperX() + .eq(CrmPermissionDO::getBizType, bizType) + .eq(CrmPermissionDO::getBizId, bizId)); + } + + default List selectByBizTypeAndBizIds(Integer bizType, Collection bizIds) { + return selectList(new LambdaQueryWrapperX() + .eq(CrmPermissionDO::getBizType, bizType) + .in(CrmPermissionDO::getBizId, bizIds)); + } + + default List selectListByBizTypeAndUserId(Integer bizType, Long userId) { + return selectList(new LambdaQueryWrapperX() + .eq(CrmPermissionDO::getBizType, bizType) + .eq(CrmPermissionDO::getUserId, userId)); + } + + default List selectListByBizTypeAndBizIdAndLevel(Integer bizType, Long bizId, Integer level) { + return selectList(new LambdaQueryWrapperX() + .eq(CrmPermissionDO::getBizType, bizType) + .eq(CrmPermissionDO::getBizId, bizId) + .eq(CrmPermissionDO::getLevel, level)); + } + + default CrmPermissionDO selectByIdAndUserId(Long id, Long userId) { + return selectOne(CrmPermissionDO::getId, id, + CrmPermissionDO::getUserId, userId); + } + + default CrmPermissionDO selectByBizIdAndUserId(Long bizId, Long userId) { + return selectOne(CrmPermissionDO::getBizId, bizId, + CrmPermissionDO::getUserId, userId); + } + + default int deletePermission(Integer bizType, Long bizId) { + return delete(new LambdaQueryWrapperX() + .eq(CrmPermissionDO::getBizType, bizType) + .eq(CrmPermissionDO::getBizId, bizId)); + } + + default Long selectListByBiz(Collection bizTypes, Collection bizIds, Collection userIds) { + return selectCount(new LambdaQueryWrapperX() + .in(CrmPermissionDO::getBizType, bizTypes) + .in(CrmPermissionDO::getBizId, bizIds) + .in(CrmPermissionDO::getUserId, userIds)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/package-info.java new file mode 100644 index 000000000..ff0e16b90 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/permission/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.permission; \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductCategoryMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductCategoryMapper.java new file mode 100644 index 000000000..3cced7356 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductCategoryMapper.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.product; + +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.product.vo.category.CrmProductCategoryListReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * CRM 产品分类 Mapper + * + * @author ZanGe丶 + */ +@Mapper +public interface CrmProductCategoryMapper extends BaseMapperX { + + default List selectList(CrmProductCategoryListReqVO reqVO) { + return selectList(new LambdaQueryWrapperX() + .likeIfPresent(CrmProductCategoryDO::getName, reqVO.getName()) + .eqIfPresent(CrmProductCategoryDO::getParentId, reqVO.getParentId()) + .orderByDesc(CrmProductCategoryDO::getId)); + } + + default CrmProductCategoryDO selectByParentIdAndName(Long parentId, String name) { + return selectOne(CrmProductCategoryDO::getParentId, parentId, CrmProductCategoryDO::getName, name); + } + + default Long selectCountByParentId(Long parentId) { + return selectCount(CrmProductCategoryDO::getParentId, parentId); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java new file mode 100644 index 000000000..30a07eec2 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.product; + +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.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import org.apache.ibatis.annotations.Mapper; + +/** + * CRM 产品 Mapper + * + * @author ZanGe丶 + */ +@Mapper +public interface CrmProductMapper extends BaseMapperX { + + default PageResult selectPage(CrmProductPageReqVO reqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_PRODUCT.getType(), + CrmProductDO::getId, userId, null, Boolean.FALSE); + // 拼接自身的查询条件 + query.selectAll(CrmProductDO.class) + .likeIfPresent(CrmProductDO::getName, reqVO.getName()) + .eqIfPresent(CrmProductDO::getStatus, reqVO.getStatus()) + .orderByDesc(CrmProductDO::getId); + return selectJoinPage(reqVO, CrmProductDO.class, query); + } + + default CrmProductDO selectByNo(String no) { + return selectOne(CrmProductDO::getNo, no); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java new file mode 100644 index 000000000..28b2298ec --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java @@ -0,0 +1,62 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.receivable; + +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.framework.mybatis.core.query.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * 回款 Mapper + * + * @author 赤焰 + */ +@Mapper +public interface CrmReceivableMapper extends BaseMapperX { + + default int updateOwnerUserIdById(Long id, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmReceivableDO::getId, id) + .set(CrmReceivableDO::getOwnerUserId, ownerUserId)); + } + + default PageResult selectPageByCustomerId(CrmReceivablePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eq(CrmReceivableDO::getCustomerId, reqVO.getCustomerId()) // 必须传递 + .eqIfPresent(CrmReceivableDO::getNo, reqVO.getNo()) + .eqIfPresent(CrmReceivableDO::getPlanId, reqVO.getPlanId()) + .orderByDesc(CrmReceivableDO::getId)); + } + + default PageResult selectPage(CrmReceivablePageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(), + CrmReceivableDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); + // 拼接自身的查询条件 + query.selectAll(CrmReceivableDO.class) + .eqIfPresent(CrmReceivableDO::getNo, pageReqVO.getNo()) + .eqIfPresent(CrmReceivableDO::getPlanId, pageReqVO.getPlanId()) + .eqIfPresent(CrmReceivableDO::getAuditStatus, pageReqVO.getAuditStatus()) + .orderByDesc(CrmReceivableDO::getId); + return selectJoinPage(pageReqVO, CrmReceivableDO.class, query); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(), ids, userId); + // 拼接自身的查询条件 + query.selectAll(CrmReceivableDO.class).in(CrmReceivableDO::getId, ids).orderByDesc(CrmReceivableDO::getId); + return selectJoinList(CrmReceivableDO.class, query); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java new file mode 100644 index 000000000..b21d30c12 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java @@ -0,0 +1,81 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.receivable; + +import cn.hutool.core.date.LocalDateTimeUtil; +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.framework.mybatis.core.query.MPJLambdaWrapperX; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 回款计划 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface CrmReceivablePlanMapper extends BaseMapperX { + + default int updateOwnerUserIdById(Long id, Long ownerUserId) { + return update(new LambdaUpdateWrapper() + .eq(CrmReceivablePlanDO::getId, id) + .set(CrmReceivablePlanDO::getOwnerUserId, ownerUserId)); + } + + default PageResult selectPageByCustomerId(CrmReceivablePlanPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eq(CrmReceivablePlanDO::getCustomerId, reqVO.getCustomerId()) // 必须传递 + .eqIfPresent(CrmReceivablePlanDO::getContractId, reqVO.getContractId()) + .orderByDesc(CrmReceivablePlanDO::getId)); + } + + default PageResult selectPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), + CrmReceivablePlanDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); + // 拼接自身的查询条件 + query.selectAll(CrmReceivablePlanDO.class) + .eqIfPresent(CrmReceivablePlanDO::getCustomerId, pageReqVO.getCustomerId()) + .eqIfPresent(CrmReceivablePlanDO::getContractId, pageReqVO.getContractId()) + .orderByDesc(CrmReceivablePlanDO::getId); + + // Backlog: 回款提醒类型 + LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); + LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now()); + if (CrmReceivablePlanPageReqVO.REMIND_TYPE_NEEDED.equals(pageReqVO.getRemindType())) { // 待回款 + query.isNull(CrmReceivablePlanDO::getReceivableId) + .gt(CrmReceivablePlanDO::getReturnTime, beginOfToday) + // TODO @dhb52:这里看看怎么改成不要使用 to_days + .apply("to_days(return_time) <= to_days(now())+ remind_days"); + } else if (CrmReceivablePlanPageReqVO.REMIND_TYPE_EXPIRED.equals(pageReqVO.getRemindType())) { // 已逾期 + query.isNull(CrmReceivablePlanDO::getReceivableId) + .lt(CrmReceivablePlanDO::getReturnTime, endOfToday); + } else if (CrmReceivablePlanPageReqVO.REMIND_TYPE_RECEIVED.equals(pageReqVO.getRemindType())) { // 已回款 + query.isNotNull(CrmReceivablePlanDO::getReceivableId) + .gt(CrmReceivablePlanDO::getReturnTime, beginOfToday) + // TODO @dhb52:这里看看怎么改成不要使用 to_days + .apply("to_days(return_time) <= to_days(now()) + remind_days"); + } + + return selectJoinPage(pageReqVO, CrmReceivablePlanDO.class, query); + } + + default List selectBatchIds(Collection ids, Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), ids, userId); + // 拼接自身的查询条件 + query.selectAll(CrmReceivablePlanDO.class).in(CrmReceivablePlanDO::getId, ids).orderByDesc(CrmReceivablePlanDO::getId); + return selectJoinList(CrmReceivablePlanDO.class, query); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmBusinessParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmBusinessParseFunction.java new file mode 100644 index 000000000..d8bb50961 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmBusinessParseFunction.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * CRM 商机的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmBusinessParseFunction implements IParseFunction { + + public static final String NAME = "getBusinessById"; + + @Resource + private CrmBusinessService businessService; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + CrmBusinessDO businessDO = businessService.getBusiness(Long.parseLong(value.toString())); + return businessDO == null ? "" : businessDO.getName(); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContactParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContactParseFunction.java new file mode 100644 index 000000000..91e8fd215 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContactParseFunction.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * CRM 联系人的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmContactParseFunction implements IParseFunction { + + public static final String NAME = "getContactById"; + + @Resource + private CrmContactService contactService; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + CrmContactDO contactDO = contactService.getContact(Long.parseLong(value.toString())); + return contactDO == null ? "" : contactDO.getName(); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContractParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContractParseFunction.java new file mode 100644 index 000000000..d3c58522e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmContractParseFunction.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * CRM 合同的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmContractParseFunction implements IParseFunction { + + public static final String NAME = "getContractById"; + + @Resource + private CrmContractService contractService; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + CrmContractDO contract = contractService.getContract(Long.parseLong(value.toString())); + return contract == null ? "" : contract.getName(); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerIndustryParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerIndustryParseFunction.java new file mode 100644 index 000000000..ae3e0b23f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerIndustryParseFunction.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY; + +/** + * 行业的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmCustomerIndustryParseFunction implements IParseFunction { + + public static final String NAME = "getCustomerIndustry"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_INDUSTRY, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerLevelParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerLevelParseFunction.java new file mode 100644 index 000000000..40bb6fb72 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerLevelParseFunction.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL; + +/** + * 客户等级的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmCustomerLevelParseFunction implements IParseFunction { + + public static final String NAME = "getCustomerLevel"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_LEVEL, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerParseFunction.java new file mode 100644 index 000000000..a58c0455d --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerParseFunction.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * CRM 客户的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmCustomerParseFunction implements IParseFunction { + + public static final String NAME = "getCustomerById"; + + @Resource + private CrmCustomerService customerService; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + CrmCustomerDO crmCustomerDO = customerService.getCustomer(Long.parseLong(value.toString())); + return crmCustomerDO == null ? "" : crmCustomerDO.getName(); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerSourceParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerSourceParseFunction.java new file mode 100644 index 000000000..95377a88e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmCustomerSourceParseFunction.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE; + +/** + * CRM 客户来源的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmCustomerSourceParseFunction implements IParseFunction { + + public static final String NAME = "getCustomerSource"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(CRM_CUSTOMER_SOURCE, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductStatusParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductStatusParseFunction.java new file mode 100644 index 000000000..cf81f5e31 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductStatusParseFunction.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 产品状态的 {@link IParseFunction} 实现类 + * + * @author anhaohao + */ +@Component +@Slf4j +public class CrmProductStatusParseFunction implements IParseFunction { + + public static final String NAME = "getProductStatusName"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.CRM_PRODUCT_STATUS, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductUnitParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductUnitParseFunction.java new file mode 100644 index 000000000..898bedd07 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmProductUnitParseFunction.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 产品单位的 {@link IParseFunction} 实现类 + * + * @author anhaohao + */ +@Component +@Slf4j +public class CrmProductUnitParseFunction implements IParseFunction { + + public static final String NAME = "getProductUnitName"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.CRM_PRODUCT_UNIT, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAdminUserParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAdminUserParseFunction.java new file mode 100644 index 000000000..224638957 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAdminUserParseFunction.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 管理员名字的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Slf4j +@Component +public class SysAdminUserParseFunction implements IParseFunction { + + public static final String NAME = "getAdminUserById"; + + @Resource + private AdminUserApi adminUserApi; + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + + // 获取用户信息 + AdminUserRespDTO user = adminUserApi.getUser(Long.parseLong(value.toString())).getCheckedData(); + if (user == null) { + log.warn("[apply][获取用户{{}}为空", value); + return ""; + } + // 返回格式 芋道源码(13888888888) + String nickname = user.getNickname(); + if (StrUtil.isEmpty(user.getMobile())) { + return nickname; + } + return StrUtil.format("{}({})", nickname, user.getMobile()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAreaParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAreaParseFunction.java new file mode 100644 index 000000000..3ccc76912 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysAreaParseFunction.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 地名的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Slf4j +@Component +public class SysAreaParseFunction implements IParseFunction { + + public static final String NAME = "getArea"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return AreaUtils.format(Integer.parseInt(value.toString())); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysBooleanParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysBooleanParseFunction.java new file mode 100644 index 000000000..3e1000bf9 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysBooleanParseFunction.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 是否类型的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class SysBooleanParseFunction implements IParseFunction { + + public static final String NAME = "getBoolean"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.BOOLEAN_STRING, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysDeptParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysDeptParseFunction.java new file mode 100644 index 000000000..5b002c9ce --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysDeptParseFunction.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 管理员名字的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Slf4j +@Component +public class SysDeptParseFunction implements IParseFunction { + + public static final String NAME = "getDeptById"; + + @Resource + private DeptApi deptApi; + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + + // 获取部门信息 + DeptRespDTO dept = deptApi.getDept(Long.parseLong(value.toString())).getCheckedData(); + if (dept == null) { + log.warn("[apply][获取部门{{}}为空", value); + return ""; + } + return dept.getName(); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysSexParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysSexParseFunction.java new file mode 100644 index 000000000..ccff080a2 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/SysSexParseFunction.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import cn.iocoder.yudao.module.system.enums.DictTypeConstants; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 行业的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class SysSexParseFunction implements IParseFunction { + + public static final String NAME = "getSex"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.USER_SEX, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java new file mode 100644 index 000000000..975a2eb51 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog; \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/package-info.java new file mode 100644 index 000000000..281e36c45 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 crm 模块的 framework 封装 + * + * @author 芋道源码 + */ +package cn.iocoder.yudao.module.crm.framework; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/annotations/CrmPermission.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/annotations/CrmPermission.java new file mode 100644 index 000000000..9ef6d4d02 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/annotations/CrmPermission.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.crm.framework.permission.core.annotations; + +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.METHOD; + +/** + * CRM 数据操作权限校验 AOP 注解 + * + * @author HUIHUI + */ +@Target({METHOD, ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface CrmPermission { + + /** + * CRM 类型 + */ + CrmBizTypeEnum[] bizType() default {}; + + /** + * CRM 类型扩展,通过 Spring EL 表达式获取到 {@link #bizType()} + * + * 目的:用于 CrmPermissionController 团队权限校验 + */ + String bizTypeValue() default ""; + + /** + * 数据编号,通过 Spring EL 表达式获取 + */ + String bizId(); + + /** + * 操作所需权限级别 + */ + CrmPermissionLevelEnum level(); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java new file mode 100644 index 000000000..14e7c71fe --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java @@ -0,0 +1,130 @@ +package cn.iocoder.yudao.module.crm.framework.permission.core.aop; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils; +import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; +import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; + +import java.util.*; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CRM_PERMISSION_DENIED; + +/** + * Crm 数据权限校验 AOP 切面 + * + * @author HUIHUI + */ +@Component +@Aspect +@Slf4j +public class CrmPermissionAspect { + + @Resource + private CrmPermissionService crmPermissionService; + + @Before("@annotation(crmPermission)") + public void doBefore(JoinPoint joinPoint, CrmPermission crmPermission) { + // 1.1 获取相关属性值 + Map expressionValues = parseExpressions(joinPoint, crmPermission); + Integer bizType = StrUtil.isEmpty(crmPermission.bizTypeValue()) ? + crmPermission.bizType()[0].getType() : (Integer) expressionValues.get(crmPermission.bizTypeValue()); // 模块类型 + // 1.2 处理兼容多个 bizId 的情况 + Object object = expressionValues.get(crmPermission.bizId()); // 模块数据编号 + Set bizIds = new HashSet<>(); + if (object instanceof Collection) { + bizIds.addAll(convertSet((Collection) object, item -> Long.parseLong(item.toString()))); + } else { + bizIds.add(Long.parseLong(object.toString())); + } + Integer permissionLevel = crmPermission.level().getLevel(); // 需要的权限级别 + + // 2. 逐个校验权限 + List permissionList = crmPermissionService.getPermissionListByBiz(bizType, bizIds); + Map> multiMap = convertMultiMap(permissionList, CrmPermissionDO::getBizId); + bizIds.forEach(bizId -> validatePermission(bizType, multiMap.get(bizId), permissionLevel)); + } + + private void validatePermission(Integer bizType, List bizPermissions, Integer permissionLevel) { + // 1. 如果是超级管理员则直接通过 + if (CrmPermissionUtils.isCrmAdmin()) { + return; + } + // 1.1 没有数据权限的情况 + if (CollUtil.isEmpty(bizPermissions)) { + // 公海数据如果没有团队成员大家也因该有读权限才对 + if (CrmPermissionLevelEnum.isRead(permissionLevel)) { + return; + } + // 没有数据权限的情况下超出了读权限直接报错,避免后面校验空指针 + throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType)); + } else { // 1.2 有数据权限但是没有负责人的情况 + if (!anyMatch(bizPermissions, item -> CrmPermissionLevelEnum.isOwner(item.getLevel()))) { + if (CrmPermissionLevelEnum.isRead(permissionLevel)) { + return; + } + } + } + + // 2.1 情况一:如果自己是负责人,则默认有所有权限 + CrmPermissionDO userPermission = CollUtil.findOne(bizPermissions, permission -> ObjUtil.equal(permission.getUserId(), getUserId())); + if (userPermission != null) { + if (CrmPermissionLevelEnum.isOwner(userPermission.getLevel())) { + return; + } + // 2.2 情况二:校验自己是否有读权限 + if (CrmPermissionLevelEnum.isRead(permissionLevel)) { + if (CrmPermissionLevelEnum.isRead(userPermission.getLevel()) // 校验当前用户是否有读权限 + || CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限 + return; + } + } + // 2.3 情况三:校验自己是否有写权限 + if (CrmPermissionLevelEnum.isWrite(permissionLevel)) { + if (CrmPermissionLevelEnum.isWrite(userPermission.getLevel())) { // 校验当前用户是否有写权限 + return; + } + } + } + // 2.4 没有权限,抛出异常 + log.info("[doBefore][userId({}) 要求权限({}) 实际权限({}) 数据校验错误]", // 打个 info 日志,方便后续排查问题、审计 + getUserId(), permissionLevel, toJsonString(userPermission)); + throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(bizType)); + } + + /** + * 获得用户编号 + * + * @return 用户编号 + */ + private static Long getUserId() { + return WebFrameworkUtils.getLoginUserId(); + } + + private static Map parseExpressions(JoinPoint joinPoint, CrmPermission crmPermission) { + // 1. 需要解析的表达式 + List expressionStrings = new ArrayList<>(2); + expressionStrings.add(crmPermission.bizId()); + if (StrUtil.isNotEmpty(crmPermission.bizTypeValue())) { // 为空则表示 bizType 有值 + expressionStrings.add(crmPermission.bizTypeValue()); + } + // 2. 执行解析 + return SpringExpressionUtils.parseExpressions(joinPoint, expressionStrings); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/package-info.java new file mode 100644 index 000000000..d895fe969 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.module.crm.framework.permission.core; \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java new file mode 100644 index 000000000..5beb5700c --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.crm.framework.permission.core.util; + +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionRoleCodeEnum; +import cn.iocoder.yudao.module.system.api.permission.PermissionApi; + +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +/** + * 数据权限工具类 + * + * @author HUIHUI + */ +public class CrmPermissionUtils { + + /** + * 校验用户是否是 CRM 管理员 + * + * @return 是/否 + */ + public static boolean isCrmAdmin() { + return SingletonManager.getPermissionApi().hasAnyRoles(getLoginUserId(), CrmPermissionRoleCodeEnum.CRM_ADMIN.getCode()).getCheckedData(); + } + + /** + * 静态内部类实现单例获取 + * + * @author HUIHUI + */ + private static class SingletonManager { + + private static final PermissionApi PERMISSION_API = SpringUtil.getBean(PermissionApi.class); + + public static PermissionApi getPermissionApi() { + return PERMISSION_API; + } + + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java new file mode 100644 index 000000000..44f408016 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.module.crm.framework.permission; \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/rpc/config/RpcConfiguration.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/rpc/config/RpcConfiguration.java new file mode 100644 index 000000000..d36e50519 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/rpc/config/RpcConfiguration.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.crm.framework.rpc.config; + +import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.PostApi; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableFeignClients(clients = {AdminUserApi.class, DeptApi.class, PostApi.class, + BpmProcessInstanceApi.class}) +public class RpcConfiguration { +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/rpc/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/rpc/package-info.java new file mode 100644 index 000000000..79ee21420 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/rpc/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package cn.iocoder.yudao.module.crm.framework.rpc; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/security/config/SecurityConfiguration.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/security/config/SecurityConfiguration.java new file mode 100644 index 000000000..1ed002c22 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/security/config/SecurityConfiguration.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.crm.framework.security.config; + +import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer; +import cn.iocoder.yudao.module.crm.enums.ApiConstants; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; + +/** + * Crm 模块的 Security 配置 + */ +@Configuration("crmSecurityConfiguration") +public class SecurityConfiguration { + + @Bean("crmAuthorizeRequestsCustomizer") + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + return new AuthorizeRequestsCustomizer() { + + @Override + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + // Swagger 接口文档 + registry.requestMatchers("/v3/api-docs/**").permitAll() // 元数据 + .requestMatchers("/swagger-ui.html").permitAll(); // Swagger UI + // Spring Boot Actuator 的安全配置 + registry.requestMatchers("/actuator").permitAll() + .requestMatchers("/actuator/**").permitAll(); + // Druid 监控 + registry.requestMatchers("/druid/**").permitAll(); + // RPC 服务的安全配置 + registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll(); + } + + }; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/security/core/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/security/core/package-info.java new file mode 100644 index 000000000..1e6b880ae --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/security/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package cn.iocoder.yudao.module.crm.framework.security.core; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java new file mode 100644 index 000000000..5e0aac646 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.crm.job.customer; + +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import com.xxl.job.core.handler.annotation.XxlJob; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +/** + * 客户自动掉入公海 Job + * + * @author 芋道源码 + */ +@Component +public class CrmCustomerAutoPutPoolJob { + + @Resource + private CrmCustomerService customerService; + + @XxlJob("customerAutoPutPoolJob") + @TenantJob + public String execute() { + int count = customerService.autoPutCustomerPool(); + return String.format("掉入公海客户 %s 个", count); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java new file mode 100644 index 000000000..144f64d02 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java @@ -0,0 +1,4 @@ +/** + * TODO 芋艿:临时占位,后续可删除 + */ +package cn.iocoder.yudao.module.crm.job; \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/package-info.java new file mode 100644 index 000000000..2ea5f9f41 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/package-info.java @@ -0,0 +1,10 @@ +/** + * crm 包下,客户关系管理(Customer Relationship Management)。 + * 例如说:客户、联系人、商机、合同、回款等等 + * + * 1. Controller URL:以 /crm/ 开头,避免和其它 Module 冲突 + * 2. DataObject 表名:以 crm_ 开头,方便在数据库中区分 + * + * 注意,由于 Crm 模块下,容易和其它模块重名,所以类名都加载 Crm 的前缀~ + */ +package cn.iocoder.yudao.module.crm; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingService.java new file mode 100644 index 000000000..2ff28d385 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingService.java @@ -0,0 +1,80 @@ +package cn.iocoder.yudao.module.crm.service.bi; + + +import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO; + +import java.util.List; + +/** + * CRM BI 排行榜 Service 接口 + * + * @author anhaohao + */ +public interface CrmBiRankingService { + + /** + * 获得合同金额排行榜 + * + * @param rankReqVO 排行参数 + * @return 合同金额排行榜 + */ + List getContractPriceRank(CrmBiRankReqVO rankReqVO); + + /** + * 获得回款金额排行榜 + * + * @param rankReqVO 排行参数 + * @return 回款金额排行榜 + */ + List getReceivablePriceRank(CrmBiRankReqVO rankReqVO); + + /** + * 获得签约合同数量排行榜 + * + * @param rankReqVO 排行参数 + * @return 签约合同数量排行榜 + */ + List getContractCountRank(CrmBiRankReqVO rankReqVO); + + /** + * 获得产品销量排行榜 + * + * @param rankReqVO 排行参数 + * @return 产品销量排行榜 + */ + List getProductSalesRank(CrmBiRankReqVO rankReqVO); + + /** + * 获得新增客户数排行榜 + * + * @param rankReqVO 排行参数 + * @return 新增客户数排行榜 + */ + List getCustomerCountRank(CrmBiRankReqVO rankReqVO); + + /** + * 获得联系人数量排行榜 + * + * @param rankReqVO 排行参数 + * @return 联系人数量排行榜 + */ + List getContactsCountRank(CrmBiRankReqVO rankReqVO); + + /** + * 获得跟进次数排行榜 + * + * @param rankReqVO 排行参数 + * @return 跟进次数排行榜 + */ + List getFollowCountRank(CrmBiRankReqVO rankReqVO); + + /** + * 获得跟进客户数排行榜 + * + * @param rankReqVO 排行参数 + * @return 跟进客户数排行榜 + */ + List getFollowCustomerCountRank(CrmBiRankReqVO rankReqVO); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingServiceImpl.java new file mode 100644 index 000000000..171132669 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingServiceImpl.java @@ -0,0 +1,134 @@ +package cn.iocoder.yudao.module.crm.service.bi; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO; +import cn.iocoder.yudao.module.crm.dal.mysql.bi.CrmBiRankingMapper; +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.validation.annotation.Validated; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +/** + * CRM BI 排行榜 Service 实现类 + * + * @author anhaohao + */ +@Service +@Validated +public class CrmBiRankingServiceImpl implements CrmBiRankingService { + + @Resource + private CrmBiRankingMapper biRankingMapper; + + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; + + @Override + public List getContractPriceRank(CrmBiRankReqVO rankReqVO) { + return getRank(rankReqVO, biRankingMapper::selectContractPriceRank); + } + + @Override + public List getReceivablePriceRank(CrmBiRankReqVO rankReqVO) { + return getRank(rankReqVO, biRankingMapper::selectReceivablePriceRank); + } + + @Override + public List getContractCountRank(CrmBiRankReqVO rankReqVO) { + return getRank(rankReqVO, biRankingMapper::selectContractCountRank); + } + + @Override + public List getProductSalesRank(CrmBiRankReqVO rankReqVO) { + return getRank(rankReqVO, biRankingMapper::selectProductSalesRank); + } + + @Override + public List getCustomerCountRank(CrmBiRankReqVO rankReqVO) { + return getRank(rankReqVO, biRankingMapper::selectCustomerCountRank); + } + + @Override + public List getContactsCountRank(CrmBiRankReqVO rankReqVO) { + return getRank(rankReqVO, biRankingMapper::selectContactsCountRank); + } + + @Override + public List getFollowCountRank(CrmBiRankReqVO rankReqVO) { + return getRank(rankReqVO, biRankingMapper::selectFollowCountRank); + } + + @Override + public List getFollowCustomerCountRank(CrmBiRankReqVO rankReqVO) { + return getRank(rankReqVO, biRankingMapper::selectFollowCustomerCountRank); + } + + /** + * 获得排行版数据 + * + * @param rankReqVO 参数 + * @param rankFunction 排行榜方法 + * @return 排行版数据 + */ + private List getRank(CrmBiRankReqVO rankReqVO, Function> rankFunction) { + // 1. 获得用户编号数组 + rankReqVO.setUserIds(getUserIds(rankReqVO.getDeptId())); + if (CollUtil.isEmpty(rankReqVO.getUserIds())) { + return Collections.emptyList(); + } + // 2. 获得排行数据 + List ranks = rankFunction.apply(rankReqVO); + if (CollUtil.isEmpty(ranks)) { + return Collections.emptyList(); + } + ranks.sort(Comparator.comparing(CrmBiRanKRespVO::getCount).reversed()); + // 3. 拼接用户信息 + appendUserInfo(ranks); + return ranks; + } + + /** + * 拼接用户信息(昵称、部门) + * + * @param ranks 排行榜数据 + */ + private void appendUserInfo(List ranks) { + Map userMap = adminUserApi.getUserMap(convertSet(ranks, CrmBiRanKRespVO::getOwnerUserId)); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); + ranks.forEach(rank -> MapUtils.findAndThen(userMap, rank.getOwnerUserId(), user -> { + rank.setNickname(user.getNickname()); + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> rank.setDeptName(dept.getName())); + })); + } + + /** + * 获得部门下的用户编号数组,包括子部门的 + * + * @param deptId 部门编号 + * @return 用户编号数组 + */ + public List getUserIds(Long deptId) { + // 1. 获得部门列表 + List deptIds = convertList(deptApi.getChildDeptList(deptId).getCheckedData(), DeptRespDTO::getId); + deptIds.add(deptId); + // 2. 获得用户编号 + return convertList(adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(), AdminUserRespDTO::getId); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java new file mode 100644 index 000000000..683070d02 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java @@ -0,0 +1,132 @@ +package cn.iocoder.yudao.module.crm.service.business; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * 商机 Service 接口 + * + * @author ljlleo + */ +public interface CrmBusinessService { + + /** + * 创建商机 + * + * @param createReqVO 创建信息 + * @param userId 用户编号 + * @return 编号 + */ + Long createBusiness(@Valid CrmBusinessSaveReqVO createReqVO, Long userId); + + /** + * 更新商机 + * + * @param updateReqVO 更新信息 + */ + void updateBusiness(@Valid CrmBusinessSaveReqVO updateReqVO); + + /** + * 更新商机相关跟进信息 + * + * @param updateFollowUpReqBOList 跟进信息 + */ + void updateBusinessFollowUpBatch(List updateFollowUpReqBOList); + + /** + * 删除商机 + * + * @param id 编号 + */ + void deleteBusiness(Long id); + + /** + * 商机转移 + * + * @param reqVO 请求 + * @param userId 用户编号 + */ + void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId); + + /** + * 更新商机关联商品 + * + * @param updateProductReqBO 请求 + */ + void updateBusinessProduct(CrmBusinessUpdateProductReqBO updateProductReqBO); + + /** + * 获得商机 + * + * @param id 编号 + * @return 商机 + */ + CrmBusinessDO getBusiness(Long id); + + /** + * 获得商机列表 + * + * @param ids 编号 + * @return 商机列表 + */ + List getBusinessList(Collection ids, Long userId); + + /** + * 获得商机列表 + * + * @param ids 编号 + * @return 商机列表 + */ + List getBusinessList(Collection ids); + + /** + * 获得商机分页 + * + * 数据权限:基于 {@link CrmBusinessDO} + * + * @param pageReqVO 分页查询 + * @param userId 用户编号 + * @return 商机分页 + */ + PageResult getBusinessPage(CrmBusinessPageReqVO pageReqVO, Long userId); + + /** + * 获得商机分页,基于指定客户 + * + * 数据权限:基于 {@link CrmCustomerDO} 读取 + * + * @param pageReqVO 分页查询 + * @return 商机分页 + */ + PageResult getBusinessPageByCustomerId(CrmBusinessPageReqVO pageReqVO); + + /** + * 获得商机分页,基于指定联系人 + * + * 数据权限:基于 {@link CrmContactDO} 读取 + * + * @param pageReqVO 分页参数 + * @return 商机分页 + */ + PageResult getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO); + + /** + * 获取关联客户的商机数量 + * + * @param customerId 客户编号 + * @return 数量 + */ + Long getBusinessCountByCustomerId(Long customerId); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java new file mode 100644 index 000000000..535578fd2 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java @@ -0,0 +1,298 @@ +package cn.iocoder.yudao.module.crm.service.business; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; +import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper; +import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessProductMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.crm.service.product.CrmProductService; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; + +/** + * 商机 Service 实现类 + * + * @author ljlleo + */ +@Service +@Validated +public class CrmBusinessServiceImpl implements CrmBusinessService { + + @Resource + private CrmBusinessMapper businessMapper; + @Resource + private CrmBusinessProductMapper businessProductMapper; + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private CrmContractService contractService; + @Resource + private CrmPermissionService permissionService; + @Resource + private CrmContactBusinessService contactBusinessService; + @Resource + private CrmProductService productService; + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_CREATE_SUB_TYPE, bizNo = "{{#business.id}}", + success = CRM_BUSINESS_CREATE_SUCCESS) + public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) { + createReqVO.setId(null); + // 1. 插入商机 + CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class).setOwnerUserId(userId); + businessMapper.insert(business); + // 1.2 插入商机关联商品 + if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话 + List productList = buildBusinessProductList(createReqVO.getProductItems(), business.getId()); + businessProductMapper.insertBatch(productList); + // 更新合同商品总金额 + businessMapper.updateById(new CrmBusinessDO().setId(business.getId()).setProductPrice( + getSumValue(productList, CrmBusinessProductDO::getTotalPrice, Integer::sum))); + } + // TODO @puhui999:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表 + createContactBusiness(business.getId(), createReqVO.getContactId()); + + // 2. 创建数据权限 + // 设置当前操作的人为负责人 + permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()) + .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("business", business); + return business.getId(); + } + + // TODO @lzxhqs:CrmContactBusinessService 调用这个;这样逻辑才能收敛哈; + private void createContactBusiness(Long businessId, Long contactId) { + CrmContactBusinessDO contactBusiness = new CrmContactBusinessDO(); + contactBusiness.setBusinessId(businessId); + contactBusiness.setContactId(contactId); + contactBusinessService.insert(contactBusiness); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_BUSINESS_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) + public void updateBusiness(CrmBusinessSaveReqVO updateReqVO) { + // 1. 校验存在 + CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId()); + + // 2.1 更新商机 + CrmBusinessDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessDO.class); + businessMapper.updateById(updateObj); + // 2.2 更新商机关联商品 + List productList = buildBusinessProductList(updateReqVO.getProductItems(), updateObj.getId()); + updateBusinessProduct(productList, updateObj.getId()); + + // TODO @商机待定:如果状态发生变化,插入商机状态变更记录表 + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class)); + LogRecordContext.putVariable("businessName", oldBusiness.getName()); + } + + @Override + public void updateBusinessFollowUpBatch(List updateFollowUpReqBOList) { + businessMapper.updateBatch(CrmBusinessConvert.INSTANCE.convertList(updateFollowUpReqBOList)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_BUSINESS_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void deleteBusiness(Long id) { + // 校验存在 + CrmBusinessDO business = validateBusinessExists(id); + // TODO @商机待定:需要校验有没关联合同。CrmContractDO 的 businessId 字段 + validateContractExists(id); + + // 删除 + businessMapper.deleteById(id); + // 删除数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_BUSINESS.getType(), id); + + // 记录操作日志上下文 + LogRecordContext.putVariable("businessName", business.getName()); + } + + private void updateBusinessProduct(List newProductList, Long businessId) { + List oldProducts = businessProductMapper.selectListByBusinessId(businessId); + List> diffList = CollectionUtils.diffList(oldProducts, newProductList, (oldValue, newValue) -> { + boolean condition = ObjectUtil.equal(oldValue.getProductId(), newValue.getProductId()); + if (condition) { + newValue.setId(oldValue.getId()); // 更新需要原始编号 + } + return condition; + }); + if (CollUtil.isNotEmpty(diffList.get(0))) { + businessProductMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + businessProductMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + businessProductMapper.deleteBatchIds(convertSet(diffList.get(2), CrmBusinessProductDO::getId)); + } + } + + private List buildBusinessProductList(List productItems, + Long businessId) { + // 校验商品存在 + Set productIds = convertSet(productItems, CrmBusinessSaveReqVO.CrmBusinessProductItem::getId); + List productList = productService.getProductList(productIds); + if (CollUtil.isEmpty(productIds) || productList.size() != productIds.size()) { + throw exception(PRODUCT_NOT_EXISTS); + } + Map productMap = convertMap(productList, CrmProductDO::getId); + return convertList(productItems, productItem -> { + CrmProductDO product = productMap.get(productItem.getId()); + return BeanUtils.toBean(product, CrmBusinessProductDO.class) + .setId(null).setProductId(productItem.getId()).setBusinessId(businessId) + .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent()) + .setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent())); + }); + } + + /** + * 删除校验合同是关联合同 + * + * @param businessId 商机id + * @author lzxhqs + */ + private void validateContractExists(Long businessId) { + if (contractService.getContractCountByBusinessId(businessId) > 0) { + throw exception(BUSINESS_CONTRACT_EXISTS); + } + } + + private CrmBusinessDO validateBusinessExists(Long id) { + CrmBusinessDO crmBusiness = businessMapper.selectById(id); + if (crmBusiness == null) { + throw exception(BUSINESS_NOT_EXISTS); + } + return crmBusiness; + } + + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}", + success = CRM_BUSINESS_TRANSFER_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER) + public void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId) { + // 1 校验商机是否存在 + CrmBusinessDO business = validateBusinessExists(reqVO.getId()); + + // 2.1 数据权限转移 + permissionService.transferPermission( + CrmBusinessConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())); + // 2.2 设置新的负责人 + businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + + // 记录操作日志上下文 + LogRecordContext.putVariable("business", business); + } + + @Override + public void updateBusinessProduct(CrmBusinessUpdateProductReqBO updateProductReqBO) { + // 更新商机关联商品 + List productList = buildBusinessProductList( + BeanUtils.toBean(updateProductReqBO.getProductItems(), CrmBusinessSaveReqVO.CrmBusinessProductItem.class), updateProductReqBO.getId()); + updateBusinessProduct(productList, updateProductReqBO.getId()); + } + + //======================= 查询相关 ======================= + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.READ) + public CrmBusinessDO getBusiness(Long id) { + return businessMapper.selectById(id); + } + + @Override + public List getBusinessList(Collection ids, Long userId) { + if (CollUtil.isEmpty(ids)) { + return ListUtil.empty(); + } + return businessMapper.selectBatchIds(ids, userId); + } + + @Override + public List getBusinessList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return ListUtil.empty(); + } + return businessMapper.selectBatchIds(ids); + } + + @Override + public PageResult getBusinessPage(CrmBusinessPageReqVO pageReqVO, Long userId) { + return businessMapper.selectPage(pageReqVO, userId); + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ) + public PageResult getBusinessPageByCustomerId(CrmBusinessPageReqVO pageReqVO) { + return businessMapper.selectPageByCustomerId(pageReqVO); + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#pageReqVO.contactId", level = CrmPermissionLevelEnum.READ) + public PageResult getBusinessPageByContact(CrmBusinessPageReqVO pageReqVO) { + // 1. 查询关联的商机编号 + List contactBusinessList = contactBusinessService.getContactBusinessListByContactId( + pageReqVO.getContactId()); + if (CollUtil.isEmpty(contactBusinessList)) { + return PageResult.empty(); + } + // 2. 查询商机分页 + return businessMapper.selectPageByContactId(pageReqVO, + convertSet(contactBusinessList, CrmContactBusinessDO::getBusinessId)); + } + + @Override + public Long getBusinessCountByCustomerId(Long customerId) { + return businessMapper.selectCount(CrmBusinessDO::getCustomerId, customerId); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java new file mode 100644 index 000000000..a2fc2d18d --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java @@ -0,0 +1,77 @@ +package cn.iocoder.yudao.module.crm.service.business; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; + +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * 商机状态 Service 接口 + * + * @author ljlleo + */ +public interface CrmBusinessStatusService { + + /** + * 创建商机状态 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createBusinessStatus(@Valid CrmBusinessStatusSaveReqVO createReqVO); + + /** + * 更新商机状态 + * + * @param updateReqVO 更新信息 + */ + void updateBusinessStatus(@Valid CrmBusinessStatusSaveReqVO updateReqVO); + + /** + * 删除商机状态 + * + * @param id 编号 + */ + void deleteBusinessStatus(Long id); + + /** + * 获得商机状态 + * + * @param id 编号 + * @return 商机状态 + */ + CrmBusinessStatusDO getBusinessStatus(Long id); + + /** + * 获得商机状态分页 + * + * @param pageReqVO 分页查询 + * @return 商机状态分页 + */ + PageResult getBusinessStatusPage(CrmBusinessStatusPageReqVO pageReqVO); + + // TODO @ljlleo 常用的 ids 之类的查询,可以封装单独的方法,不用走类似 QueryVO,用起来更方便。 + // TODO @ljlleo 方法名用 getBusinessStatusList + /** + * 获得商机状态分页 + * + * @param queryVO 查询参数 + * @return 商机状态分页 + */ + List selectList(CrmBusinessStatusQueryVO queryVO); + + /** + * 获得商机状态列表 + * + * @param ids 编号数组 + * @return 商机状态列表 + */ + List getBusinessStatusList(Collection ids); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java new file mode 100644 index 000000000..2e49e99d7 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.module.crm.service.business; + +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.business.vo.status.CrmBusinessStatusPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; +import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessStatusMapper; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_NOT_EXISTS; + +/** + * 商机状态 Service 实现类 + * + * @author ljlleo + */ +@Service +@Validated +public class CrmBusinessStatusServiceImpl implements CrmBusinessStatusService { + + @Resource + private CrmBusinessStatusMapper businessStatusMapper; + + @Override + public Long createBusinessStatus(CrmBusinessStatusSaveReqVO createReqVO) { + // 插入 + CrmBusinessStatusDO businessStatus = BeanUtils.toBean(createReqVO, CrmBusinessStatusDO.class); + businessStatusMapper.insert(businessStatus); + // 返回 + return businessStatus.getId(); + } + + @Override + public void updateBusinessStatus(CrmBusinessStatusSaveReqVO updateReqVO) { + // 校验存在 + validateBusinessStatusExists(updateReqVO.getId()); + // 更新 + CrmBusinessStatusDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessStatusDO.class); + businessStatusMapper.updateById(updateObj); + } + + @Override + public void deleteBusinessStatus(Long id) { + // 校验存在 + validateBusinessStatusExists(id); + // TODO @ljlleo 这里可以考虑,如果有商机在使用,不允许删除 + // 删除 + businessStatusMapper.deleteById(id); + } + + private void validateBusinessStatusExists(Long id) { + if (businessStatusMapper.selectById(id) == null) { + throw exception(BUSINESS_STATUS_NOT_EXISTS); + } + } + + @Override + public CrmBusinessStatusDO getBusinessStatus(Long id) { + return businessStatusMapper.selectById(id); + } + + @Override + public PageResult getBusinessStatusPage(CrmBusinessStatusPageReqVO pageReqVO) { + return businessStatusMapper.selectPage(pageReqVO); + } + + @Override + public List selectList(CrmBusinessStatusQueryVO queryVO) { + return businessStatusMapper.selectList(queryVO); + } + + @Override + public List getBusinessStatusList(Collection ids) { + return businessStatusMapper.selectBatchIds(ids); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java new file mode 100644 index 000000000..20509994e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java @@ -0,0 +1,75 @@ +package cn.iocoder.yudao.module.crm.service.business; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * 商机状态类型 Service 接口 + * + * @author ljlleo + */ +public interface CrmBusinessStatusTypeService { + + /** + * 创建商机状态类型 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createBusinessStatusType(@Valid CrmBusinessStatusTypeSaveReqVO createReqVO); + + /** + * 更新商机状态类型 + * + * @param updateReqVO 更新信息 + */ + void updateBusinessStatusType(@Valid CrmBusinessStatusTypeSaveReqVO updateReqVO); + + /** + * 删除商机状态类型 + * + * @param id 编号 + */ + void deleteBusinessStatusType(Long id); + + /** + * 获得商机状态类型 + * + * @param id 编号 + * @return 商机状态类型 + */ + CrmBusinessStatusTypeDO getBusinessStatusType(Long id); + + /** + * 获得商机状态类型分页 + * + * @param pageReqVO 分页查询 + * @return 商机状态类型分页 + */ + PageResult getBusinessStatusTypePage(CrmBusinessStatusTypePageReqVO pageReqVO); + + // TODO @ljlleo 常用的 ids 之类的查询,可以封装单独的方法,不用走类似 QueryVO,用起来更方便。 + /** + * 获得商机状态类型列表 + * + * @param queryVO 查询参数 + * @return 商机状态类型列表 + */ + List selectList(CrmBusinessStatusTypeQueryVO queryVO); + + /** + * 获得商机状态类型列表 + * + * @param ids 编号数组 + * @return 商机状态类型列表 + */ + List getBusinessStatusTypeList(Collection ids); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java new file mode 100644 index 000000000..d9845976b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java @@ -0,0 +1,132 @@ +package cn.iocoder.yudao.module.crm.service.business; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; +import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessStatusMapper; +import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessStatusTypeMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_TYPE_NOT_EXISTS; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_TYPE_NAME_EXISTS; + +/** + * 商机状态类型 Service 实现类 + * + * @author ljlleo + */ +@Service +@Validated +public class CrmBusinessStatusTypeServiceImpl implements CrmBusinessStatusTypeService { + + @Resource + private CrmBusinessStatusTypeMapper businessStatusTypeMapper; + + @Resource + private CrmBusinessStatusMapper businessStatusMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createBusinessStatusType(CrmBusinessStatusTypeSaveReqVO createReqVO) { + //检验名称是否存在 + validateBusinessStatusTypeExists(createReqVO.getName(), null); + // 插入类型 + CrmBusinessStatusTypeDO businessStatusType = BeanUtils.toBean(createReqVO, CrmBusinessStatusTypeDO.class); + businessStatusTypeMapper.insert(businessStatusType); + // 插入状态 + if (CollUtil.isNotEmpty(createReqVO.getStatusList())) { + createReqVO.getStatusList().forEach(status -> status.setTypeId(businessStatusType.getId())); + businessStatusMapper.insertBatch(BeanUtils.toBean(createReqVO.getStatusList(), CrmBusinessStatusDO.class)); + } + return businessStatusType.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateBusinessStatusType(CrmBusinessStatusTypeSaveReqVO updateReqVO) { + // 校验存在 + validateBusinessStatusTypeExists(updateReqVO.getId()); + // 校验名称是否存在 + validateBusinessStatusTypeExists(updateReqVO.getName(), updateReqVO.getId()); + // 更新类型 + CrmBusinessStatusTypeDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessStatusTypeDO.class); + businessStatusTypeMapper.updateById(updateObj); + // 更新状态(删除 + 更新) + // TODO @ljlleo 可以参考 DeliveryExpressTemplateServiceImpl 的 updateExpressTemplateFree 方法;主要没变化的,还是不删除了哈。 + businessStatusMapper.delete(updateReqVO.getId()); + updateReqVO.getStatusList().forEach(status -> status.setTypeId(updateReqVO.getId())); + businessStatusMapper.insertBatch(BeanUtils.toBean(updateReqVO.getStatusList(), CrmBusinessStatusDO.class)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteBusinessStatusType(Long id) { + // TODO 待添加被引用校验 + //... + + // 校验存在 + validateBusinessStatusTypeExists(id); + // 删除类型 + businessStatusTypeMapper.deleteById(id); + // 删除状态 + businessStatusMapper.delete(id); + } + + private void validateBusinessStatusTypeExists(Long id) { + if (businessStatusTypeMapper.selectById(id) == null) { + throw exception(BUSINESS_STATUS_TYPE_NOT_EXISTS); + } + } + + // TODO @ljlleo 这个方法,这个参考 validateDeptNameUnique 实现。 + private void validateBusinessStatusTypeExists(String name, Long id) { + CrmBusinessStatusTypeDO businessStatusTypeDO = businessStatusTypeMapper.selectByIdAndName(id, name); + if (businessStatusTypeDO != null) { + throw exception(BUSINESS_STATUS_TYPE_NAME_EXISTS); + } +// LambdaQueryWrapper wrapper = new LambdaQueryWrapperX<>(); +// if(null != id) { +// wrapper.ne(CrmBusinessStatusTypeDO::getId, id); +// } +// long cnt = businessStatusTypeMapper.selectCount(wrapper.eq(CrmBusinessStatusTypeDO::getName, name)); +// if (cnt > 0) { +// throw exception(BUSINESS_STATUS_TYPE_NAME_EXISTS); +// } + } + + @Override + public CrmBusinessStatusTypeDO getBusinessStatusType(Long id) { + return businessStatusTypeMapper.selectById(id); + } + + @Override + public PageResult getBusinessStatusTypePage(CrmBusinessStatusTypePageReqVO pageReqVO) { + return businessStatusTypeMapper.selectPage(pageReqVO); + } + + @Override + public List selectList(CrmBusinessStatusTypeQueryVO queryVO) { + return businessStatusTypeMapper.selectList(queryVO); + } + + @Override + public List getBusinessStatusTypeList(Collection ids) { + return businessStatusTypeMapper.selectBatchIds(ids); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateProductReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateProductReqBO.java new file mode 100644 index 000000000..34b2fa381 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateProductReqBO.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.crm.service.business.bo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 更新商机商品 Update Req BO + * + * @author HUIHUI + */ +@Data +public class CrmBusinessUpdateProductReqBO { + + /** + * 商机编号 + */ + @NotNull(message = "商机编号不能为空") + private Long id; + + // TODO @芋艿:再想想 + @NotEmpty(message = "产品列表不能为空") + private List productItems; + + @Schema(description = "产品列表") + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class CrmBusinessProductItem { + + @Schema(description = "产品编号", example = "20529") + @NotNull(message = "产品编号不能为空") + private Long id; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") + @NotNull(message = "产品数量不能为空") + private Integer count; + + @Schema(description = "产品折扣") + private Integer discountPercent; + + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java new file mode 100644 index 000000000..1472bf02f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java @@ -0,0 +1,93 @@ +package cn.iocoder.yudao.module.crm.service.clue; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTranslateReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * 线索 Service 接口 + * + * @author Wanwan + */ +public interface CrmClueService { + + /** + * 创建线索 + * + * @param createReqVO 创建信息 + * @param userId 用户编号 + * @return 编号 + */ + Long createClue(@Valid CrmClueSaveReqVO createReqVO, Long userId); + + /** + * 更新线索 + * + * @param updateReqVO 更新信息 + */ + void updateClue(@Valid CrmClueSaveReqVO updateReqVO); + + /** + * 更新线索相关的跟进信息 + * + * @param clueUpdateFollowUpReqBO 信息 + */ + void updateClueFollowUp(CrmUpdateFollowUpReqBO clueUpdateFollowUpReqBO); + + /** + * 删除线索 + * + * @param id 编号 + */ + void deleteClue(Long id); + + /** + * 获得线索 + * + * @param id 编号 + * @return 线索 + */ + CrmClueDO getClue(Long id); + + /** + * 获得线索列表 + * + * @param ids 编号 + * @return 线索列表 + */ + List getClueList(Collection ids, Long userId); + + /** + * 获得线索分页 + * + * @param pageReqVO 分页查询 + * @param userId 用户编号 + * @return 线索分页 + */ + PageResult getCluePage(CrmCluePageReqVO pageReqVO, Long userId); + + /** + * 线索转移 + * + * @param reqVO 请求 + * @param userId 用户编号 + */ + void transferClue(CrmClueTransferReqVO reqVO, Long userId); + + /** + * 线索转化为客户 + * + * @param reqVO 线索编号 + * @param userId 用户编号 + */ + void translateCustomer(CrmClueTranslateReqVO reqVO, Long userId); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java new file mode 100644 index 000000000..dfb044f50 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java @@ -0,0 +1,279 @@ +package cn.iocoder.yudao.module.crm.service.clue; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.spring.SpringUtil; +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.clue.vo.CrmCluePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTranslateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO; +import cn.iocoder.yudao.module.crm.convert.clue.CrmClueConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO; +import cn.iocoder.yudao.module.crm.service.followup.CrmFollowUpRecordService; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmFollowUpCreateReqBO; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.*; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS; +import static java.util.Collections.singletonList; + +/** + * 线索 Service 实现类 + * + * @author Wanwan + */ +@Service +@Validated +public class CrmClueServiceImpl implements CrmClueService { + + @Resource + private CrmClueMapper clueMapper; + + @Resource + private CrmCustomerService customerService; + + @Resource + private CrmPermissionService crmPermissionService; + @Resource + private CrmFollowUpRecordService followUpRecordService; + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_CREATE_SUB_TYPE, bizNo = "{{#clue.id}}", + success = CRM_LEADS_CREATE_SUCCESS) + public Long createClue(CrmClueSaveReqVO createReqVO, Long userId) { + // 1.1 校验关联数据 + validateRelationDataExists(createReqVO); + // 1.2 校验负责人是否存在 + if (createReqVO.getOwnerUserId() != null) { + adminUserApi.validateUserList(singletonList(createReqVO.getOwnerUserId())); + } else { + createReqVO.setOwnerUserId(userId); // 如果没有设置负责人那么默认操作人为负责人 + } + + // 2. 插入 + CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class).setId(null); + clueMapper.insert(clue); + + // 3. 创建数据权限 + CrmPermissionCreateReqBO createReqBO = new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_LEADS.getType()) + .setBizId(clue.getId()).setUserId(clue.getOwnerUserId()).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()); + crmPermissionService.createPermission(createReqBO); + + // 4. 记录操作日志上下文 + LogRecordContext.putVariable("clue", clue); + return clue.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_LEADS_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#updateReq.id", level = CrmPermissionLevelEnum.OWNER) + public void updateClue(CrmClueSaveReqVO updateReq) { + Assert.notNull(updateReq.getId(), "线索编号不能为空"); + // 1. 校验线索是否存在 + CrmClueDO oldClue = validateClueExists(updateReq.getId()); + // 2. 校验关联数据 + validateRelationDataExists(updateReq); + + // 3. 更新 + CrmClueDO updateObj = BeanUtils.toBean(updateReq, CrmClueDO.class); + clueMapper.updateById(updateObj); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmCustomerSaveReqVO.class)); + LogRecordContext.putVariable("clueName", oldClue.getName()); + } + + @Override + @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_UPDATE_SUB_TYPE, bizNo = "{{#updateReq.bizId}", + success = CRM_LEADS_UPDATE_SUCCESS) + public void updateClueFollowUp(CrmUpdateFollowUpReqBO updateReq) { + // 校验线索是否存在 + CrmClueDO oldClue = validateClueExists(updateReq.getBizId()); + + // 更新 + clueMapper.updateById(BeanUtils.toBean(updateReq, CrmClueDO.class).setId(updateReq.getBizId())); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmUpdateFollowUpReqBO.class)); + LogRecordContext.putVariable("clueName", oldClue.getName()); + + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_LEADS_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void deleteClue(Long id) { + // 1. 校验存在 + CrmClueDO clue = validateClueExists(id); + + // 2. 删除 + clueMapper.deleteById(id); + + // 3. 删除数据权限 + crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_LEADS.getType(), id); + + // 4. 删除跟进 + followUpRecordService.deleteFollowUpRecordByBiz(CrmBizTypeEnum.CRM_LEADS.getType(), id); + + // 记录操作日志上下文 + LogRecordContext.putVariable("clueName", clue.getName()); + } + + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}", + success = CRM_LEADS_TRANSFER_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void transferClue(CrmClueTransferReqVO reqVO, Long userId) { + // 1 校验线索是否存在 + CrmClueDO clue = validateClueExists(reqVO.getId()); + + // 2.1 数据权限转移 + crmPermissionService.transferPermission(CrmClueConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_LEADS.getType())); + // 2.2 设置新的负责人 + clueMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + + // 3. 记录转移日志 + LogRecordContext.putVariable("clue", clue); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void translateCustomer(CrmClueTranslateReqVO reqVO, Long userId) { + // 1.1 校验线索都存在 + Set clueIds = reqVO.getIds(); + List clues = getClueList(clueIds, userId); + if (CollUtil.isEmpty(clues) || ObjectUtil.notEqual(clues.size(), clueIds.size())) { + clueIds.removeAll(convertSet(clues, CrmClueDO::getId)); + throw exception(CLUE_ANY_CLUE_NOT_EXISTS, clueIds); + } + // 1.2 存在已经转化的,直接提示哈。避免操作的用户,以为都转化成功了 + List translatedClues = filterList(clues, + clue -> ObjectUtil.equal(Boolean.TRUE, clue.getTransformStatus())); + if (CollUtil.isNotEmpty(translatedClues)) { + throw exception(CLUE_ANY_CLUE_ALREADY_TRANSLATED, convertSet(translatedClues, CrmClueDO::getId)); + } + + // 2.1 遍历线索(未转化的线索),创建对应的客户 + clues.forEach(clue -> { + Long customerId = customerService.createCustomer(BeanUtils.toBean(clue, CrmCustomerCreateReqBO.class), userId); + clue.setCustomerId(customerId); + }); + // 2.2 更新线索 + clueMapper.updateBatch(convertList(clues, clue -> new CrmClueDO().setId(clue.getId()) + .setTransformStatus(Boolean.TRUE).setCustomerId(clue.getCustomerId()))); + // 2.3 复制跟进记录 + copyFollowUpRecords(clues); + + // 3. 记录操作日志 + for (CrmClueDO clue : clues) { + getSelf().translateCustomerLog(clue); + } + } + + /** + * 线索被转换客户后,需要将线索的跟进记录,复制到客户上 + * + * @param clues 被转化的线索 + */ + private void copyFollowUpRecords(List clues) { + List followUpRecords = followUpRecordService.getFollowUpRecordByBiz( + CrmBizTypeEnum.CRM_LEADS.getType(), convertSet(clues, CrmClueDO::getId)); + if (CollUtil.isEmpty(followUpRecords)) { + return; + } + // 创建跟进 + Map clueMap = convertMap(clues, CrmClueDO::getId); + followUpRecordService.createFollowUpRecordBatch(convertList(followUpRecords, followUpRecord -> + BeanUtils.toBean(followUpRecord, CrmFollowUpCreateReqBO.class).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) + .setBizId(clueMap.get(followUpRecord.getBizId()).getCustomerId()))); + } + + @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSLATE_SUB_TYPE, bizNo = "{{#clue.id}}", + success = CRM_LEADS_TRANSLATE_SUCCESS) + public void translateCustomerLog(CrmClueDO clue) { + // 记录操作日志上下文 + LogRecordContext.putVariable("clue", clue); + } + + private void validateRelationDataExists(CrmClueSaveReqVO reqVO) { + // 校验负责人 + if (Objects.nonNull(reqVO.getOwnerUserId()) && + Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()))) { + throw exception(USER_NOT_EXISTS); + } + } + + private CrmClueDO validateClueExists(Long id) { + CrmClueDO crmClueDO = clueMapper.selectById(id); + if (crmClueDO == null) { + throw exception(CLUE_NOT_EXISTS); + } + return crmClueDO; + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.READ) + public CrmClueDO getClue(Long id) { + return clueMapper.selectById(id); + } + + @Override + public List getClueList(Collection ids, Long userId) { + if (CollUtil.isEmpty(ids)) { + return ListUtil.empty(); + } + return clueMapper.selectBatchIds(ids, userId); + } + + @Override + public PageResult getCluePage(CrmCluePageReqVO pageReqVO, Long userId) { + return clueMapper.selectPage(pageReqVO, userId); + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private CrmClueServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java new file mode 100644 index 000000000..8a9c4192f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.crm.service.contact; + +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * CRM 联系人与商机的关联 Service 接口 + * + * @author 芋道源码 + */ +public interface CrmContactBusinessService { + + /** + * 创建联系人与商机的关联 + * + * @param createReqVO 创建信息 + */ + void createContactBusinessList(@Valid CrmContactBusinessReqVO createReqVO); + + /** + * 删除联系人与商机的关联 + * + * @param deleteReqVO 删除信息 + */ + void deleteContactBusinessList(@Valid CrmContactBusinessReqVO deleteReqVO); + + /** + * 删除联系人与商机的关联,基于联系人编号 + * + * @param contactId 联系人编号 + */ + void deleteContactBusinessByContactId(Long contactId); + + /** + * 获得联系人与商机的关联列表,基于联系人编号 + * + * @param contactId 联系人编号 + * @return 联系人商机关联 + */ + List getContactBusinessListByContactId(Long contactId); + + /** + * 新增联系人与商机的关联 + * + * @param contactBusiness 新增联系人与商机的对象 + */ + void insert(CrmContactBusinessDO contactBusiness); + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java new file mode 100644 index 000000000..7b0711334 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java @@ -0,0 +1,99 @@ +package cn.iocoder.yudao.module.crm.service.contact; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.ArrayList; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_NOT_EXISTS; + +/** + * 联系人与商机的关联 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class CrmContactBusinessServiceImpl implements CrmContactBusinessService { + + @Resource + private CrmContactBusinessMapper contactBusinessMapper; + + @Resource + @Lazy // 延迟加载,为了解决延迟加载 + private CrmBusinessService businessService; + @Resource + @Lazy // 延迟加载,为了解决延迟加载 + private CrmContactService contactService; + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#createReqVO.contactId", level = CrmPermissionLevelEnum.WRITE) + public void createContactBusinessList(CrmContactBusinessReqVO createReqVO) { + CrmContactDO contact = contactService.getContact(createReqVO.getContactId()); + if (contact == null) { + throw exception(CONTACT_NOT_EXISTS); + } + // 遍历处理,考虑到一般数量不会太多,代码处理简单 + List saveDOList = new ArrayList<>(); + createReqVO.getBusinessIds().forEach(businessId -> { + CrmBusinessDO business = businessService.getBusiness(businessId); + if (business == null) { + throw exception(BUSINESS_NOT_EXISTS); + } + // 关联判重 + if (contactBusinessMapper.selectByContactIdAndBusinessId(createReqVO.getContactId(), businessId) != null) { + return; + } + saveDOList.add(new CrmContactBusinessDO(null, createReqVO.getContactId(), businessId)); + }); + // 批量插入 + if (CollUtil.isNotEmpty(saveDOList)) { + contactBusinessMapper.insertBatch(saveDOList); + } + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#deleteReqVO.contactId", level = CrmPermissionLevelEnum.WRITE) + public void deleteContactBusinessList(CrmContactBusinessReqVO deleteReqVO) { + CrmContactDO contact = contactService.getContact(deleteReqVO.getContactId()); + if (contact == null) { + throw exception(CONTACT_NOT_EXISTS); + } + // 直接删除 + contactBusinessMapper.deleteByContactIdAndBusinessId( + deleteReqVO.getContactId(), deleteReqVO.getBusinessIds()); + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#contactId", level = CrmPermissionLevelEnum.WRITE) + public void deleteContactBusinessByContactId(Long contactId) { + contactBusinessMapper.delete(CrmContactBusinessDO::getContactId, contactId); + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#contactId", level = CrmPermissionLevelEnum.READ) + public List getContactBusinessListByContactId(Long contactId) { + return contactBusinessMapper.selectListByContactId(contactId); + } + + @Override + public void insert(CrmContactBusinessDO contactBusiness) { + contactBusinessMapper.insert(contactBusiness); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java new file mode 100644 index 000000000..d7688b8fb --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java @@ -0,0 +1,137 @@ +package cn.iocoder.yudao.module.crm.service.contact; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * CRM 联系人 Service 接口 + * + * @author 芋道源码 + */ +public interface CrmContactService { + + /** + * 创建联系人 + * + * @param createReqVO 创建信息 + * @param userId 用户编号 + * @return 编号 + */ + Long createContact(@Valid CrmContactSaveReqVO createReqVO, Long userId); + + /** + * 更新联系人 + * + * @param updateReqVO 更新信息 + */ + void updateContact(@Valid CrmContactSaveReqVO updateReqVO); + + /** + * 删除联系人 + * + * @param id 编号 + */ + void deleteContact(Long id); + + /** + * 联系人转移 + * + * @param reqVO 请求 + * @param userId 用户编号 + */ + void transferContact(CrmContactTransferReqVO reqVO, Long userId); + + /** + * 更新客户联系人负责人 + * + * @param customerId 客户编号 + * @param ownerUserId 用户编号 + */ + void updateOwnerUserIdByCustomerId(Long customerId, Long ownerUserId); + + /** + * 更新联系人相关跟进信息 + * + * @param updateFollowUpReqBOList 跟进信息 + */ + void updateContactFollowUpBatch(List updateFollowUpReqBOList); + + /** + * 获得联系人 + * + * @param id 编号 + * @return 联系人 + */ + CrmContactDO getContact(Long id); + + /** + * 获得联系人列表 + * + * @param ids 编号 + * @param userId 用户编号 + * @return 联系人列表 + */ + List getContactListByIds(Collection ids, Long userId); + + /** + * 获得联系人列表 + * + * @param ids 编号 + * @return 联系人列表 + */ + List getContactListByIds(Collection ids); + + /** + * 获得联系人列表 + * + * @return 联系人列表 + */ + List getContactList(); + + /** + * 获取联系人列表(校验权限) + * + * @param userId 用户编号 + * @return 联系人列表 + */ + List getSimpleContactList(Long userId); + + /** + * 获得联系人分页 + * + * 数据权限:基于 {@link CrmContactDO} + * + * @param pageReqVO 分页查询 + * @param userId 用户编号 + * @return 联系人分页 + */ + PageResult getContactPage(CrmContactPageReqVO pageReqVO, Long userId); + + /** + * 获得联系人分页 + * + * 数据权限:基于 {@link CrmCustomerDO} + * + * @param pageVO 分页查询 + * @return 联系人分页 + */ + PageResult getContactPageByCustomerId(CrmContactPageReqVO pageVO); + + /** + * 获取关联客户的联系人数量 + * + * @param customerId 客户编号 + * @return 数量 + */ + Long getContactCountByCustomerId(Long customerId); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java new file mode 100644 index 000000000..08ce78b81 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java @@ -0,0 +1,256 @@ +package cn.iocoder.yudao.module.crm.service.contact; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +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.contact.vo.CrmContactBusinessReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO; +import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS; +import static java.util.Collections.singletonList; + +/** + * CRM 联系人 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class CrmContactServiceImpl implements CrmContactService { + + @Resource + private CrmContactMapper contactMapper; + + @Resource + private CrmCustomerService customerService; + @Resource + private CrmPermissionService permissionService; + @Resource + @Lazy + private CrmContractService contractService; + @Resource + private CrmContactBusinessService contactBusinessService; + @Resource + private CrmBusinessService businessService; + + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_CREATE_SUB_TYPE, bizNo = "{{#contact.id}}", + success = CRM_CONTACT_CREATE_SUCCESS) + public Long createContact(CrmContactSaveReqVO createReqVO, Long userId) { + createReqVO.setId(null); + // 1. 校验 + validateRelationDataExists(createReqVO); + + // 2. 插入联系人 + CrmContactDO contact = BeanUtils.toBean(createReqVO, CrmContactDO.class); + contactMapper.insert(contact); + + // 3. 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId) + .setBizType(CrmBizTypeEnum.CRM_CONTACT.getType()).setBizId(contact.getId()) + .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + + // 4. 如果有关联商机,则需要创建关联 + if (createReqVO.getBusinessId() != null) { + contactBusinessService.createContactBusinessList(new CrmContactBusinessReqVO() + .setContactId(contact.getId()).setBusinessIds(singletonList(createReqVO.getBusinessId()))); + } + + // 5. 记录操作日志 + LogRecordContext.putVariable("contact", contact); + return contact.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_CONTACT_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) + public void updateContact(CrmContactSaveReqVO updateReqVO) { + // 1. 校验存在 + CrmContactDO oldContact = validateContactExists(updateReqVO.getId()); + validateRelationDataExists(updateReqVO); + + // 2. 更新联系人 + CrmContactDO updateObj = BeanUtils.toBean(updateReqVO, CrmContactDO.class); + contactMapper.updateById(updateObj); + + // 3. 记录操作日志 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContact, CrmContactSaveReqVO.class)); + LogRecordContext.putVariable("contactName", oldContact.getName()); + } + + /** + * 校验关联的数据都存在 + * + * @param saveReqVO 新增/修改请求 VO + */ + private void validateRelationDataExists(CrmContactSaveReqVO saveReqVO) { + // 1. 校验客户 + if (saveReqVO.getCustomerId() != null && customerService.getCustomer(saveReqVO.getCustomerId()) == null) { + throw exception(CUSTOMER_NOT_EXISTS); + } + // 2. 校验负责人 + if (saveReqVO.getOwnerUserId() != null && adminUserApi.getUser(saveReqVO.getOwnerUserId()) == null) { + throw exception(USER_NOT_EXISTS); + } + // 3. 直属上级 + if (saveReqVO.getParentId() != null && contactMapper.selectById(saveReqVO.getParentId()) == null) { + throw exception(CONTACT_NOT_EXISTS); + } + // 4. 如果有关联商机,则需要校验存在 + if (saveReqVO.getBusinessId() != null && businessService.getBusiness(saveReqVO.getBusinessId()) == null) { + throw exception(BUSINESS_NOT_EXISTS); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CONTACT_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void deleteContact(Long id) { + // 1.1 校验存在 + CrmContactDO contact = validateContactExists(id); + // 1.2 校验是否关联合同 + if (contractService.getContractCountByContactId(id) > 0) { + throw exception(CONTACT_DELETE_FAIL_CONTRACT_LINK_EXISTS); + } + + // 2. 删除联系人 + contactMapper.deleteById(id); + + // 4.1 删除数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id); + // 4.2 删除商机关联 + contactBusinessService.deleteContactBusinessByContactId(id); + + // 记录操作日志上下文 + LogRecordContext.putVariable("contactName", contact.getName()); + } + + private CrmContactDO validateContactExists(Long id) { + CrmContactDO contactDO = contactMapper.selectById(id); + if (contactDO == null) { + throw exception(CONTACT_NOT_EXISTS); + } + return contactDO; + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}", + success = CRM_CONTACT_TRANSFER_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER) + public void transferContact(CrmContactTransferReqVO reqVO, Long userId) { + // 1 校验联系人是否存在 + CrmContactDO contact = validateContactExists(reqVO.getId()); + + // 2.1 数据权限转移 + permissionService.transferPermission( + CrmContactConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CONTACT.getType())); + // 2.2 设置新的负责人 + contactMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + + // 3. 记录转移日志 + LogRecordContext.putVariable("contact", contact); + } + + @Override + public void updateOwnerUserIdByCustomerId(Long customerId, Long ownerUserId) { + contactMapper.updateOwnerUserIdByCustomerId(customerId, ownerUserId); + } + + @Override + public void updateContactFollowUpBatch(List updateFollowUpReqBOList) { + contactMapper.updateBatch(CrmContactConvert.INSTANCE.convertList(updateFollowUpReqBOList)); + } + + //======================= 查询相关 ======================= + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#id", level = CrmPermissionLevelEnum.READ) + public CrmContactDO getContact(Long id) { + return contactMapper.selectById(id); + } + + @Override + public List getContactListByIds(Collection ids, Long userId) { + if (CollUtil.isEmpty(ids)) { + return ListUtil.empty(); + } + return contactMapper.selectBatchIds(ids, userId); + } + + @Override + public List getContactListByIds(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return ListUtil.empty(); + } + return contactMapper.selectBatchIds(ids); + } + + @Override + public List getContactList() { + return contactMapper.selectList(); + } + + @Override + public List getSimpleContactList(Long userId) { + CrmContactPageReqVO reqVO = new CrmContactPageReqVO(); + reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页 + return contactMapper.selectPage(reqVO, userId).getList(); + } + + @Override + public PageResult getContactPage(CrmContactPageReqVO pageReqVO, Long userId) { + return contactMapper.selectPage(pageReqVO, userId); + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageVO.customerId", level = CrmPermissionLevelEnum.READ) + public PageResult getContactPageByCustomerId(CrmContactPageReqVO pageVO) { + return contactMapper.selectPageByCustomerId(pageVO); + } + + @Override + public Long getContactCountByCustomerId(Long customerId) { + return contactMapper.selectCount(CrmContactDO::getCustomerId, customerId); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java new file mode 100644 index 000000000..42d2848af --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java @@ -0,0 +1,146 @@ +package cn.iocoder.yudao.module.crm.service.contract; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +//import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * CRM 合同 Service 接口 + * + * @author dhb52 + */ +public interface CrmContractService { + + /** + * 创建合同 + * + * @param createReqVO 创建信息 + * @param userId 用户编号 + * @return 编号 + */ + Long createContract(@Valid CrmContractSaveReqVO createReqVO, Long userId); + + /** + * 更新合同 + * + * @param updateReqVO 更新信息 + */ + void updateContract(@Valid CrmContractSaveReqVO updateReqVO); + + /** + * 删除合同 + * + * @param id 编号 + */ + void deleteContract(Long id); + + /** + * 合同转移 + * + * @param reqVO 请求 + * @param userId 用户编号 + */ + void transferContract(CrmContractTransferReqVO reqVO, Long userId); + + /** + * 更新合同相关的更进信息 + * + * @param contractUpdateFollowUpReqBO 信息 + */ + void updateContractFollowUp(CrmUpdateFollowUpReqBO contractUpdateFollowUpReqBO); + + /** + * 发起合同审批流程 + * + * @param id 合同编号 + * @param userId 用户编号 + */ + void submitContract(Long id, Long userId); + +// /** +// * 更新合同流程审批结果 +// * +// * @param event 审批结果 +// */ +// void updateContractAuditStatus(BpmResultListenerRespDTO event); + + /** + * 获得合同 + * + * @param id 编号 + * @return 合同 + */ + CrmContractDO getContract(Long id); + + /** + * 获得合同列表 + * + * @param ids 编号 + * @return 合同列表 + */ + List getContractList(Collection ids); + + /** + * 获得合同分页 + * + * 数据权限:基于 {@link CrmContractDO} 读取 + * + * @param pageReqVO 分页查询 + * @param userId 用户编号 + * @return 合同分页 + */ + PageResult getContractPage(CrmContractPageReqVO pageReqVO, Long userId); + + /** + * 获得合同分页,基于指定客户 + * + * 数据权限:基于 {@link CrmCustomerDO} 读取 + * + * @param pageReqVO 分页查询 + * @return 联系人分页 + */ + PageResult getContractPageByCustomerId(CrmContractPageReqVO pageReqVO); + + /** + * 查询属于某个联系人的合同数量 + * + * @param contactId 联系人ID + * @return 合同 + */ + Long getContractCountByContactId(Long contactId); + + /** + * 获取关联客户的合同数量 + * + * @param customerId 客户编号 + * @return 数量 + */ + Long getContractCountByCustomerId(Long customerId); + + /** + * 根据商机ID获取关联客户的合同数量 + * + * @param businessId 商机编号 + * @return 数量 + */ + Long getContractCountByBusinessId(Long businessId); + + /** + * 获取合同商品列表 + * + * @param contactId 合同编号 + * @return 合同商品列表 + */ + List getContractProductListByContractId(Long contactId); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java new file mode 100644 index 000000000..dac575555 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java @@ -0,0 +1,363 @@ +package cn.iocoder.yudao.module.crm.service.contract; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; +import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; +import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; +import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper; +import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractProductMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.crm.service.product.CrmProductService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS; + +/** + * CRM 合同 Service 实现类 + * + * @author dhb52 + */ +@Service +@Validated +public class CrmContractServiceImpl implements CrmContractService { + + /** + * BPM 合同审批流程标识 + */ + public static final String CONTRACT_APPROVE = "contract-approve"; + + @Resource + private CrmContractMapper contractMapper; + @Resource + private CrmContractProductMapper contractProductMapper; + + @Resource + private CrmPermissionService crmPermissionService; + @Resource + private CrmProductService productService; + @Resource + private CrmCustomerService customerService; + @Resource + private CrmBusinessService businessService; + + @Resource + private AdminUserApi adminUserApi; + @Resource + private BpmProcessInstanceApi bpmProcessInstanceApi; + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_CREATE_SUB_TYPE, bizNo = "{{#contract.id}}", + success = CRM_CONTRACT_CREATE_SUCCESS) + public Long createContract(CrmContractSaveReqVO createReqVO, Long userId) { + validateRelationDataExists(createReqVO); + // 1.1 插入合同 + CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class).setId(null); + contractMapper.insert(contract); + // 1.2 插入合同关联商品 + if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话 + List productList = convertContractProductList(createReqVO, contract.getId()); + contractProductMapper.insertBatch(productList); + // 更新合同商品总金额 + contractMapper.updateById(new CrmContractDO().setId(contract.getId()).setProductPrice( + getSumValue(productList, CrmContractProductDO::getTotalPrice, Integer::sum))); + // 如果存在合同关联了商机则更新商机商品关联 + if (contract.getBusinessId() != null) { + businessService.updateBusinessProduct(new CrmBusinessUpdateProductReqBO().setId(contract.getBusinessId()) + .setProductItems(BeanUtils.toBean(createReqVO.getProductItems(), CrmBusinessUpdateProductReqBO.CrmBusinessProductItem.class))); + } + } + + // 2. 创建数据权限 + crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId) + .setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType()).setBizId(contract.getId()) + .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("contract", contract); + return contract.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_CONTRACT_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) + public void updateContract(CrmContractSaveReqVO updateReqVO) { + Assert.notNull(updateReqVO.getId(), "合同编号不能为空"); + // 1.1 校验存在 + CrmContractDO contract = validateContractExists(updateReqVO.getId()); + // 1.2 只有草稿、审批中,可以编辑; + if (!ObjectUtils.equalsAny(contract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(), + CrmAuditStatusEnum.PROCESS.getStatus())) { + throw exception(CONTRACT_UPDATE_FAIL_EDITING_PROHIBITED); + } + validateRelationDataExists(updateReqVO); + + // 2.1 更新合同 + CrmContractDO updateObj = BeanUtils.toBean(updateReqVO, CrmContractDO.class); + contractMapper.updateById(updateObj); + // 2.2 更新合同关联商品 + updateContractProduct(updateReqVO, updateObj.getId()); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(contract, CrmContractSaveReqVO.class)); + LogRecordContext.putVariable("contractName", contract.getName()); + } + + private void updateContractProduct(CrmContractSaveReqVO updateReqVO, Long contractId) { + if (CollUtil.isEmpty(updateReqVO.getProductItems())) { + return; + } + List newProductList = convertContractProductList(updateReqVO, contractId); + List oldProductList = contractProductMapper.selectListByContractId(contractId); + List> diffList = diffList(oldProductList, newProductList, (oldObj, newObj) -> { + boolean match = ObjUtil.equal(oldObj.getProductId(), newObj.getProductId()); + if (match) { + newObj.setId(oldObj.getId()); // 设置一下老的编号更新时需要使用 + } + return match; + }); + if (CollUtil.isNotEmpty(diffList.get(0))) { + contractProductMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + contractProductMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + contractProductMapper.deleteBatchIds(convertList(diffList.get(2), CrmContractProductDO::getId)); + } + } + + // TODO @合同待定:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum + + private List convertContractProductList(CrmContractSaveReqVO reqVO, Long contractId) { + // 校验商品存在 + Set productIds = convertSet(reqVO.getProductItems(), CrmContractSaveReqVO.CrmContractProductItem::getId); + List productList = productService.getProductList(productIds); + if (CollUtil.isEmpty(productIds) || productList.size() != productIds.size()) { + throw exception(PRODUCT_NOT_EXISTS); + } + Map productMap = convertMap(productList, CrmProductDO::getId); + return convertList(reqVO.getProductItems(), productItem -> { + CrmProductDO product = productMap.get(productItem.getId()); + return BeanUtils.toBean(product, CrmContractProductDO.class) + .setId(null).setProductId(productItem.getId()).setContractId(contractId) + .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent()) + .setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent())); + }); + } + + /** + * 校验关联数据是否存在 + * + * @param reqVO 请求 + */ + private void validateRelationDataExists(CrmContractSaveReqVO reqVO) { + // 1. 校验客户 + if (reqVO.getCustomerId() != null && customerService.getCustomer(reqVO.getCustomerId()) == null) { + throw exception(CUSTOMER_NOT_EXISTS); + } + // 2. 校验负责人 + if (reqVO.getOwnerUserId() != null && adminUserApi.getUser(reqVO.getOwnerUserId()) == null) { + throw exception(USER_NOT_EXISTS); + } + // 3. 如果有关联商机,则需要校验存在 + if (reqVO.getBusinessId() != null && businessService.getBusiness(reqVO.getBusinessId()) == null) { + throw exception(BUSINESS_NOT_EXISTS); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CONTRACT_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void deleteContract(Long id) { + // TODO @合同待定:如果被 CrmReceivableDO 所使用,则不允许删除 + // 校验存在 + CrmContractDO contract = validateContractExists(id); + // 删除 + contractMapper.deleteById(id); + // 删除数据权限 + crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CONTRACT.getType(), id); + + // 记录操作日志上下文 + LogRecordContext.putVariable("contractName", contract.getName()); + } + + private CrmContractDO validateContractExists(Long id) { + CrmContractDO contract = contractMapper.selectById(id); + if (contract == null) { + throw exception(CONTRACT_NOT_EXISTS); + } + return contract; + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}", + success = CRM_CONTRACT_TRANSFER_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER) + public void transferContract(CrmContractTransferReqVO reqVO, Long userId) { + // 1. 校验合同是否存在 + CrmContractDO contract = validateContractExists(reqVO.getId()); + + // 2.1 数据权限转移 + crmPermissionService.transferPermission( + CrmContractConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType())); + // 2.2 设置负责人 + contractMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + + // 3. 记录转移日志 + LogRecordContext.putVariable("contract", contract); + } + + @Override + public void updateContractFollowUp(CrmUpdateFollowUpReqBO contractUpdateFollowUpReqBO) { + contractMapper.updateById(BeanUtils.toBean(contractUpdateFollowUpReqBO, CrmContractDO.class).setId(contractUpdateFollowUpReqBO.getBizId())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_SUBMIT_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CONTRACT_SUBMIT_SUCCESS) + public void submitContract(Long id, Long userId) { + // 1. 校验合同是否在审批 + CrmContractDO contract = validateContractExists(id); + if (ObjUtil.notEqual(contract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) { + throw exception(CONTRACT_SUBMIT_FAIL_NOT_DRAFT); + } + + // 2. 创建合同审批流程实例 + String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO() + .setProcessDefinitionKey(CONTRACT_APPROVE).setBusinessKey(String.valueOf(id))).getCheckedData(); + + // 3. 更新合同工作流编号 + contractMapper.updateById(new CrmContractDO().setId(id).setProcessInstanceId(processInstanceId) + .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus())); + + // 3. 记录日志 + LogRecordContext.putVariable("contractName", contract.getName()); + } + +// @Override +// public void updateContractAuditStatus(BpmResultListenerRespDTO event) { +// // 判断下状态是否符合预期 +// if (!isEndResult(event.getResult())) { +// return; +// } +// // 状态转换 +// if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.APPROVE.getResult())) { +// event.setResult(CrmAuditStatusEnum.APPROVE.getStatus()); +// } +// if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.REJECT.getResult())) { +// event.setResult(CrmAuditStatusEnum.REJECT.getStatus()); +// } +// if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult())) { +// event.setResult(CrmAuditStatusEnum.CANCEL.getStatus()); +// } +// // 更新合同状态 +// contractMapper.updateById(new CrmContractDO().setId(Long.parseLong(event.getBusinessKey())) +// .setAuditStatus(event.getResult())); +// } + + /** + * 判断该结果是否处于 End 最终结果 + * + * @param result 结果 + * @return 是否 + */ + public static boolean isEndResult(Integer result) { + return ObjectUtils.equalsAny(result, BpmProcessInstanceResultEnum.APPROVE.getResult(), + BpmProcessInstanceResultEnum.REJECT.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult()); + } + + //======================= 查询相关 ======================= + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.READ) + public CrmContractDO getContract(Long id) { + return contractMapper.selectById(id); + } + + @Override + public List getContractList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return ListUtil.empty(); + } + return contractMapper.selectBatchIds(ids); + } + + @Override + public PageResult getContractPage(CrmContractPageReqVO pageReqVO, Long userId) { + return contractMapper.selectPage(pageReqVO, userId); + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ) + public PageResult getContractPageByCustomerId(CrmContractPageReqVO pageReqVO) { + return contractMapper.selectPageByCustomerId(pageReqVO); + } + + @Override + public Long getContractCountByContactId(Long contactId) { + return contractMapper.selectCountByContactId(contactId); + } + + @Override + public Long getContractCountByCustomerId(Long customerId) { + return contractMapper.selectCount(CrmContractDO::getCustomerId, customerId); + } + + @Override + public Long getContractCountByBusinessId(Long businessId) { + return contractMapper.selectCountByBusinessId(businessId); + } + + @Override + public List getContractProductListByContractId(Long contactId) { + return contractProductMapper.selectListByContractId(contactId); + } + + // TODO @合同待定:需要新增一个 ContractConfigDO 表,合同配置,重点是到期提醒; +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java new file mode 100644 index 000000000..f67f377ac --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigService.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.crm.service.customer; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * 客户限制配置 Service 接口 + * + * @author Wanwan + */ +public interface CrmCustomerLimitConfigService { + + /** + * 创建客户限制配置 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createCustomerLimitConfig(@Valid CrmCustomerLimitConfigSaveReqVO createReqVO); + + /** + * 更新客户限制配置 + * + * @param updateReqVO 更新信息 + */ + void updateCustomerLimitConfig(@Valid CrmCustomerLimitConfigSaveReqVO updateReqVO); + + /** + * 删除客户限制配置 + * + * @param id 编号 + */ + void deleteCustomerLimitConfig(Long id); + + /** + * 获得客户限制配置 + * + * @param id 编号 + * @return 客户限制配置 + */ + CrmCustomerLimitConfigDO getCustomerLimitConfig(Long id); + + /** + * 获得客户限制配置分页 + * + * @param pageReqVO 分页查询 + * @return 客户限制配置分页 + */ + PageResult getCustomerLimitConfigPage(CrmCustomerLimitConfigPageReqVO pageReqVO); + + /** + * 查询用户对应的配置列表 + * + * @param type 类型 + * @param userId 用户类型 + */ + List getCustomerLimitConfigListByUserId(Integer type, Long userId); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java new file mode 100644 index 000000000..eeddec327 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerLimitConfigServiceImpl.java @@ -0,0 +1,124 @@ +package cn.iocoder.yudao.module.crm.service.customer; + +import cn.hutool.core.lang.Assert; +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.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; +import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerLimitConfigMapper; +import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_LIMIT_CONFIG_NOT_EXISTS; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; + +/** + * 客户限制配置 Service 实现类 + * + * @author Wanwan + */ +@Service +@Validated +public class CrmCustomerLimitConfigServiceImpl implements CrmCustomerLimitConfigService { + + @Resource + private CrmCustomerLimitConfigMapper customerLimitConfigMapper; + + @Resource + private DeptApi deptApi; + @Resource + private AdminUserApi adminUserApi; + + @Override + @LogRecord(type = CRM_CUSTOMER_LIMIT_CONFIG_TYPE, subType = CRM_CUSTOMER_LIMIT_CONFIG_CREATE_SUB_TYPE, bizNo = "{{#limitId}}", + success = CRM_CUSTOMER_LIMIT_CONFIG_CREATE_SUCCESS) + public Long createCustomerLimitConfig(CrmCustomerLimitConfigSaveReqVO createReqVO) { + validateUserAndDept(createReqVO.getUserIds(), createReqVO.getDeptIds()); + // 插入 + CrmCustomerLimitConfigDO limitConfig = BeanUtils.toBean(createReqVO, CrmCustomerLimitConfigDO.class); + customerLimitConfigMapper.insert(limitConfig); + + // 记录操作日志上下文 + LogRecordContext.putVariable("limitType", CrmCustomerLimitConfigTypeEnum.getNameByType(limitConfig.getType())); + LogRecordContext.putVariable("limitId", limitConfig.getId()); + return limitConfig.getId(); + } + + @Override + @LogRecord(type = CRM_CUSTOMER_LIMIT_CONFIG_TYPE, subType = CRM_CUSTOMER_LIMIT_CONFIG_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_CUSTOMER_LIMIT_CONFIG_UPDATE_SUCCESS) + public void updateCustomerLimitConfig(CrmCustomerLimitConfigSaveReqVO updateReqVO) { + // 校验存在 + CrmCustomerLimitConfigDO oldLimitConfig = validateCustomerLimitConfigExists(updateReqVO.getId()); + validateUserAndDept(updateReqVO.getUserIds(), updateReqVO.getDeptIds()); + // 更新 + CrmCustomerLimitConfigDO updateObj = BeanUtils.toBean(updateReqVO, CrmCustomerLimitConfigDO.class); + customerLimitConfigMapper.updateById(updateObj); + + // 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldLimitConfig, CrmCustomerLimitConfigSaveReqVO.class)); + } + + @Override + @LogRecord(type = CRM_CUSTOMER_LIMIT_CONFIG_TYPE, subType = CRM_CUSTOMER_LIMIT_CONFIG_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CUSTOMER_LIMIT_CONFIG_DELETE_SUCCESS) + public void deleteCustomerLimitConfig(Long id) { + // 校验存在 + CrmCustomerLimitConfigDO limitConfig = validateCustomerLimitConfigExists(id); + // 删除 + customerLimitConfigMapper.deleteById(id); + + // 记录操作日志上下文 + LogRecordContext.putVariable("limitType", CrmCustomerLimitConfigTypeEnum.getNameByType(limitConfig.getType())); + } + + @Override + public CrmCustomerLimitConfigDO getCustomerLimitConfig(Long id) { + return customerLimitConfigMapper.selectById(id); + } + + @Override + public PageResult getCustomerLimitConfigPage(CrmCustomerLimitConfigPageReqVO pageReqVO) { + return customerLimitConfigMapper.selectPage(pageReqVO); + } + + private CrmCustomerLimitConfigDO validateCustomerLimitConfigExists(Long id) { + CrmCustomerLimitConfigDO limitConfigDO = customerLimitConfigMapper.selectById(id); + if (limitConfigDO == null) { + throw exception(CUSTOMER_LIMIT_CONFIG_NOT_EXISTS); + } + return limitConfigDO; + } + + /** + * 校验入参的用户和部门 + * + * @param userIds 用户 ids + * @param deptIds 部门 ids + */ + private void validateUserAndDept(Collection userIds, Collection deptIds) { + deptApi.validateDeptList(deptIds); + adminUserApi.validateUserList(userIds); + } + + @Override + public List getCustomerLimitConfigListByUserId(Integer type, Long userId) { + AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData(); + Assert.notNull(user, "用户({})不存在", userId); + return customerLimitConfigMapper.selectListByTypeAndUserIdAndDeptId(type, userId, user.getDeptId()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigService.java new file mode 100644 index 000000000..30eb6f581 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigService.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.crm.service.customer; + +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig.CrmCustomerPoolConfigSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; + +import jakarta.validation.Valid; + +/** + * 客户公海配置 Service 接口 + * + * @author Wanwan + */ +public interface CrmCustomerPoolConfigService { + + /** + * 获得客户公海配置 + * + * @return 客户公海配置 + */ + CrmCustomerPoolConfigDO getCustomerPoolConfig(); + + /** + * 保存客户公海配置 + * + * @param saveReqVO 更新信息 + */ + void saveCustomerPoolConfig(@Valid CrmCustomerPoolConfigSaveReqVO saveReqVO); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java new file mode 100644 index 000000000..303a758d2 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.crm.service.customer; + +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.poolconfig.CrmCustomerPoolConfigSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; +import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerPoolConfigMapper; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Objects; + +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; + +/** + * 客户公海配置 Service 实现类 + * + * @author Wanwan + */ +@Service +@Validated +public class CrmCustomerPoolConfigServiceImpl implements CrmCustomerPoolConfigService { + + @Resource + private CrmCustomerPoolConfigMapper customerPoolConfigMapper; + + /** + * 获得客户公海配置 + * + * @return 客户公海配置 + */ + @Override + public CrmCustomerPoolConfigDO getCustomerPoolConfig() { + return customerPoolConfigMapper.selectOne(); + } + + /** + * 保存客户公海配置 + * + * @param saveReqVO 更新信息 + */ + @Override + @LogRecord(type = CRM_CUSTOMER_POOL_CONFIG_TYPE, subType = CRM_CUSTOMER_POOL_CONFIG_SUB_TYPE, bizNo = "{{#poolConfigId}}", + success = CRM_CUSTOMER_POOL_CONFIG_SUCCESS) + public void saveCustomerPoolConfig(CrmCustomerPoolConfigSaveReqVO saveReqVO) { + // 存在,则进行更新 + CrmCustomerPoolConfigDO dbConfig = getCustomerPoolConfig(); + CrmCustomerPoolConfigDO poolConfig = BeanUtils.toBean(saveReqVO, CrmCustomerPoolConfigDO.class); + if (Objects.nonNull(dbConfig)) { + customerPoolConfigMapper.updateById(poolConfig.setId(dbConfig.getId())); + // 记录操作日志上下文 + LogRecordContext.putVariable("isPoolConfigUpdate", Boolean.TRUE); + LogRecordContext.putVariable("poolConfigId", poolConfig.getId()); + return; + } + // 不存在,则进行插入 + customerPoolConfigMapper.insert(poolConfig); + // 记录操作日志上下文 + LogRecordContext.putVariable("isPoolConfigUpdate", Boolean.FALSE); + LogRecordContext.putVariable("poolConfigId", poolConfig.getId()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java new file mode 100644 index 000000000..911260f76 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java @@ -0,0 +1,146 @@ +package cn.iocoder.yudao.module.crm.service.customer; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; +import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * 客户 Service 接口 + * + * @author Wanwan + */ +public interface CrmCustomerService { + + /** + * 创建客户 + * + * @param createReqVO 创建信息 + * @param userId 用户编号 + * @return 编号 + */ + Long createCustomer(@Valid CrmCustomerSaveReqVO createReqVO, Long userId); + + /** + * 更新客户 + * + * @param updateReqVO 更新信息 + */ + void updateCustomer(@Valid CrmCustomerSaveReqVO updateReqVO); + + /** + * 删除客户 + * + * @param id 编号 + */ + void deleteCustomer(Long id); + + /** + * 获得客户 + * + * @param id 编号 + * @return 客户 + */ + CrmCustomerDO getCustomer(Long id); + + /** + * 获得客户列表 + * + * @param ids 客户编号数组 + * @return 客户列表 + * @author ljlleo + */ + List getCustomerList(Collection ids); + + /** + * 获得客户分页 + * + * @param pageReqVO 分页查询 + * @param userId 用户编号 + * @return 客户分页 + */ + PageResult getCustomerPage(CrmCustomerPageReqVO pageReqVO, Long userId); + + /** + * 校验客户是否存在 + * + * @param customerId 客户 id + */ + void validateCustomer(Long customerId); + + /** + * 客户转移 + * + * @param reqVO 请求 + * @param userId 用户编号 + */ + void transferCustomer(CrmCustomerTransferReqVO reqVO, Long userId); + + /** + * 锁定/解锁客户 + * + * @param lockReqVO 更新信息 + * @param userId 用户编号 + */ + void lockCustomer(@Valid CrmCustomerLockReqVO lockReqVO, Long userId); + + /** + * 更新客户相关更进信息 + * + * @param customerUpdateFollowUpReqBO 请求 + */ + void updateCustomerFollowUp(CrmUpdateFollowUpReqBO customerUpdateFollowUpReqBO); + + /** + * 创建客户 + * + * @param customerCreateReq 请求信息 + * @param userId 用户编号 + * @return 客户列表 + */ + Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId); + + /** + * 批量导入客户 + * + * @param importCustomers 导入客户列表 + * @param importReqVO 请求 + * @return 导入结果 + */ + CrmCustomerImportRespVO importCustomerList(List importCustomers, CrmCustomerImportReqVO importReqVO); + + // ==================== 公海相关操作 ==================== + + /** + * 客户放入公海 + * + * @param id 客户编号 + */ + void putCustomerPool(Long id); + + /** + * 领取公海客户 + * + * @param ids 要领取的客户编号数组 + * @param ownerUserId 负责人 + * @param isReceive 是/否领取 + */ + void receiveCustomer(List ids, Long ownerUserId, Boolean isReceive); + + /** + * 【系统】客户自动掉入公海 + * + * @return 掉入公海数量 + */ + int autoPutCustomerPool(); + + PageResult getPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO, + CrmCustomerPoolConfigDO poolConfigDO, + Long loginUserId); +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java new file mode 100644 index 000000000..6351498ce --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java @@ -0,0 +1,572 @@ +package cn.iocoder.yudao.module.crm.service.customer; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*; +import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; +import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.*; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; +import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_LOCK_LIMIT; +import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_OWNER_LIMIT; +import static java.util.Collections.singletonList; + +/** + * 客户 Service 实现类 + * + * @author Wanwan + */ +@Service +@Slf4j +@Validated +public class CrmCustomerServiceImpl implements CrmCustomerService { + + @Resource + private CrmCustomerMapper customerMapper; + + @Resource + private CrmPermissionService permissionService; + @Resource + private CrmCustomerLimitConfigService customerLimitConfigService; + @Resource + @Lazy + private CrmCustomerPoolConfigService customerPoolConfigService; + @Resource + @Lazy + private CrmContactService contactService; + @Resource + @Lazy + private CrmBusinessService businessService; + @Resource + @Lazy + private CrmContractService contractService; + + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_CREATE_SUB_TYPE, bizNo = "{{#customer.id}}", + success = CRM_CUSTOMER_CREATE_SUCCESS) + public Long createCustomer(CrmCustomerSaveReqVO createReqVO, Long userId) { + createReqVO.setId(null); + // 1. 校验拥有客户是否到达上限 + validateCustomerExceedOwnerLimit(createReqVO.getOwnerUserId(), 1); + + // 2. 插入客户 + CrmCustomerDO customer = initCustomer(createReqVO, userId); + customerMapper.insert(customer); + + // 3. 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) + .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 + + // 4. 记录操作日志上下文 + LogRecordContext.putVariable("customer", customer); + return customer.getId(); + } + + /** + * 初始化客户的通用字段 + * + * @param customer 客户信息 + * @param ownerUserId 负责人编号 + * @return 客户信息 DO + */ + private static CrmCustomerDO initCustomer(Object customer, Long ownerUserId) { + return BeanUtils.toBean(customer, CrmCustomerDO.class).setOwnerUserId(ownerUserId) + .setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_CUSTOMER_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) + public void updateCustomer(CrmCustomerSaveReqVO updateReqVO) { + Assert.notNull(updateReqVO.getId(), "客户编号不能为空"); + updateReqVO.setOwnerUserId(null); // 更新的时候,要把 updateReqVO 负责人设置为空,避免修改 + // 1. 校验存在 + CrmCustomerDO oldCustomer = validateCustomerExists(updateReqVO.getId()); + + // 2. 更新客户 + CrmCustomerDO updateObj = BeanUtils.toBean(updateReqVO, CrmCustomerDO.class); + customerMapper.updateById(updateObj); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomer, CrmCustomerSaveReqVO.class)); + LogRecordContext.putVariable("customerName", oldCustomer.getName()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CUSTOMER_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void deleteCustomer(Long id) { + // 1.1 校验存在 + CrmCustomerDO customer = validateCustomerExists(id); + // 1.2 检查引用 + checkCustomerReference(id); + + // 2. 删除 + customerMapper.deleteById(id); + // 3. 删除数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); + + // 4. 记录操作日志上下文 + LogRecordContext.putVariable("customerName", customer.getName()); + } + + /** + * 校验客户是否被引用 + * + * @param id 客户编号 + */ + private void checkCustomerReference(Long id) { + if (contactService.getContactCountByCustomerId(id) > 0) { + throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_CONTACT.getName()); + } + if (businessService.getBusinessCountByCustomerId(id) > 0) { + throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_BUSINESS.getName()); + } + if (contractService.getContractCountByCustomerId(id) > 0) { + throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_CONTRACT.getName()); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}", + success = CRM_CUSTOMER_TRANSFER_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER) + public void transferCustomer(CrmCustomerTransferReqVO reqVO, Long userId) { + // 1.1 校验客户是否存在 + CrmCustomerDO customer = validateCustomerExists(reqVO.getId()); + // 1.2 校验拥有客户是否到达上限 + validateCustomerExceedOwnerLimit(reqVO.getNewOwnerUserId(), 1); + + // 2.1 数据权限转移 + permissionService.transferPermission( + CrmCustomerConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())); + // 2.2 转移后重新设置负责人 + customerMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + + // 3. 记录转移日志 + LogRecordContext.putVariable("customer", customer); + } + + @Override + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_LOCK_SUB_TYPE, bizNo = "{{#lockReqVO.id}}", + success = CRM_CUSTOMER_LOCK_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#lockReqVO.id", level = CrmPermissionLevelEnum.OWNER) + public void lockCustomer(CrmCustomerLockReqVO lockReqVO, Long userId) { + // 1.1 校验当前客户是否存在 + CrmCustomerDO customer = validateCustomerExists(lockReqVO.getId()); + // 1.2 校验当前是否重复操作锁定/解锁状态 + if (customer.getLockStatus().equals(lockReqVO.getLockStatus())) { + throw exception(customer.getLockStatus() ? CUSTOMER_LOCK_FAIL_IS_LOCK : CUSTOMER_UNLOCK_FAIL_IS_UNLOCK); + } + // 1.3 校验锁定上限。 + if (lockReqVO.getLockStatus()) { + validateCustomerExceedLockLimit(userId); + } + + // 2. 更新锁定状态 + customerMapper.updateById(BeanUtils.toBean(lockReqVO, CrmCustomerDO.class)); + + // 3. 记录操作日志上下文 + // tips: 因为这里使用的是老的状态所以记录时反着记录,也就是 lockStatus 为 true 那么就是解锁反之为锁定 + LogRecordContext.putVariable("customer", customer); + } + + @Override + public void updateCustomerFollowUp(CrmUpdateFollowUpReqBO customerUpdateFollowUpReqBO) { + customerMapper.updateById(BeanUtils.toBean(customerUpdateFollowUpReqBO, CrmCustomerDO.class).setId(customerUpdateFollowUpReqBO.getBizId())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_CREATE_SUB_TYPE, bizNo = "{{#customer.id}}", + success = CRM_CUSTOMER_CREATE_SUCCESS) + public Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId) { + // 1. 插入客户 + CrmCustomerDO customer = BeanUtils.toBean(customerCreateReq, CrmCustomerDO.class).setOwnerUserId(userId) + .setLockStatus(false).setDealStatus(false).setReceiveTime(LocalDateTime.now()); + customerMapper.insert(customer); + + // 2. 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) + .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("customer", customer); + return customer.getId(); + } + + @Override + public CrmCustomerImportRespVO importCustomerList(List importCustomers, CrmCustomerImportReqVO importReqVO) { + if (CollUtil.isEmpty(importCustomers)) { + throw exception(CUSTOMER_IMPORT_LIST_IS_EMPTY); + } + CrmCustomerImportRespVO respVO = CrmCustomerImportRespVO.builder().createCustomerNames(new ArrayList<>()) + .updateCustomerNames(new ArrayList<>()).failureCustomerNames(new LinkedHashMap<>()).build(); + importCustomers.forEach(importCustomer -> { + // 校验,判断是否有不符合的原因 + // TODO @puhui999:可以用 ValidationUtils 做参数校验;可能要封装一个方法,返回 message;这样的话,就可以在 CrmCustomerImportExcelVO 写需要校验的参数啦; + try { + validateCustomerForCreate(importCustomer); + } catch (ServiceException ex) { + respVO.getFailureCustomerNames().put(importCustomer.getName(), ex.getMessage()); + return; + } + // 情况一:判断如果不存在,在进行插入 + CrmCustomerDO existCustomer = customerMapper.selectByCustomerName(importCustomer.getName()); + if (existCustomer == null) { + // 1.1 插入客户信息 + CrmCustomerDO customer = initCustomer(importCustomer, importReqVO.getOwnerUserId()); + customerMapper.insert(customer); + respVO.getCreateCustomerNames().add(importCustomer.getName()); + // 1.2 创建数据权限 + if (importReqVO.getOwnerUserId() != null) { + permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) + .setBizId(customer.getId()).setUserId(importReqVO.getOwnerUserId()).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + } + // 1.3 记录操作日志 + getSelf().importCustomerLog(customer, false); + return; + } + + // 情况二:如果存在,判断是否允许更新 + if (!importReqVO.getUpdateSupport()) { + respVO.getFailureCustomerNames().put(importCustomer.getName(), + StrUtil.format(CUSTOMER_NAME_EXISTS.getMsg(), importCustomer.getName())); + return; + } + // 2.1 更新客户信息 + CrmCustomerDO updateCustomer = BeanUtils.toBean(importCustomer, CrmCustomerDO.class) + .setId(existCustomer.getId()); + customerMapper.updateById(updateCustomer); + respVO.getUpdateCustomerNames().add(importCustomer.getName()); + // 2.2 记录操作日志 + getSelf().importCustomerLog(updateCustomer, true); + }); + return respVO; + } + + /** + * 记录导入客户时的操作日志 + * + * @param customer 客户信息 + * @param isUpdate 是否更新;true - 更新,false - 新增 + */ + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_IMPORT_SUB_TYPE, bizNo = "{{#customer.id}}", + success = CRM_CUSTOMER_IMPORT_SUCCESS) + public void importCustomerLog(CrmCustomerDO customer, boolean isUpdate) { + LogRecordContext.putVariable("customer", customer); + LogRecordContext.putVariable("isUpdate", isUpdate); + } + + private void validateCustomerForCreate(CrmCustomerImportExcelVO importCustomer) { + // 校验客户名称不能为空 + if (StrUtil.isEmptyIfStr(importCustomer.getName())) { + throw exception(CUSTOMER_CREATE_NAME_NOT_NULL); + } + } + + // ==================== 公海相关操作 ==================== + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_POOL_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CUSTOMER_POOL_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void putCustomerPool(Long id) { + // 1. 校验存在 + CrmCustomerDO customer = customerMapper.selectById(id); + if (customer == null) { + throw exception(CUSTOMER_NOT_EXISTS); + } + // 1.2. 校验是否为公海数据 + validateCustomerOwnerExists(customer, true); + // 1.3. 校验客户是否锁定 + validateCustomerIsLocked(customer, true); + + // 2. 客户放入公海 + putCustomerPool(customer); + + // 记录操作日志上下文 + LogRecordContext.putVariable("customerName", customer.getName()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void receiveCustomer(List ids, Long ownerUserId, Boolean isReceive) { + if (!isReceive && !CrmPermissionUtils.isCrmAdmin()) { // 只有管理员可以分配 + throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.CRM_CUSTOMER.getName()); + } + + // 1.1 校验存在 + List customers = customerMapper.selectBatchIds(ids); + if (customers.size() != ids.size()) { + throw exception(CUSTOMER_NOT_EXISTS); + } + // 1.2. 校验负责人是否存在 + adminUserApi.validateUserList(singletonList(ownerUserId)); + // 1.3. 校验状态 + customers.forEach(customer -> { + // 校验是否已有负责人 + validateCustomerOwnerExists(customer, false); + // 校验是否锁定 + validateCustomerIsLocked(customer, false); + // 校验成交状态 + validateCustomerDeal(customer); + }); + // 1.4 校验负责人是否到达上限 + validateCustomerExceedOwnerLimit(ownerUserId, customers.size()); + + // 2.1 领取公海数据 + List updateCustomers = new ArrayList<>(); + List createPermissions = new ArrayList<>(); + customers.forEach(customer -> { + // 2.1. 设置负责人 + updateCustomers.add(new CrmCustomerDO().setId(customer.getId()).setOwnerUserId(ownerUserId)); + // 2.2. 创建负责人数据权限 + createPermissions.add(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) + .setBizId(customer.getId()).setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + }); + // 2.2 更新客户负责人 + customerMapper.updateBatch(updateCustomers); + // 2.3 创建负责人数据权限 + permissionService.createPermissionBatch(createPermissions); + // TODO @芋艿:要不要处理关联的联系人??? + + // 3. 记录操作日志 + AdminUserRespDTO user = null; + if (!isReceive) { + user = adminUserApi.getUser(ownerUserId).getCheckedData(); + } + for (CrmCustomerDO customer : customers) { + getSelf().receiveCustomerLog(customer, user == null ? null : user.getNickname()); + } + } + + @Override + public int autoPutCustomerPool() { + CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig(); + if (poolConfig == null || !poolConfig.getEnabled()) { + return 0; + } + // 1.1 获取没有锁定的不在公海的客户 + List customerList = customerMapper.selectListByLockAndNotPool(Boolean.FALSE); + // TODO @puhui999:下面也搞到 sql 里去哈;写 or 查询,问题不大的;低 393 到 402;原因是,避免无用的太多数据查询到 java 进程里; + List poolCustomerList = new ArrayList<>(); + poolCustomerList.addAll(filterList(customerList, customer -> + !customer.getDealStatus() && (poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime())) <= 0)); + poolCustomerList.addAll(filterList(customerList, customer -> { + if (!customer.getDealStatus()) { // 这里只处理成交的 + return false; + } + LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime()); + return (poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime)) <= 0; + })); + + // 2. 逐个放入公海 + int count = 0; + for (CrmCustomerDO customer : poolCustomerList) { + try { + getSelf().putCustomerPool(customer); + count++; + } catch (Throwable e) { + log.error("[autoPutCustomerPool][Customer 客户({}) 放入公海异常]", customer.getId(), e); + } + } + return count; + } + + private void putCustomerPool(CrmCustomerDO customer) { + // 1. 设置负责人为 NULL + int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null); + if (updateOwnerUserIncr == 0) { + throw exception(CUSTOMER_UPDATE_OWNER_USER_FAIL); + } + // 2. 删除负责人数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(), + CrmPermissionLevelEnum.OWNER.getLevel()); + + // 3. 联系人的负责人,也要设置为 null。因为:因为领取后,负责人也要关联过来,这块和 receiveCustomer 是对应的 + contactService.updateOwnerUserIdByCustomerId(customer.getId(), null); + } + + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_RECEIVE_SUB_TYPE, bizNo = "{{#customer.id}}", + success = CRM_CUSTOMER_RECEIVE_SUCCESS) + public void receiveCustomerLog(CrmCustomerDO customer, String ownerUserName) { + // 记录操作日志上下文 + LogRecordContext.putVariable("customer", customer); + LogRecordContext.putVariable("ownerUserName", ownerUserName); + } + + //======================= 查询相关 ======================= + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.READ) + public CrmCustomerDO getCustomer(Long id) { + return customerMapper.selectById(id); + } + + @Override + public List getCustomerList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return customerMapper.selectBatchIds(ids); + } + + @Override + public PageResult getCustomerPage(CrmCustomerPageReqVO pageReqVO, Long userId) { + return customerMapper.selectPage(pageReqVO, userId); + } + + public PageResult getPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageReqVO, + CrmCustomerPoolConfigDO poolConfigDO, + Long userId) { + return customerMapper.selectPutInPoolRemindCustomerPage(pageReqVO, poolConfigDO, userId); + } + + // ======================= 校验相关 ======================= + + /** + * 校验客户是否存在 + * + * @param customerId 客户 id + */ + @Override + public void validateCustomer(Long customerId) { + validateCustomerExists(customerId); + } + + private void validateCustomerOwnerExists(CrmCustomerDO customer, Boolean pool) { + if (customer == null) { // 防御一下 + throw exception(CUSTOMER_NOT_EXISTS); + } + // 校验是否为公海数据 + if (pool && customer.getOwnerUserId() == null) { + throw exception(CUSTOMER_IN_POOL, customer.getName()); + } + // 负责人已存在 + if (!pool && customer.getOwnerUserId() != null) { + throw exception(CUSTOMER_OWNER_EXISTS, customer.getName()); + } + } + + private CrmCustomerDO validateCustomerExists(Long id) { + CrmCustomerDO customerDO = customerMapper.selectById(id); + if (customerDO == null) { + throw exception(CUSTOMER_NOT_EXISTS); + } + return customerDO; + } + + private void validateCustomerIsLocked(CrmCustomerDO customer, Boolean pool) { + if (customer.getLockStatus()) { + throw exception(pool ? CUSTOMER_LOCKED_PUT_POOL_FAIL : CUSTOMER_LOCKED, customer.getName()); + } + } + + private void validateCustomerDeal(CrmCustomerDO customer) { + if (customer.getDealStatus()) { + throw exception(CUSTOMER_ALREADY_DEAL); + } + } + + /** + * 校验用户拥有的客户数量,是否到达上限 + * + * @param userId 用户编号 + * @param newCount 附加数量 + */ + private void validateCustomerExceedOwnerLimit(Long userId, int newCount) { + List limitConfigs = customerLimitConfigService.getCustomerLimitConfigListByUserId( + CUSTOMER_OWNER_LIMIT.getType(), userId); + if (CollUtil.isEmpty(limitConfigs)) { + return; + } + Long ownerCount = customerMapper.selectCountByDealStatusAndOwnerUserId(null, userId); + Long dealOwnerCount = customerMapper.selectCountByDealStatusAndOwnerUserId(true, userId); + limitConfigs.forEach(limitConfig -> { + long nowCount = limitConfig.getDealCountEnabled() ? ownerCount : ownerCount - dealOwnerCount; + if (nowCount + newCount > limitConfig.getMaxCount()) { + throw exception(CUSTOMER_OWNER_EXCEED_LIMIT); + } + }); + } + + /** + * 校验用户锁定的客户数量,是否到达上限 + * + * @param userId 用户编号 + */ + private void validateCustomerExceedLockLimit(Long userId) { + List limitConfigs = customerLimitConfigService.getCustomerLimitConfigListByUserId( + CUSTOMER_LOCK_LIMIT.getType(), userId); + if (CollUtil.isEmpty(limitConfigs)) { + return; + } + Long lockCount = customerMapper.selectCountByLockStatusAndOwnerUserId(true, userId); + Integer maxCount = CollectionUtils.getMaxValue(limitConfigs, CrmCustomerLimitConfigDO::getMaxCount); + assert maxCount != null; + if (lockCount >= maxCount) { + throw exception(CUSTOMER_LOCK_EXCEED_LIMIT); + } + } + + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private CrmCustomerServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/bo/CrmCustomerCreateReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/bo/CrmCustomerCreateReqBO.java new file mode 100644 index 000000000..6d80b0e4c --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/bo/CrmCustomerCreateReqBO.java @@ -0,0 +1,125 @@ +package cn.iocoder.yudao.module.crm.service.customer.bo; + +import cn.iocoder.yudao.framework.common.validation.Mobile; +import cn.iocoder.yudao.framework.common.validation.Telephone; +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 客户创建 Create Req BO + * + * @author HUIHUI + */ +@Data +public class CrmCustomerCreateReqBO { + + /** + * 客户名称 + */ + @NotEmpty(message = "客户名称不能为空") + private String name; + /** + * 跟进状态 + */ + private Boolean followUpStatus; + /** + * 锁定状态 + */ + private Boolean lockStatus; + /** + * 成交状态 + */ + private Boolean dealStatus; + /** + * 所属行业 + * + * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_INDUSTRY} + */ + private Integer industryId; + /** + * 客户等级 + * + * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_LEVEL} + */ + private Integer level; + /** + * 客户来源 + * + * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_SOURCE} + */ + private Integer source; + + /** + * 手机 + */ + @Mobile + private String mobile; + /** + * 电话 + */ + @Telephone + private String telephone; + /** + * 网址 + */ + private String website; + /** + * QQ + */ + private String qq; + /** + * wechat + */ + private String wechat; + + /** + * 邮箱 + */ + @Email(message = "邮箱格式不正确") + private String email; + + /** + * 客户描述 + */ + @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符") + private String description; + /** + * 备注 + */ + private String remark; + /** + * 负责人的用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + /** + * 所在地 + * + * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段 + */ + private Integer areaId; + /** + * 详细地址 + */ + private String detailAddress; + + /** + * 最后跟进时间 + */ + private LocalDateTime contactLastTime; + /** + * 最后跟进内容 + */ + private String contactLastContent; + /** + * 下次联系时间 + */ + private LocalDateTime contactNextTime; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordService.java new file mode 100644 index 000000000..d2a7cb8f9 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordService.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.crm.service.followup; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmFollowUpCreateReqBO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * 跟进记录 Service 接口 + * + * @author 芋道源码 + */ +public interface CrmFollowUpRecordService { + + /** + * 创建跟进记录 (数据权限基于 bizType、 bizId) + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createFollowUpRecord(@Valid CrmFollowUpRecordSaveReqVO createReqVO); + + /** + * 创建更进 + * + * @param list 请求 + */ + void createFollowUpRecordBatch(List list); + + /** + * 删除跟进记录 (数据权限基于 bizType、 bizId) + * + * @param id 编号 + * @param userId 用户编号 + */ + void deleteFollowUpRecord(Long id, Long userId); + + /** + * 删除跟进 + * + * @param bizType 模块类型 + * @param bizId 模块数据编号 + */ + void deleteFollowUpRecordByBiz(Integer bizType, Long bizId); + + /** + * 获得跟进记录 + * + * @param id 编号 + * @return 跟进记录 + */ + CrmFollowUpRecordDO getFollowUpRecord(Long id); + + /** + * 获得跟进记录分页 (数据权限基于 bizType、 bizId) + * + * @param pageReqVO 分页查询 + * @return 跟进记录分页 + */ + PageResult getFollowUpRecordPage(CrmFollowUpRecordPageReqVO pageReqVO); + + /** + * 获取跟进记录 + * + * @param bizType 模块类型 + * @param bizIds 模块数据编号 + * @return 跟进列表 + */ + List getFollowUpRecordByBiz(Integer bizType, Collection bizIds); + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java new file mode 100644 index 000000000..88f0b887c --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java @@ -0,0 +1,158 @@ +package cn.iocoder.yudao.module.crm.service.followup; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +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.followup.vo.CrmFollowUpRecordPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; +import cn.iocoder.yudao.module.crm.dal.mysql.followup.CrmFollowUpRecordMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; +import cn.iocoder.yudao.module.crm.service.clue.CrmClueService; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmFollowUpCreateReqBO; +import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +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.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_DELETE_DENIED; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_NOT_EXISTS; + +/** + * 跟进记录 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService { + + @Resource + private CrmFollowUpRecordMapper crmFollowUpRecordMapper; + + @Resource + @Lazy + private CrmPermissionService permissionService; + @Resource + @Lazy + private CrmBusinessService businessService; + @Resource + @Lazy + private CrmClueService clueService; + @Resource + @Lazy + private CrmContactService contactService; + @Resource + @Lazy + private CrmContractService contractService; + @Resource + @Lazy + private CrmCustomerService customerService; + + @Override + @CrmPermission(bizTypeValue = "#createReqVO.bizType", bizId = "#createReqVO.bizId", level = CrmPermissionLevelEnum.WRITE) + public Long createFollowUpRecord(CrmFollowUpRecordSaveReqVO createReqVO) { + // 创建更进记录 + CrmFollowUpRecordDO followUpRecord = BeanUtils.toBean(createReqVO, CrmFollowUpRecordDO.class); + crmFollowUpRecordMapper.insert(followUpRecord); + + LocalDateTime now = LocalDateTime.now(); + CrmUpdateFollowUpReqBO updateFollowUpReqBO = new CrmUpdateFollowUpReqBO().setBizId(followUpRecord.getBizId()) + .setContactLastTime(now).setContactNextTime(followUpRecord.getNextTime()).setContactLastContent(followUpRecord.getContent()); + // 2. 更新 bizId 对应的记录; + if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_BUSINESS.getType(), followUpRecord.getBizType())) { // 更新商机跟进信息 + businessService.updateBusinessFollowUpBatch(Collections.singletonList(updateFollowUpReqBO)); + } + if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_LEADS.getType(), followUpRecord.getBizType())) { // 更新线索跟进信息 + clueService.updateClueFollowUp(updateFollowUpReqBO); + } + if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CONTACT.getType(), followUpRecord.getBizType())) { // 更新联系人跟进信息 + contactService.updateContactFollowUpBatch(Collections.singletonList(updateFollowUpReqBO)); + } + if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CONTRACT.getType(), followUpRecord.getBizType())) { // 更新合同跟进信息 + contractService.updateContractFollowUp(updateFollowUpReqBO); + } + if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CUSTOMER.getType(), followUpRecord.getBizType())) { // 更新客户跟进信息 + customerService.updateCustomerFollowUp(updateFollowUpReqBO); + } + + // 3.1 更新 contactIds 对应的记录,不更新 lastTime 和 lastContent + if (CollUtil.isNotEmpty(createReqVO.getContactIds())) { + contactService.updateContactFollowUpBatch(convertList(createReqVO.getContactIds(), + contactId -> updateFollowUpReqBO.setBizId(contactId).setContactLastTime(null).setContactLastContent(null))); + } + // 3.2 需要更新 businessIds 对应的记录,不更新 lastTime 和 lastContent + if (CollUtil.isNotEmpty(createReqVO.getBusinessIds())) { + businessService.updateBusinessFollowUpBatch(convertList(createReqVO.getBusinessIds(), + businessId -> updateFollowUpReqBO.setBizId(businessId).setContactLastTime(null).setContactLastContent(null))); + } + return followUpRecord.getId(); + } + + @Override + public void createFollowUpRecordBatch(List list) { + if (CollUtil.isEmpty(list)) { + return; + } + crmFollowUpRecordMapper.insertBatch(BeanUtils.toBean(list, CrmFollowUpRecordDO.class)); + } + + @Override + public void deleteFollowUpRecord(Long id, Long userId) { + // 校验存在 + CrmFollowUpRecordDO followUpRecord = validateFollowUpRecordExists(id); + // 校验权限 + if (!permissionService.hasPermission(followUpRecord.getBizType(), followUpRecord.getBizId(), userId, CrmPermissionLevelEnum.OWNER)) { + throw exception(FOLLOW_UP_RECORD_DELETE_DENIED); + } + + // 删除 + crmFollowUpRecordMapper.deleteById(id); + } + + @Override + public void deleteFollowUpRecordByBiz(Integer bizType, Long bizId) { + crmFollowUpRecordMapper.deleteByBiz(bizType, bizId); + } + + private CrmFollowUpRecordDO validateFollowUpRecordExists(Long id) { + CrmFollowUpRecordDO followUpRecord = crmFollowUpRecordMapper.selectById(id); + if (followUpRecord == null) { + throw exception(FOLLOW_UP_RECORD_NOT_EXISTS); + } + return followUpRecord; + } + + @Override + public CrmFollowUpRecordDO getFollowUpRecord(Long id) { + return crmFollowUpRecordMapper.selectById(id); + } + + @Override + @CrmPermission(bizTypeValue = "#pageReqVO.bizType", bizId = "#pageReqVO.bizId", level = CrmPermissionLevelEnum.READ) + public PageResult getFollowUpRecordPage(CrmFollowUpRecordPageReqVO pageReqVO) { + return crmFollowUpRecordMapper.selectPage(pageReqVO); + } + + @Override + public List getFollowUpRecordByBiz(Integer bizType, Collection bizIds) { + return crmFollowUpRecordMapper.selectListByBiz(bizType, bizIds); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmFollowUpCreateReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmFollowUpCreateReqBO.java new file mode 100644 index 000000000..dec219e29 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmFollowUpCreateReqBO.java @@ -0,0 +1,78 @@ +package cn.iocoder.yudao.module.crm.service.followup.bo; + +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 跟进信息 Create Req BO + * + * @author HUIHUI + */ +@Data +public class CrmFollowUpCreateReqBO { + + /** + * 数据类型 + * + * 枚举 {@link CrmBizTypeEnum} + */ + @NotNull(message = "数据类型不能为空") + private Integer bizType; + /** + * 数据编号 + * + * 关联 {@link CrmBizTypeEnum} 对应模块 DO 的 id 字段 + */ + @NotNull(message = "数据编号不能为空") + private Long bizId; + + /** + * 跟进类型 + * + * 关联 {@link DictTypeConstants#CRM_FOLLOW_UP_TYPE} 字典 + */ + @NotNull(message = "跟进类型不能为空") + private Integer type; + /** + * 跟进内容 + */ + @NotEmpty(message = "跟进内容不能为空") + private String content; + /** + * 下次联系时间 + */ + @NotNull(message = "下次联系时间不能为空") + private LocalDateTime nextTime; + + /** + * 图片 + */ + private List picUrls; + /** + * 附件 + */ + private List fileUrls; + + /** + * 关联的商机编号数组 + * + * 关联 {@link CrmBusinessDO#getId()} + */ + private List businessIds; + + /** + * 关联的联系人编号数组 + * + * 关联 {@link CrmContactDO#getId()} + */ + private List contactIds; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java new file mode 100644 index 000000000..57f1849eb --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.crm.service.followup.bo; + +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 跟进信息 Update Req BO + * + * @author HUIHUI + */ +@Data +public class CrmUpdateFollowUpReqBO { + + @Schema(description = "数据编号", example = "3167") + @NotNull(message = "数据编号不能为空") + private Long bizId; + + @Schema(description = "最后跟进时间") + @DiffLogField(name = "最后跟进时间") + private LocalDateTime contactLastTime; + + @Schema(description = "下次联系时间") + @DiffLogField(name = "下次联系时间") + private LocalDateTime contactNextTime; + + @Schema(description = "最后更进内容") + @DiffLogField(name = "最后更进内容") + private String contactLastContent; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogService.java new file mode 100644 index 000000000..f13514c25 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogService.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.crm.service.message; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.backlog.vo.CrmTodayCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import jakarta.validation.Valid; + +/** + * CRM 待办消息 Service 接口 + * + * @author dhb52 + */ +public interface CrmBacklogService { + + /** + * 根据【联系状态】、【场景类型】筛选客户分页 + * + * @param pageReqVO 分页查询 + * @return 分页数据 + */ + PageResult getTodayCustomerPage(@Valid CrmTodayCustomerPageReqVO pageReqVO, Long userId); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogServiceImpl.java new file mode 100644 index 000000000..5c2417266 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogServiceImpl.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.crm.service.message; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.backlog.vo.CrmTodayCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +/** + * 待办消息 Service 实现类 + * + * @author dhb52 + */ +@Component +@Validated +public class CrmBacklogServiceImpl implements CrmBacklogService { + + @Resource + private CrmCustomerMapper customerMapper; + + @Override + public PageResult getTodayCustomerPage(CrmTodayCustomerPageReqVO pageReqVO, Long userId) { + return customerMapper.selectTodayCustomerPage(pageReqVO, userId); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java new file mode 100644 index 000000000..39b101323 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java @@ -0,0 +1,122 @@ +package cn.iocoder.yudao.module.crm.service.permission; + + +import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * crm 数据权限 Service 接口 + * + * @author HUIHUI + */ +public interface CrmPermissionService { + + /** + * 创建数据权限 + * + * @param createReqBO 创建信息 + * @return 编号 + */ + Long createPermission(@Valid CrmPermissionCreateReqBO createReqBO); + + /** + * 创建数据权限 + * + * @param createReqBOs 创建信息 + */ + void createPermissionBatch(@Valid List createReqBOs); + + /** + * 更新数据权限 + * + * @param updateReqVO 更新信息 + */ + void updatePermission(CrmPermissionUpdateReqVO updateReqVO); + + /** + * 数据权限转移 + * + * @param crmPermissionTransferReqBO 数据权限转移请求 + */ + void transferPermission(@Valid CrmPermissionTransferReqBO crmPermissionTransferReqBO); + + /** + * 删除数据权限 + * + * @param bizType 数据类型,关联 {@link CrmBizTypeEnum} + * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId() + * @param level 数据权限级别,关联 {@link CrmPermissionLevelEnum} + */ + void deletePermission(Integer bizType, Long bizId, Integer level); + + /** + * 删除数据权限 + * + * @param bizType 数据类型,关联 {@link CrmBizTypeEnum} + * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId() + */ + void deletePermission(Integer bizType, Long bizId); + + /** + * 批量删除数据权限 + * + * @param ids 权限编号 + * @param userId 用户编号 + */ + void deletePermissionBatch(Collection ids, Long userId); + + /** + * 删除指定用户数据权限 + * + * @param id 权限编号 + * @param userId 用户编号 + */ + void deleteSelfPermission(Long id, Long userId); + + /** + * 获取数据权限列表,通过 数据类型 x 某个数据 + * + * @param bizType 数据类型,关联 {@link CrmBizTypeEnum} + * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId() + * @return Crm 数据权限列表 + */ + List getPermissionListByBiz(Integer bizType, Long bizId); + + /** + * 获取数据权限列表,通过 数据类型 x 某个数据 + * + * @param bizType 数据类型,关联 {@link CrmBizTypeEnum} + * @param bizIds 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId() + * @return Crm 数据权限列表 + */ + List getPermissionListByBiz(Integer bizType, Collection bizIds); + + /** + * 获取用户参与的模块数据列表 + * + * @param bizType 模块类型 + * @param userId 用户编号 + * @return 模块数据列表 + */ + List getPermissionListByBizTypeAndUserId(Integer bizType, Long userId); + + /** + * 校验是否有指定数据的操作权限 + * + * @param bizType 数据类型,关联 {@link CrmBizTypeEnum} + * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId() + * @param userId 用户编号 + * @param level 权限级别 + * @return 是否有权限 + */ + boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum level); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java new file mode 100644 index 000000000..ba3e50c6d --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java @@ -0,0 +1,222 @@ +package cn.iocoder.yudao.module.crm.service.permission; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; +import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; +import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmPermissionMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isOwner; + +/** + * CRM 数据权限 Service 接口实现类 + * + * @author HUIHUI + */ +@Service +@Validated +public class CrmPermissionServiceImpl implements CrmPermissionService { + + @Resource + private CrmPermissionMapper permissionMapper; + + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createPermission(CrmPermissionCreateReqBO createReqBO) { + validatePermissionNotExists(Collections.singletonList(createReqBO)); + // 1. 校验用户是否存在 + adminUserApi.validateUserList(Collections.singletonList(createReqBO.getUserId())); + + // 2. 创建 + CrmPermissionDO permission = BeanUtils.toBean(createReqBO, CrmPermissionDO.class); + permissionMapper.insert(permission); + return permission.getId(); + } + + @Override + public void createPermissionBatch(List createReqBOs) { + validatePermissionNotExists(createReqBOs); + // 1. 校验用户是否存在 + adminUserApi.validateUserList(convertSet(createReqBOs, CrmPermissionCreateReqBO::getUserId)); + + // 2. 创建 + List permissions = BeanUtils.toBean(createReqBOs, CrmPermissionDO.class); + permissionMapper.insertBatch(permissions); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePermission(CrmPermissionUpdateReqVO updateReqVO) { + // 1. 校验存在 + validatePermissionExists(updateReqVO.getIds()); + // 2. 更新 + List updateDO = CrmPermissionConvert.INSTANCE.convertList(updateReqVO); + permissionMapper.updateBatch(updateDO); + } + + private void validatePermissionExists(Collection ids) { + List permissionList = permissionMapper.selectBatchIds(ids); + if (ObjUtil.notEqual(permissionList.size(), ids.size())) { + throw exception(CRM_PERMISSION_NOT_EXISTS); + } + } + + private void validatePermissionNotExists(Collection createReqBOs) { + Set bizTypes = convertSet(createReqBOs, CrmPermissionCreateReqBO::getBizType); + Set bizIds = convertSet(createReqBOs, CrmPermissionCreateReqBO::getBizId); + Set userIds = convertSet(createReqBOs, CrmPermissionCreateReqBO::getUserId); + Long count = permissionMapper.selectListByBiz(bizTypes, bizIds, userIds); + if (count > 0) { + throw exception(CRM_PERMISSION_CREATE_FAIL); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void transferPermission(CrmPermissionTransferReqBO transferReqBO) { + // 1. 校验数据权限:是否是负责人,只有负责人才可以转移 + CrmPermissionDO oldPermission = permissionMapper.selectByBizTypeAndBizIdByUserId( + transferReqBO.getBizType(), transferReqBO.getBizId(), transferReqBO.getUserId()); + String bizTypeName = CrmBizTypeEnum.getNameByType(transferReqBO.getBizType()); + if (oldPermission == null // 不是拥有者,并且不是超管 + || (!isOwner(oldPermission.getLevel()) && !CrmPermissionUtils.isCrmAdmin())) { + throw exception(CRM_PERMISSION_DENIED, bizTypeName); + } + // 1.1 校验转移对象是否已经是该负责人 + if (ObjUtil.equal(transferReqBO.getNewOwnerUserId(), oldPermission.getUserId())) { + throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS, bizTypeName); + } + // 1.2 校验新负责人是否存在 + adminUserApi.validateUserList(Collections.singletonList(transferReqBO.getNewOwnerUserId())); + + // 2. 修改新负责人的权限 + List permissions = permissionMapper.selectByBizTypeAndBizId( + transferReqBO.getBizType(), transferReqBO.getBizId()); // 获得所有数据权限 + CrmPermissionDO permission = CollUtil.findOne(permissions, + item -> ObjUtil.equal(item.getUserId(), transferReqBO.getNewOwnerUserId())); + if (permission == null) { + permissionMapper.insert(new CrmPermissionDO().setBizType(transferReqBO.getBizType()) + .setBizId(transferReqBO.getBizId()).setUserId(transferReqBO.getNewOwnerUserId()) + .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + } else { + permissionMapper.updateById(new CrmPermissionDO().setId(permission.getId()) + .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + } + + // 3. 修改老负责人的权限 + if (transferReqBO.getOldOwnerPermissionLevel() != null) { + permissionMapper.updateById(new CrmPermissionDO().setId(oldPermission.getId()) + .setLevel(transferReqBO.getOldOwnerPermissionLevel())); + } else { + permissionMapper.deleteById(oldPermission.getId()); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deletePermission(Integer bizType, Long bizId, Integer level) { + // 校验存在 + List permissions = permissionMapper.selectListByBizTypeAndBizIdAndLevel( + bizType, bizId, level); + if (CollUtil.isEmpty(permissions)) { + throw exception(CRM_PERMISSION_NOT_EXISTS); + } + + // 删除数据权限 + permissionMapper.deleteBatchIds(convertSet(permissions, CrmPermissionDO::getId)); + } + + @Override + public void deletePermission(Integer bizType, Long bizId) { + int deletedCount = permissionMapper.deletePermission(bizType, bizId); + if (deletedCount == 0) { + throw exception(CRM_PERMISSION_NOT_EXISTS); + } + } + + @Override + public void deletePermissionBatch(Collection ids, Long userId) { + List permissions = permissionMapper.selectBatchIds(ids); + if (CollUtil.isEmpty(permissions)) { + throw exception(CRM_PERMISSION_NOT_EXISTS); + } + // 校验:数据权限的模块数据编号是一致的不可能存在两个 + if (convertSet(permissions, CrmPermissionDO::getBizId).size() > 1) { + throw exception(CRM_PERMISSION_DELETE_FAIL); + } + // 校验操作人是否为负责人 + CrmPermissionDO permission = permissionMapper.selectByBizIdAndUserId(permissions.getFirst().getBizId(), userId); + if (permission == null) { + throw exception(CRM_PERMISSION_DELETE_DENIED); + } + if (!CrmPermissionLevelEnum.isOwner(permission.getLevel())) { + throw exception(CRM_PERMISSION_DELETE_DENIED); + } + + // 删除数据权限 + permissionMapper.deleteBatchIds(ids); + } + + @Override + public void deleteSelfPermission(Long id, Long userId) { + // 校验数据存在且是自己 + CrmPermissionDO permission = permissionMapper.selectByIdAndUserId(id, userId); + if (permission == null) { + throw exception(CRM_PERMISSION_NOT_EXISTS); + } + // 校验是否是负责人 + if (CrmPermissionLevelEnum.isOwner(permission.getLevel())) { + throw exception(CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER); + } + + // 删除 + permissionMapper.deleteById(id); + } + + @Override + public List getPermissionListByBiz(Integer bizType, Long bizId) { + return permissionMapper.selectByBizTypeAndBizId(bizType, bizId); + } + + @Override + public List getPermissionListByBiz(Integer bizType, Collection bizIds) { + return permissionMapper.selectByBizTypeAndBizIds(bizType, bizIds); + } + + @Override + public List getPermissionListByBizTypeAndUserId(Integer bizType, Long userId) { + return permissionMapper.selectListByBizTypeAndUserId(bizType, userId); + } + + @Override + public boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum level) { + List permissionList = permissionMapper.selectByBizTypeAndBizId(bizType, bizId); + return anyMatch(permissionList, permission -> + ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), level.getLevel())); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionCreateReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionCreateReqBO.java new file mode 100644 index 000000000..f4b4bb364 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionCreateReqBO.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.crm.service.permission.bo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +/** + * crm 数据权限 Create Req BO + * + * @author HUIHUI + */ +@Data +public class CrmPermissionCreateReqBO { + + /** + * 当前登录用户编号 + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + + /** + * Crm 类型 + */ + @NotNull(message = "Crm 类型不能为空") + @InEnum(CrmBizTypeEnum.class) + private Integer bizType; + /** + * 数据编号 + */ + @NotNull(message = "Crm 数据编号不能为空") + private Long bizId; + + /** + * 权限级别 + */ + @NotNull(message = "权限级别不能为空") + @InEnum(CrmPermissionLevelEnum.class) + private Integer level; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionTransferReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionTransferReqBO.java new file mode 100644 index 000000000..0e5933c0c --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionTransferReqBO.java @@ -0,0 +1,49 @@ +package cn.iocoder.yudao.module.crm.service.permission.bo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +/** + * 数据权限转移 Request BO + * + * @author HUIHUI + */ +@Data +public class CrmPermissionTransferReqBO { + + /** + * 当前登录用户编号 + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + + /** + * CRM 类型 + */ + @NotNull(message = "Crm 类型不能为空") + @InEnum(CrmBizTypeEnum.class) + private Integer bizType; + /** + * 数据编号 + */ + @NotNull(message = "CRM 数据编号不能为空") + private Long bizId; + + /** + * 新负责人的用户编号 + */ + @NotNull(message = "新负责人的用户编号不能为空") + private Long newOwnerUserId; + + /** + * 老负责人加入团队后的权限级别。如果 null 说明移除 + * + * 关联 {@link CrmPermissionLevelEnum} + */ + private Integer oldOwnerPermissionLevel; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryService.java new file mode 100644 index 000000000..56974da11 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryService.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.crm.service.product; + +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryListReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; + +import jakarta.validation.Valid; +import java.util.Collection; +import java.util.List; + +/** + * CRM 产品分类 Service 接口 + * + * @author ZanGe丶 + */ +public interface CrmProductCategoryService { + + /** + * 创建产品分类 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProductCategory(@Valid CrmProductCategoryCreateReqVO createReqVO); + + /** + * 更新产品分类 + * + * @param updateReqVO 更新信息 + */ + void updateProductCategory(@Valid CrmProductCategoryCreateReqVO updateReqVO); + + /** + * 删除产品分类 + * + * @param id 编号 + */ + void deleteProductCategory(Long id); + + /** + * 获得产品分类 + * + * @param id 编号 + * @return 产品分类 + */ + CrmProductCategoryDO getProductCategory(Long id); + + /** + * 获得产品分类列表 + * + * @param listReqVO 列表请求 + * @return 产品分类列表 + */ + List getProductCategoryList(CrmProductCategoryListReqVO listReqVO); + + /** + * 获得产品分类列表 + * + * @param ids 编号数组 + * @return 产品分类列表 + */ + List getProductCategoryList(Collection ids); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java new file mode 100644 index 000000000..6399039ba --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryServiceImpl.java @@ -0,0 +1,138 @@ +package cn.iocoder.yudao.module.crm.service.product; + +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryListReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; +import cn.iocoder.yudao.module.crm.dal.mysql.product.CrmProductCategoryMapper; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO.PARENT_ID_NULL; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; + +/** + * CRM 产品分类 Service 实现类 + * + * @author ZanGe丶 + */ +@Service +@Validated +public class CrmProductCategoryServiceImpl implements CrmProductCategoryService { + + @Resource(name = "crmProductCategoryMapper") + private CrmProductCategoryMapper productCategoryMapper; + + @Resource + @Lazy // 延迟加载,解决循环依赖问题 + private CrmProductService crmProductService; + + @Override + @LogRecord(type = CRM_PRODUCT_CATEGORY_TYPE, subType = CRM_PRODUCT_CATEGORY_CREATE_SUB_TYPE, bizNo = "{{#productCategoryId}}", + success = CRM_PRODUCT_CATEGORY_CREATE_SUCCESS) + public Long createProductCategory(CrmProductCategoryCreateReqVO createReqVO) { + // 1.1 校验父分类存在 + validateParentProductCategory(createReqVO.getParentId()); + // 1.2 分类名称是否存在 + validateProductNameExists(null, createReqVO.getParentId(), createReqVO.getName()); + + // 2. 插入分类 + CrmProductCategoryDO category = BeanUtils.toBean(createReqVO, CrmProductCategoryDO.class); + productCategoryMapper.insert(category); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("productCategoryId", category.getId()); + return category.getId(); + } + + @Override + @LogRecord(type = CRM_PRODUCT_CATEGORY_TYPE, subType = CRM_PRODUCT_CATEGORY_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_PRODUCT_CATEGORY_UPDATE_SUCCESS) + public void updateProductCategory(CrmProductCategoryCreateReqVO updateReqVO) { + // 1.1 校验存在 + validateProductCategoryExists(updateReqVO.getId()); + // 1.2 校验父分类存在 + validateParentProductCategory(updateReqVO.getParentId()); + // 1.3 分类名称是否存在 + validateProductNameExists(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName()); + + // 2. 更新分类 + CrmProductCategoryDO updateObj = BeanUtils.toBean(updateReqVO, CrmProductCategoryDO.class); + productCategoryMapper.updateById(updateObj); + } + + private void validateProductCategoryExists(Long id) { + if (productCategoryMapper.selectById(id) == null) { + throw exception(PRODUCT_CATEGORY_NOT_EXISTS); + } + } + + private void validateParentProductCategory(Long id) { + // 如果是根分类,无需验证 + if (Objects.equals(id, PARENT_ID_NULL)) { + return; + } + // 父分类不存在 + CrmProductCategoryDO category = productCategoryMapper.selectById(id); + if (category == null) { + throw exception(PRODUCT_CATEGORY_PARENT_NOT_EXISTS); + } + // 父分类不能是二级分类 + if (!Objects.equals(category.getParentId(), PARENT_ID_NULL)) { + throw exception(PRODUCT_CATEGORY_PARENT_NOT_FIRST_LEVEL); + } + } + + private void validateProductNameExists(Long id, Long parentId, String name) { + CrmProductCategoryDO category = productCategoryMapper.selectByParentIdAndName(parentId, name); + if (category == null + || category.getId().equals(id)) { + return; + } + throw exception(PRODUCT_CATEGORY_EXISTS); + } + + @Override + @LogRecord(type = CRM_PRODUCT_CATEGORY_TYPE, subType = CRM_PRODUCT_CATEGORY_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_PRODUCT_CATEGORY_DELETE_SUCCESS) + public void deleteProductCategory(Long id) { + // 1.1 校验存在 + validateProductCategoryExists(id); + // 1.2 校验是否还有子分类 + if (productCategoryMapper.selectCountByParentId(id) > 0) { + throw exception(product_CATEGORY_EXISTS_CHILDREN); + } + // 1.3 校验是否被产品使用 + if (crmProductService.getProductByCategoryId(id) !=null) { + throw exception(PRODUCT_CATEGORY_USED); + } + // 2. 删除 + productCategoryMapper.deleteById(id); + } + + @Override + public CrmProductCategoryDO getProductCategory(Long id) { + return productCategoryMapper.selectById(id); + } + + @Override + public List getProductCategoryList(CrmProductCategoryListReqVO listReqVO) { + return productCategoryMapper.selectList(listReqVO); + } + + @Override + public List getProductCategoryList(Collection ids) { + return productCategoryMapper.selectBatchIds(ids); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java new file mode 100644 index 000000000..6d2dd4943 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java @@ -0,0 +1,81 @@ +package cn.iocoder.yudao.module.crm.service.product; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * CRM 产品 Service 接口 + * + * @author ZanGe丶 + */ +public interface CrmProductService { + + /** + * 创建产品 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createProduct(@Valid CrmProductSaveReqVO createReqVO); + + /** + * 更新产品 + * + * @param updateReqVO 更新信息 + */ + void updateProduct(@Valid CrmProductSaveReqVO updateReqVO); + + /** + * 删除产品 + * + * @param id 编号 + */ + void deleteProduct(Long id); + + /** + * 获得产品 + * + * @param id 编号 + * @return 产品 + */ + CrmProductDO getProduct(Long id); + + /** + * 获得产品列表 + * + * @param ids 编号 + * @return 产品列表 + */ + List getProductList(Collection ids); + + /** + * 获得产品分页 + * + * @param pageReqVO 分页查询 + * @return 产品分页 + */ + PageResult getProductPage(CrmProductPageReqVO pageReqVO, Long userId); + + /** + * 获得产品 + * + * @param categoryId 分类编号 + * @return 产品 + */ + CrmProductDO getProductByCategoryId(Long categoryId); + + /** + * 获得产品列表 + * + * @param ids 产品编号 + * @return 产品列表 + */ + List getProductListByIds(Collection ids); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java new file mode 100644 index 000000000..95205524e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java @@ -0,0 +1,166 @@ +package cn.iocoder.yudao.module.crm.service.product; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +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.product.vo.product.CrmProductPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; +import cn.iocoder.yudao.module.crm.dal.mysql.product.CrmProductMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; + + +/** + * CRM 产品 Service 实现类 + * + * @author ZanGe丶 + */ +@Service +@Validated +public class CrmProductServiceImpl implements CrmProductService { + + @Resource(name = "crmProductMapper") + private CrmProductMapper productMapper; + + @Resource + private CrmProductCategoryService productCategoryService; + @Resource + private CrmPermissionService permissionService; + + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_PRODUCT_TYPE, subType = CRM_PRODUCT_CREATE_SUB_TYPE, bizNo = "{{#productId}}", + success = CRM_PRODUCT_CREATE_SUCCESS) + public Long createProduct(CrmProductSaveReqVO createReqVO) { + // 1. 校验产品 + adminUserApi.validateUserList(Collections.singleton(createReqVO.getOwnerUserId())); + validateProductNoDuplicate(null, createReqVO.getNo()); + validateProductCategoryExists(createReqVO.getCategoryId()); + + // 2. 插入产品 + CrmProductDO product = BeanUtils.toBean(createReqVO, CrmProductDO.class); + productMapper.insert(product); + + // 3. 插入数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(product.getOwnerUserId()) + .setBizType(CrmBizTypeEnum.CRM_PRODUCT.getType()).setBizId(product.getId()) + .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + + // 4. 记录操作日志上下文 + LogRecordContext.putVariable("productId", product.getId()); + return product.getId(); + } + + @Override + @LogRecord(type = CRM_PRODUCT_TYPE, subType = CRM_PRODUCT_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_PRODUCT_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_PRODUCT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) + public void updateProduct(CrmProductSaveReqVO updateReqVO) { + // 1. 校验产品 + updateReqVO.setOwnerUserId(null); // 不修改负责人 + CrmProductDO crmProductDO = validateProductExists(updateReqVO.getId()); + validateProductNoDuplicate(updateReqVO.getId(), updateReqVO.getNo()); + validateProductCategoryExists(updateReqVO.getCategoryId()); + + // 2. 更新产品 + CrmProductDO updateObj = BeanUtils.toBean(updateReqVO, CrmProductDO.class); + productMapper.updateById(updateObj); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(crmProductDO, CrmProductSaveReqVO.class)); + } + + private CrmProductDO validateProductExists(Long id) { + CrmProductDO product = productMapper.selectById(id); + if (product == null) { + throw exception(PRODUCT_NOT_EXISTS); + } + return product; + } + + private void validateProductNoDuplicate(Long id, String no) { + CrmProductDO product = productMapper.selectByNo(no); + if (product == null + || product.getId().equals(id)) { + return; + } + throw exception(PRODUCT_NO_EXISTS); + } + + private void validateProductCategoryExists(Long categoryId) { + CrmProductCategoryDO category = productCategoryService.getProductCategory(categoryId); + if (category == null) { + throw exception(PRODUCT_CATEGORY_NOT_EXISTS); + } + } + + @Override + @LogRecord(type = CRM_PRODUCT_TYPE, subType = CRM_PRODUCT_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_PRODUCT_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_PRODUCT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void deleteProduct(Long id) { + // 校验存在 + validateProductExists(id); + // 删除 + productMapper.deleteById(id); + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_PRODUCT, bizId = "#id", level = CrmPermissionLevelEnum.READ) + public CrmProductDO getProduct(Long id) { + return productMapper.selectById(id); + } + + @Override + public List getProductList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return ListUtil.empty(); + } + return productMapper.selectBatchIds(ids); + } + + @Override + public PageResult getProductPage(CrmProductPageReqVO pageReqVO, Long userId) { + return productMapper.selectPage(pageReqVO, userId); + } + + @Override + public CrmProductDO getProductByCategoryId(Long categoryId) { + return productMapper.selectOne(new LambdaQueryWrapper().eq(CrmProductDO::getCategoryId, categoryId)); + } + + @Override + public List getProductListByIds(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return productMapper.selectBatchIds(ids); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java new file mode 100644 index 000000000..ded059b28 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java @@ -0,0 +1,80 @@ +package cn.iocoder.yudao.module.crm.service.receivable; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * CRM 回款计划 Service 接口 + * + * @author 芋道源码 + */ +public interface CrmReceivablePlanService { + + /** + * 创建回款计划 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createReceivablePlan(@Valid CrmReceivablePlanCreateReqVO createReqVO, Long userId); + + /** + * 更新回款计划 + * + * @param updateReqVO 更新信息 + */ + void updateReceivablePlan(@Valid CrmReceivablePlanUpdateReqVO updateReqVO); + + /** + * 删除回款计划 + * + * @param id 编号 + */ + void deleteReceivablePlan(Long id); + + /** + * 获得回款计划 + * + * @param id 编号 + * @return 回款计划 + */ + CrmReceivablePlanDO getReceivablePlan(Long id); + + /** + * 获得回款计划列表 + * + * @param ids 编号 + * @return 回款计划列表 + */ + List getReceivablePlanList(Collection ids); + + /** + * 获得回款计划分页 + * + * 数据权限:基于 {@link CrmReceivablePlanDO} 读取 + * + * @param pageReqVO 分页查询 + * @param userId 用户编号 + * @return 回款计划分页 + */ + PageResult getReceivablePlanPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId); + + /** + * 获得回款计划分页,基于指定客户 + * + * 数据权限:基于 {@link CrmCustomerDO} 读取 + * + * @param pageReqVO 分页查询 + * @return 回款计划分页 + */ + PageResult getReceivablePlanPageByCustomerId(CrmReceivablePlanPageReqVO pageReqVO); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java new file mode 100644 index 000000000..9ac2e7f43 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java @@ -0,0 +1,166 @@ +package cn.iocoder.yudao.module.crm.service.receivable; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjectUtil; +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.receivable.vo.plan.CrmReceivablePlanCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; +import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; +import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivablePlanMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; + +// TODO @liuhongfeng:参考 CrmReceivableServiceImpl 写的 todo 哈; + +/** + * 回款计划 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { + + @Resource + private CrmReceivablePlanMapper receivablePlanMapper; + + @Resource + private CrmContractService contractService; + @Resource + private CrmCustomerService customerService; + @Resource + private CrmPermissionService permissionService; + + @Override + @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_CREATE_SUB_TYPE, bizNo = "{{#receivablePlan.id}}", + success = CRM_RECEIVABLE_PLAN_CREATE_SUCCESS) + public Long createReceivablePlan(CrmReceivablePlanCreateReqVO createReqVO, Long userId) { + // TODO @liuhongfeng:第几期的计算;基于是 contractId + contractDO 的第几个还款 + // TODO @liuhongfeng contractId:校验合同是否存在 + // 插入 + CrmReceivablePlanDO receivablePlan = CrmReceivablePlanConvert.INSTANCE.convert(createReqVO); + receivablePlan.setFinishStatus(false); + + checkReceivablePlan(receivablePlan); + + receivablePlanMapper.insert(receivablePlan); + // 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId) + .setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType()).setBizId(receivablePlan.getId()) + .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + + // 4. 记录操作日志上下文 + LogRecordContext.putVariable("receivablePlan", receivablePlan); + return receivablePlan.getId(); + } + + private void checkReceivablePlan(CrmReceivablePlanDO receivablePlan) { + + if (ObjectUtil.isNull(receivablePlan.getContractId())) { + throw exception(CONTRACT_NOT_EXISTS); + } + + CrmContractDO contract = contractService.getContract(receivablePlan.getContractId()); + if (ObjectUtil.isNull(contract)) { + throw exception(CONTRACT_NOT_EXISTS); + } + + CrmCustomerDO customer = customerService.getCustomer(receivablePlan.getCustomerId()); + if (ObjectUtil.isNull(customer)) { + throw exception(CUSTOMER_NOT_EXISTS); + } + + } + + @Override + @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) + public void updateReceivablePlan(CrmReceivablePlanUpdateReqVO updateReqVO) { + // TODO @liuhongfeng:如果已经有对应的还款,则不允许编辑; + // 校验存在 + CrmReceivablePlanDO oldReceivablePlan = validateReceivablePlanExists(updateReqVO.getId()); + + // 更新 + CrmReceivablePlanDO updateObj = CrmReceivablePlanConvert.INSTANCE.convert(updateReqVO); + receivablePlanMapper.updateById(updateObj); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanUpdateReqVO.class)); + LogRecordContext.putVariable("receivablePlan", oldReceivablePlan); + } + + @Override + @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_RECEIVABLE_PLAN_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void deleteReceivablePlan(Long id) { + // 校验存在 + CrmReceivablePlanDO receivablePlan = validateReceivablePlanExists(id); + // 删除 + receivablePlanMapper.deleteById(id); + // 删除数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); + // 记录操作日志上下文 + LogRecordContext.putVariable("receivablePlan", receivablePlan); + } + + private CrmReceivablePlanDO validateReceivablePlanExists(Long id) { + CrmReceivablePlanDO receivablePlan = receivablePlanMapper.selectById(id); + if (receivablePlan == null) { + throw exception(RECEIVABLE_PLAN_NOT_EXISTS); + } + return receivablePlan; + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#id", level = CrmPermissionLevelEnum.READ) + public CrmReceivablePlanDO getReceivablePlan(Long id) { + return receivablePlanMapper.selectById(id); + } + + @Override + public List getReceivablePlanList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return ListUtil.empty(); + } + return receivablePlanMapper.selectBatchIds(ids); + } + + @Override + public PageResult getReceivablePlanPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId) { + return receivablePlanMapper.selectPage(pageReqVO, userId); + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ) + public PageResult getReceivablePlanPageByCustomerId(CrmReceivablePlanPageReqVO pageReqVO) { + return receivablePlanMapper.selectPageByCustomerId(pageReqVO); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java new file mode 100644 index 000000000..8e9cfa0ea --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java @@ -0,0 +1,81 @@ +package cn.iocoder.yudao.module.crm.service.receivable; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * CRM 回款 Service 接口 + * + * @author 赤焰 + */ +public interface CrmReceivableService { + + /** + * 创建回款 + * + * @param createReqVO 创建信息 + * @param userId 用户编号 + * @return 编号 + */ + Long createReceivable(@Valid CrmReceivableCreateReqVO createReqVO, Long userId); + + /** + * 更新回款 + * + * @param updateReqVO 更新信息 + */ + void updateReceivable(@Valid CrmReceivableUpdateReqVO updateReqVO); + + /** + * 删除回款 + * + * @param id 编号 + */ + void deleteReceivable(Long id); + + /** + * 获得回款 + * + * @param id 编号 + * @return 回款 + */ + CrmReceivableDO getReceivable(Long id); + + /** + * 获得回款列表 + * + * @param ids 编号 + * @return 回款列表 + */ + List getReceivableList(Collection ids); + + /** + * 获得回款分页 + * + * 数据权限:基于 {@link CrmReceivableDO} 读取 + * + * @param pageReqVO 分页查询 + * @param userId 用户编号 + * @return 回款分页 + */ + PageResult getReceivablePage(CrmReceivablePageReqVO pageReqVO, Long userId); + + /** + * 获得回款分页,基于指定客户 + * + * 数据权限:基于 {@link CrmCustomerDO} 读取 + * + * @param pageReqVO 分页查询 + * @return 回款分页 + */ + PageResult getReceivablePageByCustomerId(CrmReceivablePageReqVO pageReqVO); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java new file mode 100644 index 000000000..effe0d720 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java @@ -0,0 +1,186 @@ +package cn.iocoder.yudao.module.crm.service.receivable; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +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.receivable.vo.receivable.CrmReceivableCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; +import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; +import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; + +/** + * CRM 回款 Service 实现类 + * + * @author 赤焰 + */ +@Service +@Validated +public class CrmReceivableServiceImpl implements CrmReceivableService { + + @Resource + private CrmReceivableMapper receivableMapper; + + @Resource + private CrmContractService contractService; + @Resource + private CrmCustomerService customerService; + @Resource + private CrmReceivablePlanService receivablePlanService; + @Resource + private CrmPermissionService permissionService; + + @Override + @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}", + success = CRM_RECEIVABLE_CREATE_SUCCESS) + public Long createReceivable(CrmReceivableCreateReqVO createReqVO, Long userId) { + // 插入还款 + CrmReceivableDO receivable = CrmReceivableConvert.INSTANCE.convert(createReqVO); + if (ObjectUtil.isNull(receivable.getAuditStatus())) { + receivable.setAuditStatus(CommonStatusEnum.ENABLE.getStatus()); + } + receivable.setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus()); + + // TODO @liuhongfeng:一般来说,逻辑的写法,是要先检查,后操作 db;所以,你这个 check 应该放到 CrmReceivableDO receivable 之前; + checkReceivable(receivable); + + receivableMapper.insert(receivable); + // 3. 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType()) + .setBizId(receivable.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 + // TODO @liuhongfeng:需要更新关联的 plan + + // 4. 记录操作日志上下文 + LogRecordContext.putVariable("receivable", receivable); + return receivable.getId(); + } + + // TODO @liuhongfeng:这里的括号要注意排版; + private void checkReceivable(CrmReceivableDO receivable) { + // TODO @liuhongfeng:校验 no 的唯一性 + // TODO @liuhongfeng:这个放在参数校验合适 + if (ObjectUtil.isNull(receivable.getContractId())) { + throw exception(CONTRACT_NOT_EXISTS); + } + + CrmContractDO contract = contractService.getContract(receivable.getContractId()); + if (ObjectUtil.isNull(contract)) { + throw exception(CONTRACT_NOT_EXISTS); + } + + CrmCustomerDO customer = customerService.getCustomer(receivable.getCustomerId()); + if (ObjectUtil.isNull(customer)) { + throw exception(CUSTOMER_NOT_EXISTS); + } + + CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(receivable.getPlanId()); + if (ObjectUtil.isNull(receivablePlan)) { + throw exception(RECEIVABLE_PLAN_NOT_EXISTS); + } + + } + + @Override + @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_RECEIVABLE_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) + public void updateReceivable(CrmReceivableUpdateReqVO updateReqVO) { + // 校验存在 + CrmReceivableDO oldReceivable = validateReceivableExists(updateReqVO.getId()); + // TODO @liuhongfeng:只有在草稿、审核中,可以提交修改 + + // 更新还款 + CrmReceivableDO updateObj = CrmReceivableConvert.INSTANCE.convert(updateReqVO); + receivableMapper.updateById(updateObj); + + // TODO @liuhongfeng:需要更新关联的 plan + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivable, CrmReceivableUpdateReqVO.class)); + LogRecordContext.putVariable("receivable", oldReceivable); + } + + // TODO @liuhongfeng:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum + + // TODO @liuhongfeng:缺一个发起审批的接口;只有草稿可以发起审批;CrmAuditStatusEnum + + @Override + @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_RECEIVABLE_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void deleteReceivable(Long id) { + // TODO @liuhongfeng:如果被 CrmReceivablePlanDO 所使用,则不允许删除 + // 校验存在 + CrmReceivableDO receivable = validateReceivableExists(id); + // 删除 + receivableMapper.deleteById(id); + + // 删除数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); + + // 记录操作日志上下文 + LogRecordContext.putVariable("receivable", receivable); + } + + private CrmReceivableDO validateReceivableExists(Long id) { + CrmReceivableDO receivable = receivableMapper.selectById(id); + if (receivable == null) { + throw exception(RECEIVABLE_NOT_EXISTS); + } + return receivable; + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.READ) + public CrmReceivableDO getReceivable(Long id) { + return receivableMapper.selectById(id); + } + + @Override + public List getReceivableList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return ListUtil.empty(); + } + return receivableMapper.selectBatchIds(ids); + } + + @Override + public PageResult getReceivablePage(CrmReceivablePageReqVO pageReqVO, Long userId) { + return receivableMapper.selectPage(pageReqVO, userId); + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#pageReqVO.customerId", level = CrmPermissionLevelEnum.READ) + public PageResult getReceivablePageByCustomerId(CrmReceivablePageReqVO pageReqVO) { + return receivableMapper.selectPageByCustomerId(pageReqVO); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryWrapperUtils.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryWrapperUtils.java new file mode 100644 index 000000000..5e7700902 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryWrapperUtils.java @@ -0,0 +1,113 @@ +package cn.iocoder.yudao.module.crm.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties; +import com.github.yulichang.wrapper.MPJLambdaWrapper; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +/** + * CRM 查询工具类 + * + * @author HUIHUI + */ +public class CrmQueryWrapperUtils { + + /** + * 构造 CRM 数据类型数据分页查询条件 + * + * @param query 连表查询对象 + * @param bizType 数据类型 {@link CrmBizTypeEnum} + * @param bizId 数据编号 + * @param userId 用户编号 + * @param sceneType 场景类型 + * @param pool 公海 + */ + public static , S> void appendPermissionCondition(T query, Integer bizType, SFunction bizId, + Long userId, Integer sceneType, Boolean pool) { + final String ownerUserIdField = SingletonManager.getMybatisPlusJoinProperties().getTableAlias() + ".owner_user_id"; + // 1. 构建数据权限连表条件 + if (!CrmPermissionUtils.isCrmAdmin() && ObjUtil.notEqual(pool, Boolean.TRUE)) { // 管理员,公海不需要数据权限 + query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType) + .eq(CrmPermissionDO::getBizId, bizId) // 只能使用 SFunction 如果传 id 解析出来的 sql 不对 + .eq(CrmPermissionDO::getUserId, userId)); + } + // 2.1 场景一:我负责的数据 + if (CrmSceneTypeEnum.isOwner(sceneType)) { + query.eq(ownerUserIdField, userId); + } + // 2.2 场景二:我参与的数据 + if (CrmSceneTypeEnum.isInvolved(sceneType)) { + query.ne(ownerUserIdField, userId) + .in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel()); + } + // 2.3 场景三:下属负责的数据 + if (CrmSceneTypeEnum.isSubordinate(sceneType)) { + List subordinateUsers = SingletonManager.getAdminUserApi().getUserListBySubordinate(userId).getCheckedData(); + if (CollUtil.isEmpty(subordinateUsers)) { + query.eq(ownerUserIdField, -1); // 不返回任何结果 + } else { + query.in(ownerUserIdField, convertSet(subordinateUsers, AdminUserRespDTO::getId)); + } + } + + // 3. 拼接公海的查询条件 + if (ObjUtil.equal(pool, Boolean.TRUE)) { // 情况一:公海 + query.isNull(ownerUserIdField); + } else { // 情况二:不是公海 + query.isNotNull(ownerUserIdField); + } + } + + /** + * 构造 CRM 数据类型批量数据查询条件 + * + * @param query 连表查询对象 + * @param bizType 数据类型 {@link CrmBizTypeEnum} + * @param bizIds 数据编号 + * @param userId 用户编号 + */ + public static > void appendPermissionCondition(T query, Integer bizType, Collection bizIds, Long userId) { + if (CrmPermissionUtils.isCrmAdmin()) {// 管理员不需要数据权限 + return; + } + query.innerJoin(CrmPermissionDO.class, on -> + on.eq(CrmPermissionDO::getBizType, bizType).in(CrmPermissionDO::getBizId, bizIds) + .eq(CollUtil.isNotEmpty(bizIds), CrmPermissionDO::getUserId, userId)); + } + + /** + * 静态内部类实现单例获取 + * + * @author HUIHUI + */ + private static class SingletonManager { + + private static final AdminUserApi ADMIN_USER_API = SpringUtil.getBean(AdminUserApi.class); + + private static final MybatisPlusJoinProperties MYBATIS_PLUS_JOIN_PROPERTIES = SpringUtil.getBean(MybatisPlusJoinProperties.class); + + public static AdminUserApi getAdminUserApi() { + return ADMIN_USER_API; + } + + public static MybatisPlusJoinProperties getMybatisPlusJoinProperties() { + return MYBATIS_PLUS_JOIN_PROPERTIES; + } + + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/application-dev.yaml b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/application-dev.yaml new file mode 100644 index 000000000..affbaf3c2 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/application-dev.yaml @@ -0,0 +1,104 @@ +--- #################### 数据库相关配置 #################### +spring: + # 数据源配置项 + autoconfigure: + exclude: + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 5 # 初始连接数 + min-idle: 10 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: master + datasource: + master: + name: ruoyi-vue-pro + url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&nullCatalogMeansCurrent=true + driver-class-name: com.mysql.jdbc.Driver + username: root + password: 3WLiVUBEwTbvAfsh + slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 + name: ruoyi-vue-pro + url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&nullCatalogMeansCurrent=true + driver-class-name: com.mysql.jdbc.Driver + username: root + password: 3WLiVUBEwTbvAfsh + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 400-infra.server.iocoder.cn # 地址 + port: 6379 # 端口 + database: 1 # 数据库索引 +# password: 123456 # 密码,建议生产环境开启 + +--- #################### MQ 消息队列相关配置 #################### + +--- #################### 定时任务相关配置 #################### + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator + exposure: + include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + # Spring Boot Admin Client 客户端的相关配置 + client: + instance: + service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + xss: + enable: false + exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 + - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求 + - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 + access-log: # 访问日志的配置项 + enable: false + error-code: # 错误码相关配置项 + enable: false + demo: false # 关闭演示模式 diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/application-local.yaml b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/application-local.yaml new file mode 100644 index 000000000..7d5f030fb --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/application-local.yaml @@ -0,0 +1,130 @@ +--- #################### 数据库相关配置 #################### +spring: + # 数据源配置项 + autoconfigure: + exclude: + - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置 + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 1 # 初始连接数 + min-idle: 1 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: master + datasource: + master: + name: ruoyi-vue-pro + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 + # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 + # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.master.name} # PostgreSQL 连接的示例 + # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 + # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例 + username: root + password: 123456 + # username: sa + # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W + slave: # 模拟从库,可根据自己需要修改 + name: ruoyi-vue-pro + url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 + # url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例 + # url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例 + # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 + # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例 + username: root + password: 123456 + # username: sa + # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 127.0.0.1 # 地址 + port: 6379 # 端口 + database: 0 # 数据库索引 +# password: 123456 # 密码,建议生产环境开启 + +--- #################### MQ 消息队列相关配置 #################### + +--- #################### 定时任务相关配置 #################### +xxl: + job: + enabled: false # 是否开启调度中心,默认为 true 开启 + admin: + addresses: http://127.0.0.1:8000/xxl-job-admin # 调度中心部署跟地址 + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator + exposure: + include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + # Spring Boot Admin Client 客户端的相关配置 + client: + instance: + service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + +# 日志文件配置 +logging: + level: + # 配置自己写的 MyBatis Mapper 打印日志 + cn.iocoder.yudao.module.crm.dal.mysql: debug + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + env: # 多环境的配置项 + tag: ${HOSTNAME} + security: + mock-enable: true + xss: + enable: false + exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 + - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求 + - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 + access-log: # 访问日志的配置项 + enable: false + error-code: # 错误码相关配置项 + enable: false + demo: false # 关闭演示模式 diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/application.yaml b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/application.yaml new file mode 100644 index 000000000..7860f1328 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/application.yaml @@ -0,0 +1,105 @@ +spring: + main: + allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。 + allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务 + + # Servlet 配置 + servlet: + # 文件上传相关配置项 + multipart: + max-file-size: 16MB # 单个文件大小 + max-request-size: 32MB # 设置总上传的文件大小 + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER # 解决 SpringFox 与 SpringBoot 2.6.x 不兼容的问题,参见 SpringFoxHandlerProviderBeanPostProcessor 类 + + # Jackson 配置项 + jackson: + serialization: + write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳 + write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401 + write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳 + fail-on-empty-beans: false # 允许序列化无属性的 Bean + + # Cache 配置项 + cache: + type: REDIS + redis: + time-to-live: 1h # 设置过期时间为 1 小时 + +--- #################### 接口文档配置 #################### + +springdoc: + api-docs: + enabled: true # 1. 是否开启 Swagger 接文档的元数据 + path: /v3/api-docs + swagger-ui: + enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面 + path: /swagger-ui.html + default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档 + +knife4j: + enable: true # 2.2 是否开启 Swagger 文档的 Knife4j UI 界面 + setting: + language: zh_cn + +# MyBatis Plus 的配置项 +mybatis-plus: + configuration: + map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。 + global-config: + db-config: + id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。 + # id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库 + # id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 + # id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解 + logic-delete-value: 1 # 逻辑已删除值(默认为 1) + logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) + banner: false # 关闭控制台的 Banner 打印 + type-aliases-package: ${yudao.info.base-package}.dal.dataobject + encryptor: + password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成 + +mybatis-plus-join: + banner: false # 关闭控制台的 Banner 打印 + +# Spring Data Redis 配置 +spring: + data: + redis: + repositories: + enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度 + +--- #################### MQ 消息队列相关配置 #################### + +--- #################### 定时任务相关配置 #################### + +xxl: + job: + executor: + appname: ${spring.application.name} # 执行器 AppName + logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径 + accessToken: default_token # 执行器通讯TOKEN + +--- #################### 芋道相关配置 #################### + +yudao: + info: + version: 1.0.0 + base-package: cn.iocoder.yudao.module.crm + web: + admin-ui: + url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址 + swagger: + title: 管理后台 + description: 提供管理员管理的所有功能 + version: ${yudao.info.version} + base-package: ${yudao.info.base-package} + error-code: # 错误码相关配置项 + constants-class-list: + - cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants + tenant: # 多租户相关配置项 + enable: true + ignore-urls: + +debug: false diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/bootstrap-local.yaml b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/bootstrap-local.yaml new file mode 100644 index 000000000..2de0efbf7 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/bootstrap-local.yaml @@ -0,0 +1,23 @@ +--- #################### 注册中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: 127.0.0.1:8848 + discovery: + namespace: dev # 命名空间。这里使用 dev 开发环境 + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + +--- #################### 配置中心相关配置 #################### + +spring: + cloud: + nacos: + # Nacos Config 配置项,对应 NacosConfigProperties 配置属性类 + config: + server-addr: 127.0.0.1:8848 # Nacos 服务器地址 + namespace: dev # 命名空间 dev 的ID,不能直接使用 dev 名称。创建命名空间的时候需要指定ID为 dev,这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + name: ${spring.application.name} # 使用的 Nacos 配置集的 dataId,默认为 spring.application.name + file-extension: yaml # 使用的 Nacos 配置集的 dataId 的文件拓展名,同时也是 Nacos 配置集的配置格式,默认为 properties diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/bootstrap.yaml b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/bootstrap.yaml new file mode 100644 index 000000000..f87220ec2 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/bootstrap.yaml @@ -0,0 +1,14 @@ +spring: + application: + name: crm-server + + profiles: + active: local + +server: + port: 48089 + +# 日志文件配置。注意,如果 logging.file.name 不放在 bootstrap.yaml 配置文件,而是放在 application.yaml 中,会导致出现 LOG_FILE_IS_UNDEFINED 文件 +logging: + file: + name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径 diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/logback-spring.xml b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..b1b9f3faf --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/logback-spring.xml @@ -0,0 +1,76 @@ + + + + + + + + + +       + + + ${PATTERN_DEFAULT} + + + + + + + + + + ${PATTERN_DEFAULT} + + + + ${LOG_FILE} + + + ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} + + ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} + + ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} + + ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} + + ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} + + + + + + 0 + + 256 + + + + + + + + ${PATTERN_DEFAULT} + + + + + + + + + + + + + + + + + + + + + + diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml new file mode 100644 index 000000000..10030e0a7 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/BusinessStatusTypeServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/BusinessStatusTypeServiceImplTest.java new file mode 100644 index 000000000..3c039f644 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/BusinessStatusTypeServiceImplTest.java @@ -0,0 +1,119 @@ +package cn.iocoder.yudao.module.crm.service.business; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; +import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessStatusTypeMapper; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import jakarta.annotation.Resource; + +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_TYPE_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; + +// TODO 芋艿:后续再 review +/** + * {@link CrmBusinessStatusTypeServiceImpl} 的单元测试类 + * + * @author ljlleo + */ +@Disabled // TODO 芋艿:后续 fix 补充的单测 +@Import(CrmBusinessStatusTypeServiceImpl.class) +public class BusinessStatusTypeServiceImplTest extends BaseDbUnitTest { + + @Resource + private CrmBusinessStatusTypeServiceImpl businessStatusTypeService; + + @Resource + private CrmBusinessStatusTypeMapper businessStatusTypeMapper; + + @Test + public void testCreateBusinessStatusType_success() { + // 准备参数 + CrmBusinessStatusTypeSaveReqVO createReqVO = randomPojo(CrmBusinessStatusTypeSaveReqVO.class).setId(null); + + // 调用 + Long businessStatusTypeId = businessStatusTypeService.createBusinessStatusType(createReqVO); + // 断言 + assertNotNull(businessStatusTypeId); + // 校验记录的属性是否正确 + CrmBusinessStatusTypeDO businessStatusType = businessStatusTypeMapper.selectById(businessStatusTypeId); + assertPojoEquals(createReqVO, businessStatusType, "id"); + } + + @Test + public void testUpdateBusinessStatusType_success() { + // mock 数据 + CrmBusinessStatusTypeDO dbBusinessStatusType = randomPojo(CrmBusinessStatusTypeDO.class); + businessStatusTypeMapper.insert(dbBusinessStatusType);// @Sql: 先插入出一条存在的数据 + // 准备参数 + CrmBusinessStatusTypeSaveReqVO updateReqVO = randomPojo(CrmBusinessStatusTypeSaveReqVO.class, o -> { + o.setId(dbBusinessStatusType.getId()); // 设置更新的 ID + }); + + // 调用 + businessStatusTypeService.updateBusinessStatusType(updateReqVO); + // 校验是否更新正确 + CrmBusinessStatusTypeDO businessStatusType = businessStatusTypeMapper.selectById(updateReqVO.getId()); // 获取最新的 + assertPojoEquals(updateReqVO, businessStatusType); + } + + @Test + public void testUpdateBusinessStatusType_notExists() { + // 准备参数 + CrmBusinessStatusTypeSaveReqVO updateReqVO = randomPojo(CrmBusinessStatusTypeSaveReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> businessStatusTypeService.updateBusinessStatusType(updateReqVO), BUSINESS_STATUS_TYPE_NOT_EXISTS); + } + + @Test + public void testDeleteBusinessStatusType_success() { + // mock 数据 + CrmBusinessStatusTypeDO dbBusinessStatusType = randomPojo(CrmBusinessStatusTypeDO.class); + businessStatusTypeMapper.insert(dbBusinessStatusType);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbBusinessStatusType.getId(); + + // 调用 + businessStatusTypeService.deleteBusinessStatusType(id); + // 校验数据不存在了 + assertNull(businessStatusTypeMapper.selectById(id)); + } + + @Test + public void testDeleteBusinessStatusType_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> businessStatusTypeService.deleteBusinessStatusType(id), BUSINESS_STATUS_TYPE_NOT_EXISTS); + } + + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGetBusinessStatusTypePage() { + // mock 数据 + CrmBusinessStatusTypeDO dbBusinessStatusType = randomPojo(CrmBusinessStatusTypeDO.class, o -> { // 等会查询到 + }); + businessStatusTypeMapper.insert(dbBusinessStatusType); + // 准备参数 + CrmBusinessStatusTypePageReqVO reqVO = new CrmBusinessStatusTypePageReqVO(); + + // 调用 + PageResult pageResult = businessStatusTypeService.getBusinessStatusTypePage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbBusinessStatusType, pageResult.getList().get(0)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImplTest.java new file mode 100644 index 000000000..0fd3c447f --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImplTest.java @@ -0,0 +1,181 @@ +package cn.iocoder.yudao.module.crm.service.business; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * {@link CrmBusinessServiceImpl} 的单元测试类 + * + * @author ljlleo + */ +@Disabled // TODO 芋艿:后续 fix 补充的单测 +@Import(CrmBusinessServiceImpl.class) +public class CrmBusinessServiceImplTest extends BaseDbUnitTest { + + @Resource + private CrmBusinessServiceImpl businessService; + + @Resource + private CrmBusinessMapper businessMapper; + + @Test + public void testCreateBusiness_success() { + // 准备参数 + CrmBusinessSaveReqVO reqVO = randomPojo(CrmBusinessSaveReqVO.class); + + // 调用 + Long businessId = businessService.createBusiness(reqVO, getLoginUserId()); + // 断言 + assertNotNull(businessId); + // 校验记录的属性是否正确 + CrmBusinessDO business = businessMapper.selectById(businessId); + assertPojoEquals(reqVO, business); + } + + @Test + public void testUpdateBusiness_success() { + // mock 数据 + CrmBusinessDO dbBusiness = randomPojo(CrmBusinessDO.class); + businessMapper.insert(dbBusiness);// @Sql: 先插入出一条存在的数据 + // 准备参数 + CrmBusinessSaveReqVO reqVO = randomPojo(CrmBusinessSaveReqVO.class, o -> { + o.setId(dbBusiness.getId()); // 设置更新的 ID + }); + + // 调用 + businessService.updateBusiness(reqVO); + // 校验是否更新正确 + CrmBusinessDO business = businessMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, business); + } + + @Test + public void testUpdateBusiness_notExists() { + // 准备参数 + CrmBusinessSaveReqVO reqVO = randomPojo(CrmBusinessSaveReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> businessService.updateBusiness(reqVO), BUSINESS_NOT_EXISTS); + } + + @Test + public void testDeleteBusiness_success() { + // mock 数据 + CrmBusinessDO dbBusiness = randomPojo(CrmBusinessDO.class); + businessMapper.insert(dbBusiness);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbBusiness.getId(); + + // 调用 + businessService.deleteBusiness(id); + // 校验数据不存在了 + assertNull(businessMapper.selectById(id)); + } + + @Test + public void testDeleteBusiness_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> businessService.deleteBusiness(id), BUSINESS_NOT_EXISTS); + } + + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGetBusinessPage() { + // mock 数据 + CrmBusinessDO dbBusiness = randomPojo(CrmBusinessDO.class, o -> { // 等会查询到 + o.setName(null); + o.setStatusTypeId(null); + o.setStatusId(null); + o.setContactNextTime(null); + o.setCustomerId(null); + o.setDealTime(null); + o.setPrice(null); + o.setDiscountPercent(null); + o.setProductPrice(null); + o.setRemark(null); + o.setCreateTime(null); + o.setEndStatus(null); + o.setEndRemark(null); + o.setContactLastTime(null); + o.setFollowUpStatus(null); + }); + businessMapper.insert(dbBusiness); + // 测试 name 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setName(null))); + // 测试 statusTypeId 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setStatusTypeId(null))); + // 测试 statusId 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setStatusId(null))); + // 测试 contactNextTime 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setContactNextTime(null))); + // 测试 customerId 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setCustomerId(null))); + // 测试 dealTime 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setDealTime(null))); + // 测试 price 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setPrice(null))); + // 测试 discountPercent 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setDiscountPercent(null))); + // 测试 productPrice 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setProductPrice(null))); + // 测试 remark 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setRemark(null))); + // 测试 createTime 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setCreateTime(null))); + // 测试 endStatus 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setEndStatus(null))); + // 测试 endRemark 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setEndRemark(null))); + // 测试 contactLastTime 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setContactLastTime(null))); + // 测试 followUpStatus 不匹配 + businessMapper.insert(cloneIgnoreId(dbBusiness, o -> o.setFollowUpStatus(null))); + //// 准备参数 + //CrmBusinessPageReqVO reqVO = new CrmBusinessPageReqVO(); + //reqVO.setName(null); + //reqVO.setStatusTypeId(null); + //reqVO.setStatusId(null); + //reqVO.setContactNextTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); + //reqVO.setCustomerId(null); + //reqVO.setDealTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); + //reqVO.setPrice(null); + //reqVO.setDiscountPercent(null); + //reqVO.setProductPrice(null); + //reqVO.setRemark(null); + //reqVO.setOwnerUserId(null); + //reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); + //reqVO.setRoUserIds(null); + //reqVO.setRwUserIds(null); + //reqVO.setEndStatus(null); + //reqVO.setEndRemark(null); + //reqVO.setContactLastTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); + //reqVO.setFollowUpStatus(null); + // + //// 调用 + //PageResult pageResult = businessService.getBusinessPage(reqVO); + //// 断言 + //assertEquals(1, pageResult.getTotal()); + //assertEquals(1, pageResult.getList().size()); + //assertPojoEquals(dbBusiness, pageResult.getList().get(0)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImplTest.java new file mode 100644 index 000000000..d73921f3d --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImplTest.java @@ -0,0 +1,207 @@ +package cn.iocoder.yudao.module.crm.service.clue; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; +import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CLUE_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; + +// TODO 芋艿:单测后续补; + +/** + * {@link CrmClueServiceImpl} 的单元测试类 + * + * @author Wanwan + */ +@Disabled // TODO 芋艿:后续 fix 补充的单测 +@Import(CrmClueServiceImpl.class) +public class CrmClueServiceImplTest extends BaseDbUnitTest { + + @Resource + private CrmClueServiceImpl clueService; + + @Resource + private CrmClueMapper clueMapper; + + @Test + public void testCreateClue_success() { + // 准备参数 + CrmClueSaveReqVO reqVO = randomPojo(CrmClueSaveReqVO.class); + + // 调用 + Long clueId = clueService.createClue(reqVO, getLoginUserId()); + // 断言 + assertNotNull(clueId); + // 校验记录的属性是否正确 + CrmClueDO clue = clueMapper.selectById(clueId); + assertPojoEquals(reqVO, clue); + } + + @Test + public void testUpdateClue_success() { + // mock 数据 + CrmClueDO dbClue = randomPojo(CrmClueDO.class); + clueMapper.insert(dbClue);// @Sql: 先插入出一条存在的数据 + // 准备参数 + CrmClueSaveReqVO reqVO = randomPojo(CrmClueSaveReqVO.class, o -> { + o.setId(dbClue.getId()); // 设置更新的 ID + }); + + // 调用 + clueService.updateClue(reqVO); + // 校验是否更新正确 + CrmClueDO clue = clueMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, clue); + } + + @Test + public void testUpdateClue_notExists() { + // 准备参数 + CrmClueSaveReqVO reqVO = randomPojo(CrmClueSaveReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> clueService.updateClue(reqVO), CLUE_NOT_EXISTS); + } + + @Test + public void testDeleteClue_success() { + // mock 数据 + CrmClueDO dbClue = randomPojo(CrmClueDO.class); + clueMapper.insert(dbClue);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbClue.getId(); + + // 调用 + clueService.deleteClue(id); + // 校验数据不存在了 + assertNull(clueMapper.selectById(id)); + } + + @Test + public void testDeleteClue_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> clueService.deleteClue(id), CLUE_NOT_EXISTS); + } + + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGetCluePage() { + // mock 数据 + CrmClueDO dbClue = randomPojo(CrmClueDO.class, o -> { // 等会查询到 + o.setTransformStatus(null); + o.setFollowUpStatus(null); + o.setName(null); + o.setCustomerId(null); + o.setContactNextTime(null); + o.setTelephone(null); + o.setMobile(null); + o.setAddress(null); + o.setContactLastTime(null); + o.setCreateTime(null); + }); + clueMapper.insert(dbClue); + // 测试 transformStatus 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTransformStatus(null))); + // 测试 followUpStatus 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setFollowUpStatus(null))); + // 测试 name 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setName(null))); + // 测试 customerId 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCustomerId(null))); + // 测试 contactNextTime 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactNextTime(null))); + // 测试 telephone 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTelephone(null))); + // 测试 mobile 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setMobile(null))); + // 测试 address 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setAddress(null))); + // 测试 contactLastTime 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactLastTime(null))); + // 测试 createTime 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCreateTime(null))); + // 准备参数 + CrmCluePageReqVO reqVO = new CrmCluePageReqVO(); + reqVO.setName(null); + reqVO.setTelephone(null); + reqVO.setMobile(null); + + // 调用 + PageResult pageResult = clueService.getCluePage(reqVO, 1L); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbClue, pageResult.getList().get(0)); + } + + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGetClueList() { + // mock 数据 + CrmClueDO dbClue = randomPojo(CrmClueDO.class, o -> { // 等会查询到 + o.setTransformStatus(null); + o.setFollowUpStatus(null); + o.setName(null); + o.setCustomerId(null); + o.setContactNextTime(null); + o.setTelephone(null); + o.setMobile(null); + o.setAddress(null); + o.setContactLastTime(null); + o.setCreateTime(null); + }); + clueMapper.insert(dbClue); + // 测试 transformStatus 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTransformStatus(null))); + // 测试 followUpStatus 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setFollowUpStatus(null))); + // 测试 name 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setName(null))); + // 测试 customerId 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCustomerId(null))); + // 测试 contactNextTime 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactNextTime(null))); + // 测试 telephone 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setTelephone(null))); + // 测试 mobile 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setMobile(null))); + // 测试 address 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setAddress(null))); + // 测试 contactLastTime 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setContactLastTime(null))); + // 测试 createTime 不匹配 + clueMapper.insert(cloneIgnoreId(dbClue, o -> o.setCreateTime(null))); + // 准备参数 + CrmCluePageReqVO reqVO = new CrmCluePageReqVO(); + reqVO.setName(null); + reqVO.setTelephone(null); + reqVO.setMobile(null); + reqVO.setPageSize(PAGE_SIZE_NONE); + // 调用 + List list = clueService.getCluePage(reqVO, 1L).getList(); + // 断言 + assertEquals(1, list.size()); + assertPojoEquals(dbClue, list.get(0)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/contract/ContractServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/contract/ContractServiceImplTest.java new file mode 100644 index 000000000..a511e7c1b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/contract/ContractServiceImplTest.java @@ -0,0 +1,135 @@ +package cn.iocoder.yudao.module.crm.service.contract; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTRACT_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link CrmContractServiceImpl} 的单元测试类 + * + * @author dhb52 + */ +@Import(CrmContractServiceImpl.class) +public class ContractServiceImplTest extends BaseDbUnitTest { + + @Resource + private CrmContractServiceImpl contractService; + + @Resource + private CrmContractMapper contractMapper; + + @Test + public void testCreateContract_success() { + // 准备参数 + CrmContractSaveReqVO reqVO = randomPojo(CrmContractSaveReqVO.class); + + // 调用 + Long contractId = contractService.createContract(reqVO, getLoginUserId()); + // 断言 + assertNotNull(contractId); + // 校验记录的属性是否正确 + CrmContractDO contract = contractMapper.selectById(contractId); + assertPojoEquals(reqVO, contract); + } + + @Test + public void testUpdateContract_success() { + // mock 数据 + CrmContractDO dbContract = randomPojo(CrmContractDO.class); + contractMapper.insert(dbContract);// @Sql: 先插入出一条存在的数据 + // 准备参数 + CrmContractSaveReqVO reqVO = randomPojo(CrmContractSaveReqVO.class, o -> { + o.setId(dbContract.getId()); // 设置更新的 ID + }); + + // 调用 + contractService.updateContract(reqVO); + // 校验是否更新正确 + CrmContractDO contract = contractMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, contract); + } + + @Test + public void testUpdateContract_notExists() { + // 准备参数 + CrmContractSaveReqVO reqVO = randomPojo(CrmContractSaveReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> contractService.updateContract(reqVO), CONTRACT_NOT_EXISTS); + } + + @Test + public void testDeleteContract_success() { + // mock 数据 + CrmContractDO dbContract = randomPojo(CrmContractDO.class); + contractMapper.insert(dbContract);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbContract.getId(); + + // 调用 + contractService.deleteContract(id); + // 校验数据不存在了 + assertNull(contractMapper.selectById(id)); + } + + @Test + public void testDeleteContract_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> contractService.deleteContract(id), CONTRACT_NOT_EXISTS); + } + + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGetContractPage() { + // mock 数据 + CrmContractDO dbContract = randomPojo(CrmContractDO.class, o -> { // 等会查询到 + o.setName(null); + o.setCustomerId(null); + o.setBusinessId(null); + o.setOrderDate(null); + o.setNo(null); + o.setDiscountPercent(null); + o.setProductPrice(null); + }); + contractMapper.insert(dbContract); + // 测试 name 不匹配 + contractMapper.insert(cloneIgnoreId(dbContract, o -> o.setName(null))); + // 测试 customerId 不匹配 + contractMapper.insert(cloneIgnoreId(dbContract, o -> o.setCustomerId(null))); + // 测试 no 不匹配 + contractMapper.insert(cloneIgnoreId(dbContract, o -> o.setNo(null))); + // 准备参数 + CrmContractPageReqVO reqVO = new CrmContractPageReqVO(); + reqVO.setName(null); + reqVO.setCustomerId(null); + reqVO.setBusinessId(null); + reqVO.setNo(null); + + // 调用 + PageResult pageResult = contractService.getContractPage(reqVO, getLoginUserId()); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbContract, pageResult.getList().get(0)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImplTest.java new file mode 100644 index 000000000..8f701d987 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImplTest.java @@ -0,0 +1,141 @@ +package cn.iocoder.yudao.module.crm.service.customer; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; + +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; + +// TODO 芋艿:单测后续补 + +/** + * {@link CrmCustomerServiceImpl} 的单元测试类 + * + * @author Wanwan + */ +@Disabled // TODO 芋艿:后续 fix 补充的单测 +@Import(CrmCustomerServiceImpl.class) +public class CrmCustomerServiceImplTest extends BaseDbUnitTest { + + @Resource + private CrmCustomerServiceImpl customerService; + + @Resource + private CrmCustomerMapper customerMapper; + @MockBean + private CrmPermissionService permissionService; + + @Test + public void testCreateCustomer_success() { + // 准备参数 + CrmCustomerSaveReqVO reqVO = randomPojo(CrmCustomerSaveReqVO.class); + + // 调用 + Long customerId = customerService.createCustomer(reqVO, getLoginUserId()); + // 断言 + assertNotNull(customerId); + // 校验记录的属性是否正确 + CrmCustomerDO customer = customerMapper.selectById(customerId); + assertPojoEquals(reqVO, customer); + } + + @Test + public void testUpdateCustomer_success() { + // mock 数据 + CrmCustomerDO dbCustomer = randomPojo(CrmCustomerDO.class); + customerMapper.insert(dbCustomer);// @Sql: 先插入出一条存在的数据 + // 准备参数 + CrmCustomerSaveReqVO reqVO = randomPojo(CrmCustomerSaveReqVO.class, o -> { + o.setId(dbCustomer.getId()); // 设置更新的 ID + }); + + // 调用 + customerService.updateCustomer(reqVO); + // 校验是否更新正确 + CrmCustomerDO customer = customerMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, customer); + } + + @Test + public void testUpdateCustomer_notExists() { + // 准备参数 + CrmCustomerSaveReqVO reqVO = randomPojo(CrmCustomerSaveReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> customerService.updateCustomer(reqVO), CUSTOMER_NOT_EXISTS); + } + + @Test + public void testDeleteCustomer_success() { + // mock 数据 + CrmCustomerDO dbCustomer = randomPojo(CrmCustomerDO.class); + customerMapper.insert(dbCustomer);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbCustomer.getId(); + + // 调用 + customerService.deleteCustomer(id); + // 校验数据不存在了 + assertNull(customerMapper.selectById(id)); + } + + @Test + public void testDeleteCustomer_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> customerService.deleteCustomer(id), CUSTOMER_NOT_EXISTS); + } + + @Test + public void testGetCustomerPage() { + // mock 数据 + CrmCustomerDO dbCustomer = randomPojo(CrmCustomerDO.class, o -> { // 等会查询到 + o.setName("张三"); + o.setMobile("13888888888"); + o.setTelephone("13888888888"); + o.setWebsite("https://yudao.com"); + }); + //customerMapper.insert(dbCustomer); + // 测试 name 不匹配 + //customerMapper.insert(cloneIgnoreId(dbCustomer, o -> o.setName(""))); + // 测试 mobile 不匹配 + //customerMapper.insert(cloneIgnoreId(dbCustomer, o -> o.setMobile(null))); + // 测试 telephone 不匹配 + //customerMapper.insert(cloneIgnoreId(dbCustomer, o -> o.setTelephone(null))); + // 测试 website 不匹配 + //customerMapper.insert(cloneIgnoreId(dbCustomer, o -> o.setWebsite(null))); + // 准备参数 + CrmCustomerPageReqVO reqVO = new CrmCustomerPageReqVO(); + reqVO.setName("张三"); + reqVO.setMobile("13888888888"); + //reqVO.setTelephone(null); + //reqVO.setWebsite(null); + + // 调用 + PageResult pageResult = customerService.getCustomerPage(reqVO, 1L); + // 断言 + //assertEquals(1, pageResult.getTotal()); + //assertEquals(1, pageResult.getList().size()); + //assertPojoEquals(dbCustomer, pageResult.getList().get(0)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customerlimitconfig/CrmCustomerLimitConfigServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customerlimitconfig/CrmCustomerLimitConfigServiceImplTest.java new file mode 100644 index 000000000..ecda14822 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/customerlimitconfig/CrmCustomerLimitConfigServiceImplTest.java @@ -0,0 +1,119 @@ +package cn.iocoder.yudao.module.crm.service.customerlimitconfig; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; +import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerLimitConfigMapper; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerLimitConfigServiceImpl; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_LIMIT_CONFIG_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; + +// TODO 芋艿:单测后面搞 +/** + * {@link CrmCustomerLimitConfigServiceImpl} 的单元测试类 + * + * @author Wanwan + */ +@Disabled // TODO 芋艿:后续 fix 补充的单测 +@Import(CrmCustomerLimitConfigServiceImpl.class) +public class CrmCustomerLimitConfigServiceImplTest extends BaseDbUnitTest { + + @Resource + private CrmCustomerLimitConfigServiceImpl customerLimitConfigService; + + @Resource + private CrmCustomerLimitConfigMapper customerLimitConfigMapper; + + @Test + public void testCreateCustomerLimitConfig_success() { + // 准备参数 + CrmCustomerLimitConfigSaveReqVO reqVO = randomPojo(CrmCustomerLimitConfigSaveReqVO.class); + + // 调用 + Long customerLimitConfigId = customerLimitConfigService.createCustomerLimitConfig(reqVO); + // 断言 + assertNotNull(customerLimitConfigId); + // 校验记录的属性是否正确 + CrmCustomerLimitConfigDO customerLimitConfig = customerLimitConfigMapper.selectById(customerLimitConfigId); + assertPojoEquals(reqVO, customerLimitConfig); + } + + @Test + public void testUpdateCustomerLimitConfig_success() { + // mock 数据 + CrmCustomerLimitConfigDO dbCustomerLimitConfig = randomPojo(CrmCustomerLimitConfigDO.class); + customerLimitConfigMapper.insert(dbCustomerLimitConfig);// @Sql: 先插入出一条存在的数据 + // 准备参数 + CrmCustomerLimitConfigSaveReqVO reqVO = randomPojo(CrmCustomerLimitConfigSaveReqVO.class, o -> { + o.setId(dbCustomerLimitConfig.getId()); // 设置更新的 ID + }); + + // 调用 + customerLimitConfigService.updateCustomerLimitConfig(reqVO); + // 校验是否更新正确 + CrmCustomerLimitConfigDO customerLimitConfig = customerLimitConfigMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, customerLimitConfig); + } + + @Test + public void testUpdateCustomerLimitConfig_notExists() { + // 准备参数 + CrmCustomerLimitConfigSaveReqVO reqVO = randomPojo(CrmCustomerLimitConfigSaveReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> customerLimitConfigService.updateCustomerLimitConfig(reqVO), CUSTOMER_LIMIT_CONFIG_NOT_EXISTS); + } + + @Test + public void testDeleteCustomerLimitConfig_success() { + // mock 数据 + CrmCustomerLimitConfigDO dbCustomerLimitConfig = randomPojo(CrmCustomerLimitConfigDO.class); + customerLimitConfigMapper.insert(dbCustomerLimitConfig);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbCustomerLimitConfig.getId(); + + // 调用 + customerLimitConfigService.deleteCustomerLimitConfig(id); + // 校验数据不存在了 + assertNull(customerLimitConfigMapper.selectById(id)); + } + + @Test + public void testDeleteCustomerLimitConfig_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> customerLimitConfigService.deleteCustomerLimitConfig(id), CUSTOMER_LIMIT_CONFIG_NOT_EXISTS); + } + + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGetCustomerLimitConfigPage() { + // mock 数据 + CrmCustomerLimitConfigDO dbCustomerLimitConfig = randomPojo(CrmCustomerLimitConfigDO.class, o -> { // 等会查询到 + }); + customerLimitConfigMapper.insert(dbCustomerLimitConfig); + // 准备参数 + CrmCustomerLimitConfigPageReqVO reqVO = new CrmCustomerLimitConfigPageReqVO(); + + // 调用 + PageResult pageResult = customerLimitConfigService.getCustomerLimitConfigPage(reqVO); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbCustomerLimitConfig, pageResult.getList().get(0)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivablePlanServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivablePlanServiceImplTest.java new file mode 100644 index 000000000..4f17abcba --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivablePlanServiceImplTest.java @@ -0,0 +1,137 @@ +package cn.iocoder.yudao.module.crm.service.receivable; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; +import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivablePlanMapper; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.RECEIVABLE_PLAN_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; + +// TODO 芋艿:后续,需要补充测试用例 + +/** + * {@link CrmReceivablePlanServiceImpl} 的单元测试类 + * + * @author 芋道源码 + */ +@Disabled // TODO 芋艿:后续 fix 补充的单测 +@Import(CrmReceivablePlanServiceImpl.class) +public class CrmCrmReceivablePlanServiceImplTest extends BaseDbUnitTest { + + @Resource + private CrmReceivablePlanServiceImpl receivablePlanService; + + @Resource + private CrmReceivablePlanMapper crmReceivablePlanMapper; + + @Test + public void testCreateReceivablePlan_success() { + // 准备参数 + CrmReceivablePlanCreateReqVO reqVO = randomPojo(CrmReceivablePlanCreateReqVO.class); + + // 调用 + Long receivablePlanId = receivablePlanService.createReceivablePlan(reqVO, 1L); + // 断言 + assertNotNull(receivablePlanId); + // 校验记录的属性是否正确 + CrmReceivablePlanDO receivablePlan = crmReceivablePlanMapper.selectById(receivablePlanId); + assertPojoEquals(reqVO, receivablePlan); + } + + @Test + public void testUpdateReceivablePlan_success() { + // mock 数据 + CrmReceivablePlanDO dbReceivablePlan = randomPojo(CrmReceivablePlanDO.class); + crmReceivablePlanMapper.insert(dbReceivablePlan);// @Sql: 先插入出一条存在的数据 + // 准备参数 + CrmReceivablePlanUpdateReqVO reqVO = randomPojo(CrmReceivablePlanUpdateReqVO.class, o -> { + o.setId(dbReceivablePlan.getId()); // 设置更新的 ID + }); + + // 调用 + receivablePlanService.updateReceivablePlan(reqVO); + // 校验是否更新正确 + CrmReceivablePlanDO receivablePlan = crmReceivablePlanMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, receivablePlan); + } + + @Test + public void testUpdateReceivablePlan_notExists() { + // 准备参数 + CrmReceivablePlanUpdateReqVO reqVO = randomPojo(CrmReceivablePlanUpdateReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> receivablePlanService.updateReceivablePlan(reqVO), RECEIVABLE_PLAN_NOT_EXISTS); + } + + @Test + public void testDeleteReceivablePlan_success() { + // mock 数据 + CrmReceivablePlanDO dbReceivablePlan = randomPojo(CrmReceivablePlanDO.class); + crmReceivablePlanMapper.insert(dbReceivablePlan);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbReceivablePlan.getId(); + + // 调用 + receivablePlanService.deleteReceivablePlan(id); + // 校验数据不存在了 + assertNull(crmReceivablePlanMapper.selectById(id)); + } + + @Test + public void testDeleteReceivablePlan_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> receivablePlanService.deleteReceivablePlan(id), RECEIVABLE_PLAN_NOT_EXISTS); + } + + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGetReceivablePlanPage() { + // mock 数据 + CrmReceivablePlanDO dbReceivablePlan = randomPojo(CrmReceivablePlanDO.class, o -> { // 等会查询到 + o.setPeriod(null); + o.setReturnTime(null); + o.setRemindDays(null); + o.setRemindTime(null); + o.setCustomerId(null); + o.setContractId(null); + o.setOwnerUserId(null); + o.setRemark(null); + o.setCreateTime(null); + }); + crmReceivablePlanMapper.insert(dbReceivablePlan); + // 测试 customerId 不匹配 + crmReceivablePlanMapper.insert(cloneIgnoreId(dbReceivablePlan, o -> o.setCustomerId(null))); + // 测试 contractId 不匹配 + crmReceivablePlanMapper.insert(cloneIgnoreId(dbReceivablePlan, o -> o.setContractId(null))); + // 准备参数 + CrmReceivablePlanPageReqVO reqVO = new CrmReceivablePlanPageReqVO(); + reqVO.setCustomerId(null); + reqVO.setContractId(null); + reqVO.setPageSize(PAGE_SIZE_NONE); + // 调用 + PageResult pageResult = receivablePlanService.getReceivablePlanPage(reqVO, 1L); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbReceivablePlan, pageResult.getList().get(0)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivableServiceImplTest.java b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivableServiceImplTest.java new file mode 100644 index 000000000..04c466924 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/java/cn/iocoder/yudao/module/crm/service/receivable/CrmCrmReceivableServiceImplTest.java @@ -0,0 +1,143 @@ +package cn.iocoder.yudao.module.crm.service.receivable; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; +import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals; +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.RECEIVABLE_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; + +// TODO 芋艿:等实现完,在校验下; +/** + * {@link CrmReceivableServiceImpl} 的单元测试类 + * + * @author 赤焰 + */ +@Disabled // TODO 芋艿:后续 fix 补充的单测 +@Import(CrmReceivableServiceImpl.class) +public class CrmCrmReceivableServiceImplTest extends BaseDbUnitTest { + + @Resource + private CrmReceivableServiceImpl receivableService; + + @Resource + private CrmReceivableMapper crmReceivableMapper; + + @Test + public void testCreateReceivable_success() { + // 准备参数 + CrmReceivableCreateReqVO reqVO = randomPojo(CrmReceivableCreateReqVO.class); + + // 调用 + Long receivableId = receivableService.createReceivable(reqVO, getLoginUserId()); + // 断言 + assertNotNull(receivableId); + // 校验记录的属性是否正确 + CrmReceivableDO receivable = crmReceivableMapper.selectById(receivableId); + assertPojoEquals(reqVO, receivable); + } + + @Test + public void testUpdateReceivable_success() { + // mock 数据 + CrmReceivableDO dbReceivable = randomPojo(CrmReceivableDO.class); + crmReceivableMapper.insert(dbReceivable);// @Sql: 先插入出一条存在的数据 + // 准备参数 + CrmReceivableUpdateReqVO reqVO = randomPojo(CrmReceivableUpdateReqVO.class, o -> { + o.setId(dbReceivable.getId()); // 设置更新的 ID + }); + + // 调用 + receivableService.updateReceivable(reqVO); + // 校验是否更新正确 + CrmReceivableDO receivable = crmReceivableMapper.selectById(reqVO.getId()); // 获取最新的 + assertPojoEquals(reqVO, receivable); + } + + @Test + public void testUpdateReceivable_notExists() { + // 准备参数 + CrmReceivableUpdateReqVO reqVO = randomPojo(CrmReceivableUpdateReqVO.class); + + // 调用, 并断言异常 + assertServiceException(() -> receivableService.updateReceivable(reqVO), RECEIVABLE_NOT_EXISTS); + } + + @Test + public void testDeleteReceivable_success() { + // mock 数据 + CrmReceivableDO dbReceivable = randomPojo(CrmReceivableDO.class); + crmReceivableMapper.insert(dbReceivable);// @Sql: 先插入出一条存在的数据 + // 准备参数 + Long id = dbReceivable.getId(); + + // 调用 + receivableService.deleteReceivable(id); + // 校验数据不存在了 + assertNull(crmReceivableMapper.selectById(id)); + } + + @Test + public void testDeleteReceivable_notExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用, 并断言异常 + assertServiceException(() -> receivableService.deleteReceivable(id), RECEIVABLE_NOT_EXISTS); + } + + @Test + @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 + public void testGetReceivablePage() { + // mock 数据 + CrmReceivableDO dbReceivable = randomPojo(CrmReceivableDO.class, o -> { // 等会查询到 + o.setNo(null); + o.setPlanId(null); + o.setCustomerId(null); + o.setContractId(null); + o.setProcessInstanceId(null); + o.setReturnTime(null); + o.setReturnType(null); + o.setPrice(null); + o.setOwnerUserId(null); + o.setSort(null); + o.setAuditStatus(null); + o.setRemark(null); + o.setCreateTime(null); + }); + crmReceivableMapper.insert(dbReceivable); + // 测试 no 不匹配 + crmReceivableMapper.insert(cloneIgnoreId(dbReceivable, o -> o.setNo(null))); + // 测试 planId 不匹配 + crmReceivableMapper.insert(cloneIgnoreId(dbReceivable, o -> o.setPlanId(null))); + // 测试 customerId 不匹配 + crmReceivableMapper.insert(cloneIgnoreId(dbReceivable, o -> o.setCustomerId(null))); + // 准备参数 + CrmReceivablePageReqVO reqVO = new CrmReceivablePageReqVO(); + reqVO.setNo(null); + reqVO.setPlanId(null); + reqVO.setCustomerId(null); + + // 调用 + PageResult pageResult = receivableService.getReceivablePage(reqVO, 1L); + // 断言 + assertEquals(1, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + assertPojoEquals(dbReceivable, pageResult.getList().get(0)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/resources/application-unit-test.yaml b/yudao-module-crm/yudao-module-crm-biz/src/test/resources/application-unit-test.yaml new file mode 100644 index 000000000..0f28838da --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/resources/application-unit-test.yaml @@ -0,0 +1,51 @@ +spring: + main: + lazy-initialization: true # 开启懒加载,加快速度 + banner-mode: off # 单元测试,禁用 Banner + +--- #################### 数据库相关配置 #################### + +spring: + # 数据源配置项 + datasource: + name: ruoyi-vue-pro + url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 + driver-class-name: org.h2.Driver + username: sa + password: + druid: + async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 + initial-size: 1 # 单元测试,配置为 1,提升启动速度 + sql: + init: + schema-locations: classpath:/sql/create_tables.sql + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 127.0.0.1 # 地址 + port: 16379 # 端口(单元测试,使用 16379 端口) + database: 0 # 数据库索引 + +mybatis-plus: + lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 + type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject + +--- #################### 定时任务相关配置 #################### + +--- #################### 配置中心相关配置 #################### + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项(单元测试,禁用 Lock4j) + +# Resilience4j 配置项 + +--- #################### 监控相关配置 #################### + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + info: + base-package: cn.iocoder.yudao diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/resources/logback.xml b/yudao-module-crm/yudao-module-crm-biz/src/test/resources/logback.xml new file mode 100644 index 000000000..1d071e479 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/resources/logback.xml @@ -0,0 +1,4 @@ + + + + diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/resources/sql/clean.sql b/yudao-module-crm/yudao-module-crm-biz/src/test/resources/sql/clean.sql new file mode 100644 index 000000000..138780eed --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/resources/sql/clean.sql @@ -0,0 +1,11 @@ +DELETE FROM "crm_contract"; + +DELETE FROM "crm_clue"; + +DELETE FROM "crm_receivable"; + +DELETE FROM "crm_receivable_plan"; + +DELETE FROM "crm_customer"; + +DELETE FROM "crm_customer_limit_config"; \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/test/resources/sql/create_tables.sql b/yudao-module-crm/yudao-module-crm-biz/src/test/resources/sql/create_tables.sql new file mode 100644 index 000000000..9528f3dde --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/test/resources/sql/create_tables.sql @@ -0,0 +1,162 @@ +CREATE TABLE IF NOT EXISTS "crm_contract" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar NOT NULL, + "customer_id" bigint, + "business_id" bigint, + "process_instance_id" bigint, + "order_date" varchar, + "owner_user_id" bigint, + "no" varchar, + "start_time" varchar, + "end_time" varchar, + "price" int, + "discount_percent" int, + "product_price" int, + "ro_user_ids" varchar, + "rw_user_ids" varchar, + "contact_id" bigint, + "sign_user_id" bigint, + "contact_last_time" varchar, + "remark" varchar, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '合同表'; + +CREATE TABLE IF NOT EXISTS "crm_clue" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "transform_status" bit NOT NULL, + "follow_up_status" bit NOT NULL, + "name" varchar NOT NULL, + "customer_id" bigint NOT NULL, + "contact_next_time" varchar, + "telephone" varchar, + "mobile" varchar, + "address" varchar, + "owner_user_id" bigint, + "contact_last_time" varchar, + "remark" varchar, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL, + PRIMARY KEY ("id") +) COMMENT '线索表'; + +CREATE TABLE IF NOT EXISTS "crm_receivable" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "no" varchar, + "plan_id" bigint, + "customer_id" bigint, + "contract_id" bigint, + "check_status" int, + "process_instance_id" bigint, + "return_time" varchar, + "return_type" varchar, + "price" varchar, + "owner_user_id" bigint, + "batch_id" bigint, + "sort" int, + "data_scope" int, + "data_scope_dept_ids" varchar, + "status" int NOT NULL, + "remark" varchar, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '回款'; + +CREATE TABLE IF NOT EXISTS "crm_receivable_plan" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "index_no" bigint, + "receivable_id" bigint, + "status" int NOT NULL, + "check_status" varchar, + "process_instance_id" bigint, + "price" varchar, + "return_time" varchar, + "remind_days" bigint, + "remind_time" varchar, + "customer_id" bigint, + "contract_id" bigint, + "owner_user_id" bigint, + "sort" int, + "remark" varchar, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '回款计划'; + +CREATE TABLE IF NOT EXISTS "crm_customer" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar(255), + "follow_up_status" int NOT NULL, + "lock_status" int NOT NULL, + "deal_status" int NOT NULL, + "industry_id" int, + "level" int, + "source" int, + "mobile" varchar(255), + "telephone" varchar(255), + "website" varchar(255), + "qq" varchar(255), + "wechat" varchar(255), + "email" varchar(255), + "description" varchar(255), + "remark" varchar(255), + "owner_user_id" bigint, + "area_id" int, + "detail_address" varchar(255), + "contact_last_time" datetime, + "contact_next_time" datetime, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL, + PRIMARY KEY ("id") +) COMMENT '客户表'; + +CREATE TABLE IF NOT EXISTS "crm_customer_limit_config" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "type" int NOT NULL, + "user_ids" varchar, + "dept_ids" varchar, + "max_count" int NOT NULL, + "deal_count_enabled" varchar, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL, + PRIMARY KEY ("id") +) COMMENT '客户限制配置表'; + +CREATE TABLE IF NOT EXISTS "crm_permission" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "biz_id" bigint NOT NULL, + "biz_type" int NOT NULL, + "user_id" bigint NOT NULL, + "level" int NOT NULL, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL, + PRIMARY KEY ("id") +) COMMENT '客户限制配置表'; \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/security/config/SecurityConfiguration.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/security/config/SecurityConfiguration.java index e54e16ca5..4478bfe96 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/security/config/SecurityConfiguration.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/framework/security/config/SecurityConfiguration.java @@ -8,12 +8,12 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; /** - * Pay 模块的 Security 配置 + * Erp 模块的 Security 配置 */ -@Configuration("paySecurityConfiguration") +@Configuration("erpSecurityConfiguration") public class SecurityConfiguration { - @Bean("payAuthorizeRequestsCustomizer") + @Bean("erpAuthorizeRequestsCustomizer") public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { return new AuthorizeRequestsCustomizer() { diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java index 7f558286f..179840971 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java @@ -43,9 +43,14 @@ public interface DeptApi { * @param ids 部门编号数组 * @return 部门 Map */ - default Map getDeptMap(Set ids) { + default Map getDeptMap(Collection ids) { List list = getDeptList(ids).getCheckedData(); return CollectionUtils.convertMap(list, DeptRespDTO::getId); } + @GetMapping(PREFIX + "/list-child") + @Operation(summary = "获得指定部门的所有子部门") + @Parameter(name = "id", description = "部门编号", example = "1024", required = true) + CommonResult> getChildDeptList(@RequestParam("id") Long id); + } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApi.java index 73d3d38a9..bec33087c 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApi.java @@ -1,6 +1,10 @@ package cn.iocoder.yudao.module.system.api.dept; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; import cn.iocoder.yudao.module.system.enums.ApiConstants; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -10,6 +14,8 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.Collection; +import java.util.List; +import java.util.Map; @FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = @Tag(name = "RPC 服务 - 岗位") @@ -22,4 +28,18 @@ public interface PostApi { @Parameter(name = "ids", description = "岗位编号数组", example = "1,2", required = true) CommonResult validPostList(@RequestParam("ids") Collection ids); + @GetMapping(PREFIX + "/list") + @Operation(summary = "获得岗位列表") + @Parameter(name = "ids", description = "岗位编号数组", example = "1,2", required = true) + CommonResult> getPostList(@RequestParam("ids") Collection ids); + + default Map getPostMap(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return MapUtil.empty(); + } + + List list = getPostList(ids).getData(); + return CollectionUtils.convertMap(list, PostRespDTO::getId); + } + } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/dto/PostRespDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/dto/PostRespDTO.java new file mode 100644 index 000000000..c2c8cf393 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/dto/PostRespDTO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.system.api.dept.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 岗位 Response DTO + * + * @author 芋道源码 + */ +@Schema(description = "RPC 服务 - 岗位 Response DTO") +@Data +public class PostRespDTO { + + @Schema(description = "岗位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + + @Schema(description = "岗位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小土豆") + private String name; + + @Schema(description = "岗位编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") + private String code; + + @Schema(description = "岗位排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer sort; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; // 参见 CommonStatusEnum 枚举 + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java index 113b482e4..13342ec7e 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java @@ -24,6 +24,11 @@ public interface AdminUserApi { @Parameter(name = "id", description = "用户编号", example = "1", required = true) CommonResult getUser(@RequestParam("id") Long id); + @GetMapping(PREFIX + "/list-by-subordinate") + @Operation(summary = "通过用户 ID 查询用户下属") + @Parameter(name = "id", description = "用户编号", example = "1", required = true) + CommonResult> getUserListBySubordinate(@RequestParam("id") Long id); + @GetMapping(PREFIX + "/list") @Operation(summary = "通过用户 ID 查询用户们") @Parameter(name = "ids", description = "部门编号数组", example = "1,2", required = true) @@ -64,6 +69,6 @@ public interface AdminUserApi { @GetMapping(PREFIX + "/valid") @Operation(summary = "校验用户们是否有效") @Parameter(name = "ids", description = "用户编号数组", example = "3,5", required = true) - CommonResult validateUserList(@RequestParam("ids") Set ids); + CommonResult validateUserList(@RequestParam("ids") Collection ids); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApiImpl.java index e4a4187eb..fa628224d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApiImpl.java @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.service.dept.DeptService; +import org.springframework.context.annotation.Bean; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; @@ -39,4 +40,10 @@ public class DeptApiImpl implements DeptApi { return success(true); } + @Override + public CommonResult> getChildDeptList(Long id) { + List depts = deptService.getChildDeptList(id); + return success(BeanUtils.toBean(depts, DeptRespDTO.class)); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApiImpl.java index da68d8f79..a56c56984 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApiImpl.java @@ -1,12 +1,16 @@ package cn.iocoder.yudao.module.system.api.dept; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; +import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO; import cn.iocoder.yudao.module.system.service.dept.PostService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; import jakarta.annotation.Resource; import java.util.Collection; +import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -23,4 +27,10 @@ public class PostApiImpl implements PostApi { return success(true); } + @Override + public CommonResult> getPostList(Collection ids) { + List list = postService.getPostList(ids); + return success(BeanUtils.toBean(list, PostRespDTO.class)); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java index 4cbcba894..229b7b1d6 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java @@ -1,19 +1,23 @@ package cn.iocoder.yudao.module.system.api.user; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; +import cn.iocoder.yudao.module.system.service.dept.DeptService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; import jakarta.annotation.Resource; -import java.util.Collection; -import java.util.List; -import java.util.Set; + +import java.util.*; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @RestController // 提供 RESTful API 接口,给 Feign 调用 @Validated @@ -21,6 +25,8 @@ public class AdminUserApiImpl implements AdminUserApi { @Resource private AdminUserService userService; + @Resource + private DeptService deptService; @Override public CommonResult getUser(Long id) { @@ -28,6 +34,34 @@ public class AdminUserApiImpl implements AdminUserApi { return success(BeanUtils.toBean(user, AdminUserRespDTO.class)); } + @Override + public CommonResult> getUserListBySubordinate(Long id) { + // 1.1 获取用户负责的部门 + AdminUserDO user = userService.getUser(id); + if (user == null) { + return success(Collections.emptyList()); + } + ArrayList deptIds = new ArrayList<>(); + DeptDO dept = deptService.getDept(user.getDeptId()); + if (dept == null) { + return success(Collections.emptyList()); + } + if (ObjUtil.notEqual(dept.getLeaderUserId(), id)) { // 校验为负责人 + return success(Collections.emptyList()); + } + deptIds.add(dept.getId()); + // 1.2 获取所有子部门 + List childDeptList = deptService.getChildDeptList(dept.getId()); + if (CollUtil.isNotEmpty(childDeptList)) { + deptIds.addAll(convertSet(childDeptList, DeptDO::getId)); + } + + // 2. 获取部门对应的用户信息 + List users = userService.getUserListByDeptIds(deptIds); + users.removeIf(item -> ObjUtil.equal(item.getId(), id)); // 排除自己 + return success(BeanUtils.toBean(users, AdminUserRespDTO.class)); + } + @Override public CommonResult> getUserList(Collection ids) { List users = userService.getUserList(ids); @@ -47,7 +81,7 @@ public class AdminUserApiImpl implements AdminUserApi { } @Override - public CommonResult validateUserList(Set ids) { + public CommonResult validateUserList(Collection ids) { userService.validateUserList(ids); return success(true); }