oC}s6r(v%$m7P{piQia9*%J2ajriQM_RoXjsaMlrAx_
zhcJ*=FxnV}<>dkCS1c!#uA{Qvp;UgE<+IqFjbk33dN{V{vSI3~lYPiaIcC!4>5-GK
zN0uI^B3rErPk`K;k1C(G7c8vy%_#_6he!W)>g)f`kGm5@1#)8AP0sr-=z@yA;tO0EPt{j|@NCzJltb9j)|*e6#pqyo2bQ0D
zG+$Dysh_RzaNdD-wgbS726Y#;*yqJi(UWT{tJCO4fYfVlk^lVUl
zzqOw_5^c-?U@o#HLqqZ&tXbKOc0=u*TgS828NcM%Hd7-XtdA9oM8lKUi4@yR16y81
za3^=6Cq`4*h~Opw=(DmCfEmFCrH-N_l~qVIX*AGYjkI%X_07nKo+>v`A+ol{WX6rM
znz2Y(t+{wex0RdF@EU-d0j7~+OjT|{a4o<(2Gn?I{#mN%r#$AO1092b@)GL*sG9$^
zW=>U9V8&W0Z>^X#;%vzU_<`cgkJ}NF$_KPHM0qH0&?=)uW*eS`S#AI)VXv;Yu1^*v
z%}+&%Z(`4938MnJlrT@`(;X*GuG~9>e>$;vTGwJv*J9Vy;!y*N$CE)inj%WZg;dOJ
zRH-5~gk~=Td-{5o4KJVa$SXK^QIeRQO
zBFb7K)k6CqRkWl)-sBV*e%2aoZ<@&UuLD}0P10w(QI9sB17O~@Mx5V$nYL0fbpeV&oSdCQZ=}6pQ{tp&fe4oJy~AR=uxY
zaXUS7HZpnLVOn7D@L{yRRU6Sk__Eg#+fFB3TsyC}ZeeW!oiWpn$0Nv}r5)-ZI{ITN
zOwJS^ewdIz30BZ&kl)3mGHi9vr8g)k4M_)xih1%ILPJSPQc;X4FF2q1e|KcV`sP5r
zuK*?i7>W|cWP|3vpf@}mpfbvKpvnt?87ruLTKcj7b}pr1rN`*y!J%hCB6YqwZY4}E
zUU@DXQ%lP&U)_AV-?0;w9@Q=@5uM_8A^bSNL)w}Wks~eBlSlyA97UFCxQ#4RLGuSC
zqN7bTG~3FAElO_E88c=W`6e9$n|Qe(U8PckGokrJxe@(^&l1|wKBBlg)I@m#`BF{6
zmEUQ_Q^;=ycv?HuN0j<8$ti^>D`;(AS5Kd$JWEMiBf|8FcHGZ3ZT}Z~VpfyT%5AFr
z^77AT9G3b;AdfW*j(%xE_%g=O{EgZ>=ZQI)Fc97ZDr@q_;g4$*`idg^>y+Cox_NP5
z(IJ9FwPk>)v!+Z1y{o24f$f;Z%lW>`b!3wznTfCJ66>(HH5o0AzeUaoFN&9~9l
zehQs^mqokuLebWCkTUORzWPG3-s%Ts#_Hd=)DBwiGyZ&KKNXD)?q<9X|;OK6sp&YS6)_?J}XjQ0H~k-@j4tAu)@TUv9$
zXt7ju0RJ1}ruZsn%_=~Z0x8F%AQE@98mt8+$}#PgF=7ONy+pEPxs`zo79O~5`Uu2O
zJT~yGeJsns9Y{hgq|nz(M(%@~y-J?^9HcKK_hKT)Fg`rP`{o
z;!#>c_8liSCVx&v=yM}G*CH#ceL7AQ4`a#g1w!2cxZl90^Kq&m?GJMpx(E(S{rC!*
z02wjb*zsZ%jo#bFi%&!X>o~}y>sj||*G&+Gb}sv-wta$_EWhW1vXku?i5^+SnIp^_
zX4SW7{5RyXVjB!%-h>7vEpWodB(v%)MD-LpRKyaCI7K_)@!o}WLrghA9TdfAt0#&l
z2=NOii9S}lL|j`uMf4V5X^&13gXIPQBgkkmmKl&U^NR637na9R#6b0X>MU%Nunm2?
zfDf){hMPT`E*BAg*kb>YO7?>k-z3_XSv1d7@dn}7|6*~u5I<<^FA?YYp8#8US^qh>
z7$^5sPa!phfSy({VZwOlr6Y!pnlNFkbI8QeeEiAyZvL}@rZiu6sb~`se;iGLAGD*XEv^vl#JAdA
z6{4UJSFX2O^=R5M^3X@oUb!0yXPefm>?^&eeOMvdOIGs)#He-)HfU0V_mBqFp_`xd
zaSHighGH=NP+sL1Ld*EDjm1C{Fzd+*hF|ztI$u6Y+2Njq+e9y@mYr2KQ>hy=K;ko&bcwp?96Vb5&NjR$uU6L1P&@UrL
zdDqgdi1LVLj_I@=`&v>#leFgcuft;j8g@P?}r
zLG4IqJ;K!W3F
z1@IU~VWIZj3^7J?%@Q93&Xk%jpDmU}I6~^KfLiGM@wr4gzAu(5B)R{pwVfw&xQTrWx?rPI+v(E8%QR~ULipmyNHFxHSWT8vMWMDf_N3ay_{boc+0>MA_|(2B{r
zv4*oY^WvUG${HJ*?r0-|%^2Dv6Ug05=jZGhFNlit5|kXGBsG@Q8TGU;ed6w*GHr%9X4~s1QBbjYnwQ
zu5!MFP4+h%>M;mnnt%ch>_*boC!K3)Un~@pq%CA|hbga=G%B)u&F3a(x2d#87li;t
z0Ia6J_X?GCWCE0eC+`wZ=<7%Ah+iaVFE0`=hd$(M-m+L65%R`7i|PpE)dsa=fDX5F
zM!WrLQ6!xjMu!*XnJAQnE&>!Zzt}_+
z;Ty^370bv+x{$|P9%aiZmrt=B5Yu~5N;Wb9I$&vnUke0Y=b510a;@lOqUu~aXqZ>F
zHiJPL8y_Yu*5qLrdRYd9pz!CkKqMkbD>>EVR=4LnFOGm637zOzteC
zvdMi8As&8v5y}M^3=n2=11ruKko6(~k}7@6GrLy$d*?E}(Nv0Q2HBvxP)`q#ZB(UC
zmFb}6JfeLcX#baX#R7T^=+H`WEU3$Gt`gr!M}*#B(4N0h46>gA64_eBO`^=6K~+z8
z)Eo~G%YIuS;{5o8P3{lM%iRv5EO%1vWL(MQDQd>RK^eDtws0S@l`qMypT){`La?g8Ysu
z5#Ldnbd+g|GYx|99+`0Jn@$F*m8yErEVrWFuuf!m8V-bb?30)Sv@*qVojCy608#PU
z+v~*7hJF6{Rw1nN+J>~x+PAlf4*6UGdeGn)K@WEn3<+^`K!MUv>#<&BNqfy08ut2p
z>Z8;l*QCsozn~Lg@@I~PsmSjPA9b5y_P5D?ORA-S^W(-5BHiOjvyM`vX4J_qU7pL^sgfkU4}OA}GLz?gdgaTztJr=^x~qye@Y}JGns=^O$HTkz->1
z=OSJkc!!9e#gz7@_WoG=nOO#XVT6Oo;9{I-Ce|_*s^OYwkKIArVG{_i(b}Oq#PcCZ
zYTND<`=uaJuc4C(zq{@diSjDWY~WW9z=p}(gw*}8DOQdIaxJGohO?=uJu-2lK_sqEqL>2!#%XDwNo`Il&yZ4t%dYHjHj
z+Sk6Ex@>HIWQ$0Pq>X{S_lq1yXYelOQg;ol@Z4Pr2)S?WCjvKW@ehbDetfGT&knc`
z7I`mVz-azPA$kp|(#s>EQ{MG}aEf$jBK1eUQT`--}6lBPG>s7*&Sk0ZtIShyoK|V_%N@BvctgE)>
z5n@S(*7S&&9MzQymMe?3_-!JS*i^ht3=qe(8QX*>&qdWK3*^qBpm{S*nWt^rF1m@2
zwCwGoy{OR!ZWrU)^CnLrmH$6#eXRX`yXYyhwZt8wQ+97^TTcnNwuUA5lD(b0LtJ!G
zm<&b&AJ!K`t(k5%zdZ9gQH0sRw`m507jEsN9U|+pS?DanTknzciQI{&Wk|@};al1|
zDkOQ?2ch`@EJ@}c*dpB%1)&81`|(0rdKw@2UWo*NW(=E~wdX$+)0K}{LYYCL*{RNpR*P(cryjV(q
zC=uQ*UXE%k*L;Mo2VmKZ2@?*}$qoQZB;b*;Fjx*D5uC#noBOgTioJoF9zv}5eu|+{
z+Wg|n;&~bxSeJaA0;bXeIvPBZ17B<0FS_6*@n^xXuohD>DIFedyu}h){V2WAS$sf=q-G351vaWSX%u*Alx!~J
zq(>N~g@BvZ(4FQ4rM}vyb$)}Ulyi(T(|BU#ZduJe4~p?Nf7tMbvIKXqd4#Z8
zGY$lgfsBs;J_h&%U@5iSkS?F-bGyPG@f!g2p@$9G{2_=eh6{gHxGk=od0#~P4GTf8
z4IA7)>ajHNOvrZd>`H38wC!K97R*y-F`I`lqRPUkVk^$1g`F_5?cSj^9T7znSpate
z4OV8uzytr_u6i~6I@V8PvSrjaQ^l|Y;f}4Mz3omS>BHvoRuOL-eh$$n8raF`7g7uK
z088i_96F}NgARCSm>2q)EE;Xo2XrF|)6LY8d_}JObjo;szp|jV&R0M$U)3$7yZ-6_
z0~47Uys@*9kkXTlwbT@OV1+ld7?(@KPwL{qHs;+=6eP67tEnYl?rCk_euqH(mR`!v##D(#ttQoP&T|g
zW_UVgb99h!4xayNV?Gwy_Is%LzKPo0kHuxdy@jJ6i>=n2NtB^X27r3AP)5oJLo&53
zpNaIY{V0#d1v$~F*nZT1)9l!-&DtlEFR6743PgH2NdY=VB=2P(rkMW37g
z38(757MFCrAN4})QXW9`L4bzoED8
zhrH}~jxjYqmvc9&R$8c)hSI-ANK3u&4nGE
zU2sP9GVSA5g9zr{|8jH|B{BG`CuaeCYv^GspYj^v);3A|_Ze|8?C7joXp@DtP2l`&
z3KJPRKMS;R_W9XbVeKF{a&tA2Y)#eP5!QHlhSm=RvMN9e!lxfLfo$$skUW1wnlvYv
z&d_9^(!Dn}s;C$}JRwC8*lm5AXu5!Q(7kt2rdDCKZZw=lIvBiF4CFBd*~lX=#*Fqc
zSZi@xgS;Aq^<+9PqpKeD{i};k&1U=Dv_^PKI~Hl3DXl%r?I6U4&ui_jr}!vOoKpRlLpC`EY#c>t3^NHZnj*-uMi+l^N}Jp6D%5A6k;^$~+CI{tc6
zX#JzDIpJqu2hb3ofpL>(G;g#ua0bRW@uZW9LT?fXo`Er8%#Qz2a^}+`?uXZBOf2o8
z7E7l=UXTh(FYsmI`-Da{h~BNOiMO`ZDq^hW1F?ZZn;2(xm_FuZ3QIu2YuZ(D)*&5A
z5oT&xomLX_X!I3|wiG9Pp>!;HpQc6MTe^!G@1+RkKamhEF|
z|90)|WGh{Ml5{jVtoO*1YB}WD|AG5UPi+U?2%R3b3`G=(upzF!;hM=E@o&L&!zY7C
zM=E!N9-f~9chU@9H9K16REovNw4TeWf?p7{Fw9yI%f92FanJxh&KA
zZDjUilq4NvUR-f^t?0N(>yu}_!v6&FnPJZ$v>Sl0$UKYCI+WZ^vnYK<^BfWkM}R2;
z9&@Z^0Zx^dkVC&pt+I2C8x2>=*F7&E)q-Zk$2?P%S6lnpJZgU92jLU=N?R_j%r67Bi;X(1(HKdx(An@HYTm+mdhXYG;l%
zX@~NyJ?t-0@7`2;!6?0*v{Gqae7tCU=EgpZa+-?$y?IDm>vXH;*eTM*hibtq!$nWE
z+xg?vXWomCcZL49lPMN;`~_Klob2)+|K}zV^s-m@e{NzdU*zp$NTH#7sAq4t;FozO
zY|eV=Meei)ZtUR^6u|#P%SN@z8?9@vdN(TXXhNrl`#yHT)(c+9Je)A5|EkE=
z==fV=R>#~Fca+{6Njj8N(h^tyG_@CinG(3$`yVKU)lbQRqi{Vtyao2b<(11bRJtb^)SD_0gVIZm
zq<24<-u*~=--GFWTP96wN$-0oy`m+uP2ic&AuYo$X}NSx%h2jWadTTD=gR+L<1^xO
z2~Xe~1r3Hwm$tK=HP5<0XhYgtvzq_W&U#YNfymK9>w^7@wukgVc1vcAj#fwyemS=Erc-+qmXd*3Pag!~cg+M%TKlYw6)m
zI!8w3Apk>BDmnC6H{EBxs+!(q9){#NiqU(1)eZP>I)k4EAcfUyAM2{!K-|XQl9|e20!15SH9BrceJ+k
z^PO_s`{rBL3T`hexZtZq(x8$TKcf*!1;_#5dr%z^!o4HDhQs$!ltG9N0Ki$jG64Xm
z%L-0qUaYr8p9g
ztC3-ueS4|(^1=*Z{mJqN;0Mm=t}QOL9?m#1lm5@d5KNVR-#}-S6ljOOI9w#rcMhl-fm1!081e>z|2W*D$t^fc4
delta 28456
zcmbuo2Yggj`ae8pl9}{I8i6z*0Rkj+1uTFRQF^g3iebnMAvlx3nF){>oG9P|Ht?z!
z1wm9)!~$kDt_or=SilvY0{3K8%0RiJT^~9-?{$ruJQf}t_l8$u8CY$
z;G5*1?3&E!PQEGrsjjJ(Cc9pYEFOpW&L}KhJd@mF0N~edqfxa9zOZBHxAnnXZ|B
zm&?WNi+vaQFLqt*pXHk6zr=M3mzDUc{j*)O{ccx|+ds!O$6w>B;fG6o9>3S6&9oPF
z3+Y<&*`qdOn<$rp4)ZvHTV~}v|H@wjNm%qTjp9ugp~P~`&YPD_^)#P#lOceVc-*EO8(;VboD>$+C!9#Y>m)LXXbSBUcXI%O*Wfrjoo&(
z&C_eS&C}aEd5zE)t`Rxfnz*El8@QqmRa9ClGD1~U)YqHhoxLqS+{caFv>%oCr}CO;
z`Au9tfXW9_`J8C^&0Ic+$_G=qJ6f)LPFZeqt@9>$PF-%>raHH|ZlU}TGaqu@>KW=<
z?-}OWKozH%6|UPTf4U{Vk@9C)@|!$oQunueh7;WEv3a6gqIM%J?e6f5q!xF2&LVi1
zXB5FLp3wyF_WG%1=uXd=hWcuRhpHzD+!4knllqQl(pP2hGDEdxzDNxKI;lZmniJyQri)hcF#=^RQr$=7$TuSg|
z(m|i^75^UB9}W(AeQ!2RAv)r^}*Jo6+!t%&JDSwqEe~9vbvE=_n`IVOZ=agS%$$#NlO=3FixtidY
zo@)sH+jH%5Tgdg5=Q_%K?YW-d5ziWe-+0y%{MK^=!S6g8!K2jKjh4>7r~FNp{12X+
zsnw5Es#{8bqWn5b{ut$NvE+|aK6I<4;6GHb-je^B@*6DqUnqZ@CI4T_Z?xopr8#kv
zZ{Ql?x2+K_8>!~)%aIbKW1H6qpPg#rC?7{PcW_O7e3PiSt8GZ~gF@uB9ZGphv^}5Z
zh!@q`o~%MKO8ZyVwB!Y(Ario=jqXtHY~rle(4lGULL?eK7T`L7l>pZRtkN0mCW1fkt1@5Z*u{2hLJf!3pNx@c~@tngdASftgL
z3{M1JTZLSyJyLR4X+55s4FKjD=>f0LTNA97UY|SY^+;W7DlKx}iuy&`hSDygP1{x4
z$;n)7*FG#Q7q_&dQY8)dakZ_`@Pk3
z>wH0CGf1g$$Vbs6v(_7|u4$+bdY1&Nsij-$c>D=~C($lmGB$rf+@At
z6SH-S&Qub@sFks9sNCt3%$WUD2zba}P?kY3;qr
zN|@X1(ta{DoxR@wjCnPAN}#K?dbs*+Qka3)8J7RQ4UquS^h?{hb)u~s8RZi_$ia8&j_ozmIy6_uv7U3SW#`0_)P94lEZI=B}s@YU6@2d(ya
zgYG(CSQ)gphAr(48C7(Uhv`T50KlV0VqKtmk=s}2QOcp{&{|HbIE!@v=o*e-UC`%M
z-kK41`Wk9$$;p5Y!^uuv@u9-0s9=5ArX4zMP_)aSs(ODeTP8-oFr23^gQf3xKHQJp>lKdWJtm;t`
zy?D_QQ<4)zx)@`bM8m=4(49mWX0)-^5&diO|k?qlcC;X;w-ZJtb+OOU|%u
z@uVK2XOuO>=*a94P?Pv$gqq=~X=tnu%AMK^lS?xpQN=j%(ZTRpYA7@+N#I-qhO6&Pux-QL;;*~IR}h_4HJ{jw5Gs{r891EmDC
ztZ5}#O4np2Y5*GAMogQO*bz!bvk#-n6hm5MLfYM~>qBT)k!{f}H&v<_2s5RTI=aCd
znA8v)v!srUmKCoLTA(AtG4`AZW2Rg5kH?5nLeutP>JfuMnovq2$n#tuZRbTLg_v`V
zOfaa<@2(~Pw7}Qk_Q>gkWK!Et7Zt~)u*I;k9MZZ}_Z6Ai)at5>qw<%hpkGE)2^ZNI
zv9dgQe4s|Lfa!-Af5;1Q0Gk0tQtwrlI%vDb*87Ip?V?HFTwyQJ_RU_IK9tI2DuEf~
zR#$4Z?$M*!IuQfO#{qT%u+G@kAQz%*J9n-8Lh7Y%cjLV38n^V4SD8uGqqMKx(`lIg
z7l;C_(m2O14{G2W4dqgl|T6#Y`ciO2`yxbv_k9HEQ^n53oF^rlWTm(j#9wPuIi
z$$U)J66f}Jeuq|vwBd6L#Y1fu&rKG?SSj)?WAVfaxsnFf(+tWi1ecA3fHx381Pg7E
zH*2@o)?Uc`_9M?+W|u0ejf^@9$Yj0S2X9hM4wwxQp6R##h_<`Txgg??wCZ_zjykG2
zudR9BC!+INKp+*dp1UzP50e=qRzXbBmdr2d#M+eZtE<11d{})o`S7}WN&8^7W!jGU
z1H+x3S^`$PJ=6*xa
z0+8V&V5FY#3>-mBm|$c@5v_6id~@72m&!(Za&6m+`oD-Y=ozUQGjyd^u%PTz*42wJ
zz{Lbk5WJBSo*C=eb4TXJ#nkRzEx4dEM!lR1yLJ4%-n5Ym3mi@K-~(FqLQ)}Z?WYCW
zT?-#2$A6J55}UOqSu&NC@_g_O_!;qt%az`TjjIJ{a`<@}a5aq!%~KJQRnKXx^HF#q
zFQ*1Ow9n8F{8>w>v^nj(dY+g)rDN#{!%0rG)*F!AIrOlsVtjUnYyZRnDL;vvny+@6xn-+NG)hN9N;7WjH0LwLF
z$(SxITOI{r9O5y4b21W
z;t9=vq@eAImQU;4@~|sVb``)C1V|>_oLPhdSxk7$m{gIV${BT*
zWQxZdsF8IGc#X_cH0voiO2z=Wgjl>4dCFO_4x0Q%w}z8!f%e%{*VkgSlVNBwh6T^4
znQXruk448foXUX_zIqM~p)`=yjK@yYwzn3FX6?1sj*h$NNqgHDt$k^A%Tq_&z$^RO
zoob1z&0F5z=|JE2Xq%Robpmk}aYj;vUQ#A6Uz&$}-j7$lMAd)Ms@E3KlYP%BXuDzUQ;BJC
zOB|%qq%c&wbIoVh;tvSQn|VWH5R=g@BRicQ2{7Ff^ci6vtnzl`eFD6WYVx
z3`d>z#)eLrJg3Y=)f7}s)sAf#E-ulA-d5O;6(kzQD4DZ~@SKya^xKr~Hf_airRRfF
zMxx>oR7MrgNT{o?3)Z=PO3}gs
zV5H%4jg;wQrcXa-@}%nVV`lQc3@HZhr<0o3c>?l3)UVN@#;tj`7nd~SiR|imwCUmV
zR%70e95uw-ZMMlQ-(vS1j)+nDWd3n5S*j$Rf3h8;
zZP(}4*lTSy_F3sPSZ+(McI2HL?V?ALl5#yc8%QK+9nzTR4+nyI=}${GrWK;%bU?;
zGr&J=wt{eo25YLww>af(+NQ_y267b06XKtyP5bv_L%V=AMts26Ku2daG-rp26O(4-rW7Qv
zOgqxiRyk~C!u1)eGWK@txi7KesMD62cOW(UKx)-T`JE0F_Bl}8^FU$Qfzn8#Y|w$O
z{SI^=a-gKwH~Fb)>E9(}W+or8WhNyb?XHO@i+c386%PqG38xoRvd4AKJHeE(fMZjR
zcW7gt{7`G$*+n?Di965HChV+8ZgF@LVIjc+ZSud1;vAkN?em?L+P`+DrBYQ28;`@2
zs`cH~S)WiLlC*)lO5^X(*p{L#-gQO-8`b^U+7r9ZcY1P`CI@?HgP-aqDzuqTm8bQl
zZu!rX+mfn3eN5yEyZUkRPga^IkNZ#aSt>7v`<7wQK-H5%uNYSsMm3!Eq=B~Xd3k+
z4+8vY+0)bHvDv~Ng@XNG9&9ccgTvfXUSQPK*J?eUOG}3{-YN`dorm_U>$S6=J4Iw@
zmp`|pUncamK$??)spMw^Wi|nx6F5A3MBDIu$57N()z%o#8$8iL!|Bw-8&p=q7Q&{3
zT{gqM;8JCBt0+g2ff>0lrCCI^ksg^<&Kb=UOGhv=6JR6IVG}COM$1%MayHUjqqfX*
z(S#jIo;Lf1d|K5md7(I^E0rFAMbz81%`Z$X%ykTVKfCOm>^}Rl`@EAqa9{So1DV+e
z@{9GBwJloyi|f*gY&nCzvt{HYuS`7JPkZvk;h~(|)(P)->~edrw>ws@N?j>d4n2^P
zdQD)>@V{qt`+G{a1KC|~@4qj*eC22|ptSr0nIjHl7U-#KQ>kO!xz>3gv!kA}HswHO
z&YJU482;(_PG;|YnZ1u@B&MYw>0nDMST}NCYSF3*M~Z9(RY$wp9BJ>w=j@BmIdFbr
z#)0I1+Y7g+?>+PKy@{ctjyQ+&NUANdsvrDR`1&wFbhYS`NFSpD9riJzuqubFUr@U4_Sd*l23bablF*1bHfFk@VA+e^KZ
z&KVx}(x8#HbB4veJV;RH9?oa3$;0~bneq+lVwy#n=3KhL0GXp9oW;|(Syn>
zwYXPLjRdGOUfp7+eZAwaAMN6V`X(q#VcqD3PI?3M0jSi7F_%OKGID5}#_O#ov!K1~
z@bP@W3^ZGi<(spU^?U3Ap??(OK5L#f%owo~?fFS=CViG$#C5{x&_LS%ocYA(}CKg(<2
zv#3RN-o;h(f_|TssYV9ZS;)k!we>g02#gfj2qP8VM~OZ}t7bauLjUqi;G^yPI|RCq
zZjbbNYn+HZ7(7#3zQ5l*HbcyKk!1}#2v(Ug&|}WByaHrD!ybTxOOBJRSVp^x*^e#M
z`o3G_7(r;YYG=N^ob`;ZB)r`$3YcvxX)Ggt0o`mcoKpzW+CN!qI50Q_N+Jsr*5)sO
z6^Fo+@JtF5O{*Lneepto@w^4Ccn!lo5>CwAVu(2}qACopJQ6$?PmD_Hn6As;?co?p
zXk6Vk_1&IAlxWRH&we13!XegHW*M_0ypL2ut+_jM2VHy9=;}C!%5T$-8GXWH8U9|W
zAmf<*e(@|QQ)H`z`$L4M?hzD`O2wWxnfq5!VGuT^lLmU0!QXqo$T6NqxI_Ep{e`ry
zYWU#NI2!o9j|zrhwvj&rKy|4T{K#a06aXM&B#l(3;`Kq|9plqc>+^A8h}W2z=JrWg
z4tSc>o5e91yqE$o6##u2j&a!CF&y*oLql@IXd4=W3)DEk;lw_gJB$jugOE2+dY-x}
z!PaQR2W6wTIeFZLmD9X)=^E2KI;)R56s60oI=`Y$-XCIQ`T0KI((uuL2EdCzem)<`wI4DH5Tjas%D
zW(>9)CC0MsWW_5N2t8ESgVt+ppZ8QtakgV-{47*=2RIA>EG#F#8nV1hp`cw|PCM!K
z8dE0GJ2mPq3EQM~5|};SMF`ER~mi}e7
zz2YFAW41CaCXfDwiq8SSt>!{)$Kb-e7^WG8yzQz@{k&5smg^Lb0d2CX7{vdHK0ig3
z^)`pe-NUG#2!QA1mjI?t^9C&QFM9cgw-BaPgm*t!)o1@6*Gx7gQNLmB4)1=*YCHw-
zy@OO!Xgj{B(AIqYKC#MwWKa0ieG&}uK5f*|*0K=wKyeF;WIu|Td5rWmsB+T%6YbE^
zKD2Y1^Zh7`Ce5Z=b1Ot`AJ56--T!(_|2^a~B-L(BxU%NHYyqTwDdo|iXe`2n_8cC7$xo4|W
zDIH_PS^<5L3kf&6f^qAwMPjn{*snuErmy4vxaT&E-oe5Y+EHgI!^$fB(ML4v=L%8gxQ+;z(jE}vk+|Upf#_wRh>1oODZwffFalRm{UnNYqlg&Ew9!xZ
zKlt{=wR){Xbe%BQONOaJk_CA*oxTljK??H2~{yAd9qVBY`neosv6>uW
zO}-ZOP-hvUM&y9a`Hwrj8dWS@vpM@ne&z~vd?oh|Fqu?%hFp&13V^EsSSgERmGZES
zT(8MTb?sUScngW!q*QhnsK546+c#k5XBFqqG~CxL6*(;T1o}r)Gf{x
zNp@jMYoIz0!eN0qt?kRxL}p6bK`Q8x
zPm~|(Z*&wrCq%W)NHx6xP1aCqn$1iKfE3#BHodw|K`*a`bx3KBKB_=;wYoihL4g=h
z#oA(O*$2SV3@lb7%bKE8L*H8<_J%+f#R@07EF-U;0!I@~9RHcls<%ZiW~0*}03VDn
zR}V)AVr(HdB?tB$96pRi6Bv0&tH01=*_srUIDSZYnyj@7V9Ln@&eN
z_SL2bJaz^ZVp5RUJy6P(rKahqyySWi!194;l;&(e>N)+r&SHY|BXoS5URfwcWa3Cf
z-hxViADrz=3&q-Wi)V{y4Vo}NxMTKrF;;8}a+yA{x9HN=#0@}o;y%Iw=vi#y%9fb3EN|I!{p&@=zXI;q>l)8f-5*qm!B@J
zZ&+MkN@s%&Qf@-MOMjt{=n=9}nnEt@48$n0WVL+|^?fWV!}Hs0G>_36v{d1JchoXZ
zzDJ6;?Rg3`l^`SDp8ULP8mrsuwcUiI&305rN3YA!w4%qmg?&n9K1zQY7yC5S0LLChBKS7vsos|1w=16ltvK;0oQG
zS*I^LPjqu~+o$zS=ZR_Rbs4BUc?({Jpj)+h$m-23If%}AR%7)x8Nob=nkKDsct(k;
zJY#Fwwxh0%4s<4mmDk+4q*eia!TG}J{EYhlN21Psv-vc946#Kr=_g^eZPGbh%02DNU
z8v*=yAO!$B`Q&rJHu9M6CcM01>QLWVBH!^PVUgZGewN6MOFf8ZKq&2I>b}{ci}*yp
zZnh}xhAXjK?M5Q)3HiPC!GPM8Tum)$F$%?$d-Q#?MOVc`o=O<8zNm`f+#ZxcCFq77
zy(K`QlPT~oPzR-yM+hzReIe#AO{L~`G53Ozd7}zQ!8xQ`j~~cNTHT_+ag@3$Y2WA;
zS+x1nPWOj)*?F}>sPp#E9{n;e#TlSR;rg1s-7C6v`2+=D0S~?r{57R$ZKJ3~)pz<)
zF9mz7)=Y8@O25SeOdeirV@0+*uM0H&^j}pdr7?+VF
zKE&LB(|67logBxg#n|@u=ZYTK1lw_}puK3GxZa*{5NQ8k*W2cc-W{S`Xf7J1hR~*d
zmD{0#LhNC=FfRW@LHGpWBmWK}Z3VHg
zfqyIz-TJRZ@iqXp;YukZ7j7o9ixb`64{zb?0~d<%;u}4*P=q(_O^aaZmZP&6v%n%q
zEy(|PdB%Kt0v+(2dk6XR^Q?fSWkMfk>iLHoER;%ZR=$sA7{iJqHEyUK6zWLo&i@LR11sZ1W#
ze{K>z6WHi}vgy5>MZeHtRP+akkuPgHYc(&}S&Xc?(fN(G9N531Kk|({yrLtXN)Yj=
zZR7|l>SDdjBr0BqT5q6MTFR=P`sypiczxn!qL^K+jmYq5kQvz(E*lt0(<3(sVoo*j8Y%fTdK-{JO*su(k$?vhr+M+hyVo;`
zugQ|T&LGs(O_niekcFK|+88B!(cfJvrYhIT3J{~lEPGSp*5i~D`;_?@q9Z^70BbHW
zgV_a^g=5?lR04boPQ6JDT5U)5t(S|vq{Vo%uYGZ<{Sl#$yFzp=Vd1j)undvOc=lJM
zxR;~)sw+g9ifkAeUVx{eQ96Ohc{`K!|3@3FuVeE#rgL}D=dWx;TdYQe&tE$e8owmz
z6R#A{(1b@d1aPUWtw=Tcang5W_68Vxm}y
z*;AW}%{t9qhI?XTjin-Lg8WJvi;Ji^88P8^*1Hh8dC4~Y=@nul=`+3U)=v7oM~q@A
zb)Z@`J@ctI^iaM6jl|M{42l^C#}m2C9sW24^s%|Cp0$aty4|``bg+fqPAXzi6?W)h^v`s0_@Pb8F^v4%}3Q6O}&ZuRuKP#K6$a|
ztiN!r_(%9|RQprciNh*(q4(VN9c#sK=TRV$q5rT}R5?@W(c?WJ;2izT8${j(Y>!MD
z&zr2ogYm%c`f27Q>p8QNjUmQuAGhh-ZV;W$!iJxEcY_te8mMwF?9!7>@|2%)Mma&D
zYJw(;9fJt#&F#H3aaLTRDM)BK1*_Rqspe;gzVc?#HFF>e21CP6MQV)x_|0Op(&QLH
zvQD!g&0Zl~z8vok(giL^)JVtcE752s{6Pil5pC><82fY@n_i2rlQoV0xn8L+)kUXX
zLxB)mH;FGQ?uf)?UsMA;lcGPZi(?Td{MI@l>?v&7%Kz#g-730=xH+WToHd3%eo#6x
zs$#=RWhHgfoTYBESZZjs$PSZHX4$ce?nkXFvea6Xne-Z{@&2B$afb^
z#b7ODhmnS%i}1k5`aidbeuK?KvyurjO1>*Mbx0X6Fj@1~9FDKzA%
z?YG@6a^fp7kde#ZL7{^sdVk7fr7Q)Tk}mN2Xwm2GQ%V?rcpt@Z>-Asn6MaH%7$Kv`
z1G7F41hR}fb}Z;iG423c`3&mGNRwXr07zsA$%yen*i;0QwrF%JN71t}8yndq_dE)c
zs~0rLAiZ0rUg*Do8cuKDy-k#0z5UVs!V`D;G%Ay*Q1jDH!kIRVc))GI6@jNb1@**B
zY{D{D?AyBOt=oxbY5LvU#k7QO)NqnqqW`*`uAn#T9e0Rf;#Ph74&g7FNsm(%x|>DG
z=spJuX6qXt7JZ3jX%CC8!mamvSWM{3n>*d8`v2pp4f^X3ivc1-7i|<%SLl7(XzVfi
zm^N{CEcaP~S$#|eW-8hF!sV)OnlaIvnP>nP;cH~menCy6l(d)OYa#_etSm1=OZB<Mk)*?9x~45@k;1ob`ux(fI(lalEhYZWEpKb9al(khPEx1cNI;f494XT|mL
zb7CaNUX6Ap~xmY#pDvC0i
zfjBc7V1WMjS4H?vX`HtCs+{)9*My7)i-Sc}5As+hY*_3iqstrFlWL9Bu*RrU
zlp%P4U8qAj0ABlKTH939cj=TjK#QtOR#$_jcsI_>!RuEh$$5q0GA75*~Q+HS@dtJt_UyVf0rk17=#&f;__Wz_9@Nf
zH`3^Dq_scrsTgf{vM!eDKmJpc^gzT%7R27s6csy-7YVh<*{puXA<@ZsGd2D;O`mm0
zTu2s*H}u=zI3%{(2aKf(c{TuhMrg=b^j+INX>?oy|#%W2Y-}i+m%=`pM
ze-1G}j8tF!?Jva4F033^Q2{M&VB+e^9!Dr8ls(>Ctw$OTiww1vo{NXgaE`TE65hHZhm*S{}
zIV5To$gpz#cikQ?|&is
z>FbV&(#l)VMU-{&R%F)$YygO%LI38k=&1j4M5H)2QFU>9+BY0aDxqHdt>|tKK~2Us
zr-fzbp;y)DwWT%CS7sqxn1n^mAbVv4|3lw1iPrS2Wjx!ka*qMSy(h|!yS@Zvp+D?S
zz3%yEECZ`4^^S_VmUt2yq2ciTuR3#Qh3@_8t>4oNws#PLliF8*FBUu18)p3OOJ}I;
zAwBD7@o?vRsRf76MmkN@bqnffb)YuKbp6AhMU_~kr~M-O3zt6p7g6YBHGW8U|022<
zw^KLGS@?=VX|10cYiZSMP>pkF|F!I#;fWKKrCnETG
zv70^_CbvR^IbuVcrZ!)rhhTlt(>}BcfB4G{@;DoPnv4R7`i`j#F9~iSQZNvqZ&@=_D~k_4>p_`-Vu27nfweL7c(!
z24ta*rWh~Ue_-v!%?b)#kTx>a)eL&E)kCLSwLve%_s{5WCfn!Ku`#fmUVu0`qT^tW
z?SOH&emUeBG~$*oBJ~o$%K#kJoj8etKf6%}kfz_3V&5G>pH8Lrir53Gr_lD-=u=1a
zx#`4pUeNHcJn5vN(bEJX2U3hXQ~W=Q#(I>)>UCN6k==`tW{$AIUqz(SWNp%>CTo3f
zmc2?%(I#zXNE8!q{K2^tI;L;+v6k~E%$1OcC>uxK;bYgR-s4Nn@(p_Ej0~H;w1b__
zrPRzeINQE6{9T$u`cFCbvqic-B9{zfw_clTUz!r7M!u$hoogQ)gFL;DO&%~H_ksL3
z0sao~7C=m_5o15mXySw|M`oDW8B59;AqrbR#h97GWYNhJfA+BYQg2g0zD;9J)t}C@
z(~TrW+uiy0&Fa`#Sv5yMh`;EY!jU+_pw9zFGZJELPU&iiIC#7I6DY13YCz0mh|^qyhlR`WFnv04NiZhXiqL=J-K_TrpZQSV2dF>Z
zg-+79+?{lZT6wTVp_NXU2FzoDGSGqw3OstCUgx>{dXXzxD-b`n`E&Ze}w
zF3-xq_V_N!PIQ&@_7T4K=pW!$F(dnz$gz@MP-O4Pcc%HyhUJZGS|VOhWbfwqh|piA
zhl)sKP3>KZ?f;JNbQd_xSn`UBhkG3Z->diRX1_SJ74^*92a$RRU@hvmBcW4&jY
zy=W4zUt?^Urwc@Sv|(>bno-({&P;6fAj^onfz)1IE3;Rsy~lg?7s~AYliCTb=;zJ4
zC{xb~;H5+JT@Mu!d`&HHZXZ-`pJh*#?-DP1^6Y+tZtrdHCTiQe_qM0m#eRKIrF~(@
z$i$UYcOxBFRrzT)@deZ^+0%f==>!nZ1t{i`^l8?`4f0ZX1{ZqfyX$Kkst`rUx;Y#%
z8M)Cy>8+yANY>TUr|G1+a61D1jl`pwSpV>Mb?IJeXi*()S)PUR6w1=at?HWbsrXT7
zKAM|{I}OkcykJcu-RT>HIw%^eoRJf5fET6%RqP(@Iy!Uwd(=4vpp$Fyf|9A0sbCDk(giXO8;(Ub@Ox
zFRTi>1D6KWrN(-0NwMMt_4)UO=;#;WX(N|*BXB9R
zijJRfW3`HfFW*M1cK~o(TH@L(2z>t0tY1!qpC027rTpIS8OH0M0=9;E>PqDFm7umlmm(Dc@W;(0cqNk96qDBj
zU^7BuFG1c0unFJ}0EF}$?@EM=D!xFN!c#fVcoJ@3vOE4rFBxFZtNa)RF#MSRnZ-yv
zom88?zICR|i^tcK$cYTseFN+l73T^2kG9_fRr(hL>~GnRdfN97wCCHi{X!&vJ3DTI
z5S8CvYQ2h4nc2LJ#7
diff --git a/recruitment/forms.py b/recruitment/forms.py
index 0e969c6..3d33c2a 100644
--- a/recruitment/forms.py
+++ b/recruitment/forms.py
@@ -584,11 +584,12 @@ class InterviewScheduleForm(forms.ModelForm):
class Meta:
model = InterviewSchedule
fields = [
- 'candidates', 'start_date', 'end_date', 'working_days',
+ 'candidates', 'interview_type', 'start_date', 'end_date', 'working_days',
'start_time', 'end_time', 'interview_duration', 'buffer_time',
'break_start_time', 'break_end_time'
]
widgets = {
+ 'interview_type': forms.Select(attrs={'class': 'form-control'}),
'start_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'end_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
'start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
@@ -1326,7 +1327,8 @@ class CandidateEmailForm(forms.Form):
"""Generate initial message with candidate and meeting information"""
candidate=self.candidates.first()
message_parts=[]
- if candidate.stage == 'Applied':
+
+ if candidate and candidate.stage == 'Applied':
message_parts = [
f"Than you, for your interest in the {self.job.title} role.",
f"We regret to inform you that you were not selected to move forward to the exam round at this time.",
@@ -1335,7 +1337,7 @@ class CandidateEmailForm(forms.Form):
f"Wishing you the best in your job search,",
f"The KAAUH Hiring team"
]
- elif candidate.stage == 'Exam':
+ elif candidate and candidate.stage == 'Exam':
message_parts = [
f"Than you,for your interest in the {self.job.title} role.",
f"We're pleased to inform you that your initial screening was successful!",
@@ -1346,7 +1348,7 @@ class CandidateEmailForm(forms.Form):
f"Best regards, The KAAUH Hiring team"
]
- elif candidate.stage == 'Interview':
+ elif candidate and candidate.stage == 'Interview':
message_parts = [
f"Than you, for your interest in the {self.job.title} role.",
f"We're pleased to inform you that your initial screening was successful!",
@@ -1357,7 +1359,7 @@ class CandidateEmailForm(forms.Form):
f"Best regards, The KAAUH Hiring team"
]
- elif candidate.stage == 'Offer':
+ elif candidate and candidate.stage == 'Offer':
message_parts = [
f"Congratulations, ! We are delighted to inform you that we are extending a formal offer of employment for the {self.job.title} role.",
f"This is an exciting moment, and we look forward to having you join the KAAUH team.",
@@ -1366,7 +1368,7 @@ class CandidateEmailForm(forms.Form):
f"Welcome to the team!",
f"Best regards, The KAAUH Hiring team"
]
- elif candidate.stage == 'Hired':
+ elif candidate and candidate.stage == 'Hired':
message_parts = [
f"Welcome aboard,!",
f"We are thrilled to officially confirm your employment as our new {self.job.title}.",
@@ -1589,6 +1591,17 @@ KAAUH HIRING TEAM
self.initial['message_for_agency'] = agency_message.strip()
self.initial['message_for_participants'] = participants_message.strip()
+
+
+
+class InterviewScheduleLocationForm(forms.ModelForm):
+ class Meta:
+ model=InterviewSchedule
+ fields=['location']
+ widgets={
+ 'location': forms.TextInput(attrs={'placeholder': 'Enter Interview Location'}),
+ }
+
diff --git a/recruitment/migrations/0005_scheduledinterview_meeting_type.py b/recruitment/migrations/0005_scheduledinterview_meeting_type.py
new file mode 100644
index 0000000..fe94194
--- /dev/null
+++ b/recruitment/migrations/0005_scheduledinterview_meeting_type.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.7 on 2025-11-09 11:06
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0004_remove_jobposting_participants_and_more'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='scheduledinterview',
+ name='meeting_type',
+ field=models.CharField(choices=[('Remote', 'Remote Interview'), ('Onsite', 'In-Person Interview')], default='Remote', max_length=10, verbose_name='Interview Meeting Type'),
+ ),
+ ]
diff --git a/recruitment/migrations/0006_remove_scheduledinterview_meeting_type_and_more.py b/recruitment/migrations/0006_remove_scheduledinterview_meeting_type_and_more.py
new file mode 100644
index 0000000..9270e59
--- /dev/null
+++ b/recruitment/migrations/0006_remove_scheduledinterview_meeting_type_and_more.py
@@ -0,0 +1,22 @@
+# Generated by Django 5.2.7 on 2025-11-09 11:12
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0005_scheduledinterview_meeting_type'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='scheduledinterview',
+ name='meeting_type',
+ ),
+ migrations.AddField(
+ model_name='interviewschedule',
+ name='meeting_type',
+ field=models.CharField(choices=[('Remote', 'Remote Interview'), ('Onsite', 'In-Person Interview')], default='Remote', max_length=10, verbose_name='Interview Meeting Type'),
+ ),
+ ]
diff --git a/recruitment/migrations/0007_rename_meeting_type_interviewschedule_interview_type.py b/recruitment/migrations/0007_rename_meeting_type_interviewschedule_interview_type.py
new file mode 100644
index 0000000..85e6f83
--- /dev/null
+++ b/recruitment/migrations/0007_rename_meeting_type_interviewschedule_interview_type.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.7 on 2025-11-09 11:30
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0006_remove_scheduledinterview_meeting_type_and_more'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='interviewschedule',
+ old_name='meeting_type',
+ new_name='interview_type',
+ ),
+ ]
diff --git a/recruitment/migrations/0008_interviewschedule_location.py b/recruitment/migrations/0008_interviewschedule_location.py
new file mode 100644
index 0000000..2800b75
--- /dev/null
+++ b/recruitment/migrations/0008_interviewschedule_location.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.7 on 2025-11-09 12:37
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0007_rename_meeting_type_interviewschedule_interview_type'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='interviewschedule',
+ name='location',
+ field=models.CharField(blank=True, default='Remote', null=True),
+ ),
+ ]
diff --git a/recruitment/migrations/0009_alter_zoommeeting_meeting_id.py b/recruitment/migrations/0009_alter_zoommeeting_meeting_id.py
new file mode 100644
index 0000000..602917a
--- /dev/null
+++ b/recruitment/migrations/0009_alter_zoommeeting_meeting_id.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.7 on 2025-11-09 13:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0008_interviewschedule_location'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='zoommeeting',
+ name='meeting_id',
+ field=models.CharField(blank=True, db_index=True, max_length=20, null=True, unique=True, verbose_name='Meeting ID'),
+ ),
+ ]
diff --git a/recruitment/migrations/0010_alter_zoommeeting_meeting_id.py b/recruitment/migrations/0010_alter_zoommeeting_meeting_id.py
new file mode 100644
index 0000000..d64f578
--- /dev/null
+++ b/recruitment/migrations/0010_alter_zoommeeting_meeting_id.py
@@ -0,0 +1,19 @@
+# Generated by Django 5.2.7 on 2025-11-09 13:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0009_alter_zoommeeting_meeting_id'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='zoommeeting',
+ name='meeting_id',
+ field=models.CharField(db_index=True, default=1, max_length=20, unique=True, verbose_name='Meeting ID'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/recruitment/migrations/0011_alter_scheduledinterview_zoom_meeting.py b/recruitment/migrations/0011_alter_scheduledinterview_zoom_meeting.py
new file mode 100644
index 0000000..dba9c62
--- /dev/null
+++ b/recruitment/migrations/0011_alter_scheduledinterview_zoom_meeting.py
@@ -0,0 +1,19 @@
+# Generated by Django 5.2.7 on 2025-11-09 13:50
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('recruitment', '0010_alter_zoommeeting_meeting_id'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='scheduledinterview',
+ name='zoom_meeting',
+ field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interview', to='recruitment.zoommeeting'),
+ ),
+ ]
diff --git a/recruitment/models.py b/recruitment/models.py
index b712d0c..a4c54d2 100644
--- a/recruitment/models.py
+++ b/recruitment/models.py
@@ -742,6 +742,7 @@ class ZoomMeeting(Base):
topic = models.CharField(max_length=255, verbose_name=_("Topic"))
meeting_id = models.CharField(
db_index=True, max_length=20, unique=True, verbose_name=_("Meeting ID") # Added index
+
) # Unique identifier for the meeting
start_time = models.DateTimeField(db_index=True, verbose_name=_("Start Time")) # Added index
duration = models.PositiveIntegerField(
@@ -1599,6 +1600,20 @@ class BreakTime(models.Model):
class InterviewSchedule(Base):
"""Stores the scheduling criteria for interviews"""
+ """Stores individual scheduled interviews"""
+
+ class InterviewType(models.TextChoices):
+ REMOTE = 'Remote', 'Remote Interview'
+ ONSITE = 'Onsite', 'In-Person Interview'
+
+ interview_type = models.CharField(
+ max_length=10,
+ choices=InterviewType.choices,
+ default=InterviewType.REMOTE,
+ verbose_name="Interview Meeting Type"
+ )
+
+ location=models.CharField(null=True,blank=True,default='Remote')
job = models.ForeignKey(
JobPosting, on_delete=models.CASCADE, related_name="interview_schedules", db_index=True
@@ -1636,14 +1651,16 @@ class InterviewSchedule(Base):
class ScheduledInterview(Base):
- """Stores individual scheduled interviews"""
-
+
+ #for one candidate
candidate = models.ForeignKey(
Candidate,
on_delete=models.CASCADE,
related_name="scheduled_interviews",
db_index=True
)
+
+
participants = models.ManyToManyField('Participants', blank=True)
system_users=models.ManyToManyField(User,blank=True)
@@ -1653,7 +1670,8 @@ class ScheduledInterview(Base):
"JobPosting", on_delete=models.CASCADE, related_name="scheduled_interviews", db_index=True
)
zoom_meeting = models.OneToOneField(
- ZoomMeeting, on_delete=models.CASCADE, related_name="interview", db_index=True
+ ZoomMeeting, on_delete=models.CASCADE, related_name="interview", db_index=True,
+ null=True, blank=True
)
schedule = models.ForeignKey(
InterviewSchedule, on_delete=models.CASCADE, related_name="interviews",null=True,blank=True, db_index=True
diff --git a/recruitment/tasks.py b/recruitment/tasks.py
index 415aebc..4515e1b 100644
--- a/recruitment/tasks.py
+++ b/recruitment/tasks.py
@@ -461,6 +461,7 @@ def create_interview_and_meeting(
meeting_topic = f"Interview for {job.title} - {candidate.name}"
# 1. External API Call (Slow)
+
result = create_zoom_meeting(meeting_topic, interview_datetime, duration)
if result["status"] == "success":
diff --git a/recruitment/urls.py b/recruitment/urls.py
index e8ed14e..ad838ea 100644
--- a/recruitment/urls.py
+++ b/recruitment/urls.py
@@ -234,5 +234,8 @@ urlpatterns = [
path('jobs//candidates/compose_email/', views.compose_candidate_email, name='compose_candidate_email'),
path('interview/partcipants//',views.create_interview_participants,name='create_interview_participants'),
path('interview/email//',views.send_interview_email,name='send_interview_email'),
+ path('interview/schedule/location//',views.schedule_interview_location_form,name='schedule_interview_location_form'),
+ path('interview/list',views.InterviewListView,name='interview_list')
+
]
diff --git a/recruitment/views.py b/recruitment/views.py
index 10d7e38..6cb373f 100644
--- a/recruitment/views.py
+++ b/recruitment/views.py
@@ -43,7 +43,8 @@ from .forms import (
LinkedPostContentForm,
CandidateEmailForm,
SourceForm,
- InterviewEmailForm
+ InterviewEmailForm,
+ InterviewScheduleLocationForm
)
from easyaudit.models import CRUDEvent, LoginEvent, RequestEvent
from rest_framework import viewsets
@@ -192,6 +193,43 @@ class ZoomMeetingListView(LoginRequiredMixin, ListView):
context["status_filter"] = self.request.GET.get("status", "")
context["candidate_name_filter"] = self.request.GET.get("candidate_name", "")
return context
+
+@login_required
+def InterviewListView(request):
+ interview_type=request.GET.get('interview_type','Remote')
+ print(interview_type)
+ if interview_type=='Onsite':
+ meetings=ScheduledInterview.objects.filter(schedule__interview_type=interview_type)
+ else:
+ meetings=ZoomMeeting.objects.all()
+ print(meetings)
+ return render(request, "meetings/list_meetings.html",{
+ 'meetings':meetings,
+ 'current_interview_type':interview_type
+ })
+
+ # search_query = request.GET.get("q", "") # Renamed from 'search' to 'q' for consistency
+ # if search_query:
+ # interviews = interviews.filter(
+ # Q(topic__icontains=search_query) | Q(meeting_id__icontains=search_query)
+ # )
+
+ # # Handle filter by status
+ # status_filter = request.GET.get("status", "")
+ # if status_filter:
+ # queryset = queryset.filter(status=status_filter)
+
+ # # Handle search by candidate name
+ # candidate_name = request.GET.get("candidate_name", "")
+ # if candidate_name:
+ # # Filter based on the name of the candidate associated with the meeting's interview
+ # queryset = queryset.filter(
+ # Q(interview__candidate__first_name__icontains=candidate_name) |
+ # Q(interview__candidate__last_name__icontains=candidate_name)
+ # )
+
+
+
class ZoomMeetingDetailsView(LoginRequiredMixin, DetailView):
@@ -1126,6 +1164,7 @@ def _handle_get_request(request, slug, job):
def _handle_preview_submission(request, slug, job):
+
"""
Handles the initial POST request (Preview Schedule).
Validates forms, calculates slots, saves data to session, and renders preview.
@@ -1137,6 +1176,7 @@ def _handle_preview_submission(request, slug, job):
if form.is_valid():
# Get the form data
candidates = form.cleaned_data["candidates"]
+ interview_type=form.cleaned_data["interview_type"]
start_date = form.cleaned_data["start_date"]
end_date = form.cleaned_data["end_date"]
working_days = form.cleaned_data["working_days"]
@@ -1197,6 +1237,7 @@ def _handle_preview_submission(request, slug, job):
# Save the form data to session for later use
schedule_data = {
+ "interview_type":interview_type,
"start_date": start_date.isoformat(),
"end_date": end_date.isoformat(),
"working_days": working_days,
@@ -1217,6 +1258,7 @@ def _handle_preview_submission(request, slug, job):
{
"job": job,
"schedule": preview_schedule,
+ "interview_type":interview_type,
"start_date": start_date,
"end_date": end_date,
"working_days": working_days,
@@ -1260,6 +1302,7 @@ def _handle_confirm_schedule(request, slug, job):
schedule = InterviewSchedule.objects.create(
job=job,
created_by=request.user,
+ interview_type=schedule_data["interview_type"],
start_date=datetime.fromisoformat(schedule_data["start_date"]).date(),
end_date=datetime.fromisoformat(schedule_data["end_date"]).date(),
working_days=schedule_data["working_days"],
@@ -1286,34 +1329,59 @@ def _handle_confirm_schedule(request, slug, job):
available_slots = get_available_time_slots(schedule) # This should still be synchronous and fast
# 4. Queue scheduled interviews asynchronously (FAST RESPONSE)
- queued_count = 0
- for i, candidate in enumerate(candidates):
- if i < len(available_slots):
- slot = available_slots[i]
+ if schedule.interview_type=='Remote':
+ queued_count = 0
+ for i, candidate in enumerate(candidates):
+ if i < len(available_slots):
+ slot = available_slots[i]
- # Dispatch the individual creation task to the background queue
- async_task(
- "recruitment.tasks.create_interview_and_meeting",
- candidate.pk,
- job.pk,
- schedule.pk,
- slot['date'],
- slot['time'],
- schedule.interview_duration,
- )
- queued_count += 1
+ # Dispatch the individual creation task to the background queue
+
+ async_task(
+ "recruitment.tasks.create_interview_and_meeting",
+ candidate.pk,
+ job.pk,
+ schedule.pk,
+ slot['date'],
+ slot['time'],
+ schedule.interview_duration,
+ )
+ queued_count += 1
- # 5. Success and Cleanup (IMMEDIATE RESPONSE)
- messages.success(
- request,
- f"Schedule successfully created. Queued {queued_count} interviews to be booked asynchronously. Check back in a moment!"
- )
+ # 5. Success and Cleanup (IMMEDIATE RESPONSE)
+ messages.success(
+ request,
+ f"Schedule successfully created. Queued {queued_count} interviews to be booked asynchronously. Check back in a moment!"
+ )
- # Clear both session data keys upon successful completion
- if SESSION_DATA_KEY in request.session: del request.session[SESSION_DATA_KEY]
- if SESSION_ID_KEY in request.session: del request.session[SESSION_ID_KEY]
+ # Clear both session data keys upon successful completion
+ if SESSION_DATA_KEY in request.session: del request.session[SESSION_DATA_KEY]
+ if SESSION_ID_KEY in request.session: del request.session[SESSION_ID_KEY]
+
+ return redirect("job_detail", slug=slug)
+ else:
+ for i, candidate in enumerate(candidates):
+ if i < len(available_slots):
+ slot = available_slots[i]
+ ScheduledInterview.objects.create(
+ candidate=candidate,
+ job=job,
+ # zoom_meeting=None,
+ schedule=schedule,
+ interview_date=slot['date'],
+ interview_time= slot['time']
+ )
+
+ messages.success(
+ request,
+ f"Onsite schedule Interview Create succesfully"
+ )
+
+ # Clear both session data keys upon successful completion
+ if SESSION_DATA_KEY in request.session: del request.session[SESSION_DATA_KEY]
+ if SESSION_ID_KEY in request.session: del request.session[SESSION_ID_KEY]
+ return redirect('schedule_interview_location_form',slug=schedule.slug)
- return redirect("job_detail", slug=slug)
def schedule_interviews_view(request, slug):
job = get_object_or_404(JobPosting, slug=slug)
@@ -1322,6 +1390,7 @@ def schedule_interviews_view(request, slug):
return _handle_preview_submission(request, slug, job)
else:
return _handle_get_request(request, slug, job)
+
def confirm_schedule_interviews_view(request, slug):
job = get_object_or_404(JobPosting, slug=slug)
if request.method == "POST":
@@ -4062,4 +4131,18 @@ def send_interview_email(request, slug):
+def schedule_interview_location_form(request,slug):
+ schedule=get_object_or_404(InterviewSchedule,slug=slug)
+ if request.method=='POST':
+ form=InterviewScheduleLocationForm(request.POST,instance=schedule)
+ form.save()
+ return redirect('list_meetings')
+ else:
+ form=InterviewScheduleLocationForm(instance=schedule)
+ return render(request,'interviews/schedule_interview_location_form.html',{'form':form,'schedule':schedule})
+
+
+def onsite_interview_list_view(request):
+ onsite_interviews=ScheduledInterview.objects.filter(schedule__interview_type='Onsite')
+ return render(request,'interviews/onsite_interview_list.html',{'onsite_interviews':onsite_interviews})
diff --git a/templates/base.html b/templates/base.html
index bfded3d..0463281 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -255,6 +255,16 @@