From f33cf97975914ea9450b713df9c395c8c0a7508e Mon Sep 17 00:00:00 2001 From: Faheed Date: Wed, 22 Oct 2025 13:13:03 +0300 Subject: [PATCH] .... --- .../__pycache__/settings.cpython-312.pyc | Bin 8446 -> 8446 bytes .../__pycache__/urls.cpython-312.pyc | Bin 2313 -> 2342 bytes NorahUniversity/settings.py | 6 +- NorahUniversity/urls.py | 4 +- recruitment/__pycache__/admin.cpython-312.pyc | Bin 11915 -> 11903 bytes recruitment/__pycache__/forms.cpython-312.pyc | Bin 26564 -> 25868 bytes .../linkedin_service.cpython-312.pyc | Bin 16316 -> 16316 bytes .../__pycache__/models.cpython-312.pyc | Bin 61006 -> 60843 bytes .../__pycache__/signals.cpython-312.pyc | Bin 6657 -> 3138 bytes recruitment/__pycache__/urls.cpython-312.pyc | Bin 11879 -> 11888 bytes recruitment/__pycache__/views.cpython-312.pyc | Bin 93930 -> 92389 bytes .../views_frontend.cpython-312.pyc | Bin 23025 -> 24051 bytes recruitment/forms.py | 33 +- recruitment/migrations/0001_initial.py | 7 +- ...emove_jobposting_application_start_date.py | 17 + recruitment/models.py | 12 +- recruitment/signals.py | 2 +- recruitment/views.py | 94 +-- recruitment/views_frontend.py | 26 + templates/base.html | 34 +- templates/jobs/create_job.html | 229 ++++---- templates/jobs/edit_job.html | 229 ++++---- templates/jobs/job_detail.html | 268 ++++----- templates/jobs/job_list.html | 4 +- templates/meetings/meeting_details.html | 555 +++++++----------- templates/recruitment/candidate_detail.html | 8 +- .../candidate_resume_template.html | 27 + templates/recruitment/dashboard.html | 68 +++ 28 files changed, 753 insertions(+), 870 deletions(-) create mode 100644 recruitment/migrations/0002_remove_jobposting_application_start_date.py diff --git a/NorahUniversity/__pycache__/settings.cpython-312.pyc b/NorahUniversity/__pycache__/settings.cpython-312.pyc index 160b1cfceb5f7b32aab0dfe633c253c6fd66dfd9..42db791e7b29f27065ffeae054ec23e091f91874 100644 GIT binary patch delta 29 jcmez8_|K8|G%qg~0}ve9@;yUyBX6!eBgf`y`DR7{oIMGj delta 29 jcmez8_|K8|G%qg~0}yoif6H*$$eSzA$hEmzzL^mKlk^E3 diff --git a/NorahUniversity/__pycache__/urls.cpython-312.pyc b/NorahUniversity/__pycache__/urls.cpython-312.pyc index df79ce761314b4191b32d5bc38616df503fb4f47..17d54d837030259f9ed01a70db25de4911e8dcba 100644 GIT binary patch delta 264 zcmeAaS|-GMnwOW00SNwZ|H$y)$eYNbpqQ3llp9~3S(R9nqHj~2Q<`p7lA2qPlUR}( z58_R(XOWNuE5a(ff+aOtIk6y9AEv1on=(87TS7Vci7D}5%V0{YvUBMri+V06Y_G6H-elppzzG(zzo=(_AomJO$Yg5{U1hxutk(@ZE*f}T@K3(Nk^)v{ Yx}o&CdEiC!zzY$1S6K2VcXQMM06L*$@c;k- delta 235 zcmZ1`)G5S!nwOW00SJoNzGhf$ zHB=$7AX6Wvv{)Zqjh+52p`850l=!s#qTG0xx+-=2x;EFd?qE`sol$yS-t?lp=?>p3 zEM7NRI4*FiEJ(esW`0r4d`IpT7N5xx9J/', views.form_wizard_view, name='form_wizard'), - path('/submit/', views.submit_form, name='submit_form'), + path('form_wizard//', views.form_wizard_view, name='form_wizard'), + path('form//submit/', views.submit_form, name='submit_form'), path('api/templates/', views.list_form_templates, name='list_form_templates'), path('api/templates/save/', views.save_form_template, name='save_form_template'), diff --git a/recruitment/__pycache__/admin.cpython-312.pyc b/recruitment/__pycache__/admin.cpython-312.pyc index 0b0a860ce53c120f77f595c875d38c38198f10a5..cd0678a80dfb8fd30cebcd7c9b2f9d2d5c32b0d2 100644 GIT binary patch delta 469 zcmXAk%S%F05Qm!{he$36AwJSHAGb7uik9|()ZH5v~;B zx+rf2ZPF+tvw~>T9}z?(NvpbZI*X6rd^4OmXJ_GeUa2ZdTZ3^v^!n1)nW9;!8CRAe z{l_v+6vMFfi;@_yg(;3z+q*~_qnmD!)rJkbhZ4qs6{q$ibIfQ&CKz!>0&|W$&FC#1 z>mC1GIOKSQnzPTmCoMzv_w@nSG*Jri&M;+g+xba3T)4+5i*NU}Kf{Ca{wyaS z!_NpXb|vuUd8S=NTkR-y;=z0g^lL-BW+|0CBIj7yC8??i!P z0j+@pDj*%ulp7W$1Hq<99<%Bo9V4lF1gppmPF1V1>Rwevkwp{n;oJ<(jOrGz=L=YgqrW`96s}WpXcHI^1lCW&rN9Cn%3Up zf37%uP}C}dI`p}u(_J>}|_3qqDd6V+5o+O&V<>707t8jb*xgPRD3sLsZ#+4KjOzCz9tkjJFsxw}fOVacnMFLZ=I^{38p1YrSvng*X wpL1H>CRe`Vrx_6qmJM9%%TNQ=zN3hGDfK4mf;G<7@&@Yt3Hl*__s_Tf2l8yLbN~PV diff --git a/recruitment/__pycache__/forms.cpython-312.pyc b/recruitment/__pycache__/forms.cpython-312.pyc index 9725eb3dbdf5f2ae28daf02c5c52b51aac2fc152..7d31dceac68cb3d54970d89cf0bb043d4b5d0865 100644 GIT binary patch delta 2921 zcmaKue@t7~702Iw_A_9cAI9N_4aNpzz{Frf2n`Jo!jF<)7y@cqU{B<^AqF!p3$9?af5nh4VS8xDWp7|WYwY2Ttg9NiV@E$9;qN~QWp`p?r3u|05oZ3;A*Zm5PRI0QKv zb~Xe}8E9PzMPZagyiU;C& z#khXbBUeg(WHjZWC{`||%Bc@l77eH4+BAEO_O$=#Hw8MI0>J^Ff9DecsjslXJK(V= zN+Yl%>o};ajS#RFz=U-p+_O5{UAUEYUUJ|+=@epGRgKnwf0}PtKOFm{-#6eDEiD6{ z0dH_2ZCcpl=^gYA>#|E-4wuViDX?U(kw?=?5%cVdNHssal%1E?k2Qz&)*hdPm*cR0 zC(s$__xf>+#oZgX6%@OQR~NWkMey(JEZ=Z?gJ;OsH`r(K5B7C>B}<^&;_2`2^>umt z13`P7B)8Ndy4J&lBZQ-b9}s>>I6(lyNx~T6MZ%8=A%xqmg{F1caLAU#f^a>-bYK*t zm+>z+ft_`bn@TW_3Uho^qIy&{vw};u9e?s<$BB+9L++G$!&JIuHcIJ;o9C3t@iS^J zEq}~5?hH92>a3tBLP?PpKNHOvY-7T>Hl&?03NjZRGA&P-eKrU%T-)i!i$(QLmBb<$&h$qRAhtgvtWya5FIg?|oZoDznD7%VL zT1Ap6e#XezO3u`sZJdy$)QqV#Gv2{kRFyU|nq6bw4>FwZWjM(SPvs^&uI7yPv7~Wx z$ShYYnH7{S$~VT(#Bmnq*x>kq&;eO0#Dz*}Aq!oe$pOF3O~HG~ECy#@nsT<4-wN(L z8-I<1^0aKW4?b>BhCgiA3&9EzXBUc-^dfJ-t?v*J@<&uG#FY=l1v%a*sveB*<@R#+ zDEG9g*|*mt_Q?G(93Pojnxw!UpTEm1H^{KI#WN`SEbAptr_Up|i(LhY`PPMFs3AX9 zXTc5#?!XJV{|q;;;$q_9aDF;m$}eRx;ji+$85@Hgj#rS%f(GUYHy2!GYy=F>o}5?F zB()%>6}?`+^g2xyg!2e|cec@Z1kO6wu+s1+&aI3cgW{rfvA1oE>@KZpJDn( zaK9>Rp~k0FgK+D?y)o0?m?Jelm21HI^UM!b)g?@#V@CJ~nm&V*)isey>3^q6gxgq2 z`j}?&)1VM+O-&A4YC??Nfs3_!)OSfG+=a*MyzFlHOkF)=r{MD^%Geu_Ro}o)h5PGo zF*XD4jrFzTl%7wxLy(`!7c_DpUW=9lT2V#lq38@Dnx-)XH9-S^-I%XBL&`nqug($_ z7>y0PHXUJ132$v)$^HqSZ*E~`DBp5BBML=4`ImZjTH*5#U_kErC@61eVYzU;VT9>G z>(0UgPPZX}Oi~rCu!WEUx-Nreo`!_48SHb9uzBh;C@-x5cjLQwW~4WT1Tz{7!xx+I z<=Y3(Hd{3BlWD1eQs8oPI$H~$G#@hmg_P}xAbPkeX&uc?v@pQ|qb+&N6rOC+@wMN) zDc`uIk`{}(rC0`E>r;eWoNR)uZC4gr#zxB#Ht_#{os?Q?J4EE6+hDS#3Ob%TEv&@E z_7|SEEtu_OCbTX$^MZ}~%@#4+W%J8VpA%e|_-$~vF1{Lt$_^DGCHz%&0(Y1!<_Q(Pxh z7bd;QNph>oy(B+j`AHSR-@N98Vp_3|AYWB8Xu3_z1NQF2iCcK4kNlvR=V(0N1i$b8 G@BaY(e)#VI delta 3588 zcmai03s6&M7QX*YZlrk-G+;=8BoJOf9sxm%_&`Mwc?(!!+NOaUB?yGx1S?33)3sCE z+7|az4UP)#w##&M#_eXN+s@9;bf>RrJ6&bBv&*K_wd*=v+jVNBPIcPd?VfWJXocBs zX81V&`Mz`hbN|P=_sIL~t&1%F1D#II;jg&->+Ur@ufz`;*vp|qh9fG?2zsT*2u2~r zyoVhK{lu)}t+M;kIc_+v%hy}bCl;)ds-w~isZX&-pgip#sv&X=LAJ%ghM>vvswNH{ z^!v)GfTh-CrI!!0kwqMqo01%(&b=FwpDmatFM=ewRPsmI5x( z9q>R=Mw3a~xlL+4=xAlAXc_hC#Fl5^(_TiKI1Cl%7lcdw6zG=6>eVJ=n04^q^Wj}?kH}TK^ zG|zE|xS0g=VPTeEvP^wg6-nq|2KA>}!5M^b zP?*q;YiCT^GpV+lf+9cu7N;=jBSu+b;dE)i-k@iqcf5CAQbH1|K4O(6d7Lga=z_{D zW6KKJQ9?03jPZ^8W=uIVRy(FGi^Z&zC2KgHby7bmOzF?+Nn*-H$!e0M>mwDiWGmxz zDZxfKnq_RSX0q!+bY)HYCOuQWvvTHS3x=(uupE8F6&3A9k$tjpa?4cX*+$AOiz-Nz zrH|~1iUv@WcF9r%?`Ng%dN}GEMqhH!8cdq7j$7rrOK%EFCk86b`pAK(XdBPzEWy#> z;Kb`=mn?6#*%XK!Ph)TC*8{3(Hjl%kJxMMXU*vR<+kLq&Fq5ZQ}PIFpzHwcGB|-iyug7(2m@@I4Hs z3lcYp{5d=u^c&$^vCheH0U_$+xTh2&+_PF_nHPgzWX~~%%AUDKj57HBid2jL0d&R^ z&4to`f1-#9VB_Y*L{Vu%hil0kEedmlY5}oij)+@8+!G~6MfKgp5I4j*G!5aUUcbxT z@9Q7x^9}l4gMN>+ReBNUldn!#wZl8)7Q5wpJ*PP!*)J z&7s_aX2!-~xNw8+H$RY!!8?UjtTv=6dXgp0CzVtb9?b_+PLVZ(w)phYUz7K@1aA<$ z&B58)YE(ltvZEH*vw{a@Lh2-dlFyXCOFud1G`Fo zCcK9c`{28hYW2HlNd$w?w9?Gph03~}q2I0Sro!{eS~O>fFC=&!4wvoJTq1LfU>ZIz z+bdi|b0pNhYCoUw1B&=R!Qbw6s`ioj2pG$Y*)gao|Eu~#au5td@tR!r;e1^u*Mx-2 z=>HUURM^=EQ0z8jeL`Y_%e2XO2e5_S=6S(B%OObXWqLltmlfBAtLVB4m)EY3W>8-t z3Bli?Vx5^?nHL{lw|B`j`WqZ7xKU<>qP9fg3mpFwD%WSkg0E39!546Fy_sE`5B_-l zdFF$|8%m+A+9G_3iU?fZusIrLx=vvPH?V5yGaAX2UJo@^{)({(@SFCjXUR=)2Oi(# zVY8uYo2nU`0B2P>dmVbJ>e)o-wW?1TyA8*;R#*L;!8U;PYY8c^1(VE+PVO^YfYJBfcYG?XX9 z*_yX4@u+err1{etR(SgZ=#bBRJRI8I!b)Lg`vH~=t+g3>8kC0(^@F_u??A6-bGOgi z<=N`>^okNCk}7HXIsyw!)|xcml1ZT9;jP*O?AuUfot?2qL98DavN56}#5dqQd=%bm zv}wYmj-9JpjVo2y?K%GeEOi$=(UfofnCJ%({20hpN*idLNz)RP!=LI|ro`|<0> zKi8L(9knbNYU;@4vwn%fu+Zy!#3O}y$rD!i2Ro&^qopc1eSb3B4zJ$-AUhLEdO*RK z$&+N!BpC#Df@Yd-J53jdZx8&I+`ZeOj2^8PqH3VtWmL7IIc$c)j^%LBrDW&e3D!ogZxa;X-Js0we|or*WdU1;K!f69((Pz*I9e* zbDgWOnGTo*TvxN&;q_H;#daeFS(De+ISs(6w{%8EgaR}x%_t^OUh z5|E!5-b+nf#9T>T++M^DG5Xj^g0^n)k;$%)fB9@;fQl;8uNy)&63hf;)^!&sBzWY6$(Ios?@>*X{<=?)u}7gvRhr4MC0Q zq+BDr{31W}OGE^l{wsocl5T=d8P%*)Mt4OuUm4S^s~W5nAP(7}Tga5L&4#L>$~dHl zsl%1=&AL{P-@2+1N+EJbDn*E+P&UCwHX7-PKJ*x*i<_IglM9eB$wy%<(vyAYaY&c= z(BqLV^`Q%qp5j9nAwAWHo`7_j4?Pj-X-z(iVq}#2(36m!?n6&Ty26JpLAuh1E=78V z4?SgrZaJ%(I$f7B(;YO)zM#zRa9V7OGUN=&9avOZQevto*1ipUl?5}mL0Mq3*c5fS z_IAW3mY5;&JPaz&N67Hw)imy6KzYUV103J;l!CD|WFABa*Nnqb;hSWbZb{kHnU1KNVVESApL(V;MkktDi(r@=( z$bykxjpMd^Dp)w^`_voWA*&KU3C(?)S+o|Ik;YgoWcU3SumRC9JhO-;YHKs6v83~; z=r)*bHoMd8RCx$_(2XFNV2|)Z?$?*4LH~Xi<uyfEx z`P)vx3^rCUjky7^H*c-{V}~Gr3M)dvKvo9(^Yd9Fe3svL&NC!DNN|WClHfGKTL|uM z4ztzFSD3ijwn*jg63b9oUQ%RHtqKn#wHJvOI3DBfVIC&tdj#uY`Cubk33~@`V#gqD zNGeng86j^b(TA{Sh^6Tv8nzI)5Zs1ZZm(6ja|LfB-b(~03Em|58^IZZa|9m{d_*9k z^)-#YA^4u)7Qt$ z30aTie-fYB9-rA(@nQTh?bfi_%-^8v(O*+X^km^Td+6lQTav#%`e#2J-ITO7Bb`!E z65g7eU=rLJJ&q-5j$Br3R)&-NkhGY#FDgtG!#t9kYU=!D7d#IPd^;#d9b@A zYw&0sVIA;U-0KF8GW_`!V%#M^55JXs#;(9erD<%0_IqhGV|5TV^`{tG$Q?A!Y*Q=> zK5hs9l4jozab@XYYiLOQ0bfh74wh|<)lbC%nSDl7eVL0vs~AJa&>+P;4T-k#sn@YFp%7>KQD!ZuQi-$0)0)RAADMq4DDcbnWn`5ggP`8jZ%) zJd0SQ=(~u+e4m=9U0yhmrCdQ#26IcbOR+F-qg~Q`zKdWskzYdb;zbOv%EjRKz2qz$JI*i z2tDE05hUIIPD_pImeu7Jhtts+B7mIQrEDfVR{O!oOtL$GU?>4q2ELsjl3)UvEF{Pw zNG6`RrPk7D9Xz+JS5gcP-2rAr!87W>PQ+sEDEJ#mCm60TD`8*4AU-hm8PW{1*zhL4 z&!X1j-p$TB2iHRrA0}6jb`-qM`!rS3FrI+Q02e2CFRf1p*sWh^wVR#XOrmOng#@pX z+(M(J1l0R-8;xoSWFk3@>ImuyDC2n}!775)1eAn)+JiLQOd#%{oiy4*u$Q2Kbe^Em zQv}oy^8|uK1PA`<#(0vA$|5J2s=eg6rI*qe)V>u9cQ%J==U0qmA!AYG)>l}ZR+W!~ zo-1?NB$&MNN2uKxr+*t|4KS;5erM`5V)jSE_DwQeY>Z>8;OEBS1&!p1xR?JzBY(0N zbe~00?Ix?mwv-4hW-dH;X6FYfb0V{^;b*tD=xVml@=I`+4eEA601O6`5Hyfi} zG*e6$IU`c&D{wU@r-h>go76gPc3SK!7+m#4l)iqQ>rdyQ!k zUM=-z>^M|Mt=;BO`KM%lrDqf8XiTtHTmQgeCY=+p)~2o>!&m`q-_VQw6^?9pj5WaI zjYfG51!A?hihjX4@4(iLKd`UpaKi3QG3*>1-gIw{H~_+~_W=BxmJwn6jYi(scf|50 zaS1f2+Pqo1>G9&u=FN;9hs_VY$T*xo90zl@HlrtBZ0!?qKYHR0#0$$@?X=vdPT@_$ zD}M~)br`&DtxT;he?YkQ>9%Ng6MopXB+S$Qx@9~-i}1Sjbc>DKv)Rv2w{B;#9x-1< zQ`-^n1wqQr#R7JE$7J>^bl*8t4y32#DK+MuIqVnMv2zsr1J3V^HF*!YI1O~XJ$;`D zh}gVHpI{Qu6@!U{P(TnwWEeqTA~Oj@;r$OO6MO&@U?U5Hb{Hc4he1d{td{!lV3tM> zRq#8cHWOzxgipcxZ?rEoIv1-(u}`jpRl71Uf(LgUkXMuCM6hm5)JNgWAgJ5DJ>mgm z9+KP!r&?2MrGB5T49}s--4nsO!?-<%jOs7a@S6fxH4P}pv zYWjmb5q*o;w(wu%K|IYD`7FXinaqVviibM{A7w396?LJ-rYb&>p{4E)N`!C);dlIV z(5qIn?v^C!4L1$&`6JCzge2nAxc4zB;xNr0kAUwzM`&CG87+y?XK~mNOf}b7S&Ma1 zAu-HpsWi~jr+~trTd**}$1Muu+VIC+j77tZCwfUeB$!$j6Vex#uE2eA{Gy|S9lK+& z^$65T{`3(9=@%6;ntr~xR#&<8%XSc;b5WMgCxZx}_V?+N2< zt>vk`EFuo)654IZ;;{5mjDTnJ6oygo&*z5WD{yY>^b!AmnspUyeBG*BP-AgA zRmHSm1<&@FdA&7`Jr5UKvx9cg9tl=Kxa$PlrJZ%X#0+#@i4@p;I5iae{XCjR-vIam zXghpS7I)hHuXyvqzZeC zzd^=wn(;1DPViF3;#|N^DPZ56T@=6IWnFW zzN-|8{8Wf<_=uh9pK?OM4L2)|H`O>=q*5;N{cPW%3hSy@)qUgUFb$cw4i)sh5~Tv%gj7FISV#3Y>U% zX3ym`Y#zy$ z>?#aCn-e^Ve3(miFf3;?rO6D~!bmuJwl<`rPKl2YTOj+rk!%@Q-+KZJRq(l?!K=yR z90Jdsvk`4LwPoj)u(0)Hj@Q$eD?Hyg)%)N?CnS`_Z6X37QclbjvlWbFp3FuR&OQa?%!BiNfj*!OCanrW)+J@$TYQ64XFhdv2;o z&8kmh=W=Q>Lzi&QV`zTu8(U}iRdex(E4c#YN zIDT|A7A#V2)fVhM=$Ut=!eeOqVL0&d^noH3LaXgICpI=F%mfFvdCyb$#YHA=FwyV@ z)T;{|7QCxtAoG(r)&|qABnH({7ziE!>n9h`zcHVlmoL%m%h2QEe1mvc62AtkF6OZ( z;n>9y*t^`i7$;vNo&6Bzjt$yJdnI@j2Dryd`#g8)cDJ7&)^FH#$=Ih5l^YC&HpNIE zsT#V?wDYC3xAEo`4t|*|i6x(cOPBsBi>}Dqgx~tSC;JO@yL_XE=-}vS(KiQGbk{)J zP6@3gViH>1`HE(&z}H6%`e$F~u}>iUTAE=Vs$vt?3(IvJ%(%8KSd`tnq7!&Mo_!1H z*KcPDf%y7MapKRA@1fo|KzcjnucOH+{IKXp^e2({0n9fnbM6Y12#Po}r0;nZ74=ZG z=ueXKVypXT+y7uxvPOzpVnjjVvFGj+; zXzNAY6=_bvYmZ+NMq delta 7172 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 M{XQ87T_5lIUzP#psQ>@~ diff --git a/recruitment/__pycache__/signals.cpython-312.pyc b/recruitment/__pycache__/signals.cpython-312.pyc index e9aa0133583bd346189a097ebefbd331358fdca8..9bc8496b6db39600ac6c467e47bf409b60d97a48 100644 GIT binary patch delta 769 zcmYLH&rcIU6rS1bmZj}(rEH-;RF)s6tdgP##Hi6|fTWOc77sSd&Y}g{EwkG|3H~7A zpeJc2M#8~6DbWM}fH6@IaPZ)P5ThaSpf?CH5-<9uKzx(=^38j1zU+K&7kl5u)J;VR zAPBYP@Il{&iW zqL*_DhCn5d*b$0TYiw1xcQx?8k4B-wzt}wu@C5DFso&+ucBp?lq&**bHu6jI)d%(@ zB!>^6jy8%|$Jb~WXibxz0_OSvbO@>e*=ceuhuT@oERY;AJv&Qnx0JVO5{gk46>q0# z6HrS8IEf$-z7Jl5Dg4>Xm=0|`661p8Ck;enZ~eD2J`yEOL?q3yW2AmPUX_pTV?+{s zoj8h7IZ;luqf5xevjRqA2*WBT=EN~Hi;X1vs(kdfz+I}ds(R;4V$|e5nbUe^K=0Nw zGiKh@M@#u}oBov8Up3z2hvxZkx=1Fv(!{Z9FlWfLjf=E%=-_aU!?Eyi_$*s&=(;FT zg=ZT$L^w18>?oE~@J!3gx}JI8cKLj`gJ2;WizI`6aOOJhGTwL`xr}iSGh+sO)2Mg; zp20|M`=qq)i7=IKN-NMmZU~xJ28_X=_&z8NYIR1GZN*l5v@NirtKT_CY1P(=00RE;@!zzT>sQq8UBn@`$8QK9EeEh+~aItV;|xvZ3ttPC(rK- IoYo}&0nRP4ssI20 delta 2272 zcma)8O>7%Q6yEjUuGecjZ8wfn|83$Vj)U{lCT&t$0U-^gsG_Bb1iesgcP7pf+Z*k! zn`8xwD)rDK(H;sFAi<3$w_f4Gr64%e5|XXGa4wg&G!hag-ptzhK@qUlj^6v`&G+8> z=B?x(l6R(JKS!ei1JC`>|5*O1uoX+P-)$A{veB)b94l`9k_>FH#fZbRUp;#46Ij9Z zt*#hBh%Y+5z7L&e{$#KRyqF^f-t@g>oNIwzaR~_TGavgt4I2HA>VaOLBq2ZQhBtx2 zJ3ZSx$;UWf1ZIvUQ5gAm0^0!!F`opTuS1hk5cQ&y=os?B9=9FZ4^JWSPK+}giD4^qqI^apVlRO$mMX%v9gGRn#cV5}%_S-HI@eZgI zrYeQe=+P>Tq47iE0tx2?G(q%ma>2QaCBkYH7Ez-8H8e7#sVD%UgKe0#I?o^Qf*WmZsO-&_!H^I5%>+W?Gt6ryzet z_qOf$B||}V8Y*8@EbImlS%vxaRcvUureZsGP2V&%a!y{FY(m-F?l_%Z9R9t!lNhd_}31>q;4$uJD!txz}`K#k8eGU9S*+%A{BH zGNdbV{~{@f>vH-kX~}fCg?hEB7#p?(xt6eL0!oVUP&YP*4Qo?df(mK;Q}Th^#|gRz zdO}E-O|wqQylRw{nr72@TOcU*M2Ul{nz1jo-7~wU7|4_rknK8F^>U5sY(6EQlP%C) zLY~2Pl-v#p!f^S>ja5SGbhL*k)<0JH>&D2OHr+xrUkmWalJD> z9RPO*r44l9y^C)nqDe2R?L}b6c6}7rS?LS7f4zrkH7nnWi|<5C>X`!B%3pl^S&J2-54)Q8e=ZrO7&9hKm8< z%XbXfc~7870gRqEBt|Yaa}$l+L^C(t$W7BK`}XMN8K_c@JrUDKlsVK&3{hGz208}i zXNQ~z;Zgu_ncy54)9r>fB_=)COqCj`QZqH#NKMj=nZ1nZDae?P-Mex`!hTPeR&Uk| zca!fwk2BH1U51awb`wlysF@yZq(__SsYZH=61O8k@XQ$?&cvL%;bfrHE(Y2k7;WaK z8u_Va{(K{Uo}K}@+LttN0nQ9S`|=UhjIo^fSJZ7d~Rr GlYavdGm2>d diff --git a/recruitment/__pycache__/urls.cpython-312.pyc b/recruitment/__pycache__/urls.cpython-312.pyc index 95341511093e3ca8eeddef74f4b1c805fc7872f7..47958fabea638f05aabc4f81852ec4774d410ff0 100644 GIT binary patch delta 1307 zcmaKsOH5lw6o!o%S3rVUBz8!67{|W$^^5BSoDiU-*ch9Jgz|6-Ho@4z1QMuu*l9>x zXm?qX-bFW2MA}8Xs47=%*i@mbBDJb!q>)DII#L&j)M-_zs+2|U%xQN~yzuXTzWMa#k*H&_+ zLRPr_QTwynloxX1`%R$%ZL^$TT`pFlRWjFJ>gn8*xq~1@3EpJXzHp!JmHedS zo{EL3QKCliNS2~`Zj;O|*`zg8dQ#i9m(K<7$_! zd;Z(Cm8l7${(s_2M%{Cd6C(XG(pj~#aEgRec(z1QnZIj9TwXaL=~cv_qE~rG_Dt$O zdL3w_##s9S&~pgZt1=h{`4D7VZ57pz)Rs0`Jzpc|b?hlmJD+q`w<#LtInj%%9ua({ zw!xU&U;0j=JA(B3+60;Lwe-stccDdR)xyG25{@3VQ#8##HloHoz9-;==G_Q85hN#q zxGPD~2;UNYsy?}uUqN1iH2CGB3Nq{uicuwyul@1@`~>n6A`O97*kD?>2J8bFBX5kI zGs|P0uf|z-oOH*Ju2PidS%C8Z1CwUQ=*Mi1j`C`*R2c<~-ZTYLoQHK1d7{5l(7C*$kfWZJ4png2GK zEZ5C^3{oHH?+4rK#=%VJYe7%lB$z4vv0+kRruAQp*bJDo{+B^AVDfrL=;V!?U>0EJ z`D%o9O^~h$JiSR#i5Gw_3i_aRxA0Z*VX@Lj(M5g>$dV*2yD4V#5t|QdmnnLWF9W=- zKMdLG?tod*zYE!SR>6D(<9ro``QyYN$5-+c-Q-1}_kqfnElIQ_9$TQOz&`={DbUM@ zHa3_cgBhHc7w_>6ygob&58442k>l L#j%aM#=qffMhKXe delta 1551 zcmaKtO-vg{7>11*SB3|f1I2_tn}oIo z^+s=0dnkuWB_}N9vQaMd6i!v7UUt-xMm_Z0Q>3XBsfS*=GoPwTty&-a>6_<$-xS}G)Z?*9I@?-gBeOqJO<*Ekz&&YY;I>^#G71yaoGeI|4H6Q=Y zX>-&ZTsn+VR|LBv)YXk$-9NdKr!xeNuo@w$<&xGTH}!}iCc+pIwVmjJ$_JZ+8P)|inYXn?(MSsNs;-6~

l4BzK85#ih+dTO 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 hx3VD^U~rpZTQ^#Lb|luG~r diff --git a/recruitment/forms.py b/recruitment/forms.py index 1794f3f..7d16b42 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -197,10 +197,10 @@ class JobPostingForm(forms.ModelForm): fields = [ 'title', 'department', 'job_type', 'workplace_type', 'location_city', 'location_state', 'location_country', - 'description', 'qualifications', 'salary_range', 'benefits','application_start_date' - ,'application_deadline', 'application_instructions', - 'position_number', 'reporting_to', 'joining_date', - 'created_by','open_positions','hash_tags','max_applications' + 'description', 'qualifications', 'salary_range', 'benefits', + 'application_deadline', 'application_instructions', + 'position_number', 'reporting_to', + 'open_positions','hash_tags','max_applications' ] widgets = { # Basic Information @@ -249,13 +249,11 @@ class JobPostingForm(forms.ModelForm): # 'placeholder': 'https://university.edu/careers/job123', # 'required': True # }), - 'application_start_date': forms.DateInput(attrs={ - 'class': 'form-control', - 'type': 'date' - }), + 'application_deadline': forms.DateInput(attrs={ 'class': 'form-control', - 'type': 'date' + 'type': 'date', + 'required':True }), 'open_positions': forms.NumberInput(attrs={ @@ -278,15 +276,8 @@ class JobPostingForm(forms.ModelForm): 'class': 'form-control', 'placeholder': 'Department Chair, Director, etc.' }), - 'joining_date': forms.DateInput(attrs={ - 'class': 'form-control', - 'type': 'date' - }), - - 'created_by': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'University Administrator' - }), + + 'max_applications': forms.NumberInput(attrs={ 'class': 'form-control', 'min': 1, @@ -296,19 +287,15 @@ class JobPostingForm(forms.ModelForm): def __init__(self,*args,**kwargs): - # Extract your custom argument BEFORE calling super() - self.is_anonymous_user = kwargs.pop('is_anonymous_user', False) # Now call the parent __init__ with remaining args super().__init__(*args, **kwargs) if not self.instance.pk:# Creating new job posting - if not self.is_anonymous_user: - self.fields['created_by'].initial = 'University Administrator' # self.fields['status'].initial = 'Draft' self.fields['location_city'].initial='Riyadh' self.fields['location_state'].initial='Riyadh Province' self.fields['location_country'].initial='Saudi Arabia' - + def clean_hash_tags(self): hash_tags=self.cleaned_data.get('hash_tags') diff --git a/recruitment/migrations/0001_initial.py b/recruitment/migrations/0001_initial.py index d0c43dc..bf6f650 100644 --- a/recruitment/migrations/0001_initial.py +++ b/recruitment/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.7 on 2025-10-21 11:27 +# Generated by Django 5.2.7 on 2025-10-21 22:26 import django.core.validators import django.db.models.deletion @@ -236,8 +236,8 @@ class Migration(migrations.Migration): ('salary_range', models.CharField(blank=True, help_text='e.g., $60,000 - $80,000', max_length=200)), ('benefits', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True)), ('application_url', models.URLField(blank=True, help_text='URL where candidates apply', null=True, validators=[django.core.validators.URLValidator()])), - ('application_start_date', models.DateField(blank=True, null=True)), - ('application_deadline', models.DateField(blank=True, db_index=True, null=True)), + ('application_start_date', models.DateField()), + ('application_deadline', models.DateField(db_index=True)), ('application_instructions', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True)), ('internal_job_id', models.CharField(editable=False, max_length=50, primary_key=True, serialize=False)), ('created_by', models.CharField(blank=True, help_text='Name of person who created this job', max_length=100)), @@ -251,7 +251,6 @@ class Migration(migrations.Migration): ('published_at', models.DateTimeField(blank=True, db_index=True, null=True)), ('position_number', models.CharField(blank=True, help_text='University position number', max_length=50)), ('reporting_to', models.CharField(blank=True, help_text='Who this position reports to', max_length=100)), - ('joining_date', models.DateField(blank=True, help_text='Desired start date', null=True)), ('open_positions', models.PositiveIntegerField(default=1, help_text='Number of open positions for this job')), ('max_applications', models.PositiveIntegerField(blank=True, default=1000, help_text='Maximum number of applications allowed', null=True)), ('cancel_reason', models.TextField(blank=True, help_text='Reason for canceling the job posting', verbose_name='Cancel Reason')), diff --git a/recruitment/migrations/0002_remove_jobposting_application_start_date.py b/recruitment/migrations/0002_remove_jobposting_application_start_date.py new file mode 100644 index 0000000..750027d --- /dev/null +++ b/recruitment/migrations/0002_remove_jobposting_application_start_date.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.7 on 2025-10-21 23:25 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('recruitment', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='jobposting', + name='application_start_date', + ), + ] diff --git a/recruitment/models.py b/recruitment/models.py index 91a1fb6..00a51ac 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -84,8 +84,8 @@ class JobPosting(Base): null=True, blank=True, ) - application_start_date=models.DateField(null=True, blank=True) - application_deadline = models.DateField(db_index=True, null=True, blank=True) # Added index + + application_deadline = models.DateField(db_index=True) # Added index application_instructions =CKEditor5Field( blank=True, null=True,config_name='extends' ) @@ -137,7 +137,7 @@ class JobPosting(Base): reporting_to = models.CharField( max_length=100, blank=True, help_text="Who this position reports to" ) - joining_date = models.DateField(null=True, blank=True, help_text="Desired start date") + open_positions = models.PositiveIntegerField( default=1, help_text="Number of open positions for this job" ) @@ -732,8 +732,10 @@ class FormTemplate(Base): blank=True, help_text="Description of the form template" ) created_by = models.ForeignKey( - User, on_delete=models.CASCADE, related_name="form_templates",null=True,blank=True, db_index=True - ) + User, on_delete=models.CASCADE, related_name="form_templates",null=True,blank=True, db_index=True + ) + # FIXME: on Delete model SETNULl + is_active = models.BooleanField( default=False, help_text="Whether this template is active" ) diff --git a/recruitment/signals.py b/recruitment/signals.py index ebc9f9b..2d1ed97 100644 --- a/recruitment/signals.py +++ b/recruitment/signals.py @@ -10,7 +10,7 @@ logger = logging.getLogger(__name__) @receiver(post_save, sender=JobPosting) def format_job(sender, instance, created, **kwargs): if created: - FormTemplate.objects.create(job=instance, is_active=True, name=instance.title) + # FormTemplate.objects.create(job=instance, is_active=True, name=instance.title) async_task( 'recruitment.tasks.format_job_description', instance.pk, diff --git a/recruitment/views.py b/recruitment/views.py index 73fc246..3c24c73 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -273,21 +273,12 @@ def create_job(request): if request.method == "POST": form = JobPostingForm( - request.POST, is_anonymous_user=not request.user.is_authenticated + request.POST ) # to check user is authenticated or not if form.is_valid(): try: job = form.save(commit=False) - if request.user.is_authenticated: - job.created_by = ( - request.user.get_full_name() or request.user.username - ) - else: - job.created_by = request.POST.get("created_by", "").strip() - if not job.created_by: - job.created_by = request.user.username - job.save() job_apply_url_relative=reverse('job_detail_candidate',kwargs={'slug':job.slug}) job_apply_url_absolute=request.build_absolute_uri(job_apply_url_relative) @@ -302,7 +293,7 @@ def create_job(request): else: messages.error(request, f"Please correct the errors below.{form.errors}") else: - form = JobPostingForm(is_anonymous_user=not request.user.is_authenticated) + form = JobPostingForm() return render(request, "jobs/create_job.html", {"form": form}) @@ -313,21 +304,11 @@ def edit_job(request, slug): if request.method == "POST": form = JobPostingForm( request.POST, - instance=job, - is_anonymous_user=not request.user.is_authenticated, + instance=job ) if form.is_valid(): try: - job = form.save(commit=False) - if request.user.is_authenticated: - job.created_by = ( - request.user.get_full_name() or request.user.username - ) - else: - job.created_by = request.POST.get("created_by", "").strip() - if not job.created_by: - job.created_by = "University Administrator" - job.save() + form.save() messages.success(request, f'Job "{job.title}" updated successfully!') return redirect("job_list") except Exception as e: @@ -338,7 +319,7 @@ def edit_job(request, slug): else: job = get_object_or_404(JobPosting, slug=slug) form = JobPostingForm( - instance=job, is_anonymous_user=not request.user.is_authenticated + instance=job ) return render(request, "jobs/edit_job.html", {"form": form, "job": job}) @@ -347,6 +328,7 @@ def edit_job(request, slug): def job_detail(request, slug): """View details of a specific job""" job = get_object_or_404(JobPosting, slug=slug) + # Get all candidates for this job, ordered by most recent applicants = job.candidates.all().order_by("-created_at") @@ -632,15 +614,15 @@ def application_success(request,slug): @ensure_csrf_cookie @login_required -def form_builder(request, template_slug=None): +def form_builder(request, template_id=None): """Render the form builder interface""" context = {} - if template_slug: + if template_id: template = get_object_or_404( - FormTemplate, slug=template_slug + FormTemplate, id=template_id, created_by=request.user ) context['template']=template - context["template_slug"] = template.slug + context["template_id"] = template.id context["template_name"] = template.name return render(request, "forms/form_builder.html", context) @@ -653,12 +635,12 @@ def save_form_template(request): data = json.loads(request.body) template_name = data.get("name", "Untitled Form") stages_data = data.get("stages", []) - template_slug = data.get("template_slug") + template_id = data.get("template_id") - if template_slug: + if template_id: # Update existing template template = get_object_or_404( - FormTemplate, slug=template_slug + FormTemplate, id=template_id, created_by=request.user ) template.name = template_name template.save() @@ -667,7 +649,7 @@ def save_form_template(request): else: # Create new template template = FormTemplate.objects.create( - name=template_name + name=template_name, created_by=request.user ) # Create stages and fields @@ -703,7 +685,7 @@ def save_form_template(request): return JsonResponse( { "success": True, - "template_slug": template.slug, + "template_id": template.id, "message": "Form template saved successfully!", } ) @@ -712,9 +694,9 @@ def save_form_template(request): @require_http_methods(["GET"]) -def load_form_template(request, template_slug): +def load_form_template(request, template_id): """Load an existing form template""" - template = get_object_or_404(FormTemplate, slug=template_slug) + template = get_object_or_404(FormTemplate, id=template_id, created_by=request.user) stages = [] for stage in template.stages.all(): @@ -747,7 +729,6 @@ def load_form_template(request, template_slug): "success": True, "template": { "id": template.id, - "template_slug": template.slug, "name": template.name, "description": template.description, "is_active": template.is_active, @@ -762,7 +743,7 @@ def load_form_template(request, template_slug): def form_templates_list(request): """List all form templates for the current user""" query = request.GET.get("q", "") - templates = FormTemplate.objects.filter() + templates = FormTemplate.objects.filter(created_by=request.user) if query: templates = templates.filter( @@ -802,7 +783,7 @@ def create_form_template(request): @require_http_methods(["GET"]) def list_form_templates(request): """List all form templates for the current user""" - templates = FormTemplate.objects.filter().values( + templates = FormTemplate.objects.filter(created_by=request.user).values( "id", "name", "description", "created_at", "updated_at" ) return JsonResponse({"success": True, "templates": list(templates)}) @@ -812,25 +793,26 @@ def list_form_templates(request): @require_http_methods(["DELETE"]) def delete_form_template(request, template_id): """Delete a form template""" - template = get_object_or_404(FormTemplate, id=template_id) + template = get_object_or_404(FormTemplate, id=template_id, created_by=request.user) template.delete() return JsonResponse( {"success": True, "message": "Form template deleted successfully!"} ) -def form_wizard_view(request, template_slug): +def form_wizard_view(request, template_id): """Display the form as a step-by-step wizard""" - template = get_object_or_404(FormTemplate, slug=template_slug, is_active=True) + template = get_object_or_404(FormTemplate, pk=template_id, is_active=True) job_id = template.job.internal_job_id job=template.job - is_limit_exceeded = job.is_application_limit_reached + is_limit_exceeded=job.is_application_limit_reached if is_limit_exceeded: messages.error( request, 'Application limit reached: This job is no longer accepting new applications. Please explore other available positions.' ) return redirect('job_detail_candidate',slug=job.slug) + if job.is_expired: messages.error( request, @@ -841,16 +823,14 @@ def form_wizard_view(request, template_slug): return render( request, "forms/form_wizard.html", - {"template_slug": template_slug, "job_id": job_id}, + {"template_id": template_id, "job_id": job_id}, ) -@csrf_exempt @require_POST -def submit_form(request, template_slug): +def submit_form(request, template_id): """Handle form submission""" - template = get_object_or_404(FormTemplate, slug=template_slug) - job = template.job + template = get_object_or_404(FormTemplate, id=template_id) if request.method == "POST": try: with transaction.atomic(): @@ -864,7 +844,6 @@ def submit_form(request, template_slug): {"success": False, "message": "Application limit reached for this job."} ) submission = FormSubmission.objects.create(template=template) - # Process field responses for field_id, value in request.POST.items(): if field_id.startswith("field_"): @@ -909,7 +888,7 @@ def submit_form(request, template_slug): ) submission.applicant_email = email.display_value submission.save() - # time=timezone.now() + time=timezone.now() Candidate.objects.create( first_name=first_name.display_value, last_name=last_name.display_value, @@ -917,16 +896,10 @@ def submit_form(request, template_slug): phone=phone.display_value, address=address.display_value, resume=resume.get_file if resume.is_file else None, - job=job + job=submission.template.job, + ) - return JsonResponse( - { - "success": True, - "message": "Form submitted successfully!", - "redirect_url": reverse('application_success',kwargs={'slug':job.slug}), - } - ) - # return redirect('application_success',slug=job.slug) + return redirect('application_success',slug=job.slug) except Exception as e: logger.error(f"Candidate creation failed,{e}") @@ -2071,6 +2044,7 @@ def user_detail(request, pk): user = get_object_or_404(User, pk=pk) try: + profile_instance = user.profile profile_form = ProfileImageUploadForm(instance=profile_instance) except: @@ -2224,10 +2198,6 @@ def account_toggle_status(request,pk): messages.error(f'Please correct the error below') -@login_required -def user_detail(requests,pk): - user=get_object_or_404(User,pk=pk) - return render(requests,'user/profile.html') @csrf_exempt diff --git a/recruitment/views_frontend.py b/recruitment/views_frontend.py index 50c05e3..61d5fa2 100644 --- a/recruitment/views_frontend.py +++ b/recruitment/views_frontend.py @@ -394,6 +394,27 @@ def dashboard_view(request): ).count() high_potential_ratio = round((high_potential_count / total_candidates) * 100, 1) if total_candidates > 0 else 0 + #donut chart data + jobs=models.JobPosting.objects.all() + selected_job_id=request.GET.get('selected_job_id') + print(jobs) + print(selected_job_id) + apply_counts,exam_counts,interview_counts,offer_counts=[0]*4 + if selected_job_id: + job=jobs.filter(internal_job_id=selected_job_id) + apply_counts=job.screening_candidates_count or 0 + exam_counts=job.exam_candidates_count or 0 + interview_counts=job.interview_candidates_count or 0 + offer_counts=job.offer_candidates_count or 0 + + + + + applicant_stages=['APPLIED','EXAM','INTERVIEW','OFFER'] + stage_counts=[apply_counts,exam_counts,interview_counts,offer_counts] + + + context = { 'total_jobs': total_jobs, @@ -409,8 +430,13 @@ def dashboard_view(request): 'high_potential_count': high_potential_count, 'high_potential_ratio': high_potential_ratio, 'scored_ratio': scored_ratio, + 'applicant_stages':json.dumps(applicant_stages), + 'stage_counts':json.dumps(stage_counts), + 'jobs':'jobs', + 'selected_job_id':selected_job_id } return render(request, 'recruitment/dashboard.html', context) + @login_required def candidate_offer_view(request, slug): """View for candidates in the Offer stage""" diff --git a/templates/base.html b/templates/base.html index 0250f32..b4a6c35 100644 --- a/templates/base.html +++ b/templates/base.html @@ -16,9 +16,9 @@ {% endif %} - + - + @@ -120,7 +120,7 @@ {% endif %}

@@ -323,21 +332,20 @@
- + - -
+ +
{% trans "Financial & Timeline" %}
@@ -392,19 +400,74 @@
{{ job.benefits|safe}}
{% endif %} - - - {# TAB 3 CONTENT: APPLICATION INSTRUCTIONS #} - {% if job.application_instructions %} -
-
+ {% if job.application_instructions %} +
{% trans "Application Instructions" %}
{{ job.application_instructions|safe }}
-
- {% endif %} + {% endif %} +
+ + {# TAB 3 CONTENT: APPLICATION KPIS #} +
+
+ + {# 1. Job Avg. Score #} +
+
+
+ +
{{ avg_match_score|floatformat:1 }}
+ {% trans "Avg. AI Score" %} +
+
+
+ + {# 2. High Potential Count #} +
+
+
+ +
{{ high_potential_count }}
+ {% trans "High Potential" %} +
+
+
+ + {# 3. Avg. Time to Interview #} +
+
+
+ +
{{ avg_t2i_days|floatformat:1 }}d
+ {% trans "Time to Interview" %} +
+
+
+ + {# 4. Avg. Exam Review Time #} +
+
+
+ +
{{ avg_t_in_exam_days|floatformat:1 }}d
+ {% trans "Avg. Exam Review" %} +
+
+
+
+ +

+ {% trans "KPIs based on completed applicant data." %} +

+ +
+ + + + {# FOOTER ACTIONS #} @@ -423,7 +486,7 @@ {# RIGHT COLUMN: TABBED CARDS #} -
+
{# New Card for Candidate Category Chart #}
@@ -440,44 +503,9 @@
-
-
-
{% trans "Applicant Tracking" %}
- {% include 'jobs/partials/applicant_tracking.html' %} -
-
- -
-
-
-

{% trans "Job Avg. Score" %}

-
-
{{ avg_match_score|floatformat:1 }}
-
{% trans "Average AI Match Score (0-100)" %}
-
-
-
-

{% trans "High Potential Count" %}

-
-
{{ high_potential_count }}
-
{% trans "Candidates with Score ≥ 75%" %}
-
-
-
-

{% trans "Avg. Time to Interview" %}

-
-
{{ avg_t2i_days|floatformat:1 }}d
-
{% trans "Applied to Interview (Total Funnel Speed)" %}
-
-
-
-

{% trans "Avg. Exam Review Time" %}

-
-
{{ avg_t_in_exam_days|floatformat:1 }}d
-
{% trans "Days spent between Exam and Interview" %}
-
-
-
+ {# REMOVED: Standalone Applicant Tracking Card (It is now in a tab) #} + +
{# RIGHT TABS NAVIGATION #} @@ -503,35 +536,7 @@ {# TAB 1: APPLICANTS CONTENT #} - {# TAB 2: MANAGEMENT (LinkedIn & Forms) CONTENT #} + {# NEW TAB 2: APPLICANT TRACKING CONTENT #} +
+
{% trans "Pipeline Stages" %}
+ {% include 'jobs/partials/applicant_tracking.html' %} +

{% trans "View the number of candidates currently in each stage of the hiring pipeline." %}

+
+ + {# TAB 3: MANAGEMENT (Form Template) CONTENT #}
- {# LinkedIn Integration (Content from old card) #} - - - {# Applicant Form Management (Content from old card) #}
{% trans "Form Management" %}

@@ -569,8 +577,8 @@

- {# TAB 3: INTERNAL INFO CONTENT #} -
- - {# TAB 3: INTERNAL INFO CONTENT #} -
-
{% trans "Internal Information" %}
-
-

{% trans "Internal Job ID:" %} {{ job.internal_job_id }}

-

{% trans "Created:" %} {{ job.created_at|date:"M d, Y" }}

-

{% trans "Last Updated:" %} {{ job.updated_at|date:"M d, Y" }}

- {% if job.reporting_to %} -

{% trans "Reports To:" %} {{ job.reporting_to }}

- {% endif %} -
- -
@@ -660,10 +626,8 @@
- {% include "jobs/partials/image_upload.html" %} -