From 584cf4fd9284735404bc7910802b0c339c731481 Mon Sep 17 00:00:00 2001 From: Gjm <你的邮箱> Date: Fri, 10 Apr 2026 10:26:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(system):=20=E6=B7=BB=E5=8A=A0=E7=94=B5?= =?UTF-8?q?=E7=A5=A8=E7=AE=A1=E7=90=86=E7=B3=BB=E7=BB=9F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增发票记录的增删改查功能 - 实现发票状态管理(未处理、处理失败、已成功) - 集成外部开票系统接口对接 - 添加批量处理发票功能 - 配置发票系统相关参数 - 创建发票管理前后端页面模板 - 实现发票数据与外部系统的同步机制 --- pom.xml | 45 ++- sql/sqlite3.db | Bin 192512 -> 192512 bytes .../common/constant/InvoiceConstants.java | 13 + .../monitor/job/task/SuYuanScheduledTask.java | 203 +++++++++++++ .../monitor/job/task/YinEuScheduledTask.java | 228 ++++++++++++++ .../project/monitor/job/util/HttpXml.java | 140 +++++++++ .../monitor/job/util/InvoiceXmlGenerator.java | 86 ++++++ .../monitor/job/util/InvoiceXmlSy.java | 68 +++++ .../controller/GrInvoiceRecordController.java | 136 +++++++++ .../system/record/domain/GrInvoiceRecord.java | 51 ++++ .../record/mapper/GrInvoiceRecordMapper.java | 65 ++++ .../service/IGrInvoiceRecordService.java | 63 ++++ .../impl/GrInvoiceRecordServiceImpl.java | 280 ++++++++++++++++++ src/main/resources/application-druid.yml | 3 +- src/main/resources/application.yml | 21 +- .../mybatis/system/GrInvoiceRecordMapper.xml | 131 ++++++++ src/main/resources/templates/index.html | 2 +- .../templates/system/record/add.html | 49 +++ .../templates/system/record/edit.html | 50 ++++ .../templates/system/record/record.html | 187 ++++++++++++ 20 files changed, 1817 insertions(+), 4 deletions(-) create mode 100644 src/main/java/top/lidee/common/constant/InvoiceConstants.java create mode 100644 src/main/java/top/lidee/project/monitor/job/task/SuYuanScheduledTask.java create mode 100644 src/main/java/top/lidee/project/monitor/job/task/YinEuScheduledTask.java create mode 100644 src/main/java/top/lidee/project/monitor/job/util/HttpXml.java create mode 100644 src/main/java/top/lidee/project/monitor/job/util/InvoiceXmlGenerator.java create mode 100644 src/main/java/top/lidee/project/monitor/job/util/InvoiceXmlSy.java create mode 100644 src/main/java/top/lidee/project/system/record/controller/GrInvoiceRecordController.java create mode 100644 src/main/java/top/lidee/project/system/record/domain/GrInvoiceRecord.java create mode 100644 src/main/java/top/lidee/project/system/record/mapper/GrInvoiceRecordMapper.java create mode 100644 src/main/java/top/lidee/project/system/record/service/IGrInvoiceRecordService.java create mode 100644 src/main/java/top/lidee/project/system/record/service/impl/GrInvoiceRecordServiceImpl.java create mode 100644 src/main/resources/mybatis/system/GrInvoiceRecordMapper.xml create mode 100644 src/main/resources/templates/system/record/add.html create mode 100644 src/main/resources/templates/system/record/edit.html create mode 100644 src/main/resources/templates/system/record/record.html diff --git a/pom.xml b/pom.xml index e8b2cc7..b22cf81 100644 --- a/pom.xml +++ b/pom.xml @@ -247,7 +247,50 @@ org.xerial sqlite-jdbc - + + + com.oracle.database.jdbc + ojdbc8 + 19.8.0.0 + + + + com.oracle.database.nls + orai18n + 19.8.0.0 + + + + + jakarta.xml.bind + jakarta.xml.bind-api + 2.3.3 + + + + com.sun.xml.bind + jaxb-core + 2.3.0 + + + com.sun.xml.bind + jaxb-impl + 2.3.0 + + + + jakarta.activation + jakarta.activation-api + 1.2.2 + + + + org.projectlombok + lombok + true + + + diff --git a/sql/sqlite3.db b/sql/sqlite3.db index eaa569691e3d963ac113ccf85846abf4b2a4e38b..c491636fd2a2d8f4e4a29f59aea03327be0fda66 100644 GIT binary patch literal 192512 zcmeFa349dCl`!7jC5@0k$Xu2&#%hFRNk+%?9MWj8;|LH~MiN3Jurb2QXr?6%8qJ6~ z0NEkSfB_i?V>@>6fe&J1lh}qhc5DZ5{AGWe{P)PW`|W0Tlm8ys{dO5iBu<>eb`B@m z@72*W-E&Fc0SoAsdaAmPS5>dBSFc{Za;$Cfgk-VT9~g9oL^GGe@jUlgQRKLs|K>RE zG`t1y-URRefj0+U<>3`edJ|yOtbQmLy~xc&>v6C@Lr2hiXbk-q`gimr6heNq9(@4~ zqs{1ZXd`+A9YBZCAta*_v>W{Z{RR8`Ci*&hnf)z8vA^``>OMLKE*1vRZZ4OjKL@Z4 z|G=~17WjF71N?j$xBMM0__==_{FK+h&kgh8XYU&L>1l4Bd&o$zDGL%-xqU^%y^slioO?=&0D4YoQn1Z?P)>x{HRNElCEmo_^SzXstS66L;j8)qtuvtyL7OS()QZLJL z&5*Azb4JY;i9AN5WU8+*!k@%HJ>c7XoZs=)>-(=FD1k(!YRObBNup%4TP3^6g!55t zv{V~yqGZBOW+{fah+Vjv~ZxE!+%P|)+vudjpKLuFG3^D?9k9n4a`m?d{4O~dOxF!5*jfssR>YWZ|PvLuhd>T`lSPB-Z?xv@=$dDv*6|OyX8{5aYt;7H;a zu4r1>xUQvBY;=gtE1KFmn>#mXNEcCr#MT6yF0TwkvTKPr`sj(#FGj}qeJ%QxXQmuo z6Dy8mgF{Q_On$0Ogn_4ChV;?5PDS@WH~RXqv-|b~-q*CY0>;EOZD}j8*&&wG`E>mF z*Q4(|1`VPd&*Q;iU&y&#l<`DfCCb}}fSwf_TkdGw;HVOvu29(Nh03WChx|cNGFz%d zug52gbycFv?+p+7L=#v;{sGwsp<*)}ECj4}BaC_`-2V~Z!0`>jzakNdm*E%?FB0ar zWb4^olABj7N&FrUkW^lIOCWSlcaPWK(;W(U`ugNRFxh3AhBhrt4p1sq6c&^QUH!7V z+vgmVOGUA>>CVmy$z-e)+tzf#f}y3Q3WBgSG=X4=m^x5jz#kq`KqZADdB$`hgjm+H zW*LB2tw+QPb#|&mZOW=|nn)$YUb&{Dsd-f!Ud^0qY_X$hWm88}+wvxdNYSGp6EJgb z87VBNTy8KF-y!jw$LE%}!_o}++}+M_$WOkzld<0|CA;NEHs$3NS61@7!trW$IlZ#a z?POIP`CLG^1wW+iSbp_7E{a>@IcR?Di4phg0 zj)AF&0lXim_y4I_GkTtM3|uq}R6{Rc1}+K)3UMm9 zRqBt90UZNZ90m$-iS_<}#nroBU>yS(1N!{0w*wsmS1tzh`TxqTd%fg32Jrkpo!h{{ z>ALlZ7hVzW5Co6~xRXcRad1BW$MLxWuDE1j$)ch%GZCmy6471WAMk^axn#NnBr^Ql zKzzw)9DCvv$Uafg+?ceISv1=0Qk~r*Il8k9)C}UTj=Qr46y&Pv?I2oZwm3Fr8K}iB znH^nO25N%LIyPn(sIkFrG&%0dGEg{&V{&ZBGSE6W+2gn~>p+e5jytjp)MPiC9P6_V zRI1bI|MMT>^q4vZE_Dp(^#7$^gL?jT44gj(@cb|EBOE`1@V>VGd`cL&uV#8)vDpmO z5SPyF^#tG`fFc4+t#Sb8 zi=OhBNH#fEM_6#bX@;SAgE^xtxV9-U@n5auES>)Ulsdegtd4;xfB~KUp8}PjXFxAr)*8&tAPM?oI4!Ar+ech zT4I1SgkEAWrlf_Pvt{I-CQt^lP!o<|bJE9kV|aw4y4cQ+=>q+D>r6wjqdFt~NRp!U z%!o&o%iKs+QC{()Mf|SC@s{EBy9Q{RAhxShv2`oaYbwd2t)onbUE^^vy==b9 zP`qkU#`2P^v62#dain`zUhyrr@VnN=%Q)l=2jj~!z~~SyaSHcqQj|YMut{TMQtPWs zDA58?&j0H2f7&rm|I#sVrC>m(|F4vq*2}45AQ=NX|1TLQx{Hp1D+L4k{C}m?v|dgf z1IZY`^M4MvgoFRj^IzqEl^i1GvSzlS*mX-r4oV8<2IHrs@R`5sra5`VRx7`ITb#Q> zc67s_pSv42i9=MaUV__#6kL^rAd}{(Ol~h4ScRc((8}i;@PN9k%)8A>Fpn=J2evxB zz&cE`d!30azZsp`a}4T)JpL>aWs$()wx9o~XgzFPq;HXa(+(`)WVqQ3!)~g4uWsFCQ zAEGIZd&Ejja(|gH?V7JK6mP1}%nMFvxT=nzZmWxQ`hRK;b$YIJ3|tHh==A@^z_Ol( zj)AF(0e$|Tn)RaRO2@#(z<@sgUkohkY3LZ3ni#0*pS>^bNOlConl%z%o|Nm;7o6!fHzU@Ju-!j9)G3>d3r?E&uOo03VU=wWSOKqp+5 zoCXSrmE!)7xPRu*62T0{W%BI0Ytv79+ z4RFji!*%L4!Qo&?9;^w-E`PvX1B#nM0lybykZV>2ntfaS9+%ufyq3p(dYmCwe|y01 zf?H@SD%;mMI*F3wvF9FzRL<@`cJ|P*S_-z7f~{4@NYaITZ>h)K;t7UI?Hfy_(yCGu z-2CnXEm5)?u6%dNM2msw35xzeUyXCf2|~s-9suYG*7Sh#WRI_}W*Ie`l+bpu-5CJw z1|WqjmU{tox#;tU#9k1Tb=QcETS0pR)@&dSDWQU*Ga!o_X+GdSdF(E2(kfv#gLdP( zNO?^gGj(+LDnRgCHUwCc&jiZ2em)|Xy+O0<84sE$j^>(W@ z($O@VE8`=%xKUM?EL^f0m;3mWqlex+bLv!d|B)0eYtZlWg#3XT=z!{Wb-TYu)s_Nb zU%Dp5;@3j_S_qb+-GEQC8#fy(G#PENs2c59u@Va#il^O9}q#ASzs6%pon!_^f-fZ zjVLb1DiZ9ZvC@0jYaDA^;QDn{z3ctD#P zmQWALqQ^HB4uL>C&Ov9t%wi2XhoPfDD+hnE*8}Z(;|hD%Hh*BCt1r;q>m2ep{ar9& z^vR(vqWMD(b`1`b9@G_rU8Js6vab{OhgRp%5OnV$zuqHQqw2< zY6dBU1b{)=3nR^n9+C*|>C1-Opmzon6Y;x<(m1!ctsj>{-Y&bsA&d+s#WW2M=(39x zq)5mXGK*C)JK$;%bj4MPt^ml!(;2)w#-9QgZxJh4mX)SbQHdK~0w9l% zmhr+jqJpJj*yjQa^?`oCdtj&qw8om~Qqm>~ks@yNg}vTQOIcgPgM_$Ig6~h*h?k1# zhWHEAYhr^M=&85Ztqpd`Qpo>+%jF;E_{W7Cgdd{W=)35LFEx3lc(T_wW^fKprbG@oA|BOD6PbGS)3=J{=LGHDzgGNDaqA;*WJ{ zN{js_TUs3U{7S3DB&DsJRB1_+cxmzHdT1p}fOr6Uv#ONTE}oSzIRVoqp6r13C5lP| z?pTQBbb6trDnujmsS+)LV#bkrVUWd>Ok+6I?+@T;!{L6}*N6QAFzW;cup9nz`Mu#m zUvQ&w6A(+-N+}!3nBW=o2MDE{=uRW4@|O^AvvaGn6R#*Cy%vyFN#VWHBYO!s1&vgj zBTul|7nEtjQi2~Lf)vP2YZH9?LxWz$laMb7{%(&er1-17qy#i*QUy;14T5JQA}bALjn(qaOldV)=E4_n5l@JD>&3!FrswU~&EHz?K&AW{azf^SB| zf`2;1ni3F8TEEixz*9kNqI*WfLcGj~WdT%(4Qx?k7W~slr#v*5)r zi)RVEj?_}Y%SjDnofg)B)lkILCrF9nPfiolief}@g$q_Cn47;j1rxAG@DeGRpHa+JcwuE6tv%@Sxd0Agr?PCx70mwZ5^qgSed!}FD=ki(3CH9 z^u+1to}-jK`2Oi9=}>hM`Ce)IapDAdp4=~Jn0s(Pr3A5-Yy}D-BXtG->8LAn0(E6p z6FK0iP*;iWbf!r|y+gdr)D;U5qpo^~?(rrhoR+$RxM}EC;**lPB0j09EAYxdw}Nj* z%z}S9%vusKYf%>vJQd6)x@W{J#LJ9X79fV%fuZ2$goM*#7UHJCEb&Q+S>lr#v*48h zv*4Q%v*4c&v(^O60y90n90gAWvx)8*F$?iBW0nPoVV3f1mklQ*o))_hUyF{(^8aCw zuCrU~?2<(G|Is}h{Oga70UZMu4FeH0FQ1bZtdWa2^XOA=jDGp+bboqm|C^&bBP*I# zHm+;w42Cy^ojwQb&bUD-mb=p#9H^liIc#G(Ii@(e^Hg-tYvVf)!6qIMEhfom8$I#z z*h`NXWAwjGlq9!0(I8@NLx#a&wW%YID`g*81uO z*(q0BY%aOlW~l?4B=;E2PN&IQ?@p5TNLo;(xZWBzTD=~h%s<^Xt@d;H*K*r}ZJ)iD zpRq2UxVgvHYjT-w)xAANYqg~wei|g3yV_`So81j{Zj0n<_+*I>_?$EOrw4qS{~i*@ z^1nR#5r=*RKl-C%K*zwRgMoYM>U+(WdTU*Eox8_YZK$W zk+FyNCUXp7dm76z;*DLkv{@^Bt2mraw>uNJkOHa2Kx!dq(!6$WDToHbMuUpSiNh)z zn9K3v6+0+9!i=gVS;l_hfEV)^wbGH;gNkQiKODUoN`hTM8f%a+C279wRGQN6#ln){ zO|})Y$1!qqf4*v)m3mIlJf z&7T9D+;t@j&0GT4d0KM4Ow4t5`?vYLey6*la#d3bNzxe8SqWF0#G*~ArxaY=82wL+ zW3rpr`u{M84o{t?ujfR^fQ|v!mmj%t7EA$r<-($(B8e-;!n*3wv_LzCDx>CdBbM=j z;Zr?Uk|jez|Hs1_#xqDhmT&O*n!*}B14wu(LgIx5`OXj(=N-c0BE49!91GXh^vF(M zu%_4P0wL$&nqYVcZ%Eawkb9kBZwPaD@!r%j2(*#~0Rde62_ZZn84Y{g6&V6!?y22Y zX9gLkbIU+u51iY86cbWzl|ca5-QtI{9|@UA6@jEKnJt#AB=7dGqD8Mro0i$wkd>qu zj?*V(GTK-|@wp0%82EiPAZ+Xn)&xm4*Eoo=jyNem)ouhs7A4bhSjxEywe&j%Tmy3N zm~S;8W0*BqzHC5ll|d}bU7__Nkj?)R&M>| z6Hsa`2CIh3X3H@&$9*VXl-%Pp8(T>BfG7*d7zrI=pQB&)dTX%gzxtcRgl?*82P`U4 z$6n)rEnKhM*$;xry85NL@Z$QFd%T8}%Cw*OnLb~Bm(5&tBIW(XcI z^k>RoAkwnzvLH0+Mw8{?xs6(Jjp{@Zh)-2q1FF3uvgNZ^j*tdai`8zi=0d@F^f7Mr zcJwjYkG{{(LhHGIN2T0XxiHr#d?fs*@Yli{!mwcFf6l+ezl4LT9#c^vH=pAcVW%R_ zY6eC{oob`OWU4b;;8-O*lF?!_HCRmzb*7m~ajXro!O5nL(*Q9oCdpKo9H%~A99z9) zG&e}5>B(`dY2#R=`i8nXQ+{$BbEY`;7U^?25NF}6MR1ccSJX^!V71{mMvJlDCdFg( zPxpQ&@0cli{Ql^---*8P)zP|ed;UY55GLV|HbGxo=VQ)va3~K*3sM$ywdU-N528ZIy4%2Wc2l~t7EIP z(lAd2V>yLE@XTPGIrYTo+iyo7d{+&CmSXN)h{~E^5l5odS&eh&B)eLSb7v>JnvHX3 zCA*r80P^ZYxRTK{gM?K#a4W%Cwb4>-OlYh&i)1r`cR_L-QeCOyG)NY!(NZ@zFELM6 zvt*oWNN}|@n4wg;iLP}L^tx%ushZ-APus0*7AS5#bVQDu&fUSGm(hB}3$F-w2m=2q zeK8J|@% z0k6vBxF?%H&9M3f2AE?wn?NOest$0vJ-a|HAW~^|Y|AcCv(;|2QvUxiod4&C&%K-L zG0qzUyK6m!9wz+#+LDF4@4)nMHja}|B*CmcQ=UeMd9!k;oJbPFm%_})QYv40hBBdm z&KQlHz-)F1uqxmW4`Hr46I{X0bba4RPF-U!I3}LJV*(ZcK2pVGS#TUz!0&6?4%$WF z2sq{A8%1NSWE5{Af4AZ=Ogl%r-|KOyPYwCV(P_8`Okwxq)4O4^c|eXxsSle?TAmqZ zAE1-XcB2&O=qJmA%i#P!DU=Z(0ml6wanEx?E599#srW=Xyi*zP&_&LrG2uDKZmy4X z3{2%hor^LFrH4qy-Nwz@F>S+C9MgyluX;>N623c!F>OKty{SE@)fK{memeK}9N&Zt z!cT<16&@At5=4GFKg#_*|4Q=uKWTtKGdRn+uK$yOWYNK<@Coo*obn^ADn0>Qfos>; zlv`j^xCN@1DsDl_n3QL54(or(;GQ`jb|464{BsWQlzCpKjbtBR>w z!={SKReb2kVzM^f@VNz zw{fGk#x8&Q-!ahvoheEGD^+F$`risX_3sGW$LM=P9{)G!i`;imHSZGsTsVL3|HT|f zY;-e-?!B@7$BC%+dI|RAB%88x87DKiSAj_ zq?XfCsrD?BlS;pbsR55G&}@@zbO%#Y2@|ZfY*2yq^^)x>w)3ZI%E{Z#iD|-?p_E*8 zx{Hnh9RpVi2JrenhwI~nQDGK;GgvOn6X_VbT-e>Qv(u(XhySuF6zsDbp-{fdt56bb zxa=w()U54yTtXZT3n=im!dnM#J-jw}8{h@4nDB%79g+$EoC~i9wruLi`TvFs)7^Eb zItFwM=orv3pkqMCfQ|tj13CtD4ColpF`#2W$AFFj9RoTBbPVVi&@rH6K*xZN0UZN6 z26PPQ7|=1GV?f7%jsYD5ItFwM=orv3pkqMCfQ|tj13CtD3|t5d5dB}=2~Jo9KU4qN z-&Z}CE14D{I+)-d-TM}D`48?eM@OEDo;o>xWS}Optmkgc;7D#MlRb;(q(vW zX>bd<)na)^Q)6e7*x9(OrAdT48klA)xGEe}KfAkp6*(L+Z^4?kNm`rPU0>4WwS@U*tBSW!u% zuWY}oC2e$aq<`$C17ok9qM=u}J8qAM#)rdh+}%|*M}zTVF5f>z5q^O&9j)YPm7-wC)j8u2aZ^uev*^iEK>05|xZJ@iJ3s;yiKnjJh|uiWSKiayZBUnRoj z@uFn5R*CqEF%e99Hz}wus}e(?XWb`O#11IJ{U4zG%VxJ4Y5#wP6Yl0-nd;qtckK%8 z@p4@uz?3{)4y_Bc0l>d)cgb?7vHVD(a*5F`i3-7z2W<7F0jitNZ}<01{thFxio?`* z7-dWU*Qa^@pIm8lUP=vPHDspxe~LIXQ@D-?Wy)9hv#X$+oPHyB*56vPa9d(~O`!m1 zQVrJC@AUP_4x*4U9jT#iwd(LG$PH+|^!_bHL1!xcn{oA*&{8I?2MErFNp{ePBbg)q zyEUigE+x%Jpz!ltN({P?LB}+dc+(YBVj~;@yu$mtUVI$`S2PCj{GZ3It!RIG?A~|!()#iN{_-b>>WR|Qc?>2|!4UFbFb1qIc%vO_>Ivb$s*BlXM<7~8& zi{6v;XES}-6Y0iIR=k~f&%4D^Z%)hvIp+;Cn?;JCSr_HDc}txssYYT7f-RApt0u0I z2}$aS@4S~98Z7lGkbyPcgVMVQDk3#P+Gn~NnMfDgBxbQb+z^#7y3hu;4! zaMT|i13Ct#76$Tic<8sj0%{KCQ`0+*n!S0{957IGb1pSKIn?Y&)RYASz{wZfQ>$=# zZgdRj7`T`i!1Vt*-p8TeqhF)Wt168W+4&v29Q_k?c?ZwkkS7lmhp zFAEO}dxb9uTLqucE8H!t7utlC!e@j!p+>k-xL&wgm?;?eKk*;&Kj%mJAM^jr|1JML z{@eVU{Biz8{u%zu{Db^n{tNt8$m-dQf4}B_%KbO@uTU&QKjH<1J~H%S6CCz{k%KR* z`aHJG14|HF=7ME2w#)&G6I*73ME zv1K|~=3@&cuC4+zAIxcBPBW~7tOz+}hE8JMU~mv~v7v*QrG~Y{Tx4h`rf670%o0Nz zF|RkY5_6%Ug_zeFRugl9;dWwPYiK6se8X+TyvDGKn8k*b#Jt+jM9g`H6~vrtSWe72 zhGoQ@ZD=IsEW>Asd6nT-Vip-bL(G|mTZma`xS5zU3`>bQ-C!qXfuVtz`34&?ry1&r znP;dYromt(X0E|P%p8N6n8;uvreKhW$s3Hszy=1G&?&v6(IaJahrj zPZt1vngxJN7XWU$0LY;W07Mr6LJlkd@&y5!CXap%zuXsL1o$uXFX$i8UqkOdiN1lp zhQ5NHM2|q<--Yf$0kj$Qpu5mobQ`)AdVV#!0WCnYQ9j~?-$1{Q3jZejK=^CnJHp$- z3F!4t!(HMJ2)l$~VMyqMKEFkdYC-=~%PfDYVU|BuGs_>UnC0Uo%<}u2nB{ks z%<|g`X8BDyvwU_EFav!EWcjNEWavcmR~MnmS2d>@_q@k{QP=m`Po8ddG9)A z`RM{?`N_4+a&|tmj9)`7{D;NN^1;=V&<@R`W^gVw19PakWi~a3W>M3B6*YZD)Et~i zO>ZGJ2WC)n^K@!@*aD!REdcrw7639^0Jzx#Acrjg5Xys|@&L_cjsaias6RRebPVVi z&@rH6K*zuph5@{KMM5_xbOR;didwI*;_Jmtg@Np~f{1ZxUU7LjzpEtV?D5LM;b3<- zCRZ=YeajL>U*+lmnXhTDkl$Eij)s_59d#k+Dag9UXbdkgVHjVr6`u z!6APjM6~QVoLl8|s&X)(+CJG=s|ue&O%5eGOYIv=K^LFLx7F`)$xIg?=!c1(I33+{ zH2T78<8K^&|MZiE1&bFi7MlVAe?Z(4mIH7kzfW{K0fsXu*NEbBCy=0a(I4omp=4o= zV{HpihfUjE@=(a*_u0jb;L_aL)ZNt4v8H2_2w$R1-~q@%PcW#4C|3AnXFx2yvD7XG zhkYUEb{ZC_GMtajewoD`bPkL1c26)QdVFH9$195)SJ=C@`2z!8P^w<%kjLro3ONIP za;VGYbq0fSuxoI*2dc7izrm7NNe=}ttxC#XtpGws_8ZJPLX>6-2sw)TzkojA(8utj zKRO0<3|#gYU;+S$NBQ|!L`8u8f9U^22;j0Wyk0;Z16MW%r~m)n>lk1d(9Hjs{^xRZ+~^q4F>obf0Mq|D{KFjoF#mmFmGH!sh=VCr z=7!HqO8kV#aKyz=@aN}-Z_O)SRL)20spyKUUv_unyJ5BjLicos0-nA;IS_O*(Ua!3 z6-{@FrNLOJ(!zo@ZDOe=Fvy~)Lx7|VTs%NvmNI)^z#kqet!!UrC|H1Wq>?0JF+-jZmd6o0o9^tauqH?wDPe|(VnHOW zJQ53mFENpQAq9@f2v_}JaV2&=0|PG+H+sobch9gAy3V3@4|xWa#4v>L5NuD$$)TBh zLTGkV2usgowPN`n(*HRVC;VEtp05DQgij=Rhvtq1E4d1 z-RH6@!0yKCAXj6l?sn;MhFtya0Y6lAFx~zi1zStO)~aKq*ah^sTVVGL?*dAtRi&nL zxEaEX@4_(K>}FFWxc-VOwNZ+6tRvTurFQJs&7GoU@7V|cKM0* zoU5`PuDsY)(0MuD@p3LVp3rt(XEz%s?CyF19RoTBE?W!~3jZ69sDB%w+tCtqGg^r5 z68?yufYZPKTlgXRqVOJgZAC?(7w9bfs{eip+QQi1Wr`dSh*~~j(IC+E=>zf4aAJC2 z*m>>cc23~`xwQTed4l{QPo~UWioOZRC|rn2cn|~YCTj!%N>sq(^?`TJd{ikAo4?QF z^Z0uG0q&uq>(FB2jTbRqf8V^T=Akm;;2(lB3E(m`qXI1?F8H*8Fk=q;!pkeej6(K> z&l%*Rcexp}n1dVSzkD;NqeUbtCHsZKxo9ED!l3L6=iG>1;R^GW9L>ERRmbz)O>gR) zGyPh$K*?Y3tgBHu`EYw&q3#f@G78YYb2IZ}rJYr*4uIv<7S6neWdZ2yUQZw9?O~-9 zSIUzR9B191CNa!I37LtmJ+mMZ9J?k zMbObhM@J7oTQU0F>FDW$_6_i~wys!FNu#f9zpEu}bRtkc_R@i|*G|#UtJ@v7$3shS zh=68`d#v7^UE550Nz&>JZdt6U0#(rQIYX0slvk|95x;S)?@S))6-{X6^|Af!1JQ2PypOY4}x{5gS z=$F49{mL^@vZ^`r?vc^Ri4{#N8`rgTZt(b;!VY{2J?xd;ozCDujmHOvFG6g2mK;$W z-FYgy=e6;jhhV`2dV5U`#?ccmkG=GW(PT8$RU0kU#(Gh*+KrOkXpEp4BoBQTHV=~3 zCe@|Rh0$)bM$mMei#0ON#ZzzKGST(j{x?T=Mi?d>;SFJ@Pm2nAl3PJVQp`kDNOk6h z6uFS>k`ZXV2vByJKHxH?S6dP|yISnXy@M3_c$L8{}z zHB6giN|On^_==#si-8KsX0#>s4_YBcSl8nIU%>q%hh9TYG($Kg41gYh@AKP%7x0f+ z83aBBARPq-Tv=^VvAK|w#$G=)cIw$N($DbeE0=!|4w!dS-3vyid(h*9QA4VhOx2Pk zN;bPyvYSj}Pn?Rr^B9d49XSNdt2WS-T2@YBj(+LDnRgBYVJuG8A9BF}3#M9smg}HGQo;h_Yy8lRYWbfFi`!FzkB9EvNNefRMgTh+_ z_bZoG(#%ocI0`Ym3Gk0Fjh@~a-SaxmpwAx|bb7npo*-s2rp+ZSHI2qf zEyPPbe(*4G5aYQF_WQSW_j}yw;i+R#c&$9ZD`S~_^w{XgTcbN)ijKUDGfA#U3d-Bl zXHOM@f+PGt;TQ*Z{dU6r-N!zK?y4uLV_-rII3iaSaK$AHOBNNCnaQx7#OPn%AMg*# zqGY-Q^dI@R1x4VI(}5f=qloT*Hbx!VAPgSo&5|Qhluf9T-C_o|nja$5jrV?LNu4(EtlGjJ7V*rQexm+bU3H-uIIVv|AInMHi&gs z!RIc=UC(pT{}n@qMEn0M99kl*=Of%JQ+c3^bo5Q-)_5+01yBLNpfuY}b&-zV%d1e( z%gm7u`En~1u#!96mrmw3>;Id|1D>8Y9RoTBbPVVi&@pg0Dck|1Wg# zf8bx_+qvI!FM#toK90zYZ1N~E+Y{E^j>z<^K?TCQ4)&ozIKUBsZNe;ZlnGQ5>>OGg z5!fiqGE^gA0JaOW2i0n~nCuqdD8q(fHldpE_NgNRn}*qh3dv zedjVNmR=N&+wJn_;Y)e;?ugx>Lj*+pKCj0|uF+9_R%=ejk*jo6zVtqwAY91e@%x(H z%H=ePSK@#MZI6#$55#Vnfe`qj8mu5YmAhxi>2?PcaFPj*&_QSl#_M-ELy(-}i!ZFf zzC8ih+m~aJSPK525{@}g=^w=rpW}nL;1~d9+Hi{w*anBd7hj+wG677nA{YwvV&97L zvJKT`gVkkjvFtW`S*yLQx4f#{=igRdNpt87hPs_Dd_xU^cHV&G?L(e`tiWh)>ug%p z)Dg>%5%R3Qum~<=idR9SV>!OS=awNu@!BOkNAG;1H!nHEAwT)X|RVNR8pm2p?lc3#Dqc3LvQ zdT?7+27dKmpZ<0_GMlR7;Wm;y_nH$ftRugl)^wfFzwu^0D$p_}8mzvSTmFS*s|lD+?P_nZiqS6o}m?^#Eu)d0*ATm5ifB-8JZ z)P>F3u9@Ud`lu+D;*REl8yw;C%=EWM0)8Q=dvkxOT15lyUc@=DMdeP2TyX9kx(7N{ zOx1+qJTMg8ta8SR9w-=9+lWP?vo!!?p7hGc6^)%uG!AQ%_&&+xILtFKjzT%pKEe|3 z8|>{Kbg7`k^96YBRtADtBG?<67Z8_6u9!LHQY2Y$9>jrecZl=^a&`ZJ40UMHC@yybO=%bXfxa4gzjKXaZHp%) zH*I$TBM5jpc5x%PGDVN~mna7U{s2T02R*=NQb80eV8Q1f9P-M*tMT}T z!XcPi7B5~bcJ|9G)}V7(l()mW4gAGkk5?8quCR9jQ8~~B6ML_7$m8^P0qNN%hq_!| zpf}}U*WfVR8|(>oVJ%W!t7Km%KH1gk9D=*xYPJu0H>FAg(-hvVvY0`a)gXnC05B+f zWmjlL4@m@HN4#vf4M_1|Vj_N*D;$6YthlWomqOkyyTT!i45vsiO~V7a>>>pz5^{$y z*Q%JEt<0-L6;V(ns_U{BPRanL;8wgr!ei^k*MJW?YY40$=#|>mF9)EM7$XEUxgEQu zd3BRmPA&*9D;J$Ucj^F`L!t4fz{OjDzecbSYbq6$Scg>tAdioh@xnKvf~8{E2TVq} z&mZvI12_k?#+v9-(k3x21Z~L|_IfuhWo-@1ON$#N`2K{Ac&P|%*bt;EEB!+B1TSQ- ziAi{yi1;bq2+uU%>wY>0bPQbH7{K&Dm&0*r5n6P4msBsEj)C*Tz<+Ehq6h9v#!kI4 zy8i{LCjo1oF-kU2HdAes#PJ7?p859E?D+q_FOEKOjE0q{5#75ddi-1I&*dLKGWO8k z=);dg2DfY}qBzquX^RSj~!<`UL5sc=FvV-I|zf zQRB_7DzU-^TnW6Q^#LORG`YZ94tHW$kAg%;IV?bLyb*p_lmU5ITm(MQ^U5p55ROzX zm5Z_$K;W37>~mL&&LFJRU}J%9Hfxel!C_z$pp+W{TpY;IKou>_7ihfj$!E zqo9am0qq=$$qC|}S)7*`S(Xr5MU^oh96I&>KeUM)RL!BkMNc3%sz$k&RwL1Kt79M= z7^o;J$_J%-07{aQm+2BdEN_ymIn%j$Wjs;7SBS-?MxQti%m7%oN8dV{lN^m{lQg60 z+$<$pf8fl?lhOT8jeqmL(UT9M^pONrq@aBE$hXFxIzD#lAn*xIx6uZrn^fji7`hlLGzESO5q zZo7jJfP0%_1RxfD#RMQWxi2LXfY_Z9fJs8KYG#_4UbuG1833tvE$OJ0VfDeWkB~nQ z6GLU1+G%B~t=t59r5xex0)->cCYvLaWuWzTslgGX zE9gn|2-=T!p?gpOZALxlF0>ZihHgc5z$>@`EkLtTKH`Mm2tOC1!oLYW5dK>Dj_|f{ zLU>VlT6k1=K-eV=3qwMmaJR5dXc3kRc0m#|mDC4rW=jmRZDhW+_?2EZ4U&%feP>xvqs-7OZBLYj06k{=;Hs`QU2OoP^LkY6j<0GcbpmTV_*p zXcjg7S5ebfM9smO)btiob6^HFH&3Ugr+}LM`PA&2M$O(lYRU#`x^tVCEVW#-1qrB?oYf;cwP9Ka71`qI4JBDb_gN352Q!fAhZjsgwF_8RD!NX zg(ye(SooE2R`^fhhr-_q-xW^7xRKqzoH^WqeCWjj;y>iRNNqW@xd)hS7Pp_-ILLp2 zfN*$@qCO|Dp1+E#05=E^w(G$L;lVZ+Y*%sjgAKqPX3>P<^O_Z+84m`Be3M{0L$Nj#qfEs ze2+()?Kat)6>VHSxCfkPgQZ~qen zaDM{Jp8{a{5G;S(0+#o{@`oX?{5OyK{WKE#*ar^(06xDT1j}E6<#%4N{4cQlb^t7I z@u+Vz3;P=nIJ^ixAN7OfX|R0Q2bM>{@6AP7gIGUg9_y84F+aD1+sBxbcK4|b=_9~$nnTed)v z2eC8bHc)Vr*6_DzA5V#m0F|mspMp!bM zfYY8V_@};$9{VRs0ml5(TtF|1$zp;yj_THVQA`#aobaysiy;4(xfNaHJ%CBJ?F+pF zFv)Ca`Ts`DQN#TI3&Z~#$TGJAl!00uo3jfP-vecGc(Mx=A1G~b^k*4pogLIw_GJ?& zWYcB`-J-o&23lt~S{!nAftu^>RII(b1FG{vJBJ$3}A;d%Ru2y zSfc+Et0?Nk`kH`>AjfMlVZr1+H<~upU`<7ezuL>Z38)aPCW?|maQ{U-8u*c26aZ;X zaHnyWGxbprAi*DNgHn=ngaUB*HAR4xL|YU@#`9Q$zbDvCZV)C^RFYp4R`~=G;l312 zc0ED7^Pk#hEm8iIA`&^1t7NumZ34)Qhy@I#L##!MSh(gUo>TBs5UW9WEKVB4f`4Yj zvH&r};@LkT;k1Z_xSn82#1fyBh$TL$5er@!5DUH;5exq55Np*UwoY9>@KX@0L3k`q z8pMKsX2h}pF~rhM*JXs-S9%$QNsCK$^QR^ng7>iZM1^i z!uAioy~)v;O`tIRTj8=_ha<~CVYjZq(UD!CZ~+-8>|UE?pfIZ%9qri#YJ>~VZH_hB z1PXhf7TBwDv}G5l1+D-!Ia;#|6tS9eR6fdZjsbga%YP|&T9^?y7BhhY>(MbddR zJVKJyg=+MptHkl+Uxz!g;c9F$h+>@@3XoL+wnpoaUBK|b{=J}R0iSGX42Syt0X)eL zhv94unc10KGPtRSW21Jc*38!6rwrAEiK2|6u{f&CCcw-*R09AUAU0HcNaj`GGYrHK zH$D)jAf&)Q-LxcW%W6{R8T^#8YH$#XlcucTpSi3oK&-65WJpLi?TiFb(@gioC*^cc zd{R&M;HAMU)-#fjm5i8$_~|eU)GMt$N}_5=QSehRtHF3IP8!UDe`d_G04mIOXedMQ zPm5aOmy(hqJ}FU4d{UzpyfmoQlBnQY$bFeZoD~ZJ!0q4gXA^ATr2Jv_VDi)LF5;#Y z6;GS9cu^6^8W)?5guw|MOexN^gfGUYG^Uk4Cj3}6SAuaRkwciBoUHD03IZ$2=7;xWq0N9W%D9DM|8egahW4#Bwtg-Uw$lBp8(^Tr4O46@I@Yv{osG*{n#AUnV$+??j!uWzRhqP8(N(&% z%ct4YNEt}1z&hr-K)-5NX-qIWCM!$*fF=P?l~U2lD#fE?t=b2ar&15h4oc~P*%@jN zqQ8{JiAh0&e@q9u#tZ90V4f_U*wMS>II7ck>Nsi%<_cQuN~}+>%h%;=?`T}r+9-lP zHrdnX>jnYkAe`(D1;R3Tmu}kO^M~N<&Cn2>376e9a0F2_OV%n8-{Sz9yp*HdVgqP^ z^AEt0_2v~#ZJo_4n?Z$y8m4-tCD+eNM%@2%xHmXdE!@TL1Is1wMA}QpMXZH$prb4; zSvV(gw-_aNmC5ZE!&1bL_lj3FO|Vr=1J+7LQ&N8cw{u{_0Ko%l7;TaE>!(bSY$=Ll zJLe)POpzK=6sh5yi)4}k+J0eDi4LBR*0YRl$1bp;JiK3hjijg8ps0)7nA6>;6ggG2~U6_Mz26^A8@U84O zG9BV2gn@3MkSm+d7g5$u89n1Z{?a4k_kY6$o3?(R03Ax<+(^}usaldmpkl0WrKCyg zPUXyi&2CdeSq?(gvx<0*CQxXVthvU7hyW0v0ZeluK)4MQE`>E2Z!#1D015<<+t84o z5(ZrLYHFy=WnnpT!oRN0keq+WBy2Jn(Ppkn1=b2#OBI%6tqKva1W_DUWHuw9{j~Wd z<$x?3ZFr}>SwSlxSdEa0(W(x;nlI#*6z7vB z9H6k;9rnuZPG@kShAz3-u0wK6adhXY=$_ZccODu&^d`}yXp%-xygc^OBgWX02u_5- zaWH6H=7^AsbJ0q20a^IUGc*@x-aRrJIl*$V!Q*QRYcoNwm{Ky46frRqX0xd-F%!^V zKzat`GEDc^!I1*UgtO?OZDx#!3zQ4VM)M#MW$i;)+M+iA6EjwwUehBxeZiVurwfid z57)H$9iaYU8Mv*Gdp$m)sVt^0+b*{GMI2s)X&u^+6j_Y&xmyp(l?X$Qv84)@ zy>&HK8F+$gpIF6>wdI6L2o;Q{-~=@cpTCsmfhq7GiM{v!H5j z30W|Ip2s<;O_hT~&!x^mo1f+(0RbR*Ov5Lpo*n^4tnOzT`Ul+Ojp$?a5&8w30z89$ zjDAENv4{TX7|=0r31Q&KNEzhzxsmzs`|QX}_%`!>VxQ$z6cWPhQs z4KA`1IrLT3Ec_CV+uaWoz|-I$f;T+(_Rr$V^7*ntAUCS5Sg(*2{l=j)?;M~6N3L>! z0;@$^Zp7D%!l4X1{<3fN$lH3IVz09rU-AkUmsY!+0e7`O9c7}1JAUwR^uS}GK2_r7 zByshjkQ^m(eAFVIIQ~*4?hl9DpwS~;s%n5N)!w93sRu1GQc}e$$9SsvOO=7gnsUI6Q_iPV zTt-qJd-T}o$Xhg!k}!rHPZ)nCCY-ir)!=BF(6a6!?dzidtVdAVX?ziG(1nX=XW?Y+9;MbR^S57SVr2b49vXr!#l_UwOazDD~mFUC!rzs;6?25ttj`1;x zeya-D0}7i#+{~A*bZV$H>8+bpNwX?FxC@%n`0;O#y>mQINgDWgy`H{!()3%EbTI5o zUu2-tV<2g|+dL#)+LWqkQ+oAKbkExqs|F=qEawzY6@RJH?D6{t2AzR{3<;}X(u8sU z&*$nmG=k=W&JGjk{#nHRfZGF38J?DX7*9NH1=UpDAZ>qV-aS70=(pTx@yZx6K&A?O zwsLX0dRV;=7oUNnmmd^TaCGJC781b$MUep-rdfD8RBAFe`s~s16JK#B0>cw^T4<4J zyTc@NX51W?vXT9XQ!wPymg|joIdZ`)xmHWn?c$EAs=`;4ReJ`VeHl6iQ(Qy32d%6K zzdJ;VR*mywrFj|s(t$JY9A{#V)6l_L)=9 zk3AO|d-PdQ7lZ_wCWta9>48<17FOt+u7qkptb_rAZgzr{MJFKh0(KNSiAtj_^9wSRme<}Lv<7@)Ujn%Bn39K(wy^?QL z(ylPDVbToeV=9m|UEKd?aLYJ!0^N-Yg_D9;xSszz{tJ9H_aEHD;4$ITvTrNNI-yKh zccZZBSkS8;3ka1w!B{}M8v)100t$|f1-zr!)}V5zeOvmOqir7{t~Av_=OJ9bdycvdi6A|L z6VxxJme|C`>NgH>-$}Q+!O(efq6UL$qm%$igK?`H3@bDiX-V5)X#c+*_kSmvBm9LB z6v{vw*9c$FjdG83w@=vpV*yr=;1!T|5|0vkgSHiqx~&k(C1YC|!RsCEB+j6bN!+8V z2?8u*TWK5N;(25BK-mqn9`CUsf>PAWe{MQD`Vfi@w~Chpj?NS1|kMB1z#k=W{Tf-$Lm zBtIQU(yunFtA&A_uv(_YK`Nytxun&ySzRqkE;Efwts{AHfJ(IxfV5gRtE+_p%v>!7 z3{P7v6dvhan^o1qz-6qKwvpU;{*(ffNsLyDM_nys-kG3M+D1V6oH8qF0g_7bs4Im4 zoS;ftMlb`DO|!~GL2^i|#G|efn!`*Lg8Tn;O#i=%?g0J2FAMAVKk(1RbM8KhV8AF7e7f}0V+j*;2%>^89WK)53e z4u2p57c;7fM!H0o&SOV{|&hPXCBc1gTi$%|L^22K>r^)r+t9t zkut~~8=1;ktrPwXWw8Xw*h#oSm^*`cGHLPGGIB#4mO|2f<-YNkMi?wo)iGE@0U7T$ zrUkcpWN{oEBk3sXh*n3C)Nu^l1l78lbD+>FbjnA!jHYOk2BcBMakTwFM`0tWPs8;DS$3!7U@##R0O` zM={CrsTmRoCNs^kt49{Z!Limy!BL{erx^<{xJ)F^>XBY`nWHVnryc_^u#Alc z%m2(ryYVXiW+MOdOY|NZ18u(l4iZ2=fLp=-3jGCo7qo$Y6MY@-2zU`4L5I;H&-{llJ`p@4t0>U$xDB5`%7l!Fa|wx1f+MUcSy+f8oO%0ea2FJvX3{IT#9SOT_Syq}ynCR+ zAeDY>+zMNnfpi$8f*G|KSS?f!hES`HmjaW6!M;*^DVtwL9*XXNwiK%&a)X{I58c| zlsLRv68@7E4+N1xIM^C-RFdM&$Hi-4RjA$`$xu8t6|2j(hHcRJebDvlyzpc7z|Xcm z_}S`$pWD0Pr=bgeYkjzwJK!g04gBoHJM?9%;HP;7{FpGK{`9Tz^R1iVr`8TX zH`(B)3y$`42&?(bHNwxMweaJsfuGx|;pc@Vc>YIRBgZugYtduyajwsOg?Ho?mz4?o zHpb46;L>(O4d62*ikD_Xgrkwc zU^hG1k@DW1K^b(B;Pl9uPvwXPzSI})_klw&T3>Q11n1ES5)2vPl7b{0Zo&7jVD3(L z3U?A-eO$$AjV0FY8H#1l1czJ!3<(Yk2Dr63ewf6hjx&VCXO(DJaF)c|9Q6A@^=c4g zY?J)hco;{$axTS)Qt%r0i#B=QnxDj+Dea=pEiZ~xg1jdi+(7_vmvjzq+Ey;dwgzr zdo0%)^l>jT{H<$iUb_yCWVfwox>GET1uiWtSkoqw^ICB?u|gFkW7=w861N|(@+htcc1Se)hr%1Fc(eOm6H@#An z-jzxweK^R1)oXO)vtA&M3_2U)S2|aTDxDExZ@@p8{&rQkXmeY?9FSF@$pv;;X_i>N zB@nu&yR2Mv`rN4l0Ov*P9|UQVK__UNs9;&f5|R4-pKCfciBOSB#RtSePY`IzK2brB zSTX5)NiVea?@4Dwy=Vi?msY!3!uNj_phgZ({rm`h7ad2Bqx;~dug{@&aMT|i13CsW zV_+s)#ACvISPpjk{DvY_mXAL%qoq42Z=Z$AiFdHyzpc9;B>x4)g|3}I@j(6@w1kAA z`^IFYls6C6s@>=UW!lw9Vy=Nfrx(^zK|H6x9ekC<8Ms~DG-QsF3U=zl9I#vkB0X%( z!WCftpMXBXWqcX)|1uX=_ti0=W8kvDz}K#UVtjQ5UjGZ+_c-o*!ZVj{xpuF+3p1UB zy?t7y6DHPTToe4ky_AOQggsSUC&K2Av%@CGi6nd$qEa>$^@z!-9eI6kO$byfN{qMEYa2^39!(rLztItQ@e%q*pI3X%&EDCa69?7K?&m~Qe z0l7A@Ixv<_n+wxHSO48X2!UwTZ?NObRXZE*s~ zTq><%J$I0Bbv!u>xAy7;kU66dj*LFN+oA!DqBL@)Fi>t7%Y3U6SC)u?tyd7 zUncEqlJIB@3a@h(fma*v-_bpJ zES|WZlZX^*YZ1xo5(Vmffl7{(s!=V{gJG8oHks4c4~^C`lVmaujvhN!mmJe~tkh?KJ{!1F>(LT71naV`CBLaQFsZg`lDmu{4vnE zFsY-z@Y?trM@e(n@SY}WscQPD^SZ>2P6U$3Xruu;@u;rq=<63iUdw<)P)7Cq(YN=E zef1<&9no-3V>^pUCZId6WpI^gavBvjrLwCw4crMQOB$7qqV4kua^o4!qoVNj8juqr zmdNE(h`9gr{B92Z_2<*Uzk5S3c!@pabPa&wgp@!A{a7ia#>-5_pJF0m>hdx_<#sAfJSIvO^K;UxtC$EB^CeZG zQ63!2E)gBV{zJ}COiQqWVE+)7-cqw}Xf7}M`d`KW|GLiq|8xcbJyjh87aap9@*v6M zpotj-ig_=GY{H+z${))i>(2$kK%_Ef2Isn^n6Nb?xo|izdho6BeP4_2|7tZbK4CQ$ zJ-svf&fXkJUsynSl)$llRs-aLEco7HE& z{nGfizcu>Y!T;CZwSG5oT=CTk+Zd2l3a*b2hvML-sR3E5hYdJ!2@i)t0tXO}=5b1m zE!#4VWF!L>Ej^KK9tJx$q=7&~3Tc3pm!X9~423_WpPE_^f9&VcePsW$`5=J z0$MmL@9fOIckVp)&b>4DB6aNZPl!0SQMEb(`!;q<(bj3)9L?}WWlr4vJ7W(|aR}+` zpd)eb2t18zUvvao+q&q@{X40TE@iv zf)J^s90R?9t}U>gz|C>9%kA;j)S411m+wErK~(VUu?EW=VV7$W=g%e(mF5rE6(`T% zN_BscxOhMQXfQrBki2?Z!>GN1AlqSvUk6pf6gw&mZd62+jQ{x{mVEd`O==DM{Xt5* zdtFWI+#iMyWl9|RTusETHKdF^>{U~u{*I2&K^p&7VTm9{F!q<^>iFHOicn+dMo1~& zQ)9$w=66MQzkTY<#HWAJ_S;UG-8&l*ED&xWp%lM8h#yaF zP4av%l;(+#?*bUQJ<7zdbSFOh6mHT}PmZOYoWmiVII9ak$s+rvA~)jw@#E*nKc4Ih z?5p_gJEM=zCkBVcj-HmWGKR#5zfKKbK=Be)i{ba3TI}6L(q<+RxXs}mEZ)bEP>`)u9jv* zO@R$PvkK-2Z+bWzdiQ0rfphHU+2oBYss5h$pYLiJ(9{+tO__PE`N%_s61{`yBPtM3 zDe+Y5MI6WA@aV(KsmC8deXT9PJAq5-WNPkfn1-o)IaVsKsI$0JC+{TA90RVz@PG!k zoV(F+D1N$swc{Nn1C{ac2fR=qDM(l+jrSjk4_#LZsCoL9d3hQygwn~ruM@XUAQ9CB zYVzmVH<0+#aH2nkUafr#kcpEw(5`=c>GjH=z5JV>FZo54qki{N)Rm2Yu|%-07O&wa zf4$Hgh?UR6&~%mZuiEU4ubNhxfE19t`8fSO>uPL`?8E_V5-3ckbS(gGP(vU94k^K` zZ0N)UKzZO22{-AHL^8h^v^iwj3Ubkw>OG&PttAwKv@~JL;v+1a=%srAqEM2Ptk-5@ zQui$2EUV8TORnYcqtx+h9M;%hKTKSFlqQZ`^!U1>h-1O`WtcM03>0`K7p0He{VR~o zU_S#sUI5b#4qXNt58&v`!gJ_gghE=#vINs@Yd34N6Si)z?~SPd zg%1|^CWMs*p=&2$2FA%0Aq zRO4Gc6`*Dfg#*Yn2zumueR4oCcn`(q;O>oCa6du5{|EQtX8$d2r*`1>kB#*G7lm&G z;Tv%={(KIv?)vwN%BrfwUJuiEWA3H1$@01K%qQVarlLLbE!Vb_MSd#Ec81eP!5rv> zw}-Hz@pj3le0g@WW4?RBm@xcmAz2w6QuWDlLhjvs+5^4v@nMcN^FVLUySt0Z7Az2t z%~tt`4Oqk){0R55Ifi7;D*Q1OPEj$fk{*@?cEU-Ckw@>@mUe|_+6=1top0|VdqE(0 z06}qKvIw{O)uYd3%*cS+6vpvrI7Fd96$S^Zar~zx5ccox40QyWH1>(KdBQ`uu>Fr6 z5euq_G&?L=T)^CI@}2AdY5dQ|haSE?cEbPH4RMz!!Uy=TE!O<^-xWW4B>B%%8@8-% z!;w5hT6Rz9Q=R|*=fD5Y!uLP7NC}|=!S1{^;eSE_LVpRLS|J0tCyNbORUuZyC|QL-eta5ZcV1UvV4%}~_U zwGPSakXXn&$l=}^t!!FLK^|SM_htdxT-$=uxr7Y_0ozQ5EzleY!dC1LzZD9$2AUBL zSq47|=-B!2#9l5eD982>xh?|LMR4;;THc|}>%o(hnjpeTAXIFIYv+$acBN2S{t^QW zvVn>L)|~TQw|aX62h9_=!;J=kZ`eB14DLH~0e2CCb1pQ2yCRY%RzEy4?n%OKp|ey|Jb9 zP>0Hk6*4O*QE5Te7p-Qn4f7?1#uxp|2fNP{P_Z2vEqUf3Orhc39ZZnMrX$mRgMdc z7kyN!ken{5VhQ`V3iDD{V=S+8;lCQ#mQRDrD_ocZv39JD&748BY)aE&7X33iP7KoV zNx`xb8W1y5q(hbx0ih{5B{q*uyLS|dCpF%r%$p7#jJ2a#iBGBF#FC0t9+%1seV8E( zH@%CsSc#R8AC{2|dgS^bqAGHI5Ydk2oIyk-r(0>Q+%)cmdK{5L&FE1>f9jc`gbM{7 zRbWVs+%<9|!x#*tIiFQ|J?GU3s*~nC$^UTsE8u_r%5&gZ=0Nwh#-g&yO0hSfRYWL` z=1405^@Z@jP$5CD(n<+DeCLXZiEARLCU8Cz!5&9N!=Y}yWR6gkRE=tZZ8r4(t_d#Y zpuE^kN-8CnS8)KRk>lidaMxxNtML6F5v&825o@{lH?jU%?gDw*(j0i73G6m`k%4q| zC8lX;Q-Pg{zot^Vf%ppq^Yx3R_IVIf%Wc{Pl)M;x$_11G{vcaZDb>YVP_6ES)>N#% zrVA?rd||fMf(WjzwUmXfjL&G{!e1$CEr!!{!DWDRpB?;Mq~J7YQX`xhizSBAbj4(V zlI(w0>wj?P7c(G^gmUTaKt; z^_+I6*{rNGT;$Q07F&WyR4zB5QZLY}R4M{JsH@VEpq9~-py_Tp5qkXu`&K#}tG*p>Wx>{qaE z?O3#MX_ilM?BuA%-bufIat_wR`^R;c4UyA{YzAJk@rJuo_fE)Bx~kJ3ziqQ^Satxn6y^@c36g6@k&#j5uxiJxi z$D4u)6xkLV(xyd}%G5eg9@)USh1(aAh54`7*b}J~Wt0tw$|FJ(H1BzT}aNyzy18AgIq?*eK_u<$wB z2=Li+Fa^!3#`$ZlL;3Xx5TA}b#G^o&iI7y@iSkaK%|L_1 cpYj?s7vQT)87AKU(B*%&LYS)j&s$9TKe6k$!vFvP delta 5606 zcmeHLdvH|M8NcV8O|p4F5<(y(Y;ve4JKy=v_qhF^7WRKyxTkU+78r(k3#b6d4|Hs=RiIpZ>z*94gPBk6 z0w(k5-mXUU-fIVckLwaX=s$KRH1#fM>J~vWcsn$0w~-@E4*XySl24KvE4jifV#wRT z%4*)HUA?)&6f*#UE;OTmB6d=+Ze+L`_5QTFqeQ#@15`0j-~^YAbJ#e6pAc+jWXeUWfH8VQBesC~W8(XL#=%SJ?;Hz$o)5mh@Q530#CpOkAqxQuhL zRY<<}&qj^ig^ieZot&Rnl0&s{OR}i$?(&D>S1-^bKz%@6KPeEZiN02 zkOin1Xb%tp+7EOJXz~Pj`29;D4k!h*0;oT^#!_GY4EzcM?E|6;ei>+ZwBOP{?@-CO z;beosIBHmPwG|X&8202=v@_&YvWXb*34AXuL@DG$#mt*bd$QE9)+oCQn1wYaQ&REN zB2O~0va*7=@dEs@JTHk3$-&9a*&u>T5}IVktPog5kj2>`e3K+PWnm_yI!hMbBk}Ir z`2waC#H}s1h|wOcl+iRWeOOh`#)XPo48Ne=$xeqtCkjDJ}MwwwnyjqD)1z?v~x@$fxID1%a;^VeTxgDj zw}ygY6+OT1ImNHHPggy(nz{@`K4!?rWDq3pCCSH97MYs%#_u9GQ%~;9|1NTjqVcaA zM@z=XX;WG<++bLcj||kv?1- z7C;u!>&fua;9`A%bIa~)8;(Hdif*{(?CWcgp`oIHIh}pgjp!7*4;3+|K@SIwGCOmn z%thxM5(oR^6Wbb!vm9cX7wji`r2-7K!@R==$zXIzgn2kg5QO zb}u|}`0THr*VpZcg!LtPrw2aTiz1#^742F5zylETJ1HU-SK{3kxv{q3w@h3AfwV zMZ)bc?r-zyg-s9mKs>BRa-PQ%z z80=(%)oW*4l zW9;l5(Vomyh4lvm0hQWA2G^&r6YmJdrZfwJvDsKi*aE@!?kJoPJbY5GnQc&I+G7ud z?B0#*vW`s`J3EC0Pt*i~cgn=+uhHW1&x=Rapqy0B@6Z=yGS(nSF>_P7esMI(w-i7@3AiB^}MH^U=x7q}&-bKr)GGU_bK=tEj+ObJ!nwiC-5zKknq{;mCMCoZFD_kHw= z(hCcA8lChB=a0X1e$U|8-u;=o4)>xh?F7;{p5A&79aTG(V2G|Ecm$W|;(6C<-X&(d zfVxdvbs39!oCM)q5RNq9q7r(v1Z`%B4R3*er@*@%!RUy_d+{!9)ka)tPCU|}tEh@f z4~MmtRvG7#Po4eM!E@T-v9~p%@fK961%|LiyLt&%Cr?~9YA1%UF?XtuXE{HB=XAb4ec)29Ilxc1L=GcDS(jtG>7$qEuW3=C>G9%T!6G zbjLf^C~?K>1NZ4w9#k8aZO}GFP&qY_dAglVgCuL;DC?u_bchk?@zd_4)tozU94f)I zXp>{DY7aFI5V127rcD3j19h9xIl9l*4-2A}mFIXM2aprU1>^=2fIL7sO;(=gbcGv1 z;Z)~Muit`m3^~kUhB*wT^xT5MO_phaa-cU8C^vmyfl_-|#Pa{6-2Yyb6FKV64DfzI zcW1j&b2s463u&OLq%RQiplXB9OXLgkA2LopBWKB9$j4B_ogt||`>=%_iDN?pLGUJF zIc`PmfVx61L7k_6>D0_;prCBz4OEi?zvCg=jv?Ip`fhA66d9Oh45`H}$i*zvN>AfT zJOx~gv@MU~BiaWdUaBqKiHm>iO59Vb`_@_-2q7}eq5foSXyDww6KQ{ve@%aq&JMEQ z>VP+ad?CxHMAZP~KpibK(@PG$xsbY&vh+L%T3KV@OSC@>VK(CzIj7_lB&V3Tw?tb% zh;{#3sqGrX$xK0?uBHTmYIm50OyL=PwXvG&--tFb07jH+voTqz5Q!T$o8Q7N+k diff --git a/src/main/java/top/lidee/common/constant/InvoiceConstants.java b/src/main/java/top/lidee/common/constant/InvoiceConstants.java new file mode 100644 index 0000000..2800d88 --- /dev/null +++ b/src/main/java/top/lidee/common/constant/InvoiceConstants.java @@ -0,0 +1,13 @@ +package top.lidee.common.constant; + +public class InvoiceConstants { + + /** 未处理 */ + public static final String UNTREATED = "0"; + + /** 已处理 但未成功 Processed but not successful*/ + public static final String PROCESSED_NOT_SUCCESSFUL = "1"; + + /** 已成功 */ + public static final String SUCCESSFUL = "2"; +} diff --git a/src/main/java/top/lidee/project/monitor/job/task/SuYuanScheduledTask.java b/src/main/java/top/lidee/project/monitor/job/task/SuYuanScheduledTask.java new file mode 100644 index 0000000..3214d66 --- /dev/null +++ b/src/main/java/top/lidee/project/monitor/job/task/SuYuanScheduledTask.java @@ -0,0 +1,203 @@ +package top.lidee.project.monitor.job.task; + +/* + * 发票引入定时任务 + * */ + +import org.apache.shiro.util.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import top.lidee.common.constant.InvoiceConstants; +import top.lidee.project.monitor.job.util.HttpXml; +import top.lidee.project.monitor.job.util.InvoiceXmlSy; +import top.lidee.project.system.record.domain.GrInvoiceRecord; +import top.lidee.project.system.record.mapper.GrInvoiceRecordMapper; + +import javax.annotation.Resource; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.sql.*; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Date; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 发票定时任务(更新发票状态) + * + */ + +@Component("suYuanScheduledTask") +public class SuYuanScheduledTask { + private static final Logger log = LoggerFactory.getLogger(SuYuanScheduledTask.class); + // 数据库连接信息 + + @Value("${invoice.url}") + private String URL; + + @Value("${invoice.eiInvWebService}") + private String eiInvWebServiceUrl; + /** + * 数据库用户名 + */ + @Value("${invoice.username}") + private String USERNAME; + /** + * 数据库密码 + */ + @Value("${invoice.password}") + private String PASSWORD; + @Value("${invoice.taxId}") + private String taxId; + @Value("${invoice.authCode}") + private String authCode; + @Value("${invoice.updateCode}") + private String interfaceCode; + private static final String fPath = "C:\\log\\fplog.log"; + + @Resource + private GrInvoiceRecordMapper grInvoiceRecordMapper; + + public void updateInvoice() { + GrInvoiceRecord grInvoiceRecord = new GrInvoiceRecord(); + grInvoiceRecord.setStatus(InvoiceConstants.UNTREATED); + List grInvoiceRecords = grInvoiceRecordMapper.selectGrInvoiceRecordList(grInvoiceRecord); + if (CollectionUtils.isEmpty(grInvoiceRecords)) { + System.out.println("没有待请求数据"); + return; + } + try { + + for (GrInvoiceRecord invoiceRecord : grInvoiceRecords) { + String key = invoiceRecord.getDjh(); + String txtCon = ""; + List billNos = new ArrayList<>(); + billNos.add(key); + + System.out.println("溯源 Id:" + billNos); + txtCon += "溯源 Id:" + billNos + "\n"; + //封装 请求报文 + String xmldata = InvoiceXmlSy.generateDwzsXml(billNos); + + // 组装接口需要的请求报文 + String payload = HttpXml.bulidSoapXML(interfaceCode, taxId, authCode, xmldata, "eiInterface"); + System.out.println(payload); + boolean hasError = false; + try { + // 调用接口 + Map res = HttpXml.webServiceExecute(eiInvWebServiceUrl, payload); + System.out.println("接口请求状态:" + res.get("httpStatus")); + System.out.println("接口返回的原始报文:" + res.get("data")); + txtCon += "接口请求状态:" + res.get("httpStatus") + "\n"; + txtCon += "接口返回的原始报文:" + res.get("data") + "\n"; + if (200 == (int) res.get("httpStatus")) { + try { + // 解析接口返回的报文 + HashMap serviceData = HttpXml.parseResponseXML((String) res.get("data")); + System.out.println("业务处理返回代码:" + serviceData.get("returnCode")); + System.out.println("业务处理返回提示:" + serviceData.get("returnMessage")); + System.out.println("业务处理返回数据:" + serviceData.get("data")); + + txtCon += "业务处理返回代码:" + serviceData.get("returnCode") + "\n"; + txtCon += "业务处理返回提示:" + serviceData.get("returnMessage") + "\n"; + txtCon += "业务处理返回数据:" + serviceData.get("data") + "\n"; +// appendToFile(fPath, txtCon); + String invNumber = "1"; + String xml = serviceData.get("data"); + Pattern pattern = Pattern.compile("(.*?)"); + Matcher matcher = pattern.matcher(xml); + if (matcher.find()) { + invNumber = matcher.group(1); + } + + // 如果没有被开发票(invNumber 为 1) + if (invNumber.equals("1")) { + log.error("单据号 " + key + " 还未开票"); + continue; + } + + if (serviceData.get("returnCode").equals("0000")) { + //更新数据库 + Connection conn = null; + Statement stmt = null; + ResultSet rs = null; + try { + System.out.println("更新视图开始"); + // 1. 加载 Oracle JDBC 驱动 + Class.forName("oracle.jdbc.driver.OracleDriver"); + // 2. 建立数据库连接 + conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); + // 3. 创建 Statement 对象 + stmt = conn.createStatement(); + // 4. 执行查询语句 + String sql = "update gr_dp_saset_sdhm_v a set a.zxcolumn1 = '" + invNumber + "' where a.sasettleid =" + key; + PreparedStatement pstmt = conn.prepareStatement(sql); + int rowsA = pstmt.executeUpdate(); + System.out.println("完成更新视图:" + rowsA); + invoiceRecord.setStatus(InvoiceConstants.SUCCESSFUL); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + invoiceRecord.setKpsj(sdf.format(new Date())); + invoiceRecord.setInvNumber(invNumber); + grInvoiceRecordMapper.updateGrInvoiceRecord(invoiceRecord); + } catch (Exception e) { + e.printStackTrace(); + // 发生错误,标记为已尝试但不需要重试 + hasError = true; + // 6. 关闭资源 + try { + if (rs != null) rs.close(); + if (stmt != null) stmt.close(); + if (conn != null) conn.close(); + } catch (SQLException e11) { + e11.printStackTrace(); + } + } + } else { + // 业务处理失败,标记为已尝试但不需要重试 + hasError = true; + log.error("业务处理失败:" + serviceData.get("returnMessage")); + } + } catch (Exception e) { + log.error("解析报文出错的,保持业务为处理中状态,需要检查原因"); + e.printStackTrace(); + hasError = true; + } + } else { + // HTTP 请求状态不是 200,标记为已尝试但不需要重试 + hasError = true; + log.error("HTTP 请求失败,状态码:" + res.get("httpStatus")); + } + } catch (Exception e) { + e.printStackTrace(); + hasError = true; + } + + // 如果发生错误,更新状态为 1(已尝试但不需要重试) + if (hasError) { + invoiceRecord.setStatus(InvoiceConstants.PROCESSED_NOT_SUCCESSFUL); + grInvoiceRecordMapper.updateGrInvoiceRecord(invoiceRecord); + log.error("单据号 " + key + " 处理失败,标记为已尝试状态"); + } + + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + // 追加写入模式 + public static void appendToFile(String filePath, String content) throws IOException { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath, true))) { + writer.newLine(); // 换行后再追加 + writer.write(content); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/top/lidee/project/monitor/job/task/YinEuScheduledTask.java b/src/main/java/top/lidee/project/monitor/job/task/YinEuScheduledTask.java new file mode 100644 index 0000000..0097707 --- /dev/null +++ b/src/main/java/top/lidee/project/monitor/job/task/YinEuScheduledTask.java @@ -0,0 +1,228 @@ +package top.lidee.project.monitor.job.task; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.stereotype.Component; +import top.lidee.common.constant.InvoiceConstants; +import top.lidee.project.monitor.job.util.HttpXml; +import top.lidee.project.monitor.job.util.InvoiceXmlGenerator; +import top.lidee.project.system.record.domain.GrInvoiceRecord; +import top.lidee.project.system.record.mapper.GrInvoiceRecordMapper; + +import javax.annotation.Resource; +import javax.xml.bind.JAXBException; +import java.sql.*; +import java.util.*; +import java.util.Date; + + +/** + * 发票定时任务 + * + */ +@Component("yinEuScheduledTask") +public class YinEuScheduledTask { + + @Value("${invoice.url}") + private String URL; + /** + * 数据库用户名 + */ + @Value("${invoice.username}") + private String USERNAME; + /** + * 数据库密码 + */ + @Value("${invoice.password}") + private String PASSWORD; + @Value("${invoice.taxId}") + private String taxId; + @Value("${invoice.authCode}") + private String authCode; + @Value("${invoice.interfaceCode}") + private String interfaceCode; + @Value("${invoice.iBillInterfaceWebService}") + private String iBillInterfaceWebServiceUrl; + + + @Resource + private GrInvoiceRecordMapper grInvoiceRecordMapper; + + // 每隔5秒执行一次 + public void insertInvoice() { + + System.out.println("单据引入开始执行"); + //查询是否有待处理数据 + Connection conn = null; + Statement stmt = null; + ResultSet rs = null; + + try { + // 1. 加载Oracle JDBC驱动 + Class.forName("oracle.jdbc.driver.OracleDriver"); + // 2. 建立数据库连接 + conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); + // 3. 创建Statement对象 + stmt = conn.createStatement(); + // 4. 执行查询语句 + String sql = "SELECT g.*,COUNT(*) OVER (PARTITION BY g.SASETTLEID) AS MXS FROM GR_DP_SASET_V g"; + sql = "SELECT DJH,DJRQ,FPZL,KPLX,GMF_MC,HSBZ,XMMC,GGXH,DW,XMSL,XMDJ,XMJE,SL,ZKJE,CREATE_PERSON_NAME,BZ,SASETTLEID,SASETTLEDTLID,MXS FROM (SELECT t.*,COUNT(*) OVER (PARTITION BY DJH) AS MXS,\n" + + "ROW_NUMBER() OVER (PARTITION BY DJH ORDER BY SASETTLEID) AS rn FROM GR_DP_SASET_V t) WHERE rn = 1"; + rs = stmt.executeQuery(sql); + if (!rs.isBeforeFirst()) { + System.out.println("警告:查询结果为空,没有待处理的单据数据!"); + } else { + System.out.println("查询成功,开始处理单据数据..."); + } + // 5. 处理查询结果 + while (rs.next()) { + String SASETTLEID = rs.getString("SASETTLEID"); + // 1. 构造根对象 + InvoiceXmlGenerator.RequestCommonDjlr request = new InvoiceXmlGenerator.RequestCommonDjlr(); + + // 2. 构造发票集合 + InvoiceXmlGenerator.CommonDjlrFpts fpts = new InvoiceXmlGenerator.CommonDjlrFpts(); + List fptList = new ArrayList<>(); + + // 3. 构造一张发票头 + InvoiceXmlGenerator.CommonDjlrFpt fpt = new InvoiceXmlGenerator.CommonDjlrFpt(); + String djh = rs.getString("DJH"); + String djrq = rs.getString("DJRQ"); + String fpzl = rs.getString("FPZL"); + String gmfMc = rs.getString("GMF_MC"); + GrInvoiceRecord grInvoiceRecord = new GrInvoiceRecord(); + grInvoiceRecord.setDjh(djh); + grInvoiceRecord.setDjrq(djrq); + grInvoiceRecord.setFpzl(fpzl); + grInvoiceRecord.setGmfMc(gmfMc); + grInvoiceRecord.setCreateTime(new Date()); + fpt.setDjh(djh); + fpt.setDjrq(djrq); + fpt.setFpzl(fpzl); + fpt.setKplx(rs.getString("KPLX")); + fpt.setGmfMc(gmfMc); + fpt.setHsbz(rs.getString("HSBZ")); + fpt.setKpr("方国旭"); + fpt.setSkr("方国旭"); + fpt.setFhr("方国旭"); + fpt.setBz(rs.getString("BZ")); + fpt.setZzbm("ORG001"); +// fpt.setCreatePersonName(rs.getString("CREATE_PERSON_NAME")); + fpt.setCreatePersonName("陈志"); + + // 4. 构造明细集合 + InvoiceXmlGenerator.CommonDjlrXmxxs xmxxs = new InvoiceXmlGenerator.CommonDjlrXmxxs(); + List xmxxList = new ArrayList<>(); + if (rs.getString("MXS").toString().equals("1")) { + System.out.println("查询明细数据:1条"); + InvoiceXmlGenerator.CommonDjlrXmxx xm1 = new InvoiceXmlGenerator.CommonDjlrXmxx(); + xm1.setXh("1"); + xm1.setXmmc(rs.getString("XMMC")); + xm1.setGgxh(rs.getString("GGXH")); + xm1.setDw(rs.getString("DW")); + xm1.setXmsl(rs.getString("XMSL")); + xm1.setXmje(rs.getString("XMJE")); + xm1.setSl(rs.getString("SL")); + xm1.setZkje(rs.getString("ZKJE")); + xmxxList.add(xm1); + + } else { + //多条明细 + ResultSet rsmx = null; + String sqlmx = "SELECT * FROM GR_DP_SASET_V where SASETTLEID=" + rs.getString("SASETTLEID"); + rsmx = stmt.executeQuery(sqlmx); + System.out.println("查询明细数据:" + rsmx.getFetchSize() + "条"); + int i = 1; + while (rsmx.next()) { + InvoiceXmlGenerator.CommonDjlrXmxx xm1 = new InvoiceXmlGenerator.CommonDjlrXmxx(); + xm1.setXh(i + ""); + xm1.setXmmc(rsmx.getString("XMMC")); + xm1.setGgxh(rsmx.getString("GGXH")); + xm1.setDw(rsmx.getString("DW")); + xm1.setXmsl(rsmx.getString("XMSL")); + xm1.setXmje(rsmx.getString("XMJE")); + xm1.setSl(rsmx.getString("SL")); + xm1.setZkje(rsmx.getString("ZKJE")); + xmxxList.add(xm1); + i++; + } + } + + xmxxs.setCommonDjlrXmxxList(xmxxList); + fpt.setCommonDjlrXmxxs(xmxxs); + + fptList.add(fpt); + fpts.setCommonDjlrFptList(fptList); + request.setCommonDjlrFpts(fpts); + + // 5. 生成 XML 字符串 + String xmldata = InvoiceXmlGenerator.generateXml(request).replace("", ""); + //封装 请求报文 + // 组装接口需要的请求报文 + String payload = HttpXml.bulidSoapXML(interfaceCode, taxId, authCode, xmldata, "impSoHeaderInterface"); + + try { + // 调用接口 + Map res = HttpXml.webServiceExecute(iBillInterfaceWebServiceUrl, payload); + System.out.println("接口请求状态:" + res.get("httpStatus")); + System.out.println("接口返回的原始报文:" + res.get("data")); + if (200 == (int) res.get("httpStatus")) { + try { + // 解析接口返回的报文 +// HashMap serviceData= parseResponseXML((String) res.get("data")); +// System.out.println("业务处理返回代码:"+serviceData.get("returnCode")); +// System.out.println("业务处理返回提示:"+serviceData.get("returnMessage")); +// System.out.println("业务处理返回数据:"+serviceData.get("data")); + if (res.get("data").toString().contains("returnCode>0000</returnCode>")) { + //更新视图 + System.out.println("更新视图"); + String sqlupdate = "update GR_DP_SASET_V a set a.zxcolumn2 = '1' where a.sasettleid =" + SASETTLEID; + PreparedStatement pstmt = conn.prepareStatement(sqlupdate); + pstmt.execute(); + System.out.println("完成更新视图"); + grInvoiceRecord.setStatus(InvoiceConstants.UNTREATED); + grInvoiceRecord.setCreateTime(new Date()); + grInvoiceRecordMapper.insertGrInvoiceRecord(grInvoiceRecord); + } + + } catch (Exception e) { + System.out.println("解析报文出错的,保持业务为处理中状态,需要检查原因"); + e.printStackTrace(); + } + } + } catch (Exception e) { + e.printStackTrace(); + System.out.println("数据库操作失败"); + } + System.out.println(xmldata); + } + + } catch (ClassNotFoundException e) { + System.out.println("Oracle JDBC驱动未找到!"); + e.printStackTrace(); + } catch (SQLException | JAXBException e) { + System.out.println("数据库访问异常!"); + e.printStackTrace(); + } finally { + // 6. 关闭资源 + try { + if (rs != null) rs.close(); + if (stmt != null) stmt.close(); + if (conn != null) conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + +// // 1. XML构造根对象 +// InvoiceXmlGenerator.RequestCommonDjlr request = new InvoiceXmlGenerator.RequestCommonDjlr(); +// +// // 2. 构造发票集合 +// InvoiceXmlGenerator.CommonDjlrFpts fpts = new InvoiceXmlGenerator.CommonDjlrFpts(); +// List fptList = new ArrayList<>(); + + + System.out.println("定时任务完成"); + } + +} diff --git a/src/main/java/top/lidee/project/monitor/job/util/HttpXml.java b/src/main/java/top/lidee/project/monitor/job/util/HttpXml.java new file mode 100644 index 0000000..eb7ba5e --- /dev/null +++ b/src/main/java/top/lidee/project/monitor/job/util/HttpXml.java @@ -0,0 +1,140 @@ +package top.lidee.project.monitor.job.util; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + + +public class HttpXml { + + /* + * 数据请求 + * @param requesturl 请求地址 + * @param soapXML 请求报文 + * */ + public static Map webServiceExecute(String requesturl, String soapXML) throws Exception { + HttpURLConnection conn = null; + OutputStream os = null; + Map reslut = new HashMap<>(); + try { + //1创建连接 + URL url = new URL(requesturl); + conn = (HttpURLConnection) url.openConnection(); + //2设置连接的配置 + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", "text/xml;charset=utf-8"); + conn.setDoInput(true); + conn.setDoOutput(true); + //3打开连接发送数据 + os = conn.getOutputStream(); + os.write(soapXML.getBytes()); + + String temp = null; + //4接收服务端的响应 + //4.1http的响应码 + int responseCode = conn.getResponseCode(); + //4.2获取返回报文 + BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); + StringBuilder response = new StringBuilder(); + String responseLine; + while ((responseLine = br.readLine()) != null) { + response.append(responseLine.trim()); + } + //简单模拟封装返回结果 + reslut.put("httpStatus", responseCode); + reslut.put("data", response.toString()); + + } catch (Exception e) { + throw new RuntimeException("调用"+requesturl+"webservice出错!", e); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + return reslut; + } + + /** + * 构建Soap请求报文 + * @param interfaceCode 接口编码 + * @param taxId 税号 + * @param authCode 授权码 + * @param content 内层业务报文的明文 + */ + public static String bulidSoapXML(String interfaceCode, String taxId, String authCode, String content, String ffmc) { + //数据报文通过base64编码 + String payload = Base64.getEncoder().encodeToString(content.getBytes(StandardCharsets.UTF_8)); + return "\n" + + "\n" + + "\n" + + "\n" + + "\t \t\n" + + "\t \t\t\n" + + "\t\t1.0 \t\t\n" + + "\t\t" + taxId + " \t\t\n" + + "\t\t" + interfaceCode + " \t\t\n" + + "\t\t" + authCode + " \t\n" + + "\t \t\n" + + "\t\t \t\t\n" + + "\t\t0000 \t\t\n" + + "\t\t \t\n" + + "\t \t\n" + + "\t \t\t\n" + + "\t\t \t\t\t\n" + + "\t\t\t0 \t\t\t\n" + + "\t\t\t0 \t\t\t\n" + + "\t\t\t0 \t\t\n" + + "\t\t \t\t\n" + + "\t\t" + payload + "\t\n" + + "\t\n" + + "\t]]>\n" + + "\t\n" + + + "\n" + + "\n" + + ""; + } + + public static HashMap parseResponseXML(String resData){ + try { + // 获取DocumentBuilderFactory实例 + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + // 配置安全选项防止XXE攻击 + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + // 创建DocumentBuilder + DocumentBuilder builder = factory.newDocumentBuilder(); + // 解析XML文件 + Document document = builder.parse(new ByteArrayInputStream(resData.getBytes(StandardCharsets.UTF_8))); + // 获取根元素 + Element root = document.getDocumentElement(); + // 获取out节点 + Node out = root.getFirstChild().getFirstChild().getFirstChild(); + Document outXML = builder.parse(new ByteArrayInputStream(out.getTextContent().getBytes(StandardCharsets.UTF_8))); + // 解析返回的业务处理结果 + String returnCode = outXML.getElementsByTagName("returnCode").item(0).getTextContent(); + String returnMessage = outXML.getElementsByTagName("returnMessage").item(0).getTextContent(); + String data = outXML.getElementsByTagName("content").item(0).getTextContent(); + HashMap resMap = new HashMap<>(); + resMap.put("returnCode", returnCode); + resMap.put("returnMessage", new String(Base64.getDecoder().decode(returnMessage))); + resMap.put("data", new String(Base64.getDecoder().decode(data))); + return resMap; + }catch (Exception e){ + throw new RuntimeException("解析返回报文报错",e); + } + + } +} diff --git a/src/main/java/top/lidee/project/monitor/job/util/InvoiceXmlGenerator.java b/src/main/java/top/lidee/project/monitor/job/util/InvoiceXmlGenerator.java new file mode 100644 index 0000000..60035d4 --- /dev/null +++ b/src/main/java/top/lidee/project/monitor/job/util/InvoiceXmlGenerator.java @@ -0,0 +1,86 @@ +package top.lidee.project.monitor.job.util; + + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; +import java.io.StringWriter; +import java.util.List; + +public class InvoiceXmlGenerator { + + // ========== XML 映射类 ========== + @XmlRootElement(name = "REQUEST_COMMON_DJLR") + @XmlType(propOrder = {"commonDjlrFpts"}) + public static class RequestCommonDjlr { + private CommonDjlrFpts commonDjlrFpts; + @XmlElement(name = "COMMON_DJLR_FPTS") + public CommonDjlrFpts getCommonDjlrFpts() { return commonDjlrFpts; } + public void setCommonDjlrFpts(CommonDjlrFpts commonDjlrFpts) { this.commonDjlrFpts = commonDjlrFpts; } + } + + @XmlType(propOrder = {"commonDjlrFptList"}) + public static class CommonDjlrFpts { + private List commonDjlrFptList; + @XmlElement(name = "COMMON_DJLR_FPT") + public List getCommonDjlrFptList() { return commonDjlrFptList; } + public void setCommonDjlrFptList(List commonDjlrFptList) { this.commonDjlrFptList = commonDjlrFptList; } + } + + @XmlType(propOrder = {"djh", "djrq", "fpzl", "kplx", "gmfMc", "hsbz", "kpr", "skr", "fhr", "bz", "zzbm", "createPersonName", "commonDjlrXmxxs"}) + public static class CommonDjlrFpt { + private String djh, djrq, fpzl, kplx, gmfMc, hsbz, kpr, skr, fhr, bz, zzbm, createPersonName; + private CommonDjlrXmxxs commonDjlrXmxxs; + // getter/setter (均使用 @XmlElement 指定标签名) + @XmlElement(name = "DJH") public String getDjh() { return djh; } public void setDjh(String djh) { this.djh = djh; } + @XmlElement(name = "DJRQ") public String getDjrq() { return djrq; } public void setDjrq(String djrq) { this.djrq = djrq; } + @XmlElement(name = "FPZL") public String getFpzl() { return fpzl; } public void setFpzl(String fpzl) { this.fpzl = fpzl; } + @XmlElement(name = "KPLX") public String getKplx() { return kplx; } public void setKplx(String kplx) { this.kplx = kplx; } + @XmlElement(name = "GMF_MC") public String getGmfMc() { return gmfMc; } public void setGmfMc(String gmfMc) { this.gmfMc = gmfMc; } + @XmlElement(name = "HSBZ") public String getHsbz() { return hsbz; } public void setHsbz(String hsbz) { this.hsbz = hsbz; } + @XmlElement(name = "KPR") public String getKpr() { return kpr; } public void setKpr(String kpr) { this.kpr = kpr; } + @XmlElement(name = "SKR") public String getSkr() { return skr; } public void setSkr(String skr) { this.skr = skr; } + @XmlElement(name = "FHR") public String getFhr() { return fhr; } public void setFhr(String fhr) { this.fhr = fhr; } + @XmlElement(name = "BZ") public String getBz() { return bz; } public void setBz(String bz) { this.bz = bz; } + @XmlElement(name = "ZZBM") public String getZzbm() { return zzbm; } public void setZzbm(String zzbm) { this.zzbm = zzbm; } + @XmlElement(name = "CREATE_PERSON_NAME") public String getCreatePersonName() { return createPersonName; } public void setCreatePersonName(String createPersonName) { this.createPersonName = createPersonName; } + @XmlElement(name = "COMMON_DJLR_XMXXS") public CommonDjlrXmxxs getCommonDjlrXmxxs() { return commonDjlrXmxxs; } public void setCommonDjlrXmxxs(CommonDjlrXmxxs commonDjlrXmxxs) { this.commonDjlrXmxxs = commonDjlrXmxxs; } + } + + @XmlType(propOrder = {"commonDjlrXmxxList"}) + public static class CommonDjlrXmxxs { + private List commonDjlrXmxxList; + @XmlElement(name = "COMMON_DJLR_XMXX") + public List getCommonDjlrXmxxList() { return commonDjlrXmxxList; } + public void setCommonDjlrXmxxList(List commonDjlrXmxxList) { this.commonDjlrXmxxList = commonDjlrXmxxList; } + } + + @XmlType(propOrder = {"xh", "xmmc", "ggxh", "dw", "xmsl", "xmje", "sl", "zkje"}) + public static class CommonDjlrXmxx { + private String xh, xmmc, ggxh, dw, xmsl, xmje, sl, zkje; + @XmlElement(name = "XH") public String getXh() { return xh; } public void setXh(String xh) { this.xh = xh; } + @XmlElement(name = "XMMC") public String getXmmc() { return xmmc; } public void setXmmc(String xmmc) { this.xmmc = xmmc; } + @XmlElement(name = "GGXH") public String getGgxh() { return ggxh; } public void setGgxh(String ggxh) { this.ggxh = ggxh; } + @XmlElement(name = "DW") public String getDw() { return dw; } public void setDw(String dw) { this.dw = dw; } + @XmlElement(name = "XMSL") public String getXmsl() { return xmsl; } public void setXmsl(String xmsl) { this.xmsl = xmsl; } + @XmlElement(name = "XMJE") public String getXmje() { return xmje; } public void setXmje(String xmje) { this.xmje = xmje; } + @XmlElement(name = "SL") public String getSl() { return sl; } public void setSl(String sl) { this.sl = sl; } + @XmlElement(name = "ZKJE") public String getZkje() { return zkje; } public void setZkje(String zkje) { this.zkje = zkje; } + } + + // ========== 生成 XML 字符串的方法 ========== + public static String generateXml(RequestCommonDjlr request) throws JAXBException { + JAXBContext context = JAXBContext.newInstance(RequestCommonDjlr.class); + Marshaller marshaller = context.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); + + StringWriter sw = new StringWriter(); + marshaller.marshal(request, sw); + return sw.toString(); + } + +} \ No newline at end of file diff --git a/src/main/java/top/lidee/project/monitor/job/util/InvoiceXmlSy.java b/src/main/java/top/lidee/project/monitor/job/util/InvoiceXmlSy.java new file mode 100644 index 0000000..3f5ed5b --- /dev/null +++ b/src/main/java/top/lidee/project/monitor/job/util/InvoiceXmlSy.java @@ -0,0 +1,68 @@ +package top.lidee.project.monitor.job.util; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.StringWriter; +import java.util.List; + +public class InvoiceXmlSy { + + /** + * 生成 XML 字符串 + * @param billNoList 单据号列表,每个元素将生成一个 节点 + * @return 格式化的 XML 字符串(不含 XML 声明) + * @throws ParserConfigurationException XML 解析器配置异常 + * @throws TransformerException XML 转换异常 + */ + public static String generateDwzsXml(List billNoList) throws ParserConfigurationException, TransformerException { + // 1. 创建 Document 对象 + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.newDocument(); + + // 2. 创建根元素 REQUEST_COMMON_DWZS,并设置 class 属性 + Element root = doc.createElement("REQUEST_COMMON_DWZS"); + root.setAttribute("class", "REQUEST_COMMON_DWZS"); + doc.appendChild(root); + + // 3. 创建 COMMON_DWZS_HEADERS 元素 + Element headers = doc.createElement("COMMON_DWZS_HEADERS"); + root.appendChild(headers); + + // 4. 遍历单据号列表,为每个单据号创建一个 COMMON_DWZS_HEADER + for (String billNo : billNoList) { + Element header = doc.createElement("COMMON_DWZS_HEADER"); + header.setAttribute("class", "COMMON_DWZS_HEADER"); + headers.appendChild(header); + + Element billNoElem = doc.createElement("BILLNO"); + // 如果单据号为 null,则设为空字符串,避免 null 值导致异常 + billNoElem.setTextContent(billNo != null ? billNo : ""); + header.appendChild(billNoElem); + } + + // 5. 将 Document 对象转换为格式化的 XML 字符串 + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + + // 设置输出属性:省略 XML 声明,启用缩进(4空格) + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); + + StringWriter writer = new StringWriter(); + transformer.transform(new DOMSource(doc), new StreamResult(writer)); + return writer.toString(); + } + +} diff --git a/src/main/java/top/lidee/project/system/record/controller/GrInvoiceRecordController.java b/src/main/java/top/lidee/project/system/record/controller/GrInvoiceRecordController.java new file mode 100644 index 0000000..a849c97 --- /dev/null +++ b/src/main/java/top/lidee/project/system/record/controller/GrInvoiceRecordController.java @@ -0,0 +1,136 @@ +package top.lidee.project.system.record.controller; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.*; +import top.lidee.framework.aspectj.lang.annotation.Log; +import top.lidee.framework.aspectj.lang.enums.BusinessType; +import top.lidee.framework.web.controller.BaseController; +import top.lidee.framework.web.domain.AjaxResult; +import top.lidee.common.utils.poi.ExcelUtil; +import top.lidee.framework.web.page.TableDataInfo; +import top.lidee.project.system.record.domain.GrInvoiceRecord; +import top.lidee.project.system.record.service.IGrInvoiceRecordService; + +/** + * 发票Controller + * + * @author yuheng + * @date 2026-04-02 + */ +@Controller +@RequestMapping("/system/record") +public class GrInvoiceRecordController extends BaseController +{ + private String prefix = "system/record"; + + @Autowired + private IGrInvoiceRecordService grInvoiceRecordService; + + @RequiresPermissions("system:record:view") + @GetMapping() + public String record() + { + return prefix + "/record"; + } + + /** + * 查询发票列表 + */ + @RequiresPermissions("system:record:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(GrInvoiceRecord grInvoiceRecord) + { + startPage(); + List list = grInvoiceRecordService.selectGrInvoiceRecordList(grInvoiceRecord); + return getDataTable(list); + } + + /** + * 导出发票列表 + */ + @RequiresPermissions("system:record:export") + @Log(title = "发票", businessType = BusinessType.EXPORT) + @PostMapping("/export") + @ResponseBody + public AjaxResult export(GrInvoiceRecord grInvoiceRecord) + { + List list = grInvoiceRecordService.selectGrInvoiceRecordList(grInvoiceRecord); + ExcelUtil util = new ExcelUtil(GrInvoiceRecord.class); + return util.exportExcel(list, "发票数据"); + } + + /** + * 新增发票 + */ + @GetMapping("/add") + public String add() + { + return prefix + "/add"; + } + + /** + * 新增保存发票 + */ + @RequiresPermissions("system:record:add") + @Log(title = "发票", businessType = BusinessType.INSERT) + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(GrInvoiceRecord grInvoiceRecord) + { + return toAjax(grInvoiceRecordService.insertGrInvoiceRecord(grInvoiceRecord)); + } + + /** + * 修改发票 + */ + @RequiresPermissions("system:record:edit") + @GetMapping("/edit/{id}") + public String edit(@PathVariable("id") String id, ModelMap mmap) + { + GrInvoiceRecord grInvoiceRecord = grInvoiceRecordService.selectGrInvoiceRecordById(id); + mmap.put("grInvoiceRecord", grInvoiceRecord); + return prefix + "/edit"; + } + + /** + * 修改保存发票 + */ + @RequiresPermissions("system:record:edit") + @Log(title = "发票", businessType = BusinessType.UPDATE) + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(GrInvoiceRecord grInvoiceRecord) + { + return toAjax(grInvoiceRecordService.updateGrInvoiceRecord(grInvoiceRecord)); + } + + /** + * 删除发票 + */ + @RequiresPermissions("system:record:remove") + @Log(title = "发票", businessType = BusinessType.DELETE) + @PostMapping( "/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + return toAjax(grInvoiceRecordService.deleteGrInvoiceRecordByIds(ids)); + } + + /** + * 批量处理发票 + */ + @RequiresPermissions("system:record:list") + @Log(title = "发票", businessType = BusinessType.UPDATE) + @PostMapping("/batchProcess") + @ResponseBody + public AjaxResult batchProcess(@RequestParam List idList) + { + return toAjax(grInvoiceRecordService.batchProcess(idList)); + } + +} diff --git a/src/main/java/top/lidee/project/system/record/domain/GrInvoiceRecord.java b/src/main/java/top/lidee/project/system/record/domain/GrInvoiceRecord.java new file mode 100644 index 0000000..3c1e6a9 --- /dev/null +++ b/src/main/java/top/lidee/project/system/record/domain/GrInvoiceRecord.java @@ -0,0 +1,51 @@ +package top.lidee.project.system.record.domain; + +import lombok.Data; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import top.lidee.framework.aspectj.lang.annotation.Excel; +import top.lidee.framework.web.domain.BaseEntity; + +import java.util.List; + +/** + * 发票对象 gr_invoice_record + * + * @author yuheng + * @date 2026-04-02 + */ +@Data +public class GrInvoiceRecord extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** */ + private Long id; + + /**单据号*/ + @Excel(name = "") + private String djh; + + /**单据日期*/ + @Excel(name = "") + private String djrq; + + /**发票种类*/ + @Excel(name = "") + private String fpzl; + + /**开票时间*/ + @Excel(name = "") + private String kpsj; + + /**开票状态*/ + @Excel(name = "") + private String status; + + private List statusList; + /**客户名称*/ + private String gmfMc; + + private String invNumber; + + +} diff --git a/src/main/java/top/lidee/project/system/record/mapper/GrInvoiceRecordMapper.java b/src/main/java/top/lidee/project/system/record/mapper/GrInvoiceRecordMapper.java new file mode 100644 index 0000000..e4c7991 --- /dev/null +++ b/src/main/java/top/lidee/project/system/record/mapper/GrInvoiceRecordMapper.java @@ -0,0 +1,65 @@ +package top.lidee.project.system.record.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Param; +import top.lidee.project.system.record.domain.GrInvoiceRecord; + +/** + * 发票Mapper接口 + * + * @author yuheng + * @date 2026-04-02 + */ +public interface GrInvoiceRecordMapper +{ + /** + * 查询发票 + * + * @param id 发票主键 + * @return 发票 + */ + public GrInvoiceRecord selectGrInvoiceRecordById(String id); + + /** + * 查询发票列表 + * + * @param grInvoiceRecord 发票 + * @return 发票集合 + */ + public List selectGrInvoiceRecordList(GrInvoiceRecord grInvoiceRecord); + + /** + * 新增发票 + * + * @param grInvoiceRecord 发票 + * @return 结果 + */ + public int insertGrInvoiceRecord(GrInvoiceRecord grInvoiceRecord); + + /** + * 修改发票 + * + * @param grInvoiceRecord 发票 + * @return 结果 + */ + public int updateGrInvoiceRecord(GrInvoiceRecord grInvoiceRecord); + + /** + * 删除发票 + * + * @param id 发票主键 + * @return 结果 + */ + public int deleteGrInvoiceRecordById(String id); + + /** + * 批量删除发票 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteGrInvoiceRecordByIds(String[] ids); + + List selectGrInvoiceRecordListByIdList(@Param("list") List idList); +} diff --git a/src/main/java/top/lidee/project/system/record/service/IGrInvoiceRecordService.java b/src/main/java/top/lidee/project/system/record/service/IGrInvoiceRecordService.java new file mode 100644 index 0000000..135f08b --- /dev/null +++ b/src/main/java/top/lidee/project/system/record/service/IGrInvoiceRecordService.java @@ -0,0 +1,63 @@ +package top.lidee.project.system.record.service; + +import java.util.List; +import top.lidee.project.system.record.domain.GrInvoiceRecord; + +/** + * 发票Service接口 + * + * @author yuheng + * @date 2026-04-02 + */ +public interface IGrInvoiceRecordService +{ + /** + * 查询发票 + * + * @param id 发票主键 + * @return 发票 + */ + public GrInvoiceRecord selectGrInvoiceRecordById(String id); + + /** + * 查询发票列表 + * + * @param grInvoiceRecord 发票 + * @return 发票集合 + */ + public List selectGrInvoiceRecordList(GrInvoiceRecord grInvoiceRecord); + + /** + * 新增发票 + * + * @param grInvoiceRecord 发票 + * @return 结果 + */ + public int insertGrInvoiceRecord(GrInvoiceRecord grInvoiceRecord); + + /** + * 修改发票 + * + * @param grInvoiceRecord 发票 + * @return 结果 + */ + public int updateGrInvoiceRecord(GrInvoiceRecord grInvoiceRecord); + + /** + * 批量删除发票 + * + * @param ids 需要删除的发票主键集合 + * @return 结果 + */ + public int deleteGrInvoiceRecordByIds(String ids); + + /** + * 删除发票信息 + * + * @param id 发票主键 + * @return 结果 + */ + public int deleteGrInvoiceRecordById(String id); + + int batchProcess(List idList); +} diff --git a/src/main/java/top/lidee/project/system/record/service/impl/GrInvoiceRecordServiceImpl.java b/src/main/java/top/lidee/project/system/record/service/impl/GrInvoiceRecordServiceImpl.java new file mode 100644 index 0000000..1959483 --- /dev/null +++ b/src/main/java/top/lidee/project/system/record/service/impl/GrInvoiceRecordServiceImpl.java @@ -0,0 +1,280 @@ +package top.lidee.project.system.record.service.impl; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.sql.*; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.collections.CollectionUtils; +import org.springframework.beans.factory.annotation.Value; +import top.lidee.common.constant.InvoiceConstants; +import top.lidee.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import top.lidee.common.utils.StringUtils; +import top.lidee.project.monitor.job.util.HttpXml; +import top.lidee.project.monitor.job.util.InvoiceXmlSy; +import top.lidee.project.system.record.mapper.GrInvoiceRecordMapper; +import top.lidee.project.system.record.domain.GrInvoiceRecord; +import top.lidee.project.system.record.service.IGrInvoiceRecordService; +import top.lidee.common.utils.text.Convert; + +import javax.annotation.Resource; + +/** + * 发票Service业务层处理 + * + * @author yuheng + * @date 2026-04-02 + */ +@Service +public class GrInvoiceRecordServiceImpl implements IGrInvoiceRecordService { + @Autowired + private GrInvoiceRecordMapper grInvoiceRecordMapper; + + @Value("${invoice.url}") + private String URL; + + @Value("${invoice.eiInvWebService}") + private String eiInvWebServiceUrl; + /** + * 数据库用户名 + */ + @Value("${invoice.username}") + private String USERNAME; + /** + * 数据库密码 + */ + @Value("${invoice.password}") + private String PASSWORD; + @Value("${invoice.taxId}") + private String taxId; + @Value("${invoice.authCode}") + private String authCode; + @Value("${invoice.updateCode}") + private String interfaceCode; + private static final String fPath = "C:\\log\\fplog.log"; + + + /** + * 查询发票 + * + * @param id 发票主键 + * @return 发票 + */ + @Override + public GrInvoiceRecord selectGrInvoiceRecordById(String id) { + return grInvoiceRecordMapper.selectGrInvoiceRecordById(id); + } + + /** + * 查询发票列表 + * + * @param grInvoiceRecord 发票 + * @return 发票 + */ + @Override + public List selectGrInvoiceRecordList(GrInvoiceRecord grInvoiceRecord) { + if (StringUtils.isNotBlank(grInvoiceRecord.getStatus()) && InvoiceConstants.UNTREATED.equals(grInvoiceRecord.getStatus())) { + List statusList = new ArrayList<>(); + statusList.add(InvoiceConstants.UNTREATED); + statusList.add(InvoiceConstants.PROCESSED_NOT_SUCCESSFUL); + grInvoiceRecord.setStatus(null); + grInvoiceRecord.setStatusList(statusList); + } + return grInvoiceRecordMapper.selectGrInvoiceRecordList(grInvoiceRecord); + } + + /** + * 新增发票 + * + * @param grInvoiceRecord 发票 + * @return 结果 + */ + @Override + public int insertGrInvoiceRecord(GrInvoiceRecord grInvoiceRecord) { + grInvoiceRecord.setCreateTime(DateUtils.getNowDate()); + return grInvoiceRecordMapper.insertGrInvoiceRecord(grInvoiceRecord); + } + + /** + * 修改发票 + * + * @param grInvoiceRecord 发票 + * @return 结果 + */ + @Override + public int updateGrInvoiceRecord(GrInvoiceRecord grInvoiceRecord) { + grInvoiceRecord.setUpdateTime(DateUtils.getNowDate()); + return grInvoiceRecordMapper.updateGrInvoiceRecord(grInvoiceRecord); + } + + /** + * 批量删除发票 + * + * @param ids 需要删除的发票主键 + * @return 结果 + */ + @Override + public int deleteGrInvoiceRecordByIds(String ids) { + return grInvoiceRecordMapper.deleteGrInvoiceRecordByIds(Convert.toStrArray(ids)); + } + + /** + * 删除发票信息 + * + * @param id 发票主键 + * @return 结果 + */ + @Override + public int deleteGrInvoiceRecordById(String id) { + return grInvoiceRecordMapper.deleteGrInvoiceRecordById(id); + } + + @Override + public int batchProcess(List idList) { + if (CollectionUtils.isEmpty(idList)) { + return 1; + } + List grInvoiceRecords = grInvoiceRecordMapper.selectGrInvoiceRecordListByIdList(idList); + if (CollectionUtils.isEmpty(grInvoiceRecords)) { + System.out.println("没有待请求数据"); + return 1; + } + try { + for (GrInvoiceRecord invoiceRecord : grInvoiceRecords) { + String key = invoiceRecord.getDjh(); + String txtCon = ""; + List billNos = new ArrayList<>(); + billNos.add(key); + + System.out.println("溯源 Id:" + billNos); + txtCon += "溯源 Id:" + billNos + "\n"; + //封装 请求报文 + String xmldata = InvoiceXmlSy.generateDwzsXml(billNos); + + // 组装接口需要的请求报文 + String payload = HttpXml.bulidSoapXML(interfaceCode, taxId, authCode, xmldata, "eiInterface"); + System.out.println(payload); + boolean hasError = false; + try { + // 调用接口 + Map res = HttpXml.webServiceExecute(eiInvWebServiceUrl, payload); + System.out.println("接口请求状态:" + res.get("httpStatus")); + System.out.println("接口返回的原始报文:" + res.get("data")); + txtCon += "接口请求状态:" + res.get("httpStatus") + "\n"; + txtCon += "接口返回的原始报文:" + res.get("data") + "\n"; + if (200 == (int) res.get("httpStatus")) { + try { + // 解析接口返回的报文 + HashMap serviceData = HttpXml.parseResponseXML((String) res.get("data")); + System.out.println("业务处理返回代码:" + serviceData.get("returnCode")); + System.out.println("业务处理返回提示:" + serviceData.get("returnMessage")); + System.out.println("业务处理返回数据:" + serviceData.get("data")); + + txtCon += "业务处理返回代码:" + serviceData.get("returnCode") + "\n"; + txtCon += "业务处理返回提示:" + serviceData.get("returnMessage") + "\n"; + txtCon += "业务处理返回数据:" + serviceData.get("data") + "\n"; +// appendToFile(fPath, txtCon); + String invNumber = "1"; + String xml = serviceData.get("data"); + Pattern pattern = Pattern.compile("(.*?)"); + Matcher matcher = pattern.matcher(xml); + if (matcher.find()) { + invNumber = matcher.group(1); + } + + // 如果没有被开发票(invNumber 为 1) + if (invNumber.equals("1")) { + System.out.println("单据号 " + key + " 还未开票"); + continue; + } + + if (serviceData.get("returnCode").equals("0000")) { + //更新数据库 + Connection conn = null; + Statement stmt = null; + ResultSet rs = null; + try { + System.out.println("更新视图开始"); + // 1. 加载 Oracle JDBC 驱动 + Class.forName("oracle.jdbc.driver.OracleDriver"); + // 2. 建立数据库连接 + conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); + // 3. 创建 Statement 对象 + stmt = conn.createStatement(); + // 4. 执行查询语句 + String sql = "update gr_dp_saset_sdhm_v a set a.zxcolumn1 = '" + invNumber + "' where a.sasettleid =" + key; + PreparedStatement pstmt = conn.prepareStatement(sql); + int rowsA = pstmt.executeUpdate(); + System.out.println("完成更新视图:" + rowsA); + invoiceRecord.setStatus(InvoiceConstants.SUCCESSFUL); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + invoiceRecord.setKpsj(sdf.format(new Date())); + invoiceRecord.setInvNumber(invNumber); + grInvoiceRecordMapper.updateGrInvoiceRecord(invoiceRecord); + } catch (Exception e) { + e.printStackTrace(); + // 发生错误,标记为已尝试但不需要重试 + hasError = true; + // 6. 关闭资源 + try { + if (rs != null) rs.close(); + if (stmt != null) stmt.close(); + if (conn != null) conn.close(); + } catch (SQLException e11) { + e11.printStackTrace(); + } + } + } else { + // 业务处理失败,标记为已尝试但不需要重试 + hasError = true; + System.out.println("业务处理失败:" + serviceData.get("returnMessage")); + } + } catch (Exception e) { + System.out.println("解析报文出错的,保持业务为处理中状态,需要检查原因"); + e.printStackTrace(); + hasError = true; + } + } else { + // HTTP 请求状态不是 200,标记为已尝试但不需要重试 + hasError = true; + System.out.println("HTTP 请求失败,状态码:" + res.get("httpStatus")); + } + } catch (Exception e) { + e.printStackTrace(); + hasError = true; + } + // 如果发生错误,更新状态为 1(已尝试但不需要重试) + if (hasError) { + invoiceRecord.setStatus(InvoiceConstants.PROCESSED_NOT_SUCCESSFUL); + grInvoiceRecordMapper.updateGrInvoiceRecord(invoiceRecord); + System.out.println("单据号 " + key + " 处理失败,标记为已尝试状态"); + } + + } + } catch (Exception e) { + e.printStackTrace(); + } + return 1; + } + + + // 追加写入模式 + public static void appendToFile(String filePath, String content) throws IOException { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath, true))) { + writer.newLine(); // 换行后再追加 + writer.write(content); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/resources/application-druid.yml b/src/main/resources/application-druid.yml index 3cfe970..6035786 100644 --- a/src/main/resources/application-druid.yml +++ b/src/main/resources/application-druid.yml @@ -7,7 +7,8 @@ spring: druid: # 主库数据源 root master: - url: jdbc:sqlite:D:/uploadPath/SQLite/sqlite3.db?date_string_format=yyyy-MM-dd HH:mm:ss +# url: jdbc:sqlite:D:/work/gr_fapiao/sql/sqlite3.db?date_string_format=yyyy-MM-dd HH:mm:ss + url: jdbc:sqlite:c:/SQLite/sql/sqlite3.db?date_string_format=yyyy-MM-dd HH:mm:ss username: password: # 从库数据源 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9470c32..570f94f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -150,4 +150,23 @@ gen: # 自动去除表前缀,默认是true autoRemovePre: true # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) - tablePrefix: sys_ \ No newline at end of file + tablePrefix: sys_ +invoice: + # 开票系统地址 + url: jdbc:oracle:thin:@192.168.1.247:1521:gryy + # 开票系统用户名 + username: GRYYINV + # 开票系统密码 + password: xxb147258368 + + taxId: 9134040072334670XN + + authCode: 7G5X2YSSMA + + interfaceCode: DJLR + + updateCode: DWZS + + eiInvWebService: https://csxtqd.nuocity.com/open/services/eiInvWebService?wsdl + + iBillInterfaceWebService: https://csxtqd.nuocity.com/open/services/iBillInterfaceWebService?wsdl diff --git a/src/main/resources/mybatis/system/GrInvoiceRecordMapper.xml b/src/main/resources/mybatis/system/GrInvoiceRecordMapper.xml new file mode 100644 index 0000000..68c7e45 --- /dev/null +++ b/src/main/resources/mybatis/system/GrInvoiceRecordMapper.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + select id, + djh, + djrq, + fpzl, + kpsj, + create_by, + create_time, + update_by, + update_time, + status, + gmf_mc, + inv_number + from gr_invoice_record + + + + + + + + insert into gr_invoice_record + + id, + djh, + djrq, + fpzl, + kpsj, + create_by, + create_time, + update_by, + update_time, + status, + gmf_mc, + inv_number, + + + #{id}, + #{djh}, + #{djrq}, + #{fpzl}, + #{kpsj}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{status}, + #{gmfMc}, + #{invNumber}, + + + + + update gr_invoice_record + + djh = #{djh}, + djrq = #{djrq}, + fpzl = #{fpzl}, + kpsj = #{kpsj}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + status = #{status}, + gmf_mc = #{gmfMc}, + inv_number = #{invNumber}, + + where id = #{id} + + + + delete + from gr_invoice_record + where id = #{id} + + + + delete from gr_invoice_record where id in + + #{id} + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 91f74e6..0067c5a 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -26,7 +26,7 @@