From fe40fbc5d8734327f62caaed606e293710461d35 Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Tue, 23 Jun 2026 12:55:03 +0530 Subject: [PATCH 1/2] CHANGE_HR_PRE_POLICY_COUNT_AND_POLICY_LIST --- app/Controllers/EmployeeRestController.php | 75 ++++++++++++++++----- app/Models/EmployeePolicyModel.php | 42 +++++++++--- public/sample_excel/enrollment.xlsx | Bin 89706 -> 7940 bytes 3 files changed, 90 insertions(+), 27 deletions(-) diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index 2245a06..c6ef839 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -3162,8 +3162,9 @@ class EmployeeRestController extends AdminController public function getPolicyLevelEmployeeSummaryData() { - $hr_id = $this->request->getGet('hr_id'); + $client_id = $this->request->getGet('client_id'); + $client_branch_id = $this->request->getGet('client_branch_id'); $restAuthController = new RestAuthenticationController; //call and get allowed policy data from post enrollment $queryParams = [ @@ -3174,35 +3175,78 @@ class EmployeeRestController extends AdminController $HRAccessData = json_decode($HRAccessRes,true); if(isset($HRAccessData['data']['allowed_pre_policies'])) { - $policyId = json_decode($HRAccessData['data']['allowed_pre_policies'],true); + $allowedPolicyIds = json_decode($HRAccessData['data']['allowed_pre_policies'],true); }else{ - $policyId = []; + $allowedPolicyIds = []; } - if(count($policyId) == 0){ - return $this->respond(['status' => 'failed','code' => (count($policyId) ? 200 : 200),'data' => [] ], 200); + if(empty($allowedPolicyIds)){ + return $this->respond(['status' => 'failed','code' => 200,'data' => [] ], 200); + } + + $openEnrollmentPolicies = $this->employeePolicyModel + ->select('employee_polices.client_policy_id') + ->join('employees', 'employee_polices.employee_id = employees.id') + ->whereIn('employee_polices.client_policy_id', $allowedPolicyIds) + ->where('md5(employees.client_id)', $client_id) + ->where('employees.client_branch_id', $client_branch_id) + ->where('employee_polices.enrollment_open_date <= CURDATE()', null, false) + ->where('employee_polices.enrollment_close_date >= CURDATE()', null, false) + ->where('employee_polices.enrollment_open_date IS NOT NULL', null, false) + ->where('employee_polices.enrollment_close_date IS NOT NULL', null, false) + ->where('employees.is_active', 1) + ->where('employee_polices.is_active', 1) + ->whereIn('employee_polices.status', ['draft', 'enrolled']) + ->whereIn('employees.emp_status', ['draft', 'enrolled']) + ->groupBy('employee_polices.client_policy_id') + ->findAll(); + + $policyId = array_column($openEnrollmentPolicies, 'client_policy_id'); + + if(empty($policyId)){ + return $this->respond(['status' => 'failed','code' => 200,'data' => [] ], 200); } - $ClientPolicyData = $this->clientPolicyModel->select('client_policy.id as client_policy_id , client_policy.client_id as client_id, client_policy.policy_type_id as policy_type_id, client_policy.is_addon as is_addon , client_policy.open_for_enrollment as OpenForEnrollment , client_policy.inception_type as inception_type, client_policy.policy_no as policy_no, client_policy.insurer_id as insurer_id, DATE_FORMAT(client_policy.policy_end_date, "%d-%m-%Y") AS policy_expiry_date ') - ->where('md5(client_policy.client_id)', $this->request->getGet('client_id') ) - ->where('client_policy.client_branch_id', $this->request->getGet('client_branch_id') ) + ->where('md5(client_policy.client_id)', $client_id ) + ->where('client_policy.client_branch_id', $client_branch_id ) ->where('client_policy.is_active', 1 ) ->where('client_policy.policy_status', 1) ->whereIn('client_policy.id', $policyId) ->findAll(); + + $loggedInCounts = []; + $clientPolicyIds = array_column($ClientPolicyData, 'client_policy_id'); + if (!empty($clientPolicyIds)) { + $loggedInRows = $this->employeeModel + ->select('employee_polices.client_policy_id, COUNT(DISTINCT employees.id) as logged_in_count') + ->join('employee_polices', 'employees.id = employee_polices.employee_id') + ->join('auth_history', 'employees.id = auth_history.user_id AND auth_history.user_type = "employee"', 'inner') + ->whereIn('employee_polices.client_policy_id', $clientPolicyIds) + ->where('md5(employees.client_id)', $client_id) + ->where('employees.client_branch_id', $client_branch_id) + ->where('employees.relationship', 'Self') + ->where('employees.is_active', 1) + ->where('employee_polices.is_active', 1) + ->groupBy('employee_polices.client_policy_id') + ->findAll(); + + foreach ($loggedInRows as $loggedInRow) { + $loggedInCounts[$loggedInRow['client_policy_id']] = (int) $loggedInRow['logged_in_count']; + } + } + $result = []; - // dd( $ClientPolicyData); foreach ($ClientPolicyData as $key => $value) { $policyTypeData = $this->policyTypeModel->where('id',$value['policy_type_id'])->get()->getRow(); $insurerData = $this->insurerModel->where('id',$value['insurer_id'])->get()->getRow(); - $value['type'] = $policyTypeData->policy_type; - $value['policy_name'] = $policyTypeData->long_name; + $value['type'] = $policyTypeData->policy_type ?? null; + $value['policy_name'] = $policyTypeData->long_name ?? null; $value['insurer_name'] = $insurerData->name ?? null; $value['insurer_short_name'] = $insurerData->short_name ?? null; - $employeeDetails = $this->employeePolicyModel->getEmployeePolicy( client_id:$value['client_id'],policy_id: $value['client_policy_id'],status:0,branch_id:$this->request->getGet('client_branch_id')); + $employeeDetails = $this->employeePolicyModel->getEmployeePolicy( client_id:$value['client_id'],policy_id: $value['client_policy_id'],status:0,branch_id:$client_branch_id); $enrolledCount = 0; $draftCount = 0; if(count($employeeDetails)) @@ -3219,18 +3263,17 @@ class EmployeeRestController extends AdminController $value['totalMembersCount'] = count($employeeDetails); $value['membersCountOfEnrolled'] = $enrolledCount; $value['membersCountOfDraft'] = $draftCount; + $value['membersCountOfLoggedIn'] = $loggedInCounts[$value['client_policy_id']] ?? 0; array_push($result,$value); } - - if($result) { - return $this->respond(['status' => 'success','code' => (count($result) ? 200 : 200),'data' => $result ], 200); + return $this->respond(['status' => 'success','code' => 200,'data' => $result ], 200); }else{ - return $this->respond(['status' => 'failed','code' => (count($result) ? 200 : 200),'data' => [] ], 200); + return $this->respond(['status' => 'failed','code' => 200,'data' => [] ], 200); } } diff --git a/app/Models/EmployeePolicyModel.php b/app/Models/EmployeePolicyModel.php index 1753172..bf6ad26 100755 --- a/app/Models/EmployeePolicyModel.php +++ b/app/Models/EmployeePolicyModel.php @@ -88,14 +88,14 @@ class EmployeePolicyModel extends Model $status_query = "employee_polices.status"; // Default fallback - if ($status_type == "hr") { - $status_query = "CASE - WHEN employee_polices.status = 'enrolled' THEN 'under process' - ELSE employee_polices.status - END as status"; - } + // if ($status_type == "hr") { + // $status_query = "CASE + // WHEN employee_polices.status = 'enrolled' THEN 'under process' + // ELSE employee_polices.status + // END as status"; + // } - $result = $this->select([ + $selectColumns = [ 'employee_polices.*', 'policy_type.policy_type as policy_name', 'im.short_name as insurer_short_name', @@ -214,9 +214,19 @@ class EmployeePolicyModel extends Model ELSE NULL END )) AS newly_added_updated_at", + ]; - $status_query - ], false) + if ($status_type === 'hr') { + $selectColumns[] = "(CASE + WHEN emp.relationship = 'Self' + THEN IF(auth_history.user_id IS NOT NULL, 'Yes', 'No') + ELSE NULL + END) AS logged_in"; + } + + $selectColumns[] = $status_query; + + $result = $this->select($selectColumns, false) ->join('employees emp', 'employee_polices.employee_id = emp.id') ->join('client_policy cp', 'employee_polices.client_policy_id = cp.id') //cp - client policy ->join('policies pm', 'cp.policy_id = pm.id', 'left') //pm - policy master @@ -226,8 +236,18 @@ class EmployeePolicyModel extends Model ->join('tpa tpam', 'cp.tpa_id = tpam.id', 'left') //tpam - tpa master ->join('tpa_branch tpab', 'cp.tpa_branch_id = tpab.id', 'left') //tpab - tpa branch ->join('clients cm', 'cp.client_id = cm.id') //cm - client master - ->join('client_branch', 'emp.client_branch_id = client_branch.id') //cm - client master - ->orderBy('emp.emp_code', 'ASC') + ->join('client_branch', 'emp.client_branch_id = client_branch.id'); //cm - client master + + if ($status_type === 'hr') { + $result->join( + "(SELECT user_id FROM auth_history WHERE user_type = 'employee' GROUP BY user_id) auth_history", + 'emp.id = auth_history.user_id', + 'left', + false + ); + } + + $result->orderBy('emp.emp_code', 'ASC') ->orderBy('employee_polices.employee_id', 'ASC'); diff --git a/public/sample_excel/enrollment.xlsx b/public/sample_excel/enrollment.xlsx index 97cd3191d28915a6893bc17bd6223f18780690e6..833c360ddde631cde447daa3f6215c2b0d04db70 100755 GIT binary patch delta 6334 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 P1PwzV3C=w2?;Inj)$^YF9`}3R^Sqz0$Ne64 zwaZFOTuww%Qc|RwaQlGBZypPU&wX4q9Z62Eo|@i7(#a!4;zizR6iX(ew?I7b{VtMTBJs&4Bp z*uCqRzNFXl+N&q!{d=3PIsCHs?dvt8$<+$k_R0xo7O%Sdre*JT?FABqD>9s_^sgS3 zslT{7RUXeecwuOh<7>SSLEYPSyPlZtv0Fj;Oh%C4-6)~@r$c_8ij`lN%zEXbaGpw8 zlO5vdvoc0KI=CS1u%Xk8*4^=5b3C5M_bTlEM4ou08uPMNoV;h|*wCilR^KX(+mwAQ zPwN-!Gmlw27Qfl<>q)B7-DM>qSyZ|4r_VwcZitGA?E1g@B=awye(O-&?t0QEt+@dM zc0R+>MeeJ#F3(=?lX#_RW%{f8u37h>x^Mc5EgN}bnNF#w+HiPUdLgH`uCcA-ZQiA( zb&6^^aKGN{%Mz<=!*@+x4)C*(SjXPqe)F@9T2$S1_5A&X2e0MmA3LD5>1>wko7E-9 zTc$Q-QQ2phi+0JadT>KO(PgyAZT6R9TakTMMl(ZhIC|3dMc&mCgwo1v+V8%bxHltw zNz}Ag2_NgX>s-2X>H4`ZAJ?~kMH6pM(yzK-Sw|k(K6UG>_BAC6VSC+LwY;1yaki7g zE2gCNZOjjCXUi{MmMYO^*dZescfr!>W!=W5(^Go~d`@mGIp|grWBam0L-)Yz%QSt5 zTl0RFn_%Jc&+L-fbiZ(jtFni-b1g&OzNukn|D?*y+aS35oomC?sjab#B-i zjnuCUGjE6MD(f2siRb061n=0g!|IK}!g=?V1~T61Tp=*F=17}|YBlVbp|q{-m2x9m zMbM~I85&mYdQH>dBzbYyES$9Hr#0Av+|J8x75Xn9w6*U(FgC!PC|qgF5(Y&=5@jns z;!YD0aToiu;LQJ5aJ+z%o0I0Ry7pUnmD)KHQ?%xin=|fmixw8MR8CYyo_V))BjM9y zna_%14sM~Yx%Nf8*k#j1yKrmjw%|r5yuFQ@C!|S!V^;28wqHDc{m@py^U$E}HprT- zHs0nk6H;C+Ve?to0)TR{|)gC zXKs=%tmYnnJCkz!n!@tnD`9tvHkz~EXm8_~SUQMbL~H5z7td{Kr{B^Mmz`Di^_Eyj zMzZpufg6FXIUlMY1i6n~)#C@ui6o@)Ce3g8`&G#Ml=@HK?7cOo$V@^+_vj&Z=7L($ zs?It|g@~UO+d>QXA0@^zq~cus?{F5U6$Sdce0WZlH3@?Yd6j+ zRMaLqEyr;)?;rb6Y#`TtHFcX~^6pNH&uP~`?F+KX+?+0Z+1_lOwR-DwcE2Rv zsXtU`<&!cs;A>I{J{3=U6qsasxa}tpqEF}bBW` z|AkM%12doNw@;g!D(GE*(K@Ts?cQZl(i_3SrpiY_d5g=Rq^wC+zIjk8vv*O+=n4b5 zi>GfoNl#fB;~+K8hE zb;?|Ac33>VVSh5#I@4ikEV}GA^tSZir2c{#9ZRp?jEX5-b+$a@2K~?*nX0bQ;jybs z^TURgPYt%Vgb*)Y%OAJDUwg{r#S7^q^w4foW#ye`*pBw>Czx+}<$n09=cj8sgSXS*Lg*Y`K{(|*;F+?pbORBDg-rJaXyPNQ!;29=+#dYv{nd)+#nq^*wX0%FUbMJpZ9JZx37fv(?Is%3Ib?4mCx7FfX$+^ReTJd2+^Pe!KIIBh;FL|-XypQd=adV1ks@qiH z^ti(tUa>+Cn#wnrnyuQ|*fdkGLo~`~ZFo_$THxDFwNZ)T$}d=rg;$m`VSoEq9%D#O zVY7zh^_oZSw6$+m{*)*?i+yXx$A*g9D+_I{-|kvz-IDvlU;$|KE?h``#4MPlVZ46% zhjokQOG-g~li}SP!spTTBQAEy3!ZOpb2au*+55}ZR#V3n6^oacZohUjMV7HUXJcynuF*1Ce$InSz)UxH zWfcm}j?E0n43#sfP;^+JF{gUg_9K^CFSSWLT_Jt!-f?+$o%ltHa!ZK69qsh~(vQlAp81_ltbw zidSJ_GY<@T+5T*GqscadPuu9`^Dz8oO3(I-*F6o79kMzuaV7M{r4>hC?bE&#yTViQ z%F~WjcP@nTE${u5Dz|9t!pB2bWv@J68Sv$a*O}fbs|}6yUOZ=Z!YHS+lJ|m_bVUB{ z%+yfPGqU3W)3tKU*U!10I_Z(5&%JroZ=}$qqR{r8{oWokul57v8Ltg5yPC_q7`pNN z((!9Gy_Yu*odF*-*Ly|^nr|!M#TJ}?wY_YW<-=QVZXYu+M|ZNPEH~sN@>YorS+fO& z$-Fx$O^(gS_q{q}TgSPujV-nF__d3@b}JU`e?|G|6np2`SfZ=zt=_;FrZrMOdpP85 zR(Tw@eqE(w38BT0Cy&2#Cpl&XEm5nQ)GwOpn@q1_4XhVQGMM(cxrrZYo?RGQp;7PV(}K-5x_{JI5k-C_9Y9=R zC_ceX7>u6Ra3P(eK4*m-{ki&Oi5}9&AZ@K_gwLskwT?*-X7e-p^qDxHO~+!zDPODK z*KFFer0M=Na^~P!6fS#iW3A7+c#oi0m-Ys#2~-OTeOe|H!(uKe1t>PEezQ*ZlGCx>UL==O&cCAf>P5otVy(Fm1AJ)RUpcsJqxC^KIrDi=rUhAj zRmUyPCx_t#O?#f7UswL2A=%K^duU~T^vbmB$JQ?t-tGEkEh0s%uDP(UZ%-l6$bkJ?PJHNU;_|9zO3{)e|eT02{Wq*2p@bt`04`la_qM1&Pl#XIAJ6v^9LFDa|cc|TxWHCj}Z;npbf zZe-QIglzf6mc!em%!k3Y{AFc(6-PJ5ER7C)Z;&(n?e65O_*|8_*`gS95tOUJ3B7>8O0-z%s+Cy$@-Lflh#Trhv>cgqeTl>9u3GSDdT(iYHoN-4}3mP z&)kM+vYHgXbNld{maVVT{k+a?pUD{_SiPIpd9?SK$mT7PB>TPI+Wcct?6Cd4v9cb{ zw)Br=@q#dd+sXB(ER`0YHC)$fhdaOQ%**Z84F3$h0@*oE5i@<8oC5O7ng=dO|1?|6 zf-o;>8N2`Gg6dq#GTw!Z>;cv~PFrseGAM_3+UVcnl5zhs@`_Lu|L zxMl45-)ss`)=BOcno$0yU+3ltf8rSN=q?h`-Ba@@k>vF4&LY6ZerKqbT)>#!xgwJZ z_mmA!5?$CaR@NoJ|5gWOFHOfy_aT`W{1j;vHF{QbYpTaLu9ATGm$0$L4{!lOA}nP>OKby7IZArPc>mG5MNn zdnK(hPwxHsC~fuK^xl{SK_<;hC9gMEvRm28Ep#ts^AU%kpFirv)tX*?KdSUOx2DZg_hy+{{^}X{xcIoa zC}`o!B`3E`lEj76=VgwCxJngud!)#=u(sNYVPP-@2~! z`aRv=S4vVbR;%G1d!L!b0sFiX*VbIiPXBbf_g(Gc!P2K^551hTWbcax!E(YOa_fx4 z3#=`)mT&diQ1wgYUfa8Eea%+Rx%M^la@~6RUZ`qSSLlc?T6AhVr`lw^(*!ktTPk%! z?{%)iL^u5DQqBGCQXO`8|F%+B&pka={O?$|eoDXdm_pM5)j9L4&TuME?Y;E)+zkS~ z#O%xHEb268|7_mDw7xXWv9@!)PbAz31X6N>cx?974Jpa~>j%10Ri<62T|MZ9ZvZhKei&Uf=~Q;^HfetA6W{L;rcpL-X0AIqu; zjNv96U%Nw0u(>?*12p<(UAyel^*o_5X+rz{@4vM8_TBTpQ-0dhi|F>Nd*%0L7Hi3c zw=`x**46KkNT|w*(KFaHcCP8+G*9A7LQH_)@{oQ?q>|x|>dicp+jHyYoygNvTP3_x zmsVXv0{ellmEQC$4`i|2!v2|ER`tGosBk z4;vjydGVFr)@kQA?y32?E`0t3&R>7xYxjDdmGrUZf$=8dGCwy)pmV*#x6jq`K_nZV{ zgW3y6t6tfJ#%bAoihW<+Ww71NA@r9GKQC0-w%tkG^O25xje1)4OwMT**~D|j5(@=` zimee{Vi#OLJmPo+*Wb}7w)ARd``(Dvy0hroy2!)b3@_F4Ek|WUIT`Nem$Ym zrpm+DFHCpE&fUA6UU}m87|xfpyuIPc%!6WjI!gTLUk)vO=j{_qXGJYMO?ed>(xIGd7mU?^!v~&JK@A8*RX^8aCS@+1a^Ig|R zyxUi89rv?&MrW5UIjJJrHr`VG(AKp3{?sh~Sv9KOUW2s#86|atYu-A3b=dywnGScx zx3$~JUu*3r%;Sq0f3|jt!j%<%pZoTUw13~8{r8PoLF2*mOG>XWTc&twlKm2u^Df^l zT)vL=hO2GWdEZMlR8ei4@)k1Fd|P>4;Zn&l-i|{M|(2{90CR04DuAOb8H}vJi#8SaA5gPR*&s;EZ}z4 z4HyaP@7JaY28_5_&AgG0TVygX!?~i#|IC??GzS5H$f(eFXowX!(a|%&AMSmTk;U?v z7`mTUUNrcu%+AO#z;iS{i{DpPYs5M`Iy~jwJlqzesTnv@Q=>`tpX6p#G^e#VItIMf zci?|{HZ*Xpv}}OSZM&b=RABV#mb2!yB7MJ!EWt3hvzC?GXH+Ce9|{n3cR5xxb!%pI zGLjSrSc5$i8i&~aMuvj^nzC9=vd>Vdw*XU~D^OF^UK0`gL znm6&<8ih^;jIry+l*r9*jaa@OXA<(sQ(xmX9l2*i285qsxI(k3AVA7F$#0@NX@r$p z*xF3ySJy9^Z0)_ENe=Xj9_=lS$t4aCRTTO8^=>o})RYOsk*4X$_ZiF2&@4Yy9iElx zKU|^P6fiYWldC)YshU;D9m=mC9KGdi6htIH)HDD)LtnS>q?~=VtK72guRun);IOHz@U&)&=;C zbEkC48~vOViXIJ)*d=KkDj4jE+gO%Z(bP12Kg)sJ+8f$xR6*iq4=#IBHs(F~={{?d zpT`+)zQXX=Pj%CqSWh*{o;)LNe{W_{;8q?cuRUSZ(<}R$tck0@W3Ko z*Th(}(NtHvQ?vchiwuFs=;{i-Fq7Wo03Mq-u<&YQ-&jW8U}^5aXpiclug^ZI+7Gry zS~+a;c;%ftJU?r~$R{bu@X%n@4J+X_wd#@uRt|jkS7j`R!PXZ^1^yiacU#D6q&BtK%;W}ND+8_gRZBd0X`KWf)iZDN-SU*^d? zy5Ai5s698MzxSeMrhoSefvWdtu^r1_(SM|3>RrK?9@WYl@l^7ghlO9t%qzopr)_Nx z^k^A0`kc)2pJA^zvTS%nK_QbbmN`)tSs5;1@8-PDD0(M&x8d>Gt`tX2|L*&o+N;AI zQ_WyN&@tGRl0hDCyp=@e_15GcZFc1IG+Bbj{4ADTMPcHYxxgs!U6G$}!K?dY{8187 zaIs?~zggP3nLFt^qDda__IBiV^v&YBn+A|eI^rphzSJ*I?tC;}zr2+<&(3gTYZWn` zbFFQV7_Ye0qa)YcI*T-tYu-M!JTuU%q?Wb$NTE?{PdsIuoonvu_#&6Jxz&eUqN~(= z&r^5gQ?yBWV@;2$w7&O2Vu4Y6eZ0&>*3@K29cA+LL3^$$@m9=$LtXvO>iYxyuJ@TY zSo-bHZW+}UT;0e%G&1h~COlr_^u+7Bik7@Nqb+x1l+wBzMinzOyibL`*_nSRP_MVm zjnvYx9b-_hlRe3T`JX%Ry`czI?hrDjpbbBmu1&5AGA++@i5(#|zrb$gHPLba#cot^G#@3*vFpq6qm^y10ehnzcRsD-It zP)}FSQ7=({uimactS-JrZjH(sojZTyaMStg$DHIDgiy4#n(9)i0~YH1=dJkyJx68>}7topAXgzCS3gdz!oO!88l&ERb1FtuxI_4f^A zy&GOg_(xJbypfRg4}@4bJcIC$q-uC2f%s44-S7ee@y}(#h~RQI2IYc3loXWfa`rz8 z&!|PGkWgL!2V$U8Xj1(>LeZ@pmL?4RAEH4Rs2cu0*?8Z$I$x*I>i1$^?Wa@tz0#>H zOBi@Jy!5wHJ#0uA_cG4-_Sv~b%BB2worTVW4Wp>r@G=fs)zhoIfscQJI%0`Lp z)sZ>@7T?6Q+WhZGySpYq>D}-=f>QPH+P^3F=GW^4SXZm+1Q`Eqp|qr3e&#(_P@=+* z{+T=f{k8(#vs7{iI3hQ|KIx37sl^#cSmRdYobD6*#6gX;KCh zI>%zfxTRA3R!Oo)1WT{PF|}2b@>QX8JSLP|YRPZKlRYl6^xilM|1zI4q|iALBgQL@ z=C@jqJrGOpy<=*(CS^pSb228BSNe+I3dkN0S$YkQsr{OiF@?^l7%_gSl%O?=?D3qX z*Y21)s7aYn=;X$P@=GlRtxU4V2bNy1W9q0TWlEuw7b7MpjTW@NBzv$~dc%&XlbRHs zLMJ~aR8aa#(8?iujI#8&j;VZ2ia?=L5F0x_sZWs1Q~RE`+jLdAz;VvqEI^07A)8RfUb2>ak7kapgR>W0lla| z0{T#y640N@k$^!|yd(^x0!bJ}WlF+WDn}BYqvECD1uBq&m#IuCm`deHK`IqL6Q)za zOvt1%XTsZ5&P;fhik}5@s9+X+L}ku`1ys%~_=Jj|4NItCHhf8C&W3NOoY}CFik}1D zQ^6egk;pN)@G})J4cnz~6c~Fi9=0Qaoa~@nwwP^VH zP=^NQLp>UEKHNm(%!gZOcsXcF19H%U#*~BBG>#nHMZ?R(Jv1N>_tBX0@DPn74}YQI z7r#t44ByZ=i(w@VzXZOgfhF)GjkyFi&^SxrXBvJfY^Q;xkV9iGg}pS+ zQuu|2SAxSdpajQhOeM&rag?A49j^?<=|CAu(wWL|Hl3pk=hE@Zpd1}6gNk(KGPsz| zSq7EqcpOxr102NDnK-CU=is0g9lspv(7|%3M`tdFo9LY7a0?x;0!`^a1zOOVD$ttF zQGvVY_!V#u9jt)+=*$)H5S_CE{zAvEgvaP$B?NTlO6W@Gtc31#yejmf16AllXR1Pf zI!6@-(eZc~MhAEpMQ7q+ES-ag=jixV@B$sIf|u#cRWOy#Sp}(d{A!p^2dg2I&Rh*| z(>bf*T{>P3=Fou}d_-re!2&u*4L+gc)nN%8sKb|ZraF8>=cvO=y2 zY@l=2z|VBN25hGT4alK0HDE8DqXECr@tSa$4m9C7ov8`AbdDwzVc@l(I0I-wNd{93 z&Sr45;9LfNEtF$`wNR14TniU7IBTIY1FsEL7(g518BA@c&fsW6Ee3ua)M0>iP>;b} z2RAV|>);j!UI&^ofDW`^Fm<3cgQEj?G4Si*9tKzs_c563;UNZRJ^Y1%-vEy>zy=5y z%ni_$!Px-a8F*dj#Q?g{hr!f^{tS*T3}WE*U>F1F!6*h(55_Wt|H9%N1FsJ+Fn~V1 z%wX!nR0c;MQW^M-Fr5K5LMDT`5#DBSHp06MyaCK%00a1l!8Cvc42}VO!oY8WB@D0$ zzGN^r!8Z)fCRoY9Z-(y~U^D#4U~YyD49;fwnSnQi?F?WDISi&D>}7Ba;THzp2o5uV z5gcbQjUbo7F@hrL_$^R89c+P;>C7!~b~ggO~sFjX4fja5H1nQ+TP2i?A)0Pq%%#Sbvnls?n=j- z!9D4~?51t{iGLv1e3GBtc%i>Xs4{VN=sma#YyVwv>blo4S?~ zhOnt`DS0Mr8e2-*giUixiP+K|n_7hPgiV`pp0Mc<&Rd#;+pA{t029muEC*N_U?{*y z_`gX=P^-$pVS;&pvqVjD$Z73AJ-@CTf^q9$-1Z$^b(FM#3M5gb#4L)wD6e zJiu~*l>vqVjD$Z52@?+_yoc$wDi3fsoRCmt184{ zf_Z@D04oCw1sDl`7!p2H(=Nbaf_Z@D04oCw1sDl`7!p>iX&2%!!92infRzD;0*r(| z3<=BBvI}sSU>;yOz{&ta0Y<_fhJ-EhQdt&0X2oi<4hdgin4L-i4jaOpz%qfA0tN+) zh2OU;WEbM<)iA+4z;b|<0fqvMgg*=k6CDzUVY;14A+Af_GRxwuS(O?#ggJp_0xJa! z3K$E&b60qwMk>Hzf_Z@D04oCw1sDl`7!ua1kqU8`U>;yOz{&ta0Y<_fhJ+Prg9SKD zFb}XCU}b=z03+cKL&6UERaq9kX0O$391;fM1-tYD95#eGfn@?K1q=!p3%_qy81ywW z%zD`=KT&{7vuhuM{tgMkEgl=h48g+i|5+iV-OI19$*(P+@?RY+L%or7_2aZe5fupu ziHX`V1tk|v3)@9v6#Ej2-=zJH-VZ&6>}?O0Ny?49S;fuCm=evhZ9id_i3#QbmV>`l z2G`|%UCbN~BqZ6kV^9B|`@l<(pQsh^h=KvZ_<-O`wGsb=Bli_+vO>@{z~>VM2l;(u z0a4I3Ab6I>RT6Y%3Hry#gIW9@rND^{)>JcpP=+;mOEBmdD9{xQ5V=|PBU7wNBSDYA zdx|J{(JUBone=JAyR1+{|lz5E(7zn;bK5e$=gjRS%*Bgcu1fplJ}fNRJfma*f1 z$q=w*UI@M@3Hn&V311=yx{UICfVE z-lp+7h?#H6k4N*#6F-wDUw9AR(Hu)5`tbx)q>-Jq{9ebv_XGU#a=xQCf6&ptY9MgT znLOG_<|hgIoB8ava-)ETG(k*0d6Xq+i#IA7;|`hghm4}VS(|+X^`SCEemK8xfZrN# zG|}eBWh>QZG%xk%_dQJ#HoU&4xxz-!_cXqFX+!hUffQ349l4lS?(SPiArT_KArAo&oS%;9kl5iebR;T~x?ql%Rj{`buk{1wSkXbd| zyc<>} zkgNWlJpPEfb|&-bU7CMeND+eP(#^~Gi>$c1Xq@Q!wOO%^b?-U(C`IsmsUU7Ee^Crq zcjW}pb+B2ngLUsU`RFCVa}7b^-c!8X{#RI zo+v$*QpRI@vGN*;l(Cc^9^0Rl*G!ZiPnpGMhq3Znh?MaZH9k9*mDfg;o=CCcvoEmn zI*62sl#_gRDl4y(C_R}H!)K?n^16tW$&_1s_H9;P4^et5rHs$cVdeD^DN`vue0BjV zZ-6MxO_?QNm$34_5-HpiH39n#D{qJ>%}cQou-~)tMu-$%%1Hscft5E#l;)?z2-xkc zya^(OpK?pU?q%go5v2twWdimvE00H{2vT|kY%VKLKoku^B0x3>O#`?fBnq^HkQguy zLgK(C2u%k^g3t^=3PKVfBnU}@#2_RE=s{>E$PPlYKyeV74XT3B9MBqsq`_bik^!Q@ zXfBWqM)LqJ7|8APq1Wf;53>DAEG5p=d3@g(7XB9g5Zg<4~jn zY(mj`a3mCM0Hjc)3qnGX9!LyD`hXsaHiGO>WB`go(I!w8iZ+ARP-F-Ozm=|N7}^44 z!_ZcM3q!_0I}Dis<1l0jY{HNkI1+}o0a6$;2O(j|0wjhZOF$1p+d+01vI50n$Qo3I zAp&R(Lp#7=7}^O$!_h7v8;*VkxNu|xw8PPEU>uJ20Gn{M7aR#kwty6l>_A93+6NND z(SAS=M+ZQ5I64T5!_gs76^`sdYdCTMgWvcn8i9TRvJvPoz(t@VKsy2*1;!ER7_f;z zPT)ucIu1w?$Qgu0AOI30kPDzkpc5cF0-XfK5y%x(MIblO8i7uM!3abIqLIiQ$VMU$ zfQv*VpdE=kfpH}A0ydH8G&mB8&Hz#*@&+N1$Oj}wB40p{L}x*EB=Q5rk;orZMWO)E z8i@kIU?d^~(I^xIWTQ|pz(t`DpdE!mfpHWH12$1892|*45r7nhB0)$LiUNsIC>qeC z5Cqv#C1w>=e zT_78S?g3m3$_Cmo=sqxxK{>!C20Z{rV$efCib1&`BnCYKi7_Y-&|}bJkR5~aL2(Q! z097%l5VXdiA}|<(SU@xuJpr<@=qbR(qGF&Oi=F}FSX2URVo@nL5{sS#QY?A_LSoTN zkQj?z0eUQY4YFfV87PiLZ$MQndJ9@(Q8^flMHN6a4pjo#IP?zS;!qXPjziVJI1arB zHgTv19En3804WaDf{-}$5hTW;IzW#@pFnmTst3h!r~y>Pp+?Xehnm1(9BKxl6!aO$ zQcw%PQBW(;rl2-pOhN6yhJrf45ei}h5(RaF5DMaeL<;HxbPDPQ*%Z_RiYcfUR8deL zXr-WjF!=4BO*9^T0kZMvE5OC0L7*LvhJbNA8U{A;XapRIN27ogkH$bqJQ@dy@n{0j z)_BARgYifJM9(1+7uj=YnhWk65_Qo&hs0cr&mnOan{#No z%aLmr+g=DFY!kgSV#0-En)oPgw9Y!Z-s*MWr0e<1wlC#8v-JAdiZUFzl2W)`V?P1q13 zb(fy@Y1=F82^)K1?~G5Ildvaj+=M-EpSIJ&p0EiJ_I!NW!i7Cy6DREX`m|jX_IxX$ z*By&mOfV0y9AIUDp#US{|0dz=J1;9CCYT3U4zM!7P=Jx}haq8@tx67#CNFiz0+<=A z$sS0kgxC<~1eOV`6fh`YEd0J*;pKa1eFqcF11twv8DJ>DNch8$(Bg!djauS?g!eGr zR^q+YzT7#%LG;m7!)uTe&?>Be&=Ns#02vI%K=sf7z!{F{xBrGV4I$UyDq=# zj)jZab~T#=2~`jq!koY|ft3OV1&oE?w=2B-0A253f_Z@D04oCw1sDl`91^~Ri|@R| z1oHsP0agYW3NRA>FeGHi`{v-{ZQI|$O$QRp)dqpt(mOA)LClc95e9+D!+^OWvx`MU z=D$>%zEQAarSjJVw!Di2CYT3U4zM!#e~W^C`7Vb9OfV0y9AIUDp#US{4@1Hh`JWsT zFu^>)a)6Zqh60R)KMV=GkW73UDp*Qdt&0X2oi<4hh&0<^+}rtQ0UPU@ZK;U7^k)p-&zY%mXY3SQ%g_ zz)1MRkkB&A;;dPfTB1Y3Fif{oDa3WjV?&q|SSGMiz@UJ!@H=;fHhF!A1WYgwupD4z zfS~{*;SWQ?9{D_n1WYgwupD4zfS~{*;SWQ?PI+I41WYgwupD4zfS~{*;SWQ?L3qI~ zy#UuCzbebZ*X*^LjY9%9ggJp_0xJa!3K$E&Z&#Qoz@^!>4?%y2gbKAmUo*q3m!0z1 zAZ7>_hL>aBr^qbA!NZH63ohan4iPFZDVc4#xlHj$)kEBtvqiZMeJgF}8+bO@eyOeS zpsn|tTYYKPix)2j{K}`;cyFbH^IXmD%0IOg$DUv&U`hC!Rgmxzme{I1#NCsRzGLBP zc0^6~AU5>9P88k4$vhrC`>5Y`Zw*UhvbDXmyZT}Ghtr<-QW`2Acl)m;`{|^euIMJM z9SB?_(|JF;$*s0DqqG2TSGxDzA^D!uydl!=_dQe2eaF)TX+JI9p}m-(tzO>~xYsps zF&D^k-5>k!*6D~hQQB#&L@?Hbx6bYxfAMtZ%BS&ylV857jK5+{H@bX=Twg;VKkn;! zSohX;@6R9JlExd$V@5KQD=yjF^KEr&ys!B)Crb+kG$NgEJ-lUB~dav)) zP7?dCz{?A#oo-fa&LObJH~bDqXm`qxgMNdIKGRV8DaV?ddb7bECW47dN=>5mHp@~ zj}wcF?woqKA$Dxlj(m@+*@dSJGjFoJm}d+ZC9jn`*lY0kGO<2$06tcFn6FW;;jm%F zt;boXrgonxEgIBh+&@tmSaWKF3Ageqaw~G_?{O=@4tSpOmdlDAh}co_e9HUiC(XPF z&rLTx6&-RLOUIQw2X%K@Nk}4H(x#Yg(?q;4ONs2VnkFhIA}1m#DJjxTxP73ga^p{* zMMOkyh>D0T6t=&A__%61lAK&UHNAM>V(#yz(}mstnts^b{f}n1d=Lpr63+T6{PX)oY!qJ0f6YF6+S7~Z_D3h! z$zN;jCw%J{!X^9t2_DFbh#WK_p7L@!<>k1?*WJm}{*O-Z+ng)@?)p813-Rv^{(C9^ adl7e8O&7kQh{$Z=AuG&~@j>A=i2N^gnQi<4 From 130de0bad68134b60d0c0de8abab7befa793b12a Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Wed, 24 Jun 2026 09:55:45 +0530 Subject: [PATCH 2/2] FEAT_REMINDER_MAIL_CONFIG_FOR_HR --- app/Config/Routes.php | 6 + app/Controllers/DashboardController.php | 24 +- app/Controllers/EmployeeRestController.php | 193 ++++++++ app/Database/reminder_mail_config.sql | 18 + app/Models/ReminderMailConfigModel.php | 342 +++++++++++++ docs/reminder-mail-api.md | 540 +++++++++++++++++++++ 6 files changed, 1110 insertions(+), 13 deletions(-) create mode 100644 app/Database/reminder_mail_config.sql create mode 100644 app/Models/ReminderMailConfigModel.php create mode 100644 docs/reminder-mail-api.md diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 37baad8..192d43b 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -507,6 +507,12 @@ $routes->group("employeeRest", ['filter' => [ 'GlobalPostFileUploadGuard', 'appS $routes->get("copyActiveEmployeeAndDependentDetails", "EmployeeRestController::copyActiveEmployeeAndDependentDetails"); $routes->get("getExcelFileErrors/(:any)", "EmployeeController::getExcelFileErrors/$1"); + $routes->post("sendReminderMail", "EmployeeRestController::sendReminderMail"); + $routes->get("sendReminderMail", "EmployeeRestController::sendReminderMail"); + + $routes->get("getReminderMailConfig", "EmployeeRestController::getReminderMailConfig"); + $routes->post("saveReminderMailConfig", "EmployeeRestController::saveReminderMailConfig"); + }); $routes->group("employeeRest", ['filter' => ['appSignature','AuthApiRateLimitFilter'] ], function ($routes) { diff --git a/app/Controllers/DashboardController.php b/app/Controllers/DashboardController.php index 5eaea14..1739b54 100755 --- a/app/Controllers/DashboardController.php +++ b/app/Controllers/DashboardController.php @@ -19,6 +19,7 @@ use App\Models\JobModel; use App\Models\ClientPolicyModel; use App\Models\NotificationModel; use App\Models\EmployeePolicyModel; +use App\Models\ReminderMailConfigModel; use App\Controllers\PendingActionsContrller; use App\Controllers\EmpDataServiceController; @@ -32,6 +33,7 @@ class DashboardController extends AdminController protected $clientPolicyModel; protected $notificationModel; protected $employeePolicyModel; + protected $reminderMailConfigModel; protected $policyTransactionModel; protected $policyStatus; protected $colorShades; @@ -45,8 +47,9 @@ class DashboardController extends AdminController $this->userMessageModel = new UserMessageModel(); $this->clientPolicyModel = new ClientPolicyModel(); $this->notificationModel = new NotificationModel(); - $this->employeePolicyModel = new EmployeePolicyModel(); - $this->clientModel = new ClientModel(); + $this->employeePolicyModel = new EmployeePolicyModel(); + $this->reminderMailConfigModel = new ReminderMailConfigModel(); + $this->clientModel = new ClientModel(); $this->policyTransactionModel = new PolicyTransactionModel(); $this->myLogger = \Config\Services::mylogger(); @@ -438,14 +441,12 @@ class DashboardController extends AdminController } else { // crone - if (!empty($client_policy['reminder_date'])) { + $shouldSendToday = $this->reminderMailConfigModel->shouldSendTodayForPolicy( + (int) $client_policy['id'], + $client_policy['reminder_date'] ?? null + ); - $currentDate = date('d'); - $remainder_dates = explode(",", $client_policy['reminder_date']); - - foreach ($remainder_dates as $date) { - - if ($currentDate == $date) { + if ($shouldSendToday) { $this->myLogger->logme('error', 'Notification should be sent for client policy ID: ' . $client_policy['id']); @@ -526,11 +527,8 @@ class DashboardController extends AdminController $reminder_whole_mail[] = $mail_result; } - } - } - } else { - $this->myLogger->logme('error', "send Remainder crone --- Remainder date is empty()"); + $this->myLogger->logme('error', 'send Remainder crone --- reminder not scheduled for today'); } } diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index c6ef839..a4a0f85 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -33,6 +33,7 @@ use App\Models\AuditHistoryModel; use App\Models\SIMappingModel; use App\Models\InsurerModel; use App\Models\HrFileUploadModel; +use App\Models\ReminderMailConfigModel; use App\Controllers\Jobs ; use App\Controllers\JobWorker ; @@ -80,6 +81,7 @@ class EmployeeRestController extends AdminController protected $employeeHelper; protected $insurerModel; protected $hrFileUploadModel; + protected ReminderMailConfigModel $reminderMailConfigModel; public function __construct() @@ -108,6 +110,7 @@ class EmployeeRestController extends AdminController $this->employeeHelper = new EmployeeHelper(); $this->insurerModel = new InsurerModel(); $this->hrFileUploadModel = new HrFileUploadModel(); + $this->reminderMailConfigModel = new ReminderMailConfigModel(); } @@ -4677,4 +4680,194 @@ class EmployeeRestController extends AdminController return $policyIds; } + public function getReminderMailConfig() + { + try { + $clientPolicyId = $this->request->getGet('client_policy_id') + ?? $this->request->getPost('client_policy_id'); + + if (empty($clientPolicyId)) { + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => 'client_policy_id is required', + ], 200); + } + + $clientPolicy = $this->clientPolicyModel->find($clientPolicyId); + + if (empty($clientPolicy)) { + return $this->respond([ + 'status' => false, + 'code' => 404, + 'message' => 'Client policy not found', + ], 200); + } + + $config = $this->reminderMailConfigModel->getByClientPolicyId((int) $clientPolicyId); + + if (empty($config) && !empty($clientPolicy['reminder_date'])) { + $config = [ + 'client_policy_id' => (int) $clientPolicyId, + 'frequency' => ReminderMailConfigModel::FREQUENCY_CUSTOM, + 'reminder_days' => $clientPolicy['reminder_date'], + 'is_enabled' => 1, + 'is_active' => 1, + 'source' => 'legacy_client_policy', + ]; + } + + if (!empty($config)) { + $config = $this->reminderMailConfigModel->enrichConfig($config); + } + + return $this->respond([ + 'status' => true, + 'code' => 200, + 'data' => $config, + 'working_day_options' => $this->reminderMailConfigModel->getWorkingDayOptions(), + ], 200); + } catch (\Exception $e) { + return $this->respond([ + 'status' => false, + 'code' => 500, + 'message' => $e->getMessage(), + ], 500); + } + } + + public function saveReminderMailConfig() + { + try { + $requestData = $this->getReminderMailConfigRequestData(); + + $clientPolicyId = $requestData['client_policy_id'] ?? null; + $configId = $requestData['id'] ?? null; + $frequency = strtolower(trim((string) ($requestData['frequency'] ?? ''))); + $reminderDays = $requestData['reminder_days'] + ?? $requestData['working_days'] + ?? null; + $isEnabled = $requestData['is_enabled'] ?? null; + $hrId = $requestData['hr_id'] ?? null; + + if (empty($clientPolicyId)) { + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => 'client_policy_id is required', + ], 200); + } + + if (empty($frequency)) { + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => 'frequency is required', + ], 200); + } + + $clientPolicy = $this->clientPolicyModel->find($clientPolicyId); + + if (empty($clientPolicy)) { + return $this->respond([ + 'status' => false, + 'code' => 404, + 'message' => 'Client policy not found', + ], 200); + } + + $result = $this->reminderMailConfigModel->saveConfig( + (int) $clientPolicyId, + $frequency, + $reminderDays !== null ? (string) $reminderDays : null, + $isEnabled === null ? 1 : (int) $isEnabled, + [ + 'id' => $configId !== null && $configId !== '' ? (int) $configId : null, + 'hr_id' => $hrId !== null && $hrId !== '' ? (int) $hrId : null, + ] + ); + + if (!$result['status']) { + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => $result['message'], + ], 200); + } + + return $this->respond([ + 'status' => true, + 'code' => 200, + 'action' => $result['action'] ?? 'saved', + 'message' => 'Reminder mail configuration saved successfully', + 'data' => $result['data'], + ], 200); + } catch (\Exception $e) { + return $this->respond([ + 'status' => false, + 'code' => 500, + 'message' => $e->getMessage(), + ], 500); + } + } + + private function getReminderMailConfigRequestData(): array + { + $json = $this->request->getJSON(true); + + if (is_array($json) && !empty($json)) { + return $json; + } + + return array_filter([ + 'id' => $this->request->getVar('id'), + 'client_policy_id' => $this->request->getVar('client_policy_id'), + 'frequency' => $this->request->getVar('frequency'), + 'reminder_days' => $this->request->getVar('reminder_days'), + 'working_days' => $this->request->getVar('working_days'), + 'is_enabled' => $this->request->getVar('is_enabled'), + 'hr_id' => $this->request->getVar('hr_id'), + ], static fn ($value) => $value !== null && $value !== ''); + } + + public function sendReminderMail() + { + try { + $clientPolicyId = $this->request->getGet('client_policy_id') + ?? $this->request->getPost('client_policy_id'); + + if (empty($clientPolicyId)) { + return $this->respond([ + 'status' => false, + 'code' => 400, + 'message' => 'client_policy_id is required', + ], 200); + } + + $clientPolicy = $this->clientPolicyModel->find($clientPolicyId); + + if (empty($clientPolicy)) { + return $this->respond([ + 'status' => false, + 'code' => 404, + 'message' => 'Client policy not found', + ], 200); + } + + $dashboardController = new DashboardController(); + + return $dashboardController->sendManualReminder( + $clientPolicy['client_id'], + $clientPolicy['client_branch_id'], + $clientPolicyId + ); + } catch (\Exception $e) { + return $this->respond([ + 'status' => false, + 'code' => 500, + 'message' => $e->getMessage(), + ], 500); + } + } + } \ No newline at end of file diff --git a/app/Database/reminder_mail_config.sql b/app/Database/reminder_mail_config.sql new file mode 100644 index 0000000..d2267de --- /dev/null +++ b/app/Database/reminder_mail_config.sql @@ -0,0 +1,18 @@ +-- Reminder mail configuration table (fresh install + upgrade + legacy data migration) + +CREATE TABLE IF NOT EXISTS `reminder_mail_config` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `client_policy_id` INT UNSIGNED NOT NULL, + `frequency` ENUM('daily', 'weekly', 'monthly', 'custom', 'working_days') NOT NULL DEFAULT 'custom', + `reminder_days` VARCHAR(255) DEFAULT NULL COMMENT 'weekly: 0-6 (Sun-Sat); working_days: 1-7 (Mon-Sun) or Mon,Tue,...; monthly/custom: 1-31; daily: NULL', + `is_enabled` TINYINT(1) NOT NULL DEFAULT 1, + `is_active` TINYINT(1) NOT NULL DEFAULT 1, + `created_by` INT UNSIGNED DEFAULT NULL, + `updated_by` INT UNSIGNED DEFAULT NULL, + `created_at` DATETIME DEFAULT NULL, + `updated_at` DATETIME DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_reminder_mail_config_client_policy_id` (`client_policy_id`), + KEY `idx_reminder_mail_config_frequency` (`frequency`), + KEY `idx_reminder_mail_config_is_enabled` (`is_enabled`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/app/Models/ReminderMailConfigModel.php b/app/Models/ReminderMailConfigModel.php new file mode 100644 index 0000000..50132cc --- /dev/null +++ b/app/Models/ReminderMailConfigModel.php @@ -0,0 +1,342 @@ + 1, + 'monday' => 1, + 'tue' => 2, + 'tuesday' => 2, + 'wed' => 3, + 'wednesday' => 3, + 'thu' => 4, + 'thursday' => 4, + 'fri' => 5, + 'friday' => 5, + 'sat' => 6, + 'saturday' => 6, + 'sun' => 7, + 'sunday' => 7, + ]; + + private const WORKING_DAY_LABELS = [ + 1 => 'Mon', + 2 => 'Tue', + 3 => 'Wed', + 4 => 'Thu', + 5 => 'Fri', + 6 => 'Sat', + 7 => 'Sun', + ]; + + protected $table = 'reminder_mail_config'; + protected $primaryKey = 'id'; + protected $useAutoIncrement = true; + protected $returnType = 'array'; + protected $useTimestamps = true; + protected $createdField = 'created_at'; + protected $updatedField = 'updated_at'; + protected $allowedFields = [ + 'client_policy_id', + 'frequency', + 'reminder_days', + 'is_enabled', + 'is_active', + 'created_by', + 'updated_by', + ]; + + protected $allowCallbacks = true; + + public function getByClientPolicyId(int $clientPolicyId): ?array + { + return $this->where('client_policy_id', $clientPolicyId) + ->where('is_active', 1) + ->first(); + } + + public function shouldSendTodayForPolicy(int $clientPolicyId, ?string $legacyReminderDate = null): bool + { + $config = $this->getByClientPolicyId($clientPolicyId); + + if ($config) { + if (empty($config['is_enabled'])) { + return false; + } + + return $this->shouldSendToday($config); + } + + if (empty($legacyReminderDate)) { + return false; + } + + $currentDate = date('d'); + + foreach (explode(',', $legacyReminderDate) as $date) { + if ($currentDate == trim($date)) { + return true; + } + } + + return false; + } + + public function shouldSendToday(array $config): bool + { + switch ($config['frequency']) { + case self::FREQUENCY_DAILY: + return true; + + case self::FREQUENCY_WEEKLY: + return in_array((int) date('w'), $this->parseDays($config['reminder_days'] ?? ''), true); + + case self::FREQUENCY_MONTHLY: + case self::FREQUENCY_CUSTOM: + return in_array((int) date('j'), $this->parseDays($config['reminder_days'] ?? ''), true); + + case self::FREQUENCY_WORKING_DAYS: + return in_array((int) date('N'), $this->parseWorkingDays($config['reminder_days'] ?? ''), true); + + default: + return false; + } + } + + public function parseDays(?string $days): array + { + if ($days === null || trim($days) === '') { + return []; + } + + return array_values(array_unique(array_map( + 'intval', + array_filter(array_map('trim', explode(',', $days)), static fn ($day) => $day !== '') + ))); + } + + public function getWorkingDayOptions(): array + { + $options = []; + + foreach (self::WORKING_DAY_LABELS as $value => $label) { + $options[] = [ + 'value' => $value, + 'label' => $label, + 'key' => strtolower($label), + ]; + } + + return $options; + } + + public function parseWorkingDays(?string $days): array + { + if ($days === null || trim($days) === '') { + return []; + } + + $parsed = []; + + foreach (explode(',', $days) as $day) { + $day = trim($day); + + if ($day === '') { + continue; + } + + if (is_numeric($day)) { + $num = (int) $day; + + // Accept 0 as Sunday (same as 7 in ISO-8601 date('N')) + if ($num === 0) { + $num = 7; + } + + $parsed[] = $num; + continue; + } + + $key = strtolower($day); + + if (isset(self::WORKING_DAY_NAMES[$key])) { + $parsed[] = self::WORKING_DAY_NAMES[$key]; + } + } + + return array_values(array_unique($parsed)); + } + + public function normalizeWorkingDays(?string $days): ?string + { + $parsed = $this->parseWorkingDays($days); + + if (empty($parsed)) { + return null; + } + + sort($parsed); + + return implode(',', $parsed); + } + + public function formatWorkingDayLabels(?string $days): array + { + $labels = []; + + foreach ($this->parseWorkingDays($days) as $day) { + if (isset(self::WORKING_DAY_LABELS[$day])) { + $labels[] = self::WORKING_DAY_LABELS[$day]; + } + } + + return $labels; + } + + public function enrichConfig(array $config): array + { + if (($config['frequency'] ?? '') === self::FREQUENCY_WORKING_DAYS) { + $config['working_day_labels'] = $this->formatWorkingDayLabels($config['reminder_days'] ?? null); + } + + return $config; + } + + public function normalizeReminderDays(string $frequency, ?string $reminderDays): ?string + { + if ($frequency === self::FREQUENCY_DAILY) { + return null; + } + + if ($frequency === self::FREQUENCY_WORKING_DAYS) { + return $this->normalizeWorkingDays($reminderDays); + } + + return $reminderDays !== null ? trim((string) $reminderDays) : null; + } + + public function validateConfig(string $frequency, ?string $reminderDays): ?string + { + $allowedFrequencies = [ + self::FREQUENCY_DAILY, + self::FREQUENCY_WEEKLY, + self::FREQUENCY_MONTHLY, + self::FREQUENCY_CUSTOM, + self::FREQUENCY_WORKING_DAYS, + ]; + + if (!in_array($frequency, $allowedFrequencies, true)) { + return 'Invalid frequency. Allowed values: daily, weekly, monthly, custom, working_days.'; + } + + if ($frequency === self::FREQUENCY_DAILY) { + return null; + } + + if ($frequency === self::FREQUENCY_WORKING_DAYS) { + $days = $this->parseWorkingDays($reminderDays); + + if (empty($days)) { + return 'reminder_days is required. Use Mon-Sun or 1-7 (0 also accepted for Sunday).'; + } + + foreach ($days as $day) { + if ($day < 1 || $day > 7) { + return 'Working day reminder_days must be between 1 (Mon) and 7 (Sun).'; + } + } + + return null; + } + + $days = $this->parseDays($reminderDays); + + if (empty($days)) { + return 'reminder_days is required for the selected frequency.'; + } + + if ($frequency === self::FREQUENCY_WEEKLY) { + foreach ($days as $day) { + if ($day < 0 || $day > 6) { + return 'Weekly reminder_days must be between 0 (Sunday) and 6 (Saturday).'; + } + } + + return null; + } + + foreach ($days as $day) { + if ($day < 1 || $day > 31) { + return 'Monthly/custom reminder_days must be between 1 and 31.'; + } + } + + return null; + } + + /** + * @param array{id?: int|null, hr_id?: int|null} $options + */ + public function saveConfig( + int $clientPolicyId, + string $frequency, + ?string $reminderDays, + int $isEnabled = 1, + array $options = [] + ): array { + $id = isset($options['id']) && $options['id'] !== '' ? (int) $options['id'] : null; + $hrId = isset($options['hr_id']) && $options['hr_id'] !== '' + ? (int) $options['hr_id'] + : null; + + $validationError = $this->validateConfig($frequency, $reminderDays); + + if ($validationError !== null) { + return ['status' => false, 'message' => $validationError]; + } + + $payload = [ + 'client_policy_id' => $clientPolicyId, + 'frequency' => $frequency, + 'reminder_days' => $this->normalizeReminderDays($frequency, $reminderDays), + 'is_enabled' => $isEnabled, + 'is_active' => 1, + ]; + + if ($id) { + $existing = $this->where('id', $id)->where('is_active', 1)->first(); + + if (empty($existing)) { + return ['status' => false, 'message' => 'Reminder mail configuration not found']; + } + + if ((int) $existing['client_policy_id'] !== $clientPolicyId) { + return ['status' => false, 'message' => 'client_policy_id does not match the configuration record']; + } + + $payload['updated_by'] = $hrId; + $this->update($id, $payload); + $config = $this->find($id); + + return ['status' => true, 'action' => 'updated', 'data' => $this->enrichConfig($config)]; + } + + $payload['created_by'] = $hrId; + $payload['updated_by'] = $hrId; + + $this->insert($payload); + $config = $this->find($this->getInsertID()); + + return ['status' => true, 'action' => 'created', 'data' => $this->enrichConfig($config)]; + } +} diff --git a/docs/reminder-mail-api.md b/docs/reminder-mail-api.md new file mode 100644 index 0000000..a11435e --- /dev/null +++ b/docs/reminder-mail-api.md @@ -0,0 +1,540 @@ +# Reminder Mail API Documentation + +Base path for all endpoints: `/employeeRest` + +**Authentication:** JWT + app signature (same filters as other `employeeRest` routes) + +**Content types:** JSON body or form-urlencoded (where noted) + +**HTTP status:** All responses return HTTP `200`. Check the JSON `status` and `code` fields for success or failure. + +--- + +## Table of Contents + +1. [Get Configuration](#1-get-configuration) +2. [Save / Update Configuration](#2-save--update-configuration) +3. [Send Reminder Mail (Manual)](#3-send-reminder-mail-manual) +4. [Frequency Reference](#frequency-reference) +5. [Working Days Reference](#working-days-reference) +6. [Quick Reference](#quick-reference) + +--- + +## 1. Get Configuration + +Fetch reminder mail configuration for a client policy. + +| | | +|---|---| +| **Method** | `GET` | +| **URL** | `/employeeRest/getReminderMailConfig` | + +### Request Parameters + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `client_policy_id` | Yes | integer | Client policy ID | + +### Sample Request + +```http +GET /employeeRest/getReminderMailConfig?client_policy_id=123 +``` + +### Success Response — Config Exists + +```json +{ + "status": true, + "code": 200, + "data": { + "id": 10, + "client_policy_id": 123, + "frequency": "working_days", + "reminder_days": "1,3,5", + "working_day_labels": ["Mon", "Wed", "Fri"], + "is_enabled": 1, + "is_active": 1, + "created_by": 45, + "updated_by": 45, + "created_at": "2026-06-23 10:00:00", + "updated_at": "2026-06-23 11:30:00" + }, + "working_day_options": [ + { "value": 1, "label": "Mon", "key": "mon" }, + { "value": 2, "label": "Tue", "key": "tue" }, + { "value": 3, "label": "Wed", "key": "wed" }, + { "value": 4, "label": "Thu", "key": "thu" }, + { "value": 5, "label": "Fri", "key": "fri" }, + { "value": 6, "label": "Sat", "key": "sat" }, + { "value": 7, "label": "Sun", "key": "sun" } + ] +} +``` + +### Success Response — Legacy Fallback (no config row, legacy `reminder_date` on policy) + +```json +{ + "status": true, + "code": 200, + "data": { + "client_policy_id": 123, + "frequency": "custom", + "reminder_days": "1,15,28", + "is_enabled": 1, + "is_active": 1, + "source": "legacy_client_policy" + }, + "working_day_options": [ + { "value": 1, "label": "Mon", "key": "mon" }, + { "value": 2, "label": "Tue", "key": "tue" }, + { "value": 3, "label": "Wed", "key": "wed" }, + { "value": 4, "label": "Thu", "key": "thu" }, + { "value": 5, "label": "Fri", "key": "fri" }, + { "value": 6, "label": "Sat", "key": "sat" }, + { "value": 7, "label": "Sun", "key": "sun" } + ] +} +``` + +### Success Response — No Config + +```json +{ + "status": true, + "code": 200, + "data": null, + "working_day_options": [ + { "value": 1, "label": "Mon", "key": "mon" }, + { "value": 2, "label": "Tue", "key": "tue" }, + { "value": 3, "label": "Wed", "key": "wed" }, + { "value": 4, "label": "Thu", "key": "thu" }, + { "value": 5, "label": "Fri", "key": "fri" }, + { "value": 6, "label": "Sat", "key": "sat" }, + { "value": 7, "label": "Sun", "key": "sun" } + ] +} +``` + +### Error Responses + +```json +{ + "status": false, + "code": 400, + "message": "client_policy_id is required" +} +``` + +```json +{ + "status": false, + "code": 404, + "message": "Client policy not found" +} +``` + +--- + +## 2. Save / Update Configuration + +Create or update reminder mail configuration. + +| | | +|---|---| +| **Method** | `POST` | +| **URL** | `/employeeRest/saveReminderMailConfig` | + +### Insert vs Update + +| Condition | Action | +|-----------|--------| +| `id` **not** sent | **Insert** new record | +| `id` **sent** | **Update** existing record by primary key | + +### Request Parameters + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `client_policy_id` | Yes | integer | Client policy ID | +| `frequency` | Yes | string | `daily`, `weekly`, `monthly`, `custom`, `working_days` | +| `reminder_days` | Depends | string | Comma-separated days (see [Frequency Reference](#frequency-reference)) | +| `working_days` | No | string | Alias for `reminder_days` when `frequency` is `working_days` | +| `is_enabled` | No | integer | `1` (enabled) or `0` (disabled). Default: `1` | +| `hr_id` | No | integer | Stored as `created_by` / `updated_by`. Null if omitted | +| `id` | No | integer | Primary key — required for update | + +### Sample Payload — Insert (Daily) + +```json +{ + "client_policy_id": 123, + "frequency": "daily", + "is_enabled": 1, + "hr_id": 45 +} +``` + +### Sample Payload — Insert (Working Days — weekdays only) + +```json +{ + "client_policy_id": 123, + "frequency": "working_days", + "reminder_days": "Mon,Wed,Fri", + "is_enabled": 1, + "hr_id": 45 +} +``` + +### Sample Payload — Insert (Working Days — including Sat/Sun) + +```json +{ + "client_policy_id": 123, + "frequency": "working_days", + "reminder_days": "Mon,Wed,Fri,Sat,Sun", + "is_enabled": 1, + "hr_id": 45 +} +``` + +Or numeric: `"1,3,5,6,7"` (Sat=6, Sun=7; `0` is also accepted for Sunday). + +Alternative using `working_days` alias: + +```json +{ + "client_policy_id": 123, + "frequency": "working_days", + "working_days": "Mon,Tue,Thu", + "hr_id": 45 +} +``` + +### Sample Payload — Insert (Weekly) + +```json +{ + "client_policy_id": 123, + "frequency": "weekly", + "reminder_days": "1,3,5", + "hr_id": 45 +} +``` + +### Sample Payload — Insert (Monthly) + +```json +{ + "client_policy_id": 123, + "frequency": "monthly", + "reminder_days": "1,15", + "hr_id": 45 +} +``` + +### Sample Payload — Insert (Custom) + +```json +{ + "client_policy_id": 123, + "frequency": "custom", + "reminder_days": "5,10,20", + "hr_id": 45 +} +``` + +### Sample Payload — Update + +```json +{ + "id": 10, + "client_policy_id": 123, + "frequency": "working_days", + "reminder_days": "Mon,Tue,Wed,Thu,Fri", + "is_enabled": 1, + "hr_id": 45 +} +``` + +### Success Response — Created + +```json +{ + "status": true, + "code": 200, + "action": "created", + "message": "Reminder mail configuration saved successfully", + "data": { + "id": 10, + "client_policy_id": 123, + "frequency": "working_days", + "reminder_days": "1,3,5", + "working_day_labels": ["Mon", "Wed", "Fri"], + "is_enabled": 1, + "is_active": 1, + "created_by": 45, + "updated_by": 45, + "created_at": "2026-06-23 10:00:00", + "updated_at": "2026-06-23 10:00:00" + } +} +``` + +### Success Response — Updated + +```json +{ + "status": true, + "code": 200, + "action": "updated", + "message": "Reminder mail configuration saved successfully", + "data": { + "id": 10, + "client_policy_id": 123, + "frequency": "working_days", + "reminder_days": "1,2,3,4,5", + "working_day_labels": ["Mon", "Tue", "Wed", "Thu", "Fri"], + "is_enabled": 1, + "is_active": 1, + "created_by": 45, + "updated_by": 45, + "created_at": "2026-06-23 10:00:00", + "updated_at": "2026-06-23 12:00:00" + } +} +``` + +### Error Responses + +```json +{ + "status": false, + "code": 400, + "message": "client_policy_id is required" +} +``` + +```json +{ + "status": false, + "code": 400, + "message": "frequency is required" +} +``` + +```json +{ + "status": false, + "code": 404, + "message": "Client policy not found" +} +``` + +```json +{ + "status": false, + "code": 400, + "message": "Reminder mail configuration not found" +} +``` + +```json +{ + "status": false, + "code": 400, + "message": "client_policy_id does not match the configuration record" +} +``` + +```json +{ + "status": false, + "code": 400, + "message": "Invalid frequency. Allowed values: daily, weekly, monthly, custom, working_days." +} +``` + +```json +{ + "status": false, + "code": 400, + "message": "reminder_days is required. Use Mon-Sun or 1-7 (0 also accepted for Sunday)." +} +``` + +```json +{ + "status": false, + "code": 400, + "message": "Working day reminder_days must be between 1 (Mon) and 7 (Sun)." +} +``` + +### Audit Fields (`hr_id`) + +| Action | `created_by` | `updated_by` | +|--------|--------------|--------------| +| Insert + `hr_id` sent | `hr_id` | `hr_id` | +| Insert + `hr_id` omitted | `null` | `null` | +| Update + `hr_id` sent | unchanged | `hr_id` | +| Update + `hr_id` omitted | unchanged | `null` | + +--- + +## 3. Send Reminder Mail (Manual) + +Manually trigger reminder mail for a policy. Sends immediately regardless of schedule configuration. + +| | | +|---|---| +| **Method** | `GET` or `POST` | +| **URL** | `/employeeRest/sendReminderMail` | + +### Request Parameters + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `client_policy_id` | Yes | integer | Client policy ID | + +### Sample Request (POST) + +```http +POST /employeeRest/sendReminderMail +Content-Type: application/json + +{ + "client_policy_id": 123 +} +``` + +### Sample Request (GET) + +```http +GET /employeeRest/sendReminderMail?client_policy_id=123 +``` + +### Success Response + +```json +{ + "status": true, + "code": 200, + "message": "Mail sent successfully" +} +``` + +### Error Responses + +```json +{ + "status": false, + "code": 400, + "message": "client_policy_id is required" +} +``` + +```json +{ + "status": false, + "code": 404, + "message": "Client policy not found" +} +``` + +```json +{ + "status": false, + "code": 200, + "message": "There is no data to send", + "message2": "Failed" +} +``` + +```json +{ + "status": false, + "code": 200, + "message": "There is no data to send", + "message2": "No policy data found to send" +} +``` + +--- + +## Frequency Reference + +| `frequency` | `reminder_days` required? | Format | Example | +|-------------|---------------------------|--------|---------| +| `daily` | No | — | — | +| `weekly` | Yes | Weekday `0–6` (Sun–Sat) | `"1,3,5"` | +| `monthly` | Yes | Day of month `1–31` | `"1,15,28"` | +| `custom` | Yes | Day of month `1–31` | `"5,10,20"` | +| `working_days` | Yes | Weekday `1–7` (Mon–Sun) or day names | `"Mon,Wed,Fri,Sat"` or `"1,3,5,6"` | + +--- + +## Working Days Reference + +Used when `frequency = working_days`. Select any combination of weekdays including Saturday and Sunday. + +| Label | Numeric Value | +|-------|---------------| +| Mon | 1 | +| Tue | 2 | +| Wed | 3 | +| Thu | 4 | +| Fri | 5 | +| Sat | 6 | +| Sun | 7 | + +Accepted input formats: + +- Day names: `Mon`, `Tue`, `Wed`, `Thu`, `Fri`, `Sat`, `Sun` (case-insensitive) +- Numeric: `1`–`7` (ISO-8601, matches PHP `date('N')`) +- Sunday alias: `0` is normalized to `7` +- Comma-separated combinations: `"Mon,Wed,Sat"` or `"1,3,6"` + +Stored in DB as normalized numeric string (e.g. `"1,3,6,7"`). + +--- + +## Quick Reference + +| API | Method | Purpose | +|-----|--------|---------| +| `/employeeRest/getReminderMailConfig` | GET | Fetch config by `client_policy_id` | +| `/employeeRest/saveReminderMailConfig` | POST | Create (no `id`) or update (with `id`) | +| `/employeeRest/sendReminderMail` | GET / POST | Manually send reminder mail | + +--- + +## Notes + +1. **Scheduled sends (cron)** use the saved configuration from the `reminder_mail_config` table. +2. **Manual send** via `/sendReminderMail` ignores the schedule and sends immediately. +3. **`working_day_options`** is returned on every get-config response for UI dropdown/checkbox rendering. +4. **Legacy policies** without a config row may still expose `client_policy.reminder_date` as `frequency: custom` with `source: legacy_client_policy`. + +--- + +## Database + +Configuration is stored in the `reminder_mail_config` table. + +| Column | Description | +|--------|-------------| +| `id` | Primary key | +| `client_policy_id` | Unique per policy | +| `frequency` | Schedule type | +| `reminder_days` | Comma-separated day values | +| `is_enabled` | Enable/disable reminders | +| `created_by` / `updated_by` | Set from `hr_id` in API payload | +| `created_at` / `updated_at` | Timestamps | + +SQL migration: + +- `app/Database/reminder_mail_config.sql`