From a54693a7bf35a16cd1a6533c098d2f1ac2cb979b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9A=93=E6=9C=88=E5=BD=92=E5=B0=98?= Date: Tue, 4 Mar 2025 03:35:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=9B=B4=E5=A4=9A?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=B1=BB=E5=9E=8B=E6=94=AF=E6=8C=81=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=85=A8=E9=83=A8=E9=80=9A=E8=BF=87=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/code.py | 110 +++++++++++++++++++++++++++++----- assets/templates/上传模版.csv | 13 ++++ assets/templates/上传模版.xls | Bin 0 -> 19968 bytes assets/templates/查询模版.csv | 18 ++++++ assets/templates/查询模版.xls | Bin 0 -> 19968 bytes 5 files changed, 125 insertions(+), 16 deletions(-) create mode 100644 assets/templates/上传模版.csv create mode 100644 assets/templates/上传模版.xls create mode 100644 assets/templates/查询模版.csv create mode 100644 assets/templates/查询模版.xls diff --git a/api/code.py b/api/code.py index 098bd84..67dd768 100644 --- a/api/code.py +++ b/api/code.py @@ -36,31 +36,47 @@ codeAPI = APIRouter( ) -@codeAPI.get("/template", summary="获取上传编码模板") +@codeAPI.get("/template/{type}", summary="获取上传编码模板") @Log(title="获取上传编码模板", business_type=BusinessType.SELECT) @Auth(permission_list=["code:btn:uploadTemplate"]) -async def get_upload_template(request: Request, current_user=Depends(LoginController.get_current_user)): - template_path = os.path.join(os.path.abspath(os.getcwd()), 'assets', 'templates', '上传模版.xlsx') +async def get_upload_template(request: Request, type: str = Path(description="文件类型"), + current_user=Depends(LoginController.get_current_user)): + if type not in ["xlsx", "xls", "csv"]: + raise ServiceException(message="文件类型错误!") + template_path = os.path.join(os.path.abspath(os.getcwd()), 'assets', 'templates', f'上传模版.{type}') + media_type = { + "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "xls": "application/vnd.ms-excel", + "csv": "text/csv" + }.get(type) if not os.path.exists(template_path): raise ServiceException(message="文件不存在!") return FileResponse( path=template_path, - filename="上传模版.xlsx", - media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + filename=f"上传模版.{type}", + media_type=media_type ) -@codeAPI.get("/queryTemplate", summary="获取查询编码模板") +@codeAPI.get("/queryTemplate/{type}", summary="获取查询编码模板") @Log(title="获取查询编码模板", business_type=BusinessType.SELECT) @Auth(permission_list=["code:btn:queryTemplate"]) -async def get_query_template(request: Request, current_user=Depends(LoginController.get_current_user)): - template_path = os.path.join(os.path.abspath(os.getcwd()), 'assets', 'templates', '查询模版.xlsx') +async def get_query_template(request: Request, type: str = Path(description="文件类型"), + current_user=Depends(LoginController.get_current_user)): + if type not in ["xlsx", "xls", "csv"]: + raise ServiceException(message="文件类型错误!") + template_path = os.path.join(os.path.abspath(os.getcwd()), 'assets', 'templates', f'查询模版.{type}') if not os.path.exists(template_path): raise ServiceException(message="文件不存在!") + media_type = { + "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "xls": "application/vnd.ms-excel", + "csv": "text/csv" + }.get(type) return FileResponse( path=template_path, - filename="查询模版.xlsx", - media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + filename=f"查询模版.{type}", + media_type=media_type ) @@ -97,7 +113,17 @@ async def add_code_by_file(request: Request, id: str = Path(description="文件I uploader_id = await file.first().values(id="uploader__id") if str(uploader_id["id"]) == user_id: try: - df = pd.read_excel(file.absolute_path, dtype={"code": str}) + media_type = { + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":"excel", + "application/vnd.ms-excel":"excel", + "text/csv": "csv" + } + if not media_type.get(file.file_type): + raise ServiceException(message="文件类型错误!") + if media_type.get(file.file_type) == "excel": + df = pd.read_excel(file.absolute_path, dtype={"code": str}) + else: + df = pd.read_csv(file.absolute_path, dtype={"code": str}) df["code"] = df["code"].astype(str).str.zfill(8) for index, row in df.iterrows(): row["code"] = row["code"].replace(".", "").replace("/", "").replace("_", "").replace("-", @@ -370,7 +396,17 @@ async def get_code_list(request: Request, query_text = "" query_count = 0 dataList = [] - df = pd.read_excel(file.absolute_path) + media_type = { + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "excel", + "application/vnd.ms-excel": "excel", + "text/csv": "csv" + } + if not media_type.get(file.file_type): + raise ServiceException(message="文件类型错误!") + if media_type.get(file.file_type) == "excel": + df = pd.read_excel(file.absolute_path, dtype={"code": str}) + else: + df = pd.read_csv(file.absolute_path, dtype={"code": str}) for index, row in df.iterrows(): query_count += 1 query_text += row["text"] + "\n" @@ -821,13 +857,14 @@ async def feedback_audit(request: Request, ) if code: await request.app.state.es.create(index=ElasticSearchConfig.ES_INDEX, - id=code.id, - body={"id": code.id, - "code": code.code, - "description": code.description}) + id=code.id, + body={"id": code.id, + "code": code.code, + "description": code.description}) await feedback.save() return Response.success(msg="审核成功!") + @codeAPI.delete("/deleteCodeImport/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="删除编码导入") @codeAPI.post("/deleteCodeImport/{id}", response_class=JSONResponse, response_model=BaseResponse, @@ -991,3 +1028,44 @@ async def code_import_audit(request: Request, params: UpdateCodeImportStatusPara success, failed = await async_bulk(request.app.state.es, actions) logger.info(f"成功导入 {success} 条数据,失败 {failed} 条") return Response.success() + + +@codeAPI.put("/codeImportAudit/all", response_class=JSONResponse, response_model=BaseResponse, summary="全部审核通过") +@codeAPI.post("/codeImportAudit/all", response_class=JSONResponse, response_model=BaseResponse, summary="全部审核通过") +@Log(title="全部审核通过", business_type=BusinessType.UPDATE) +@Auth(permission_list=["code:btn:codeImportAuditAll"]) +async def code_import_audit_all(request: Request, current_user: dict = Depends(LoginController.get_current_user)): + sub_departments = current_user.get("sub_departments") + actions = [] + if codeImports := await CodeImport.filter(user__department__id__in=sub_departments, del_flag=1): + for codeImport in codeImports: + codeImport.status = 1 + code = codeImport.code.replace(".", "").replace("/", "").replace("_", "").replace("-", + "").replace( + ":", "").replace("?", "").replace(":", "").strip() + user_id = current_user.get("id") + codeInfo = await Code.create( + code=code, + description=codeImport.description, + user_id=user_id, + ) + if codeInfo: + # 构造 Bulk 导入数据 + actions.append( + { + "_index": ElasticSearchConfig.ES_INDEX, + "_id": codeInfo.id, # 以 code 作为 ID + "_source": { + "id": codeInfo.id, + "code": codeInfo.code, + "description": codeInfo.description + } + } + ) + await codeImport.save() + if await request.app.state.es.indices.exists(index=ElasticSearchConfig.ES_INDEX): + await request.app.state.es.indices.create(index=ElasticSearchConfig.ES_INDEX, ignore=400) + success, failed = await async_bulk(request.app.state.es, actions) + logger.info(f"成功导入 {success} 条数据,失败 {failed} 条") + return Response.success() + return Response.error() diff --git a/assets/templates/上传模版.csv b/assets/templates/上传模版.csv new file mode 100644 index 0000000..54e8abc --- /dev/null +++ b/assets/templates/上传模版.csv @@ -0,0 +1,13 @@ +code,description +01012100,Live purebred breeding horses +01012900,Live horses other than purebred breeding horses +01013000,Live asses +01019030,Mules and hinnies imported for immediate slaughter +01019040,Mules and hinnies not imported for immediate slaughter +01022100,Live purebred breeding cattle +01022920,Cows imported specially for dairy purposes +01022940,Live cattle other than purebred or those imported for dairy purposes +01023100,Live purebred breeding buffalo +01023900,"Live buffalo, other than purebred breeding animals" +01029000,"Live bovine animals, other than cattle and buffalo" +01031000,Live purebred breeding swine diff --git a/assets/templates/上传模版.xls b/assets/templates/上传模版.xls new file mode 100644 index 0000000000000000000000000000000000000000..0b554fa78b43015d0717f6b9ffe46f4694d682ad GIT binary patch literal 19968 zcmeHP2Urxz^6y<1SVSZUD4?(e1p$E-2_}+RF4k8Oz)K+jmuW= zv@9o_;77Dbl|ql`u;?PV#z+MtLTupz(^U$Ef+i9Im;VcYBM*E5T^*qEv>-|#YD2_2 z*M%sBs0XnQM16>LAsRre2hk8BGy!4^(F9_Bh^7$DAeuvL0I?xN3y78wtspjn*chTU zL>q`Qh}d>8+QW4dR{C$`!oRhueMmg~lAta+k}+@|ME9K&h@z~V85@^K8r{!`oXQ9Out6L_yL8Ba#TFO2eiSEz+3$|p!kBC&8i z1_cCyQ?>orcWYZd_UGTK@${t7i*>ZgVS{4blj$O&1D(H|h{+DgNjMVn0AeUf0Q*EM z7vv^443+(7LJ*}xmH?I<})mB0|y=2734c2ZnFN z0Tkc&)X|PQ)`O#n?5AYzP!u_gUH5=nO6Fl@lIh^p*@OG`V(5yre=of*Rjx>_TvGvh z9UeVnKyz<$1YN_y5yNP#uD`1QzeIqJ{^7?b#E%Cb`TcqIA;e37t|vhE62O-V@EZ&8 z8wt?s3()lh=-hT9eBr%lA>h;sB3GCVzHdfEp~Rs^Ej5E$3MEbu+4NBGwzZ@cv`kBW5mg%z z5^e*n(~2w1D9v9golBS95lWV664#`G3aE^F7A54J0-sR{36KTI;65Tlx(Lz~ow)`6 z0UZZaK7JuOu=Dc&H`7JbT%g*|V%pCH8ad^+-)s7xK+of+>j}`C3DBK+={S2~>Wf|P zIHTdCE9)iS*w|d(T%SlRsdj?1A3lDZ5%JOSsfv$|Gbf6Eoj@xLfJqVUADm6`(Q$^w zqQhj0=2z08-Lmb%*%-yYz}R@P{$kMIozfp?Xbka~n&d z*zp82vG)mPVi#0qaxK7qNLxTn6=2o~1S(W3$rZp3suD;i>0E(Sj>%XfIMD(a`;P>0l}c5sIG1OFKnb{{r79sUEsa}3 zAjc=T{-p&E;B>pe**X)Lw!~8^7X%m3{$&tcfnz`v{Y#O5!o5m=`rfE{@W2#yQ;d;uhhEi;68Ku}X zMkzLpQHo7tlv=-jy_%=ki&@p4;&umIGp1WI*0&;Eqz1xcS$|H$bO2EaG)?u6i5g?4 zMI@V%mOJ2{z?!R7Kq%F4@7ATTnL!s$^xNZoiIYo zpw6W6ECA9RvQ$2)UKXe17?4^tHa8YXQSeuSxU)dyo_rvbTGa;ub39le^s6LBh%tvr z<5>Vi4p}On;Eaw53`i|}(gI`{kfPwP1o32n$UXT$7|rl3U`|UG2>oh}5n{|?(s&jC z@q#SIoQNo0mIMP*3!iwiK#GFD62ylEBKPD2VJRnMjxP&@e$~hbG3GF7JPY`w6=bP= z!hTR^Kx*NW)+~^s;I9N}!vc|e@__(@G67^){t9@V)50e>XGouLJSK=o`X zR}6Q8bfUjaoq7=d5nHI5DiL~Sqsjn2l!5BsK z;2Q*}i`==9A!^*EBT|Im8WOY9h``H z9NoWf`;fLVIZ|#NG=Mr#L5(zigig-WP3S8oy`i^UJjwz^oS!CES}fAr&__H zMo=*3(5zaeZp1(WfeCH_blfgSOY`Qx&wkW}oswJ81MN z18oNNq~=o`sJK3DqUKX(z_NwmQ=?B2j_cE=8hxsQZlT$ye9(>>eX4@?)a+9}XeW(6 zWuVQVo2dB|M{=%Do2mJf8PRRwy8?|qML4ccoi+MY1?{cbr+m;Z8hxsQ_R;K9K4@2s zK4qZIh_AX&y?A`uT+OFA+n@)%R+@c^a9p3dY4oWIy0vDX@#Yf11`DKMN3(ze6zN&9@uz`|7}EAQTX{BWAj<)2MUmvuK7uup zOfHSv`{8-eguDH7_dUPbH9ysNc6M;+5Ewe|z}SF*^#dOCsmjZ=bLyke zUh(3>nVyZ-m}jgS+0%2y&y&BkIbQjBzRN%xhnBu)LyZ=BpDH=)R5zh@gndp=%kmfB zR-PKuP&%{3c#Z4X?-S;9Oc}5EQn_taJBx=BUD?W6?XF8lG~0c2d1HA-n1kD{x&^OC z7o|*fIP&(wgm~7hOJ90zg)Gj@t(!{AqN9*|BVOl?y>sc4~_Vnu9V4i$!*3gureV<$JN&MYJJI-n2`V9?t_nS0x z(W+0`6Uw&)g*bGzJU?-IVT^RoXxnZ&`I62NCf&Vs@0>pWam@7T;iJ3sXfVUpacNwJ zO_a1w*rs=-zrGsUF3s}Dq^d`s%Xj~p<-fvzQc~4x-L;pD+je+%#(c_$;)1j(??PUG zy0(3oU)8<24ZN!szw)p8Q1IjD0H-zUJGviBd0VmD(f!!xNBiS#CYR|pGKfoA>F|xj z=k=h3*-grp)yp2$%KG)q+^2;zd`+Bm?pv2OwrF$0DzQWAfu_mRCSYB!=Fg#z{EkuFMlkVnKsUE_t^bLt=|>j589M_=d$axZC$50 z50)nvwO#GBFlK6BSFLy7_mIC&nK0MLaa&=;`K7~0SlqJ@tZy9O(O&L#b9d#h{Z`%Vc~tqVq%?;I(v`|3`zecQ4#gWG1g6i=%EGVRuyyD^Ja4Bt5GU6bcS zvcA2!`t<%|?c+B;PCMrAK3JX+WOb6h28Aw0Yp#82NX8 zIj1rv9h&L4$*td_qM3)g5z9Ii)cU)2j8 zQMZZD#MJu7B0|r5o9w)BXhY7R#wK=hL#M8m<=ZaI?^?HqSO1@!A6E6e>MPk2^KEnA z(Cfb(@9op1*Zm_QW4>GLX80y0JF6sWgV&k^!-xJl;B-{LvY~BT>+2+)i?>}hvYB(K zX;$YBC*B;l6fM85A3CtV%fxr?GaDr|ePv=%8NT4;!qbc9WX71=w;!@3ynSSYrq5b_ zb7Sh3p#%49AGKm^+YLt21FbS#PH8=!k!SSMu=DG2&C=S2j29+Y@pS@i=J{uP?bf}}*`_kpJh;KnqWVCo{$b$6m zD$X}}5_f6Zvc>t|Oq=~+O}kHneJ={T zIgHfb>oWFJ#c0v3-s?8sI%s-y)UTJd9}O6!-~Qp*_q}#^RBRot&_1D4x%$$c@{8j; zt@mggFzvlUB78H%8|JWak#ULC4O4YLS%0J8pNAmN8&=!!>m{1U4LLLC{y6fqN8PMf znMbExO${kI-LOfr`}%q9-Pfj)a;LNyJFB}^6?c0T>eM+b%~d=FZid- zJa4nk=?CroFU6E6w>zFT^+M*lf+o_zDGAfpPQJT$*pc2>*S0N|TdnW1&tsv9;f&k8 zj`Z0U6mj)}QAxLscUy)#q!r8`JM5;-k`)$Z-oe-2nfJNrGkn|JjTaQVPkR)t=yft} zyX)hQk}*CDf@2?ruiohXu=-_EPQ1?xv*`KV zoq9BN=;P!jUox(qW4GM=E3^82NZ;nWcJ6>jc562To z*BkD?s(s=RQ=fB_!W}NBT)Z;=LwfT#-2rnaxAM#D&?sj>W#R1lTS|;hmAUR%lGoVl zXQ$gi=hr75%bnDqWMNE9;v$d!-|D!gZf}wI)Usc^(Fg5LS3O+kp4ndEtY}w}{X_iV z=$&tp+gy~rZT0riN5?0g^Rhjw3@7Tf(OdE%zM@y}?jo@%yxC(2T?IJsQyw^@Y4h z_+fq?to`P(N(Q%30ZG}g);<7!Cq+_-u$|+%n0zK;ZTu<g)ll{vy;c8Te(uIUXb;!Ad+v0ENE7>sZ(l6%CXaYNJ#l zgYk*uve00Lm1etrXA{r+^~zH2iS;p zm*H(&#pawsLBXE(v|tfnC3Bt!m$gaMbdAOF^c~b3U*g%G^#spiAA(k@LafQ{3Rd9w@^$?P+dI}2mYm2gnJ!nY>l&y*Jp$9Deuxzf)IFtx2kJe2A1Aryz-;&-ejl(*(OszHREm-g zJWOZ;FQ(yzy68K|IY})PnL&CQ#hpPNn*w(f6`uGzvaa#{8y%kdH)CC|hIf^EpcTCD zgCFWdh$j`U`7>t`$%n=Idi@v@g_#d_n?%P6(Y6K?;3A zf$-rrlfpc-5A#q0_YX2D%tMd1n}!utq$~GFVKkprX$^P@M9n0|9`;$XF%tnld$qI zjTMDm5i13s>T!aIgBs@jFZ_KxfVU@+4l&FVmtudUI^HKkbMfV@b@@TCHzW-j$cgIj zh5yrM)Q9RWwK)S1OTQ}RC(bqLh=hDqHvDI0KFTAX(3vAo%4q)owln1?LOv_fDElf6 zaSYHSCj$w|aQiEUCv-4KlkSj3z)tJu@KUcr_0qj#2p#nSdkq4iLg|O9DbzpNN|XkF+=u!>JPnq~i@P zH(46pXIR5fqy^xi|GP>G&?4bs zw`LHsAN3QL7MJF`SsCRn2K!wGyg~83#jbT4j}(9;4MEJmHmstpDq6{Dpm>ecP}5?0TrN5 zu&raj_JdWFgb8R*Q{aH@2m2I0E9|IFBMtjdcW5`gV9^O%eFU_i5g-`^=U8a*5wKLm z^e5&yqbq3-RP{EAeF*z1&g#A3*qmA)a)Tr8?suc;xO#%0V<4{s#7FZ_aFb;FnV p!0Ji|;PZx5dzi&~Q8ZsjdqG|c$nyp?AEksR{6T{pWd47K{{sw>>ly$6 literal 0 HcmV?d00001 diff --git a/assets/templates/查询模版.csv b/assets/templates/查询模版.csv new file mode 100644 index 0000000..c7153d8 --- /dev/null +++ b/assets/templates/查询模版.csv @@ -0,0 +1,18 @@ +text +horses +Live purebred breeding cattle + + + + + + + + + + + + + + + diff --git a/assets/templates/查询模版.xls b/assets/templates/查询模版.xls new file mode 100644 index 0000000000000000000000000000000000000000..7d44296139ea659187a2924725928a20b385ada3 GIT binary patch literal 19968 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