diff --git a/.gitignore b/.gitignore index be97383..53db28a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,12 @@ # Ignore env File here .env +/writable/pdf/forex/* +/writable/pdf/trips/* +/writable/uploads/logo/* +/writable/uploads/passport/* +/writable/uploads/signature/* +/writable/uploads/template/* + + + /public/assets/images/signature/* diff --git a/app/Config/Filters.php b/app/Config/Filters.php index d5ee887..d6fea7a 100755 --- a/app/Config/Filters.php +++ b/app/Config/Filters.php @@ -14,6 +14,7 @@ use CodeIgniter\Filters\PerformanceMetrics; use CodeIgniter\Filters\SecureHeaders; use App\Filters\JwtAuthFilter; use App\Filters\SignedUrlFilter; +use App\Filters\VerifyAppSignature; class Filters extends BaseFilters { /** @@ -35,8 +36,9 @@ class Filters extends BaseFilters 'forcehttps' => ForceHTTPS::class, 'pagecache' => PageCache::class, 'performance' => PerformanceMetrics::class, - 'jwtAuth' => JwtAuthFilter::class, - 'signedUrl' => SignedUrlFilter::class, + 'jwtAuth' => JwtAuthFilter::class, + 'signedUrl' => SignedUrlFilter::class, + 'appSignature' => VerifyAppSignature::class, ]; diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 9cd32d7..79c9a6b 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -16,21 +16,39 @@ $routes->post('acs', 'SamlController::acs'); $routes->post('logout', 'SamlController::slo'); $routes->get('metadata', 'SamlController::metadata'); +$routes->get('auth/msRedirectionHandler', 'AuthController::msRedirectionHandler'); -//login api +$routes->group('api', ['filter' => 'appSignature'], function ($routes) { + +// Login api $routes->post('auth/login', 'AuthController::login'); $routes->get('auth/oauthClient', 'AuthController::oauthClient'); $routes->post('auth/oauthlogin', 'AuthController::oauthlogin'); -$routes->get('auth/msRedirectionHandler', 'AuthController::msRedirectionHandler'); +// MS OAuth api's +$routes->get('auth/mslogin', 'AuthController::mslogin'); +$routes->get('auth/verifyMSAuthUser', 'AuthController::verifyMSAuthUser'); + +// Google OAuth api's +$routes->get('googleoauth', 'AuthController::receiveGoogleOAuthResponse'); +$routes->get('auth/googlelogin', 'AuthController::initiateGoogleOAuth'); +$routes->get('logout', 'AuthController::logout'); + //forgot password $routes->post('forgotPassword/verifyUser', 'UserController::verifyUser'); $routes->post('forgotPassword/changePassword', 'UserController::forgotChangePassword'); +}); + + + + //api's with token - $routes->group('api', ['filter' => 'jwtAuth'], function ($routes) { + $routes->group('api', ['filter' => ["jwtAuth","appSignature"] ], function ($routes) { + + $routes->get('user/refreshUserToken', 'AuthController::refreshUserToken'); //Organization @@ -97,8 +115,8 @@ $routes->post('forgotPassword/changePassword', 'UserController::forgotChangePass $routes->get('plans/forexDownload', 'PlanController::forexDownload'); $routes->get('plans/cancle_plan', 'PlanController::canclePlan'); $routes->get('plans/get_plan_approval_status', 'PlanController::get_plan_approval_status'); - $routes->get('plans/plan_pdf', 'PlanController::plan_pdf'); + $routes->get('plans/plan_policy_action_status', 'PlanController::plan_policy_action_status'); //Master $routes->get('getDropdownMaster', 'MasterController::getDropdownMaster'); @@ -128,6 +146,8 @@ $routes->post('forgotPassword/changePassword', 'UserController::forgotChangePass $routes->post('createForexPerdiem', 'MasterController::createForexPerdiem'); $routes->get('findForexPerdiem', 'MasterController::findForexPerdiem'); $routes->put('updateForexPerdiem/(:num)', 'MasterController::updateForexPerdiem/$1'); + $routes->post('forex_signature_upload', 'MasterController::forex_signature_upload'); + $routes->get('getForexSignaturePath', 'MasterController::getForexSignaturePath'); // Hotels Master $routes->get('getHotels', 'MasterController::getHotels'); $routes->post('createHotels', 'MasterController::createHotels'); @@ -163,15 +183,9 @@ $routes->get('review', 'PlanController::protectedPage', ['filter' => 'signedUrl' $routes->get('url', 'PlanController::url'); -// MS OAuth api's -$routes->get('auth/mslogin', 'AuthController::mslogin'); -$routes->get('auth/verifyMSAuthUser', 'AuthController::verifyMSAuthUser'); - - - -$routes->get('googleoauth', 'AuthController::receiveGoogleOAuthResponse'); -$routes->get('auth/googlelogin', 'AuthController::initiateGoogleOAuth'); -$routes->get('logout', 'AuthController::logout'); + + + diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php index d77ba70..915b0c3 100644 --- a/app/Controllers/AuthController.php +++ b/app/Controllers/AuthController.php @@ -324,5 +324,48 @@ class AuthController extends ResourceController } + //re-Fetch token + + public function refreshUserToken() + { + + + $user_id = $this->request->getGet(); + $user = $this->userModel->where('user_id', $user_id)->where('is_active',1)->first(); + // print_r($user);die; + if (!$user) { + return $this->failUnauthorized('Invalid User'); + } + + + //get service for the organization + $orgService = $this->organizationModel->find($user['org_id']); + $user['service'] = json_decode($orgService['services_ids'],true); + foreach ($user['service'] as $key => $value) + { + $serviceData = $this->serviceModel->find($value['service_id']); + $user['service'][$key]['name'] = $serviceData['name']; + $user['service'][$key]['icon'] = $serviceData['icon']; + $user['service'][$key]['order'] = $serviceData['order']; + } + + //get role + $user['role'] = $this->userModel->getRole($user['role_id']); + + //find user has the all access + $user['plan_action'] = getUserPlanCreationRestrictionStatus($user); + + // Generate JWT Token + $token = generateJWT($user); + + + return $this->respond([ + 'status' => 200, + 'message' => 'Login successful', + 'token' => $token + ]); + } + + } diff --git a/app/Controllers/MasterController.php b/app/Controllers/MasterController.php index 755a69f..8248552 100644 --- a/app/Controllers/MasterController.php +++ b/app/Controllers/MasterController.php @@ -19,6 +19,7 @@ use App\Models\ForexPerdiemModel; use App\Models\HotelModel; use App\Models\CostCenterModel; use App\Models\AirlineModel; +use App\Models\MailTemplateModel; class MasterController extends ResourceController { @@ -40,6 +41,7 @@ class MasterController extends ResourceController protected $hotelModel; protected $costCenterModel; protected $airlineModel; + protected $mailTemplateModel; public function __construct() { @@ -60,6 +62,7 @@ class MasterController extends ResourceController $this->hotelModel = new HotelModel(); $this->costCenterModel = new CostCenterModel(); $this->airlineModel = new AirlineModel(); + $this->mailTemplateModel = new MailTemplateModel(); } @@ -162,20 +165,27 @@ class MasterController extends ResourceController $user_id = $this->request->getVar('user_id'); $user = $this->userModel->where('user_id', $user_id)->first(); + $groupData = $this->groupModel->find($user['group_id']); + if($groupData) { if($trip_type == 1) { $policyId = $groupData['domestic_policy_id'];}else{ $policyId = $groupData['international_policy_id'];} + // dd($policyId); if($policyId != null) { - $minFlightClassKey = $this->policyDetailsModel->where('policy_id',$policyId)->where('service_id',1)->get()->getRow()->class; - $minTrainClassKey = $this->policyDetailsModel->where('policy_id',$policyId)->where('service_id',2)->get()->getRow()->class; - $minHotelClassKey = $this->policyDetailsModel->where('policy_id',$policyId)->where('service_id',5)->get()->getRow()->class; + $minFlightRow = $this->policyDetailsModel->where('policy_id', $policyId)->where('service_id', 1)->get()->getRow(); + + $minTrainRow = $this->policyDetailsModel->where('policy_id', $policyId)->where('service_id', 2)->get()->getRow(); + + $minHotelRow = $this->policyDetailsModel->where('policy_id', $policyId)->where('service_id', 5)->get()->getRow(); + + $minFlightClassKey = $minFlightRow ? $minFlightRow->class : null; + $minTrainClassKey = $minTrainRow ? $minTrainRow->class : null; + $minHotelClassKey = $minHotelRow ? $minHotelRow->class : null; + } - // echo "Flight 3 but ".$minFlightClassKey; //3 - // echo "\n train 1 but ".$minTrainClassKey; //1 - // echo "\n hotel 5 but ".$minHotelClassKey; //5 - // die; + //flight $allFlightClass = $this->dropdownModel->where('dropdown', 'flight_class')->orderBy('dropdown_key','ASC')->findAll(); @@ -624,6 +634,90 @@ class MasterController extends ResourceController } + public function forex_signature_upload() + { + + $data = $this->request->getPost(); + + // Handle file upload + $file = $this->request->getFile('signature'); + if ($file && $file->isValid() && !$file->hasMoved()) { + $newName = $file->getRandomName(); // Generate a unique name + // $uploadPath = WRITEPATH.'uploads/signature'; + $uploadPath = FCPATH.'public/assets/images/signature'; + + // print_r( $uploadPath); die; + + // Move file to the specified directory + $file->move($uploadPath, $newName); + + // Save file path in database + $path = $uploadPath . '/' . $newName; + } else { + return $this->failValidationErrors(['passport_document' => 'Invalid file upload']); + } + + try { + + $this->mailTemplateModel->set(['image_location' => $newName])->where('template_name','forex')->update(); + + + return $this->respondCreated([ + 'status' => 201, + 'message' => 'organization created successfully', + 'data' => $data + ]); + } catch (\Exception $e) { + return $this->failServerError('Failed to create organization: ' . $e->getMessage()); + } + } + + // public function getForexSignaturePath() + // { + + // $data = $this->mailTemplateModel->where('template_name','forex')->first(); + + // return $this->respond([ + // 'status' => 200, + // 'message' => 'success', + // 'data' => $data + + // ]); + + // } + + public function getForexSignaturePath() + { + $data = $this->mailTemplateModel->where('template_name', 'forex')->first(); + + if (!$data || empty($data['image_location'])) { + return $this->respond([ + 'status' => 404, + 'message' => 'Image not found', + 'url' => null + ]); + } + + // If the DB stores only the filename, adjust accordingly + $fileName = basename($data['image_location']); + + + $imageUrl = base_url('public/assets/images/signature/' . $fileName); + + // Add both the URL and ready-made HTML tag + $data['image_url'] = $imageUrl; + $data['image_tag'] = 'Signature'; + + return $this->respond([ + 'status' => 200, + 'message' => 'success', + 'url' => $imageUrl + ]); + } + + + + diff --git a/app/Controllers/PlanController.php b/app/Controllers/PlanController.php index 92c796e..16da085 100644 --- a/app/Controllers/PlanController.php +++ b/app/Controllers/PlanController.php @@ -112,8 +112,9 @@ class PlanController extends ResourceController $a1Data = $this->planStatusModel->select('plan_id')->where('a1_id',$user_id)->where('a1_action','Approval')->where('is_active',1)->findAll(); $a2Data = $this->planStatusModel->select('plan_id')->where('a2_id',$user_id)->where('a2_action','Approval')->where('is_active',1)->findAll(); $a3Data = $this->planStatusModel->select('plan_id')->where('a3_id',$user_id)->where('a3_action','Approval')->where('is_active',1)->findAll(); + $a4Data = $this->planStatusModel->select('plan_id')->where('a4_id',$user_id)->where('a4_action','Approval')->where('is_active',1)->findAll(); - $mergedArray = array_merge($a1Data, $a2Data, $a3Data); + $mergedArray = array_merge($a1Data, $a2Data, $a3Data, $a4Data); $planIds = array_unique(array_column($mergedArray, "plan_id")); $plans = []; if (count($planIds)) { @@ -514,6 +515,12 @@ class PlanController extends ResourceController sendPlanUpdationMail($planId); } + // Send mail to agent if plan status is 3 + $plan = $this->planModel->where('plan_id', $planId)->where('status', 3)->first(); + if ($plan) { + send_email_agent(env('ORG_id'), $planId); + } + return $this->respond(['status' => 'success', 'plan_id' => $planId]); } catch (\Exception $e) { @@ -775,15 +782,27 @@ class PlanController extends ResourceController $status = true; } + //approver 4 + $a4Data = $this->planStatusModel->where('plan_id',$plan_id)->where('a4_id',$user_id)->where('a4_action','Approval')->where('is_active',1)->findAll(); + if($a4Data){ + $this->planStatusModel->set(['is_a4_action_done'=>1,'a4_action_done_on'=>date('Y-m-d H:i:s'),'a4_reject_reason'=>null,'a4_action_done_by'=>$actionDoneBy]) + ->where('plan_id',$plan_id) + ->where('a4_id',$user_id) + ->where('a4_action','Approval') + ->where('is_active',1) + ->update(); + $status = true; + } + //response if($status) { - //update pdf content - $this->generatePlanPdf($plan_id); - //update plan status updatePlanStatus($plan_id); + + //update pdf content + $this->generatePlanPdf($plan_id); //send approval mail sendPlanApprovalOrRejectMail($plan_id,'A'); @@ -791,6 +810,13 @@ class PlanController extends ResourceController //send parallel or Sequential mail sendPlanCreationMail($plan_id, 'send_mail_to_only_approver'); + // Send mail to agent if plan status is 3 + $plan = $this->planModel->where('plan_id', $plan_id)->where('status', 3)->first(); + if ($plan) { + send_email_agent(env('ORG_id'), $plan_id); + } + + return $this->respond(['status' => 200, 'message' => 'success' ]); }else{ return $this->respond(['status' => 404, 'message' => 'failed' ]); @@ -859,6 +885,18 @@ class PlanController extends ResourceController $status = true; } + //approver 4 + $a4Data = $this->planStatusModel->where('plan_id',$plan_id)->where('a4_id',$user_id)->where('a4_action','Approval')->where('is_active',1)->findAll(); + if($a4Data){ + $this->planStatusModel->set(['is_a4_action_done'=>0, 'a4_reject_reason'=>$reason, 'a4_action_done_on'=>date('Y-m-d H:i:s'),'a4_action_done_by'=>$actionDoneBy]) + ->where('plan_id',$plan_id) + ->where('a4_id',$user_id) + ->where('a4_action','Approval') + ->where('is_active',1) + ->update(); + $status = true; + } + //response if($status) @@ -980,8 +1018,11 @@ class PlanController extends ResourceController $todayStart = date('Y-m-d 00:00:00'); $todayEnd = date('Y-m-d 23:59:59'); - $startOfWeek = date('Y-m-d 00:00:00', strtotime('monday this week')); - $endOfWeek = date('Y-m-d 23:59:59', strtotime('sunday this week')); + // $startOfWeek = date('Y-m-d 00:00:00', strtotime('monday this week')); + // $endOfWeek = date('Y-m-d 23:59:59', strtotime('sunday this week')); + $startOfWeek = date('Y-m-d 00:00:00', strtotime('-7 days')); + $endOfWeek = date('Y-m-d 23:59:59'); // today + $typeResultToday = []; $typeResultWeekly = []; @@ -1177,7 +1218,7 @@ class PlanController extends ResourceController $plan['flight'] = $flights; //get accomodation details - $plan['accomodation'] = $this->accomodationModel->select('c_accomodation.* , C.dropdown_value as hotel_class') + $plan['accomodation'] = $this->accomodationModel->select('c_accomodation.*, C.dropdown_value as hotel_class') ->join('m_dropdown C', 'C.dropdown_key = c_accomodation.class AND C.dropdown = "hotel_class"', 'left') ->where('c_accomodation.plan_id', $planId) ->where('c_accomodation.is_active', 1)->findAll(); @@ -1240,6 +1281,11 @@ class PlanController extends ResourceController $userId = $plan['user_id'] ?? $plan['traveller_id']; + //current plan status + $status = getCurrentPlanStatus($planId); + + $plan['status_details'] = $status; + //get user data $plan['user_data'] = $this->userModel->where('user_id', $userId)->first(); $plan['view'] = $view; @@ -1304,7 +1350,13 @@ class PlanController extends ResourceController ->where('c_forex.forex_id', $forexId)->where('c_forex.is_active', 1) ->first(); $data['org_data'] = $this->organizationModel->where('org_id', $data['forexData']['org_id'])->where('is_active', 1)->first(); + $templateData = $this->mailTemplateModel->where('org_id', $data['forexData']['org_id'] )->where('template_name', 'forex' )->first(); + + //placeholder data + $logoFilename = basename($data['org_data']['logo']); + $logo = base_url("assets/images/logo/" . $logoFilename); + $data['forexData']['org_logo'] = 'org_logo'; $data['forexData']['org_name'] = $data['org_data']['name']; $data['forexData']['forex_card_number'] = $data['forexData']['card_number']; $data['forexData']['passport_place_of_issue'] = $data['forexData']['place_of_issue']; @@ -1314,9 +1366,9 @@ class PlanController extends ResourceController $data['forexData']['place_of_visit'] = $data['forexData']['country_name']; $data['forexData']['staying_duration'] = $data['forexData']['duration']; $data['forexData']['total_forex_exchange_amount'] = $data['forexData']['perdiem_amount'] + $data['forexData']['transport'] + $data['forexData']['accommodation'] + $data['forexData']['telephone']; - - - $templateData = $this->mailTemplateModel->where('org_id', $data['forexData']['org_id'] )->where('template_name', 'forex' )->first(); + $signatuteFileName = basename($templateData['image_location']); + $imageUrl = base_url('public/assets/images/signature/' . $signatuteFileName); + $data['forexData']['signature_image'] = 'Signature'; //replace the placeholder foreach ($data['forexData'] as $key => $value) { @@ -1324,6 +1376,8 @@ class PlanController extends ResourceController $templateData['body_html'] = str_replace($placeHolder, $value ?? '', $templateData['body_html']); } $html = $templateData['body_html']; + + // echo $html; die; // $html = view('forex',$data); @@ -1432,16 +1486,26 @@ class PlanController extends ResourceController public function url() { + // echo '
';
+        // $user = $this->userModel->where('email', 'pavithrakavi1100@gmail.com')->first();
+        // echo getUserPlanCreationRestrictionStatus($user); die;
+        // print_r(getPlanApproverAction(126)); die;
 
+        return $this->generatePlanPdf(136, true);
+        die;
+
+        updatePlanStatus(57); die;
+        palnStatusHandler(57, false); die;
+
+       
         echo '
';
-        // sendPlanCreationMail(23);
-        print_r(getPlanApproverAction(32)); die;
+        // sendPlanCreationMail(39);
+        
 
         // echo '
';
         // print_r(getAllowedClassForUser(2,4)['hotel']); die;
 
-        return $this->generatePlanPdf(53, true);
-        die;
+        
 
         return send_email_agent(env('ORG_id') , 62);
         return updatePlanStatus(62);
@@ -1526,6 +1590,65 @@ class PlanController extends ResourceController
     }
 
 
+    public function plan_policy_action_status()
+    {
+        $plan_id = $this->request->getGet('plan_id') ?? null;
+
+
+        $planData = $this->planStatusModel->select('c_plan_status.* ,CONCAT_WS(a1.first_name," ",a1.last_name) as a1_user_name,CONCAT_WS(a2.first_name," ",a2.last_name) as a2_user_name,CONCAT_WS(a3.first_name," ",a3.last_name) as a3_user_name,CONCAT_WS(a4.first_name," ",a4.last_name) as a4_user_name')
+        ->join('m_users a1', 'a1.user_id = c_plan_status.a1_id ', 'left')
+        ->join('m_users a2', 'a2.user_id = c_plan_status.a2_id ', 'left')
+        ->join('m_users a3', 'a3.user_id = c_plan_status.a3_id ', 'left')
+        ->join('m_users a4', 'a4.user_id = c_plan_status.a4_id ', 'left')
+        ->where('c_plan_status.plan_id', $plan_id)
+        ->where('c_plan_status.is_active', 1)
+        ->first();
+
+        $data = [];
+
+        if($planData){
+
+            $a1 = [
+                    "action" => $planData['a1_action'],
+                    "approver" => $planData['a1_user_name'],
+                    "is_action_done" => $planData['is_a1_action_done'] == 1 ? 'Done' : 'Not Done',
+                    "action_on" => $planData['is_a1_action_done'] == 1 ? $planData['a1_action_done_on'] : '-'
+                  ];
+            if($planData['a1_action'] != 'None'){ array_push($data, $a1); }
+
+            $a2 = [
+                "action" => $planData['a2_action'],
+                "approver" => $planData['a2_user_name'],
+                "is_action_done" => $planData['is_a2_action_done'] == 1 ? 'Done' : 'Not Done',
+                "action_on" => $planData['is_a2_action_done'] == 1 ? $planData['a2_action_done_on'] : '-'
+              ];
+            if($planData['a2_action'] != 'None'){ array_push($data, $a2); }
+
+            $a3 = [
+                "action" => $planData['a3_action'],
+                "approver" => $planData['a3_user_name'],
+                "is_action_done" => $planData['is_a3_action_done'] == 1 ? 'Done' : 'Not Done',
+                "action_on" => $planData['is_a3_action_done'] == 1 ? $planData['a3_action_done_on'] : '-'
+            ];
+            if($planData['a3_action'] != 'None'){ array_push($data, $a3); }
+
+            $a4 = [
+                "action" => $planData['a4_action'],
+                "approver" => $planData['a4_user_name'],
+                "is_action_done" => $planData['is_a4_action_done'] == 1 ? 'Done' : 'Not Done',
+                "action_on" => $planData['is_a4_action_done'] == 1 ? $planData['a4_action_done_on'] : '-'
+            ];
+            if($planData['a4_action'] != 'None'){ array_push($data, $a4); }
+
+        }
+
+       
+
+        return $this->respond(['status' => 200, 'message' => 'success', 'data' => $data ]);
+ 
+    } 
+
+
 
 
 
diff --git a/app/Controllers/UserController.php b/app/Controllers/UserController.php
index ad32be0..50596b9 100644
--- a/app/Controllers/UserController.php
+++ b/app/Controllers/UserController.php
@@ -656,7 +656,7 @@ public function userUpload()
 {
     
     $predefinedHeaders = ['First Name', 'Last Name', 'Email', 'Mobile Number', 'Password' , 'Role' ,'Employee Code','Group Name',
-    'First Approver Email','Second Approver Email','Third Approver Email','Exceptional Approver Email'];
+    'First Approver Email','Second Approver Email','Third Approver Email','Fourth Approver Email'];
 
     $uploadDir = WRITEPATH . 'uploads/userUploadFiles/';
 
@@ -728,7 +728,7 @@ public function userUpload()
     $insertUsers     = [];
 
     $dbHeaders = ['first_name','last_name','email','mobile_no','password','role_id','employee_code','group_id',
-    'first_approver_email','second_approver_email','third_approver_email','exceptional_approver_email'];
+    'first_approver_email','second_approver_email','third_approver_email','fourth_approver_email'];
 
     $rolesList = $this->userModel->getRolesList();
 
@@ -781,9 +781,9 @@ public function userUpload()
         }
 
 
-        if (!empty($user['exceptional_approver_email']) && !filter_var($user['exceptional_approver_email'], FILTER_VALIDATE_EMAIL)) {
+        if (!empty($user['fourth_approver_email']) && !filter_var($user['fourth_approver_email'], FILTER_VALIDATE_EMAIL)) {
             $fail++;
-            $failedUsers[] = ['data'  => $user['first_name'].' '.$user['last_name'], 'reason' => 'Invalid exceptional_approver_email format' ];
+            $failedUsers[] = ['data'  => $user['first_name'].' '.$user['last_name'], 'reason' => 'Invalid fourth_approver_email format' ];
             continue;
         }
 
@@ -799,7 +799,7 @@ public function userUpload()
         $user['first_approver_email'] = filter_var($user['first_approver_email'], FILTER_SANITIZE_EMAIL);
         $user['second_approver_email'] = filter_var($user['second_approver_email'], FILTER_SANITIZE_EMAIL);
         $user['third_approver_email'] = filter_var($user['third_approver_email'], FILTER_SANITIZE_EMAIL);
-        $user['exceptional_approver_email'] = filter_var($user['exceptional_approver_email'], FILTER_SANITIZE_EMAIL);
+        $user['fourth_approver_email'] = filter_var($user['fourth_approver_email'], FILTER_SANITIZE_EMAIL);
         $user['role_id'] = $rolesList[$role] ?? 4;
         $user['group_id'] = $groupsList[$group] ?? null;
         $user['org_id'] = env('ORG_id');
@@ -841,7 +841,7 @@ public function userUpload()
     $this->userModel->updateApprover('first', $insertedIds);
     $this->userModel->updateApprover('second', $insertedIds);
     $this->userModel->updateApprover('third', $insertedIds);
-    $this->userModel->updateApprover('exceptional', $insertedIds);
+    $this->userModel->updateApprover('fourth', $insertedIds);
 
 
     return $this->response->setJSON([
diff --git a/app/Filters/VerifyAppSignature.php b/app/Filters/VerifyAppSignature.php
new file mode 100644
index 0000000..a5257b2
--- /dev/null
+++ b/app/Filters/VerifyAppSignature.php
@@ -0,0 +1,36 @@
+getHeaderLine('App-Signature');
+
+        // Load the server's expected signature from the .env
+        $validSignature = getenv('APP_SIGNATURE');
+
+        // Check if signature is valid
+        if ($clientSignature !== $validSignature) {
+            return service('response')
+                ->setStatusCode(403)
+                ->setJSON([
+                    'status' => false,
+                    'message' => 'Forbidden: Invalid App Signature',
+                ]);
+        }
+
+        // allow request to proceed
+    }
+
+    public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
+    {
+        // nothing to do after response
+    }
+}
diff --git a/app/Helpers/common_helper.php b/app/Helpers/common_helper.php
index b234c3e..4c66aff 100644
--- a/app/Helpers/common_helper.php
+++ b/app/Helpers/common_helper.php
@@ -51,19 +51,55 @@ if (!function_exists('checkUserExist')) {
 if (!function_exists('getUserPlanCreationRestrictionStatus')) {
     function getUserPlanCreationRestrictionStatus($user)
     {
-        if($user['group_id'] != null && ( $user['first_approver'] != null || $user['second_approver'] != null || $user['third_approver'] != null ) )
+        if($user['group_id'] != null)
         {
             $groupModel = new GroupModel();
             $groupData = $groupModel->find($user['group_id']);
             if($groupData)
             {
-                if($groupData['domestic_policy_id'] != null && $groupData['international_policy_id'] != null)
-                    return 'Both Type Plan Creation Allowed';
-                else if($groupData['domestic_policy_id'] != null)
-                    return 'Only Domestic Plan Creation Allowed';
-                else if($groupData['international_policy_id'] != null)
-                    return 'Only International Plan Creation Allowed';
-                
+                $domesticPolicyId = $groupData['domestic_policy_id'];
+                $internationalPolicyId = $groupData['international_policy_id'];
+                $policyDetailsModel = new PolicyDetailsModel();
+
+                if($domesticPolicyId != null && $internationalPolicyId != null)
+                {
+                    $domesticPolicy = $policyDetailsModel->where('policy_id',$domesticPolicyId)->where('service_id', 1)->find();
+                    $domesticCheck =  checkApproverSetOrNotBasedOnPolicy($domesticPolicy,$user);
+                    $internationalPolicy = $policyDetailsModel->where('policy_id',$internationalPolicyId)->where('service_id', 1)->find();
+                    $internationalCheck =  checkApproverSetOrNotBasedOnPolicy($internationalPolicy,$user);
+
+                    if($domesticCheck == true && $internationalCheck == true)
+                        return 'Both Type Plan Creation Allowed';
+                    else
+                        return 'Plan Creation Not Allowed'; 
+
+                }
+                else if($domesticPolicyId != null)
+                {
+                    $domesticPolicy = $policyDetailsModel->where('policy_id',$domesticPolicyId)->where('service_id', 1)->find();
+                    $check =  checkApproverSetOrNotBasedOnPolicy($domesticPolicy,$user);
+                    $internationalPolicy = $policyDetailsModel->where('policy_id',$domesticPolicyId)->where('service_id', 1)->find();
+                    $check =  checkApproverSetOrNotBasedOnPolicy($internationalPolicy,$user);
+
+                    if($check == true)
+                        return 'Only Domestic Plan Creation Allowed';
+                    else
+                        return 'Plan Creation Not Allowed'; 
+                    
+                }
+                else if($internationalPolicyId != null)
+                {
+
+                    $internationalPolicy = $policyDetailsModel->where('policy_id',$internationalPolicyId)->where('service_id', 1)->find();
+                    $check = checkApproverSetOrNotBasedOnPolicy($internationalPolicy,$user);
+
+                    if($check == true)
+                        return 'Only International Plan Creation Allowed';
+                    else
+                        return 'Plan Creation Not Allowed'; 
+                   
+                }
+   
 
             }else{ 
                 return 'Plan Creation Not Allowed'; 
@@ -77,6 +113,50 @@ if (!function_exists('getUserPlanCreationRestrictionStatus')) {
     }
 }
 
+function checkApproverSetOrNotBasedOnPolicy($Policy,$user)
+{
+    // Step 1: Determine required approver levels (a1 to a4)
+    $requiredApprovers = [];
+
+    foreach (['', '_exceptional', '_amendment'] as $type) {
+        for ($i = 1; $i <= 4; $i++) {
+            $key = "a{$i}{$type}_action";
+            if (isset($Policy[0][$key]) && $Policy[0][$key] === 'Approval') {
+                $requiredApprovers[] = $i; // add level (1 to 4)
+            }
+        }
+    }
+
+    // Remove duplicates (in case multiple types require same level)
+    $requiredApprovers = array_unique($requiredApprovers);
+
+    // Step 2: Check user profile for each required approver level
+    $missingApprovers = [];
+
+    foreach ($requiredApprovers as $level) {
+        $profileKey = match ($level) {
+            1 => 'first_approver',
+            2 => 'second_approver',
+            3 => 'third_approver',
+            4 => 'fourth_approver',
+        };
+
+        if (empty($user[$profileKey]) || $user[$profileKey] == 0) {
+            $missingApprovers[] = $profileKey;
+        }
+    }
+
+    // Step 3: Final check
+    if (!empty($missingApprovers)) {
+        // One or more approvers missing
+        // echo "Missing approvers in user profile: " . implode(', ', $missingApprovers);
+        return false;
+    } else {
+        // echo "All required approvers are set in user profile.";
+        return true;
+    }
+
+}
 
 if (!function_exists('isDataChanged')) {
     function isDataChanged($model, $id, array $newData): bool
@@ -131,7 +211,10 @@ if (!function_exists('isFlightTripDataChanged')) {
         }
 
         // Step 1: Fetch old data from the model
-        $oldData = $model->whereIn('flight_id', $flightIds)->where('is_active', 1)->findAll();
+        if(!empty($flightIds))
+            $oldData = $model->whereIn('flight_id', $flightIds)->where('is_active', 1)->findAll();
+        else
+            $oldData = [];
 
        
         if (!$oldData) {
@@ -208,9 +291,17 @@ function getDelegatedPlans($org_id, $id)
                                   ->where('created_on >=', $startDate)
                                   ->where('created_on <=', $endDate)
                                   ->findAll();
+
+        $a4Data = $planStatusModel->select('plan_id,a4_id as user_id')
+                                  ->where('a4_id',$userId)
+                                  ->where('a4_action','Approval')
+                                  ->where('is_active',1)
+                                  ->where('created_on >=', $startDate)
+                                  ->where('created_on <=', $endDate)
+                                  ->findAll();
         
        // Merge results (each record has plan_id + user_id)
-       $merged = array_merge($a1Data, $a2Data, $a3Data);
+       $merged = array_merge($a1Data, $a2Data, $a3Data, $a4Data);
 
        // Merge into final array
        $allPlanData = array_merge($allPlanData, $merged);
@@ -218,9 +309,9 @@ function getDelegatedPlans($org_id, $id)
     }
    
     //get already approved data in delegated flow
-    $a1 = $planStatusModel->select('plan_id,a1_id as user_id')->where('a1_action_done_by',$id)->where('a1_action','Approval')->where('is_active',1)->findAll();
-    $a2 = $planStatusModel->select('plan_id,a2_id as user_id')->where('a2_action_done_by',$id)->where('a2_action','Approval')->where('is_active',1)->findAll();
-    $a3 = $planStatusModel->select('plan_id,a3_id as user_id')->where('a3_action_done_by',$id)->where('a3_action','Approval')->where('is_active',1)->findAll();
+    // $a1 = $planStatusModel->select('plan_id,a1_id as user_id')->where('a1_action_done_by',$id)->where('a1_action','Approval')->where('is_active',1)->findAll();
+    // $a2 = $planStatusModel->select('plan_id,a2_id as user_id')->where('a2_action_done_by',$id)->where('a2_action','Approval')->where('is_active',1)->findAll();
+    // $a3 = $planStatusModel->select('plan_id,a3_id as user_id')->where('a3_action_done_by',$id)->where('a3_action','Approval')->where('is_active',1)->findAll();
 
     //need to discuss with sir
     // $mergedApprovedArray = array_merge($a1, $a2, $a3);
@@ -299,172 +390,92 @@ if (!function_exists('format_date_for_client')) {
     }
 }
 
-
-
-// if (!function_exists('palnStatusHandler')) {
-//     function palnStatusHandler($plan_id, $service_id, $action)
-//     {
-
-        
-//         $planModel = new PlanModel();
-//         $planStatusModel = new PlanStatusModel();
-//         $userModel = new UserModel();
-//         $groupModel = new GroupModel();
-//         $policyModel = new PolicyModel();
-//         $policyDetailsModel = new PolicyDetailsModel();
-
-//         if($action == 'DEL')
-//         {
-//             // Delete status
-//             $planStatusModel->set(['is_active' => 0])->where('plan_id',$plan_id)->where('service_id',$service_id)->where('is_active',1)->update();
-//             return true;
-//         }
-
-//         if($action == 'ADD')
-//         {
-//             //check already created
-//             $status = $planStatusModel->where('plan_id',$plan_id)->where('service_id',$service_id)->where('is_active',1)->first();
-//             if ($status) { return false; }
-//         }
-        
-
-//        // Fetch plan data
-//         $planData = $planModel->where('plan_id', $plan_id)->first();
-//             if (!$planData) { return false; }
-//             $userId = $planData['user_id'] ?? $planData['traveller_id'];
-
-//         // Fetch user data
-//         $userData = $userModel->where('user_id', $userId)->first();
-//             if (!$userData) { return false; }
-//             $groupId = $userData['group_id'];
-//             $a1Id = $userData['first_approver'];
-//             $a2Id = $userData['second_approver'];
-//             $a3Id = $userData['third_approver'];
-
-//         // Fetch group data
-//         $groupData = $groupModel->where('group_id', $groupId)->first();
-//             if (!$groupData) { return false; }
-//             $policyId = ($planData['trip_type'] == 1) ? $groupData['domestic_policy_id'] : $groupData['international_policy_id'];
-
-//         // Fetch policy service data
-//         $policyServiceData = $policyDetailsModel->where('policy_id', $policyId)->where('service_id', $service_id)->first();
-//             if (!$policyServiceData) {return false; }
-//             $policyA1Action = $policyServiceData['a1_action'];
-//             $policyA2Action = $policyServiceData['a2_action'];
-//             $policyA3Action = $policyServiceData['a3_action'];
-//             $policyParallelAction = $policyServiceData['parallel_process_from'];
-        
-
-//         if($action == 'ADD')
-//         {
-//             $data['plan_id'] = $plan_id;
-//             $data['service_id'] = $service_id;
-//             $data['a1_id'] = $a1Id;
-//             $data['a1_action'] = $policyA1Action;
-//             $data['a2_id'] = $a2Id;
-//             $data['a2_action'] = $policyA2Action;
-//             $data['a3_id'] = $a3Id;
-//             $data['a3_action'] = $policyA3Action;
-//             $data['parallel_process_from'] = $policyParallelAction;
-//             // Insert Status
-//             $planStatusModel->insert($data);
-//         }
-
-  
-//     }
-// }
-
-
-    if (!function_exists('isValidUploadedFile')) {
-        function isValidUploadedFile($file): bool
-        {
-            return $file && $file->isValid();
-        }
-    }
-
-    if (!function_exists('isAllowedExtension')) {
-        function isAllowedExtension($file, array $allowedExtensions = [] ): bool
-        {
-            $ext = strtolower($file->getClientExtension());
-
-            $result = in_array($ext, $allowedExtensions);
-
-            $result = empty($allowedExtensions) ? true : $result ;
-
-            return $result;
-        }
-    }
-
-
-    if (!function_exists('isExcelNotEmpty')) {
-        function isExcelNotEmpty(array $sheetData): bool
-        {
-            return !empty($sheetData) && count($sheetData) >= 2;
-        }
-    }
-
-    if(!function_exists('createDirectoryWith0777Permission')){
-
-        function createDirectoryWith0777Permission( $uploadDir)
-        {
-            if (!is_dir($uploadDir)) {
-                mkdir($uploadDir, 0777, true); // recursive creation with full permission
-            }
-
-        }
-
-    }
-
-
-
-
-    function getAllowedClassForUser($trip_type,$user_id)
+if (!function_exists('isValidUploadedFile')) {
+    function isValidUploadedFile($file): bool
     {
+        return $file && $file->isValid();
+    }
+}
 
-        $userModel = new UserModel();
-        $groupModel = new GroupModel();
-        $policyDetailsModel = new PolicyDetailsModel();
-        $policyDetailsModel = new PolicyDetailsModel();
+if (!function_exists('isAllowedExtension')) {
+    function isAllowedExtension($file, array $allowedExtensions = [] ): bool
+    {
+        $ext = strtolower($file->getClientExtension());
 
-        $user = $userModel->where('user_id', $user_id)->first();
-        $groupData = $groupModel->find($user['group_id']);
-       
-        if($groupData)
-        {
-            if($trip_type == 1) { $policyId = $groupData['domestic_policy_id'];}else{ $policyId = $groupData['international_policy_id'];}
-            if($policyId != null)
-            {
-                $allowedFlightClass = $policyDetailsModel->select('c_policy_details.class, m_dropdown.dropdown_value as allowed_class')
-                                    ->join('m_dropdown', 'dropdown_key = c_policy_details.class AND dropdown = "flight_class"', 'left')
-                                     ->where('policy_id',$policyId)
-                                     ->where('service_id',1)
-                                     ->first();
-                $data['flight'] = $allowedFlightClass['allowed_class'];
+        $result = in_array($ext, $allowedExtensions);
 
-                $allowedTrainClass = $policyDetailsModel->select('c_policy_details.class, m_dropdown.dropdown_value as allowed_class')
-                                     ->join('m_dropdown', 'dropdown_key = c_policy_details.class AND dropdown = "train_class"', 'left')
-                                      ->where('policy_id',$policyId)
-                                      ->where('service_id',2)
-                                      ->first();
-                $data['train'] = $allowedTrainClass['allowed_class'];
+        $result = empty($allowedExtensions) ? true : $result ;
 
-                $allowedHotelClass = $policyDetailsModel->select('c_policy_details.class, m_dropdown.dropdown_value as allowed_class')
-                                      ->join('m_dropdown', 'dropdown_key = c_policy_details.class AND dropdown = "hotel_class"', 'left')
-                                       ->where('policy_id',$policyId)
-                                       ->where('service_id',5)
-                                       ->first();
-                $data['hotel'] = $allowedHotelClass['allowed_class'];
-            }
+        return $result;
+    }
+}
 
+if (!function_exists('isExcelNotEmpty')) {
+    function isExcelNotEmpty(array $sheetData): bool
+    {
+        return !empty($sheetData) && count($sheetData) >= 2;
+    }
+}
 
-           return $data;
+if(!function_exists('createDirectoryWith0777Permission')){
 
+    function createDirectoryWith0777Permission( $uploadDir)
+    {
+        if (!is_dir($uploadDir)) {
+            mkdir($uploadDir, 0777, true); // recursive creation with full permission
         }
-       
-       
 
     }
 
+}
+
+function getAllowedClassForUser($trip_type,$user_id)
+{
+
+    $userModel = new UserModel();
+    $groupModel = new GroupModel();
+    $policyDetailsModel = new PolicyDetailsModel();
+    $policyDetailsModel = new PolicyDetailsModel();
+
+    $user = $userModel->where('user_id', $user_id)->first();
+    $groupData = $groupModel->find($user['group_id']);
+    
+    if($groupData)
+    {
+        if($trip_type == 1) { $policyId = $groupData['domestic_policy_id'];}else{ $policyId = $groupData['international_policy_id'];}
+        if($policyId != null)
+        {
+            $allowedFlightClass = $policyDetailsModel->select('c_policy_details.class, m_dropdown.dropdown_value as allowed_class')
+                                ->join('m_dropdown', 'dropdown_key = c_policy_details.class AND dropdown = "flight_class"', 'left')
+                                    ->where('policy_id',$policyId)
+                                    ->where('service_id',1)
+                                    ->first();
+            $data['flight'] = $allowedFlightClass['allowed_class'];
+
+            $allowedTrainClass = $policyDetailsModel->select('c_policy_details.class, m_dropdown.dropdown_value as allowed_class')
+                                    ->join('m_dropdown', 'dropdown_key = c_policy_details.class AND dropdown = "train_class"', 'left')
+                                    ->where('policy_id',$policyId)
+                                    ->where('service_id',2)
+                                    ->first();
+            $data['train'] = $allowedTrainClass['allowed_class'];
+
+            $allowedHotelClass = $policyDetailsModel->select('c_policy_details.class, m_dropdown.dropdown_value as allowed_class')
+                                    ->join('m_dropdown', 'dropdown_key = c_policy_details.class AND dropdown = "hotel_class"', 'left')
+                                    ->where('policy_id',$policyId)
+                                    ->where('service_id',5)
+                                    ->first();
+            $data['hotel'] = $allowedHotelClass['allowed_class'];
+        }
+
+
+        return $data;
+
+    }
+    
+    
+
+}
+
 
 
 
diff --git a/app/Helpers/mail_helper.php b/app/Helpers/mail_helper.php
index c431723..66017ed 100644
--- a/app/Helpers/mail_helper.php
+++ b/app/Helpers/mail_helper.php
@@ -45,16 +45,24 @@ if (!function_exists('sendPlanCreationMail')) {
             if ($value['is_action_done'] == 1) { continue; }
             // Skip if action is None
             if ($value['action'] === 'None') { continue; }
+            // mail success check
+            if ($value['is_mail_send'] == 1) { continue; }
             // skip with below condition
             if($mailOn == 'send_mail_to_only_approver' && $action['parallel_process_from'] == 1){ continue; }
 
+           
+
            if($value['action'] == 'Approval'){ $template = 'plan_creation_approval_notification_to_approver'; }else if($value['action'] == 'Notification'){ $template = 'plan_creation_notification_to_approver'; }
             
             $userData = $userModel->where('user_id', $value['user_id'])->first();
 
             $url = urlButton($planId, $value['user_id']);
     
-            send_email($userData['org_id'] , $userData['email'], $template , $userData , $planData, $url);
+            $res = send_email($userData['org_id'] , $userData['email'], $template , $userData , $planData, $url);
+            if($res['status'] = 'success')
+            {
+                $planStatusModel->set([ $value['mail_send_key'] => 1 ])->where('plan_id', $planId)->where('is_active', 1)->update(); 
+            }
 
             // if action is notification update the action status as 1 (done)
             $parallelProcessValue =  $action['parallel_process_from'];
@@ -70,7 +78,11 @@ if (!function_exists('sendPlanCreationMail')) {
                 {
                     sendPlanCreationMail($planId , 'send_mail_to_only_approver');
                 }
-                if($parallelProcessValue == 3)
+                if($parallelProcessValue == 3 && ($value['action_key'] == 'a1_action' || $value['action_key'] == 'a2_action') )
+                {
+                    sendPlanCreationMail($planId , 'send_mail_to_only_approver');
+                }
+                if($parallelProcessValue == 4 && ($value['action_key'] == 'a1_action' || $value['action_key'] == 'a2_action' || $value['action_key'] == 'a3_action'))
                 {
                     sendPlanCreationMail($planId , 'send_mail_to_only_approver');
                 }
@@ -142,6 +154,10 @@ if (!function_exists('sendPlanApprovalOrRejectMail')) {
             if (isset($value['a3_status'])) {
                 $statusHtml .= '
  • Approver 3: ' . $value['a3_status'] . '
  • '; } + + if (isset($value['a4_status'])) { + $statusHtml .= '
  • Approver 4: ' . $value['a4_status'] . '
  • '; + } } $statusHtml .= ''; $userData['status_details'] = $statusHtml; @@ -179,7 +195,6 @@ if (!function_exists('sendPlanUpdationMail')) { // Fetch user data $userData = $userModel->where('user_id', $userId)->first(); - // send mail to traveller send_email($planData['org_id'] , $userData['email'], 'plan_updation_notification_to_traveller' , $userData , $planData); @@ -194,6 +209,8 @@ if (!function_exists('sendPlanUpdationMail')) { if ($value['is_action_done'] == 1) { continue; } // Skip if action is None if ($value['action'] === 'None') { continue; } + // mail success check + if ($value['is_mail_send'] === 1) { continue; } if($value['action'] == 'Approval'){ $template = 'plan_updation_approval_notification_to_approver'; }else if($value['action'] == 'Notification'){ $template = 'plan_updation_notification_to_approver'; } @@ -201,7 +218,11 @@ if (!function_exists('sendPlanUpdationMail')) { $url = urlButton($planId, $value['user_id']); - send_email($userData['org_id'] , $userData['email'], $template , $userData , $planData, $url); + $res = send_email($userData['org_id'] , $userData['email'], $template , $userData , $planData, $url); + if($res['status'] = 'success') + { + $planStatusModel->set([ $value['mail_send_key'] => 1 ])->where('plan_id', $planId)->where('is_active', 1)->update(); + } // if action is notification update the action status as 1 (done) $parallelProcessValue = $action['parallel_process_from']; @@ -217,7 +238,11 @@ if (!function_exists('sendPlanUpdationMail')) { { sendPlanCreationMail($planId , 'send_mail_to_only_approver'); } - if($parallelProcessValue == 3) + if($parallelProcessValue == 3 && ($value['action_key'] == 'a1_action' || $value['action_key'] == 'a2_action') ) + { + sendPlanCreationMail($planId , 'send_mail_to_only_approver'); + } + if($parallelProcessValue == 4 && ($value['action_key'] == 'a1_action' || $value['action_key'] == 'a2_action' || $value['action_key'] == 'a3_action')) { sendPlanCreationMail($planId , 'send_mail_to_only_approver'); } @@ -268,37 +293,89 @@ if (!function_exists('sendUserCreationMail')) { $html = $templateData['body_html']; $toEmailId = $mailData['email']; - $email = \Config\Services::email(); - $email->initialize([ - 'protocol' => 'smtp', - 'SMTPHost' => $orgData['mail_host'], - 'SMTPUser' => $orgData['mail_user_name'], - 'SMTPPass' => $orgData['mail_password'], - 'SMTPPort' => (int)$orgData['mail_port'], - 'mailType' => 'html' - ]); - $email->setFrom($orgData['sender_email'], $orgData['name']); - $email->setTo($toEmailId); - $email->setSubject($subject); - $email->setMessage($html); + + // Set up ZeptoMail API request + $endpoint = "https://api.zeptomail.com/v1.1/email"; + $apiKey = env('ZOHO_API_KEY'); + $sender = env('ZOHO_SENDER_MAIL'); + + $payload = [ + "from" => [ + "address" => $sender, + "name" => $orgData['name'] ?? "TripApprovalTool" + ], + "to" => [ + ["email_address" => ["address" => $toEmailId]] + ], + "subject" => $subject, + "htmlbody" => $html, + ]; + + $headers = [ + "Content-Type: application/json", + "Authorization: Zoho-enczapikey " . $apiKey + ]; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $endpoint); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if (curl_errno($ch)) { + $error_msg = curl_error($ch); + log_message('error', "[EMAIL ERROR] Curl failed: {$error_msg}"); + return ['status' => 'failed', 'code' => 500, 'message' => 'Curl error', 'error' => $error_msg]; + } + + curl_close($ch); + + $resp = json_decode($response, true); + + if ($httpCode === 200 && isset($resp['request_id'])) { + log_message('info', "[EMAIL SENT] To: {$toEmailId}, Template: {$mailData['template']}"); + return ['status' => 'success', 'code' => 200, 'message' => 'Email sent successfully', 'data' => $toEmailId]; + } else { + log_message('error', "[EMAIL FAILED] HTTP: {$httpCode}, Response: {$response}"); + return ['status' => 'failed', 'code' => $httpCode, 'message' => 'Email sending failed', 'response' => $response]; + } + + + // $email = \Config\Services::email(); + // $email->initialize([ + // 'protocol' => 'smtp', + // 'SMTPHost' => $orgData['mail_host'], + // 'SMTPUser' => $orgData['mail_user_name'], + // 'SMTPPass' => $orgData['mail_password'], + // 'SMTPPort' => (int)$orgData['mail_port'], + // 'mailType' => 'html' + // ]); + // $email->setFrom($orgData['sender_email'], $orgData['name']); + // $email->setTo($toEmailId); + // $email->setSubject($subject); + // $email->setMessage($html); - if ($email->send()) { + // if ($email->send()) { - log_message('debug', 'Email Success to ' . $toEmailId); - return ['status' => 'success', 'code' => 200, 'message' => 'Email Sent Successfully...', 'data' => $toEmailId]; - } else { - log_message('debug', 'Email sending failed: ' . $email->printDebugger(['headers'])); - return ['status' => 'failed', 'code' => 404, 'message' => 'Email Sent Failed...', 'data' => $toEmailId]; - } + // log_message('debug', 'Email Success to ' . $toEmailId); + // return ['status' => 'success', 'code' => 200, 'message' => 'Email Sent Successfully...', 'data' => $toEmailId]; + // } else { + // log_message('debug', 'Email sending failed: ' . $email->printDebugger(['headers'])); + // return ['status' => 'failed', 'code' => 404, 'message' => 'Email Sent Failed...', 'data' => $toEmailId]; + // } } } -if (!function_exists('send_email')) { +// if (!function_exists('send_email')) { // function send_email($org_id , $toEmailId, $template , $userData , $planData = null, $url = null) // { // try { @@ -400,113 +477,253 @@ if (!function_exists('send_email')) { // } - if (!function_exists('send_email')) { - function send_email($org_id, $toEmailId, $template, $userData, $planData = null, $url = null) - { - try { - // Load models - $organizationModel = new \App\Models\OrganizationModel(); - $mailTemplateModel = new \App\Models\MailTemplateModel(); + // if (!function_exists('send_email')) { + // function send_email($org_id, $toEmailId, $template, $userData, $planData = null, $url = null) + // { + // try { + // // Load models + // $organizationModel = new \App\Models\OrganizationModel(); + // $mailTemplateModel = new \App\Models\MailTemplateModel(); - // Fetch organization & template - $orgData = $organizationModel->where('org_id', $org_id)->first(); - $templateData = $mailTemplateModel->where('org_id', $org_id)->where('template_name', $template)->first(); + // // Fetch organization & template + // $orgData = $organizationModel->where('org_id', $org_id)->first(); + // $templateData = $mailTemplateModel->where('org_id', $org_id)->where('template_name', $template)->first(); - if (!$orgData || !$templateData) { - log_message('error', "[EMAIL ERROR] Missing org/template data for org_id={$org_id}, template={$template}"); - return ['status' => 'failed', 'code' => 404, 'message' => 'Missing organization or template data']; - } + // if (!$orgData || !$templateData) { + // log_message('error', "[EMAIL ERROR] Missing org/template data for org_id={$org_id}, template={$template}"); + // return ['status' => 'failed', 'code' => 404, 'message' => 'Missing organization or template data']; + // } - // Replace placeholders from user data - foreach ($userData as $key => $value) { - $templateData['body_html'] = str_replace('%' . $key . '%', $value ?? '', $templateData['body_html']); - } + // // Replace placeholders from user data + // foreach ($userData as $key => $value) { + // $templateData['body_html'] = str_replace('%' . $key . '%', $value ?? '', $templateData['body_html']); + // } - // Replace placeholders from plan data - if (!empty($planData)) { - foreach ($planData as $key => $value) { - $templateData['body_html'] = str_replace('%' . $key . '%', $value ?? '', $templateData['body_html']); - } + // // Replace placeholders from plan data + // if (!empty($planData)) { + // foreach ($planData as $key => $value) { + // $templateData['body_html'] = str_replace('%' . $key . '%', $value ?? '', $templateData['body_html']); + // } - // Exceptional reason block - if (!empty($planData['exceptional_plan_reason'])) { - $reasonHtml = ' - - - - - - - -
    - Reason for travel policy exception -
    - ' . esc($planData['exceptional_plan_reason']) . ' -
    '; - $templateData['body_html'] .= $reasonHtml; - } - } + // // Exceptional reason block + // if (!empty($planData['exceptional_plan_reason'])) { + // $reasonHtml = ' + // + // + // + // + // + // + // + //
    + // Reason for travel policy exception + //
    + // ' . esc($planData['exceptional_plan_reason']) . ' + //
    '; + // $templateData['body_html'] .= $reasonHtml; + // } + // } - // Add review URL if present - if ($url !== null) { - $templateData['body_html'] = str_replace('%trip_review_link%', $url, $templateData['body_html']); - } + // // Add review URL if present + // if ($url !== null) { + // $templateData['body_html'] = str_replace('%trip_review_link%', $url, $templateData['body_html']); + // } - // Initialize email - $email = \Config\Services::email(false); - $email->initialize([ - 'protocol' => 'smtp', - 'SMTPHost' => $orgData['mail_host'], - 'SMTPUser' => $orgData['mail_user_name'], - 'SMTPPass' => $orgData['mail_password'], // Use app password if Gmail - 'SMTPPort' => (int) $orgData['mail_port'], - 'SMTPCrypto' => 'tls', // ensure STARTTLS - 'mailType' => 'html', - 'charset' => 'utf-8', - 'newline' => "\r\n", - 'wordWrap' => true, - ]); + // // Initialize email + // $email = \Config\Services::email(false); + // $email->initialize([ + // 'protocol' => 'smtp', + // 'SMTPHost' => $orgData['mail_host'], + // 'SMTPUser' => $orgData['mail_user_name'], + // 'SMTPPass' => $orgData['mail_password'], + // 'SMTPPort' => (int) $orgData['mail_port'], + // // 'SMTPCrypto' => 'tls', // ensure STARTTLS + // 'mailType' => 'html', + // 'charset' => 'utf-8', + // 'newline' => "\r\n", + // 'wordWrap' => true, + // ]); - $email->setFrom($orgData['sender_email'], $orgData['name'] ?? 'TripApprovalTool'); - $email->setTo($toEmailId); - $email->setSubject($templateData['subject']); - $email->setMessage($templateData['body_html']); + // $email->setFrom($orgData['sender_email'], $orgData['name'] ?? 'TripApprovalTool'); + // $email->setTo($toEmailId); + // $email->setSubject($templateData['subject']); + // $email->setMessage($templateData['body_html']); - // Attach file if exists - // if (!empty($planData['pdf_file_path']) && file_exists($planData['pdf_file_path'])) { - // $email->attach($planData['pdf_file_path']); - // } + // // Attach file if exists + // // if (!empty($planData['pdf_file_path']) && file_exists($planData['pdf_file_path'])) { + // // $email->attach($planData['pdf_file_path']); + // // } + // // $filePath = WRITEPATH . 'pdf/trips/trip_' . $planData['plan_id'] . '.pdf'; + // // if (!empty($filePath) && file_exists($filePath) && is_readable($filePath)) { - if (!empty($planData['pdf_file_path']) && file_exists($planData['pdf_file_path']) && is_readable($planData['pdf_file_path'])) { - $email->attach( - $planData['pdf_file_path'], - 'attachment', - basename($planData['pdf_file_path']), - 'application/pdf' - ); - } else { - log_message('error', "[EMAIL] PDF file not attached. Path: {$planData['pdf_file_path']}"); - } + // // $email->attach($filePath, 'attachment', basename($filePath), 'application/pdf'); + + // // } else { + // // log_message('error', "[EMAIL] PDF file not attached. Path: {$filePath}"); + // // } + + // $filePath = WRITEPATH . 'pdf/trips/trip_' . $planData['plan_id'] . '.pdf'; + + // if (!empty($filePath) && file_exists($filePath) && is_readable($filePath)) { + // // Read the file content + // $fileContent = file_get_contents($filePath); + + // if ($fileContent === false) { + // log_message('error', "[EMAIL] Failed to read PDF file content. Path: {$filePath}"); + // // Handle error: perhaps skip attachment or throw an exception + // } else { + // // Attach the file content directly + // $email->attach($fileContent, 'attachment', basename($filePath), 'application/pdf'); + // log_message('info', "[EMAIL] PDF file attached successfully. Path: {$filePath}"); + // } + // } else { + // log_message('error', "[EMAIL] PDF file not attached. Path: {$filePath}"); + // } - // Send email - if ($email->send()) { - log_message('info', "[EMAIL SENT] To: {$toEmailId}, Template: {$template}"); - return ['status' => 'success', 'code' => 200, 'message' => 'Email sent successfully', 'data' => $toEmailId]; - } else { - // Log full debugger info - $debug = $email->printDebugger(['headers', 'subject', 'body']); - log_message('error', "[EMAIL FAILED] To: {$toEmailId}, Template: {$template}\n" . strip_tags($debug)); - return ['status' => 'failed', 'code' => 500, 'message' => 'Email sending failed', 'debug' => strip_tags($debug)]; - } + // // Send email + // if ($email->send()) { + // log_message('info', "[EMAIL SENT] To: {$toEmailId}, Template: {$template}"); + // $email->clear(true); // 'true' clears attachments as well + // return ['status' => 'success', 'code' => 200, 'message' => 'Email sent successfully', 'data' => $toEmailId]; + // } else { + // // Log full debugger info + // $debug = $email->printDebugger(['headers', 'subject', 'body']); + // log_message('error', "[EMAIL FAILED] To: {$toEmailId}, Template: {$template}\n" . strip_tags($debug)); + // $email->clear(true); // 'true' clears attachments as well + // return ['status' => 'failed', 'code' => 500, 'message' => 'Email sending failed', 'debug' => strip_tags($debug)]; + // } - } catch (\Throwable $e) { - log_message('critical', "[EMAIL EXCEPTION] To: {$toEmailId}, Error: " . $e->getMessage() . ' on line ' . $e->getLine()); - return ['status' => 'failed', 'code' => 500, 'message' => 'Exception: ' . $e->getMessage()]; + // } catch (\Throwable $e) { + // log_message('critical', "[EMAIL EXCEPTION] To: {$toEmailId}, Error: " . $e->getMessage() . ' on line ' . $e->getLine()); + // return ['status' => 'failed', 'code' => 500, 'message' => 'Exception: ' . $e->getMessage()]; + // } + // } + // } + + +// } + + +// zoho api service +if (!function_exists('send_email')) { + function send_email($org_id, $toEmailId, $template, $userData, $planData = null, $url = null) + { + try { + $organizationModel = new \App\Models\OrganizationModel(); + $mailTemplateModel = new \App\Models\MailTemplateModel(); + + $orgData = $organizationModel->where('org_id', $org_id)->first(); + $templateData = $mailTemplateModel->where('org_id', $org_id)->where('template_name', $template)->first(); + + if (!$orgData || !$templateData) { + log_message('error', "[EMAIL ERROR] Missing org/template data for org_id={$org_id}, template={$template}"); + return ['status' => 'failed', 'code' => 404, 'message' => 'Missing organization or template data']; } + + // Replace placeholders + foreach ($userData as $key => $value) { + $templateData['body_html'] = str_replace('%' . $key . '%', $value ?? '', $templateData['body_html']); + } + + if (!empty($planData)) { + foreach ($planData as $key => $value) { + $templateData['body_html'] = str_replace('%' . $key . '%', $value ?? '', $templateData['body_html']); + } + + if (!empty($planData['exceptional_plan_reason'])) { + $templateData['body_html'] .= ' + + + + + + + +
    + Reason for travel policy exception +
    + ' . esc($planData['exceptional_plan_reason']) . ' +
    '; + } + } + + if ($url !== null) { + $templateData['body_html'] = str_replace('%trip_review_link%', $url, $templateData['body_html']); + } + + // Set up ZeptoMail API request + $endpoint = "https://api.zeptomail.com/v1.1/email"; + $apiKey = env('ZOHO_API_KEY'); + $sender = env('ZOHO_SENDER_MAIL'); + + $attachments = []; + + if (!empty($planData)) { + $filePath = WRITEPATH . 'pdf/trips/trip_' . $planData['plan_id'] . '.pdf'; + if (!empty($filePath) && file_exists($filePath) && is_readable($filePath)) { + $attachments[] = [ + "content" => base64_encode(file_get_contents($filePath)), + "mime_type" => "application/pdf", + "name" => basename($filePath) + ]; + } + } + + $payload = [ + "from" => [ + "address" => $sender, + "name" => $orgData['name'] ?? "TripApprovalTool" + ], + "to" => [ + ["email_address" => ["address" => $toEmailId]] + ], + "subject" => $templateData['subject'], + "htmlbody" => $templateData['body_html'], + ]; + + if (!empty($attachments)) { + $payload["attachments"] = $attachments; + } + + $headers = [ + "Content-Type: application/json", + "Authorization: Zoho-enczapikey " . $apiKey + ]; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $endpoint); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if (curl_errno($ch)) { + $error_msg = curl_error($ch); + log_message('error', "[EMAIL ERROR] Curl failed: {$error_msg}"); + return ['status' => 'failed', 'code' => 500, 'message' => 'Curl error', 'error' => $error_msg]; + } + + curl_close($ch); + + $resp = json_decode($response, true); + + if ($httpCode === 200 && isset($resp['request_id'])) { + log_message('info', "[EMAIL SENT] To: {$toEmailId}, Template: {$template}"); + return ['status' => 'success', 'code' => 200, 'message' => 'Email sent successfully', 'data' => $toEmailId]; + } else { + log_message('error', "[EMAIL FAILED] HTTP: {$httpCode}, Response: {$response}"); + return ['status' => 'failed', 'code' => $httpCode, 'message' => 'Email sending failed', 'response' => $response]; + } + + } catch (\Throwable $e) { + log_message('critical', "[EMAIL EXCEPTION] To: {$toEmailId}, Error: " . $e->getMessage() . ' on line ' . $e->getLine()); + return ['status' => 'failed', 'code' => 500, 'message' => 'Exception: ' . $e->getMessage()]; } } - } if (!function_exists('send_email_agent')) { @@ -552,15 +769,7 @@ if (!function_exists('send_email_agent')) { return false; } - $email = \Config\Services::email(); - $email->initialize([ - 'protocol' => 'smtp', - 'SMTPHost' => $orgData['mail_host'], - 'SMTPUser' => $orgData['mail_user_name'], - 'SMTPPass' => $orgData['mail_password'], - 'SMTPPort' => (int)$orgData['mail_port'], - 'mailType' => 'html' - ]); + foreach ($userData as $key => $value) { $jsonString = $value['agent_supported_service_ids']; @@ -578,35 +787,118 @@ if (!function_exists('send_email_agent')) { if (!empty($common)) { - //send mail to agent - $placeHolderData['agent_name'] = $value['first_name'].' '.$value['last_name']; + // Create a fresh copy of template for each user + $htmlTemplate = $templateData['body_html']; - foreach ($placeHolderData as $placeHolderkey => $placeHoldervalue) { + + //send mail to agent + $placeHolderData = [ + 'agent_name' => $value['first_name'].' '.$value['last_name'] + ]; + + foreach ($placeHolderData as $placeHolderkey => $placeHoldervalue) { $placeHolder = '%'.$placeHolderkey.'%'; - $templateData['body_html'] = str_replace($placeHolder, $placeHoldervalue ?? '', $templateData['body_html']); + $htmlTemplate = str_replace($placeHolder, $placeHoldervalue ?? '', $htmlTemplate); } $subject = $templateData['subject']; - $html = $templateData['body_html']; + $html = $htmlTemplate; - //trigger_email_to agent - $email->setFrom($orgData['sender_email'], $orgData['name']); - $email->setTo($value['email']); - $email->setSubject($subject); - $email->setMessage($html); - - if(!empty($planData)){ - $email->attach($planData['pdf_file_path']); + + // Set up ZeptoMail API request + $endpoint = "https://api.zeptomail.com/v1.1/email"; + $apiKey = env('ZOHO_API_KEY'); + $sender = env('ZOHO_SENDER_MAIL'); + + $attachments = []; + + $filePath = WRITEPATH . 'pdf/trips/trip_' . $planData['plan_id'] . '.pdf'; + if (!empty($filePath) && file_exists($filePath) && is_readable($filePath)) { + $attachments[] = [ + "content" => base64_encode(file_get_contents($filePath)), + "mime_type" => "application/pdf", + "name" => basename($filePath) + ]; } - if ($email->send()) { - + $payload = [ + "from" => [ + "address" => $sender, + "name" => $orgData['name'] ?? "TripApprovalTool" + ], + "to" => [ + ["email_address" => ["address" => $value['email']]] + ], + "subject" => $subject, + "htmlbody" => $html, + ]; + + if (!empty($attachments)) { + $payload["attachments"] = $attachments; + } + + $headers = [ + "Content-Type: application/json", + "Authorization: Zoho-enczapikey " . $apiKey + ]; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $endpoint); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if (curl_errno($ch)) { + $error_msg = curl_error($ch); + log_message('error', "[EMAIL ERROR] Curl failed: {$error_msg}"); + return ['status' => 'failed', 'code' => 500, 'message' => 'Curl error', 'error' => $error_msg]; + } + + curl_close($ch); + + $resp = json_decode($response, true); + + if ($httpCode === 200 && isset($resp['request_id'])) { log_message('debug', 'Email Success to agent plan_id = ' .$plan_id.'email = ' . $value['email']); - } else { - log_message('debug', 'Email sending failed: agent plan_id = ' .$plan_id.'email = ' . $value['email'].'error = '.$email->printDebugger(['headers'])); - + log_message('error', "[EMAIL FAILED] plan_id = {$plan_id}, email = {$value['email']}, HTTP: {$httpCode}, Response: {$response}"); } + + + + + // $email = \Config\Services::email(); + // $email->initialize([ + // 'protocol' => 'smtp', + // 'SMTPHost' => $orgData['mail_host'], + // 'SMTPUser' => $orgData['mail_user_name'], + // 'SMTPPass' => $orgData['mail_password'], + // 'SMTPPort' => (int)$orgData['mail_port'], + // 'mailType' => 'html' + // ]); + + // //trigger_email_to agent + // $email->setFrom($orgData['sender_email'], $orgData['name']); + // $email->setTo($value['email']); + // $email->setSubject($subject); + // $email->setMessage($html); + + // if(!empty($planData)){ + // $email->attach($planData['pdf_file_path']); + // } + + // if ($email->send()) { + + // log_message('debug', 'Email Success to agent plan_id = ' .$plan_id.'email = ' . $value['email']); + + // } else { + // log_message('debug', 'Email sending failed: agent plan_id = ' .$plan_id.'email = ' . $value['email'].'error = '.$email->printDebugger(['headers'])); + + // } } } diff --git a/app/Helpers/status_helper.php b/app/Helpers/status_helper.php index d265e9d..4b8005c 100644 --- a/app/Helpers/status_helper.php +++ b/app/Helpers/status_helper.php @@ -53,6 +53,7 @@ if (!function_exists('palnStatusHandler')) { $a1Id = $userData['first_approver']; $a2Id = $userData['second_approver']; $a3Id = $userData['third_approver']; + $a4Id = $userData['fourth_approver']; $groupData = $groupModel->where('group_id', $groupId)->first(); if (!$groupData) { @@ -110,25 +111,29 @@ if (!function_exists('palnStatusHandler')) { log_message('error', "Policy service details not found for service_id: {$topPriorityService['service_id']}, policy_id: {$policyId}"); return false; } - + // dd($policyServiceDetails); $isExceptional = $planData['exceptional_plan_reason'] !== null && $planData['exceptional_plan_reason'] !== ''; + // dd($isExceptional); if ($isExceptional) { $policyA1Action = $policyServiceDetails['a1_exceptional_action']; $policyA2Action = $policyServiceDetails['a2_exceptional_action']; $policyA3Action = $policyServiceDetails['a3_exceptional_action']; + $policyA4Action = $policyServiceDetails['a4_exceptional_action']; $policyParallelAction = $policyServiceDetails['exceptional_parallel_process_from']; } else if ($is_data_edited) { $policyA1Action = $policyServiceDetails['a1_amendment_action']; $policyA2Action = $policyServiceDetails['a2_amendment_action']; $policyA3Action = $policyServiceDetails['a3_amendment_action']; + $policyA4Action = $policyServiceDetails['a4_amendment_action']; $policyParallelAction = $policyServiceDetails['amendment_parallel_process_from']; } else { $policyA1Action = $policyServiceDetails['a1_action']; $policyA2Action = $policyServiceDetails['a2_action']; $policyA3Action = $policyServiceDetails['a3_action']; + $policyA4Action = 'None'; $policyParallelAction = $policyServiceDetails['parallel_process_from']; } @@ -145,9 +150,14 @@ if (!function_exists('palnStatusHandler')) { 'a3_id' => $a3Id, 'a3_action' => $policyA3Action, 'is_a3_action_done' => ($policyA3Action == 'None') ? 1 : 0, + 'a4_id' => $a4Id, + 'a4_action' => $policyA4Action, + 'is_a4_action_done' => ($policyA4Action == 'None') ? 1 : 0, 'parallel_process_from' => $policyParallelAction ]; + // dd($data); + if ($is_data_edited == false) { log_message('info', "Creating new plan status for plan_id: {$plan_id}"); $planStatusModel->insert($data); @@ -345,17 +355,25 @@ if (!function_exists('updatePlanStatus')) { $a3ApproveCount = count($a3); } + $a4 = $planStatusModel->where('plan_id',$plan_id)->where('a4_action','Approval')->where('is_active',1)->findAll(); + $a4ApproveSum = 0; + $a4ApproveCount = 0; + if(count($a4)) + { + $a4ApproveSum = array_sum(array_column($a4, 'is_a4_action_done')); + $a4ApproveCount = count($a4); + } + + + $count = ( $a1ApproveCount + $a2ApproveCount + $a3ApproveCount + $a4ApproveCount); + $sum = ( $a1ApproveSum + $a2ApproveSum + $a3ApproveSum + $a4ApproveSum); - $count = ( $a1ApproveCount + $a2ApproveCount + $a3ApproveCount); - $sum = ( $a1ApproveSum + $a2ApproveSum + $a3ApproveSum); - if($count == $sum) { // Approved $planModel->set(['status'=>3])->where('plan_id',$plan_id)->update(); - //send main to agent - send_email_agent(env('ORG_id') , $plan_id); + }else if($count != $sum && $sum > 0) { //Partially Approved @@ -466,6 +484,34 @@ if (!function_exists('getCurrentPlanStatus')) { } } + $a4 = $planStatusModel->where('plan_id',$plan_id)->where('a4_action','Approval')->where('is_active',1)->findAll(); + if(count($a4)) + { + $user4 = $userModel->where('user_id', $a4[0]['a4_action_done_by'])->first(); + if(!$user4){ + $user4 = $userModel->where('user_id', $a4[0]['a4_id'])->first(); + } + $hasRejectReason = !empty(array_filter($a4, fn($row) => !empty($row['a4_reject_reason']))); + if($user4) + { + $userId = $user4['user_id']; + + $a4ApproveSum = array_sum(array_column($a4, 'is_a4_action_done')); + $a4ApproveCount = count($a4); + + $a4Status['a4_id'] = $userId; + if($a4ApproveSum == $a4ApproveCount) + { + $a4Status['a4_status'] = 'Plan approved by '.$user4['first_name'].' '.$user4['last_name']; + }else if($hasRejectReason){ + $a4Status['a4_status'] = 'Plan rejected by '.$user4['first_name'].' '.$user4['last_name'].', Reason-'.$a4[0]['a4_reject_reason']; + }else{ + $a4Status['a4_status'] = 'Plan approval pending from '.$user4['first_name'].' '.$user4['last_name']; + } + array_push($currentStatus,$a4Status); + } + } + return $currentStatus; @@ -473,6 +519,7 @@ if (!function_exists('getCurrentPlanStatus')) { } if (!function_exists('getPlanApproverAction')) { + // initially written code without parallel , sequential // function getPlanApproverAction($plan_id , ) // { @@ -578,33 +625,129 @@ if (!function_exists('getPlanApproverAction')) { // } + // 3 level of parallel , sequential + // function getPlanApproverAction($plan_id) + // { + // $planStatusModel = new PlanStatusModel(); + // $userModel = new UserModel(); + // $result = ['approver_data' => []]; + // $serviceIds = []; + + // // A1, A2, A3 - handle all same way + // $approverAction = [ + // ['key' => 'a1', 'approver' => 1], + // ['key' => 'a2', 'approver' => 2], + // ['key' => 'a3', 'approver' => 3], + // ]; + + // foreach ($approverAction as $stage) { + // $field = $stage['key']; + // $approverNumber = $stage['approver']; + // $actionField = "{$field}_action"; + // $idField = "{$field}_id"; + // $doneField = "is_{$field}_action_done"; + + // $record = $planStatusModel->where('plan_id', $plan_id) + // ->whereIn($actionField, ['Approval', 'Notification', 'None']) + // ->where('is_active', 1) + // ->first(); + + // if ($record) { + // $user = $userModel->where('user_id', $record[$idField])->first(); + // if ($user) { + // $result['approver_data'][] = [ + // 'approver' => $approverNumber, + // 'user_id' => $record[$idField], + // 'email' => $user['email'], + // 'action' => $record[$actionField], + // 'is_action_done' => $record[$doneField], + // 'where_key' => $idField, + // 'action_key' => $actionField, + // 'action_done_key' => $doneField, + // ]; + // } + // } + // } + + // // Get parallel process value + // $statusData = $planStatusModel->where('plan_id', $plan_id)->where('is_active', 1)->first(); + // $result['parallel_process_from'] = $statusData['parallel_process_from'] ?? null; + + // $parallelFrom = (int) $result['parallel_process_from']; + + // foreach ($result['approver_data'] as $key => &$approver) { + // $approver['status'] = 'pending'; // default + + // if ($parallelFrom === 1) { + // // All parallel + // $approver['status'] = 'active'; + // } elseif ($parallelFrom === 2) { + // if ($approver['approver'] === 1) { + // $approver['status'] = 'active'; + // } elseif (in_array($approver['approver'], [2, 3])) { + // // Check if approver 1 is done + // $a1Done = false; + // foreach ($result['approver_data'] as $a) { + // if ($a['approver'] === 1 && $a['is_action_done']) { + // $a1Done = true; + // break; + // } + // } + // $approver['status'] = $a1Done ? 'active' : 'waiting'; + // } + // } elseif ($parallelFrom === 3) { + // // Sequential: 1 -> 2 -> 3 + // $a1Done = false; + // $a2Done = false; + + // foreach ($result['approver_data'] as $a) { + // if ($a['approver'] === 1 && $a['is_action_done']) $a1Done = true; + // if ($a['approver'] === 2 && $a['is_action_done']) $a2Done = true; + // } + + // if ($approver['approver'] === 1) { + // $approver['status'] = 'active'; + // } elseif ($approver['approver'] === 2) { + // $approver['status'] = $a1Done ? 'active' : 'waiting'; + // } elseif ($approver['approver'] === 3) { + // $approver['status'] = ($a1Done && $a2Done) ? 'active' : 'waiting'; + // } + // } + // } + // unset($approver); // best practice when using & reference in foreach + + + // return $result; + // } + + + // latest 4 level of parallel , sequential function getPlanApproverAction($plan_id) { $planStatusModel = new PlanStatusModel(); $userModel = new UserModel(); $result = ['approver_data' => []]; - $serviceIds = []; - - // A1, A2, A3 - handle all same way + $approverAction = [ ['key' => 'a1', 'approver' => 1], ['key' => 'a2', 'approver' => 2], ['key' => 'a3', 'approver' => 3], + ['key' => 'a4', 'approver' => 4], ]; - + foreach ($approverAction as $stage) { $field = $stage['key']; $approverNumber = $stage['approver']; $actionField = "{$field}_action"; $idField = "{$field}_id"; $doneField = "is_{$field}_action_done"; - + $mailField = "is_{$field}_mail_send"; + $record = $planStatusModel->where('plan_id', $plan_id) - ->whereIn($actionField, ['Approval', 'Notification', 'None']) - ->where('is_active', 1) - ->first(); - + ->whereIn($actionField, ['Approval', 'Notification', 'None']) + ->where('is_active', 1) + ->first(); if ($record) { $user = $userModel->where('user_id', $record[$idField])->first(); if ($user) { @@ -617,62 +760,70 @@ if (!function_exists('getPlanApproverAction')) { 'where_key' => $idField, 'action_key' => $actionField, 'action_done_key' => $doneField, + 'mail_send_key' => $mailField, + 'is_mail_send' => $record[$mailField], ]; } } } - - // Get parallel process value + $statusData = $planStatusModel->where('plan_id', $plan_id)->where('is_active', 1)->first(); $result['parallel_process_from'] = $statusData['parallel_process_from'] ?? null; - $parallelFrom = (int) $result['parallel_process_from']; - foreach ($result['approver_data'] as $key => &$approver) { + foreach ($result['approver_data'] as &$approver) { $approver['status'] = 'pending'; // default + // Flags + $a1Done = $a2Done = $a3Done = false; + foreach ($result['approver_data'] as $a) { + if ($a['approver'] === 1 && $a['is_action_done']) $a1Done = true; + if ($a['approver'] === 2 && $a['is_action_done']) $a2Done = true; + if ($a['approver'] === 3 && $a['is_action_done']) $a3Done = true; + } + if ($parallelFrom === 1) { - // All parallel + // All approvers active $approver['status'] = 'active'; + } elseif ($parallelFrom === 2) { if ($approver['approver'] === 1) { $approver['status'] = 'active'; - } elseif (in_array($approver['approver'], [2, 3])) { - // Check if approver 1 is done - $a1Done = false; - foreach ($result['approver_data'] as $a) { - if ($a['approver'] === 1 && $a['is_action_done']) { - $a1Done = true; - break; - } - } + } elseif (in_array($approver['approver'], [2, 3, 4])) { $approver['status'] = $a1Done ? 'active' : 'waiting'; } - } elseif ($parallelFrom === 3) { - // Sequential: 1 -> 2 -> 3 - $a1Done = false; - $a2Done = false; - foreach ($result['approver_data'] as $a) { - if ($a['approver'] === 1 && $a['is_action_done']) $a1Done = true; - if ($a['approver'] === 2 && $a['is_action_done']) $a2Done = true; + } elseif ($parallelFrom === 3) { + if ($approver['approver'] === 1) { + $approver['status'] = 'active'; + } elseif ($approver['approver'] === 2) { + $approver['status'] = $a1Done ? 'active' : 'waiting'; + } elseif (in_array($approver['approver'], [3, 4])) { + $approver['status'] = ($a1Done && $a2Done) ? 'active' : 'waiting'; } + } elseif ($parallelFrom === 4) { if ($approver['approver'] === 1) { $approver['status'] = 'active'; } elseif ($approver['approver'] === 2) { $approver['status'] = $a1Done ? 'active' : 'waiting'; } elseif ($approver['approver'] === 3) { $approver['status'] = ($a1Done && $a2Done) ? 'active' : 'waiting'; + } elseif ($approver['approver'] === 4) { + $approver['status'] = ($a1Done && $a2Done && $a3Done) ? 'active' : 'waiting'; } + } else { + $approver['status'] = 'pending'; // fallback } } - unset($approver); // best practice when using & reference in foreach + unset($approver); // clean reference - return $result; } + + + } @@ -735,6 +886,23 @@ if (!function_exists('getApproverCurrentAction')) { } } + $a4 = $planStatusModel->where('plan_id',$plan_id)->where('a4_id',$user_id)->where('a4_action','Approval')->where('is_active',1)->findAll(); + if(count($a4)) + { + $hasRejectReason = !empty(array_filter($a4, fn($row) => !empty($row['a4_reject_reason']))); + $a4ApproveSum = array_sum(array_column($a4, 'is_a4_action_done')); + $a4ApproveCount = count($a4); + + if($a4ApproveSum == $a4ApproveCount) + { + return 'Approved'; + }else if($hasRejectReason){ + return 'Rejected'; + }else{ + return 'Approval pending'; + } + } + } } diff --git a/app/Models/MailTemplateModel.php b/app/Models/MailTemplateModel.php index 124926a..0b06b0d 100644 --- a/app/Models/MailTemplateModel.php +++ b/app/Models/MailTemplateModel.php @@ -17,6 +17,7 @@ class MailTemplateModel extends Model 'template_name', 'subject', 'body_html', + 'image_location', 'created_by', 'updated_by', 'is_active', diff --git a/app/Models/PlanModel.php b/app/Models/PlanModel.php index 90fe2fb..18ea1a4 100644 --- a/app/Models/PlanModel.php +++ b/app/Models/PlanModel.php @@ -46,7 +46,8 @@ class PlanModel extends Model IB.dropdown_value as is_billable_value, POT.dropdown_value as purpose_of_travel_value, FD.dropdown_value as functional_department_value, - Dep.name as cost_center_value' + Dep.name as cost_center_value, + Forex.forex_id' ) ->join('m_users U', 'U.user_id = m_plan.user_id ', 'left') ->join('m_users C', 'C.user_id = m_plan.created_by ', 'left') @@ -57,6 +58,7 @@ class PlanModel extends Model ->join('m_dropdown POT', 'POT.dropdown_key = m_plan.purpose_of_travel AND POT.dropdown = "plan_purpose_of_travel"', 'left') ->join('m_dropdown FD', 'FD.dropdown_key = m_plan.functional_department AND FD.dropdown = "plan_functional_department"', 'left') ->join('m_department Dep', 'Dep.department_id = m_plan.cost_center_id ', 'left') + ->join('c_forex Forex', 'Forex.plan_id = m_plan.plan_id ', 'left') ->where('m_plan.is_active', 1)->where('m_plan.org_id', $org_id); if($getBy == 'ALL' || $getBy == null) diff --git a/app/Models/PlanStatusModel.php b/app/Models/PlanStatusModel.php index f168ded..0772ebb 100644 --- a/app/Models/PlanStatusModel.php +++ b/app/Models/PlanStatusModel.php @@ -12,9 +12,10 @@ class PlanStatusModel extends Model protected $allowedFields = [ 'plan_id', 'service_id', - 'a1_id', 'a1_action', 'is_a1_action_done', 'a1_reject_reason', 'a1_action_done_on','a1_action_done_by', - 'a2_id', 'a2_action', 'is_a2_action_done', 'a2_reject_reason', 'a2_action_done_on','a2_action_done_by', - 'a3_id', 'a3_action', 'is_a3_action_done', 'a3_reject_reason', 'a3_action_done_on','a3_action_done_by', + 'a1_id', 'a1_action', 'is_a1_action_done', 'a1_reject_reason', 'a1_action_done_on','a1_action_done_by','is_a1_mail_send', + 'a2_id', 'a2_action', 'is_a2_action_done', 'a2_reject_reason', 'a2_action_done_on','a2_action_done_by','is_a2_mail_send', + 'a3_id', 'a3_action', 'is_a3_action_done', 'a3_reject_reason', 'a3_action_done_on','a3_action_done_by','is_a3_mail_send', + 'a4_id', 'a4_action', 'is_a4_action_done', 'a4_reject_reason', 'a4_action_done_on','a4_action_done_by','is_a4_mail_send', 'parallel_process_from', 'is_active', 'created_on', 'created_by' diff --git a/app/Models/PolicyDetailsModel.php b/app/Models/PolicyDetailsModel.php index 4652c2a..d0b855a 100644 --- a/app/Models/PolicyDetailsModel.php +++ b/app/Models/PolicyDetailsModel.php @@ -29,6 +29,7 @@ class PolicyDetailsModel extends Model 'a1_amendment_action', 'a2_amendment_action', 'a3_amendment_action', + 'a4_amendment_action', 'amendment_parallel_process_from', 'is_active', 'created_on', diff --git a/app/Models/ReportsModel.php b/app/Models/ReportsModel.php index cec9530..64c41d3 100644 --- a/app/Models/ReportsModel.php +++ b/app/Models/ReportsModel.php @@ -74,6 +74,7 @@ class ReportsModel extends Model GROUP BY plan_id ) AS al ON m_plan.plan_id = al.plan_id + WHERE mts.is_active = 1 AND mts.status_value = 'Approved' @@ -415,8 +416,11 @@ class ReportsModel extends Model JOIN m_plan mp ON cf.plan_id = mp.plan_id - WHERE cf.created_on >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY) - AND cf.created_on < DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY), INTERVAL 7 DAY) + WHERE cf.created_on >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) + AND cf.created_on < DATE_ADD(CURDATE(), INTERVAL 1 DAY) + + -- WHERE cf.created_on >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY) + -- AND cf.created_on < DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY), INTERVAL 7 DAY) -- previous week -- WHERE cf.created_on >= DATE_SUB(CURDATE(), INTERVAL (WEEKDAY(CURDATE()) + 7) DAY) @@ -482,8 +486,11 @@ class ReportsModel extends Model JOIN m_plan mp ON ca.plan_id = mp.plan_id - WHERE ca.created_on >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY) - AND ca.created_on < DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY), INTERVAL 7 DAY) + WHERE ca.created_on >= DATE_SUB(CURDATE(), INTERVAL 6 DAY) + AND ca.created_on < DATE_ADD(CURDATE(), INTERVAL 1 DAY) + + -- WHERE ca.created_on >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY) + -- AND ca.created_on < DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY), INTERVAL 7 DAY) -- previous week -- WHERE cf.created_on >= DATE_SUB(CURDATE(), INTERVAL (WEEKDAY(CURDATE()) + 7) DAY) @@ -547,8 +554,11 @@ class ReportsModel extends Model JOIN m_plan mp ON cf.plan_id = mp.plan_id - WHERE cf.created_on >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY) - AND cf.created_on < DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY), INTERVAL 7 DAY) + WHERE cf.created_on >= DATE_SUB(CURDATE(), INTERVAL 6 DAY) + AND cf.created_on < DATE_ADD(CURDATE(), INTERVAL 1 DAY) + + -- WHERE cf.created_on >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY) + -- AND cf.created_on < DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY), INTERVAL 7 DAY) -- previous week -- WHERE cf.created_on >= DATE_SUB(CURDATE(), INTERVAL (WEEKDAY(CURDATE()) + 7) DAY) diff --git a/app/Models/UserModel.php b/app/Models/UserModel.php index 3c7ed00..5a617fa 100644 --- a/app/Models/UserModel.php +++ b/app/Models/UserModel.php @@ -10,7 +10,7 @@ class UserModel extends Model protected $primaryKey = 'user_id'; // Primary key // Fields that can be mass assigned - protected $allowedFields = ['first_name','last_name','password','email','mobile_no','alternate_mobile_no','date_of_birth','address','gender','postal_code','country_code','employee_code','role_id','department_id','group_id','first_approver','first_approver_email','second_approver','second_approver_email','third_approver','third_approver_email','exceptional_approver','exceptional_approver_email','user_type','passport_number','place_of_issue','passport_document','passport_firstname','passport_middlename','passport_lastname','nationality','date_of_issue','date_of_expiry','created_on','created_by','updated_on','updated_by','is_active','org_id', 'temp_password', 'forex_pre_paid_card_number', 'emergency_contact_number', 'd_seat_preference', 'd_meal_preference', 'i_seat_preference', 'i_meal_preference', 'agent_supported_service_ids', 'd_additonalInfo', 'i_additonalInfo', 'forex_expiry_date', 'delegated_to_user_id', 'delegation_start_date', 'delegation_end_date','last_login_at','company_name']; + protected $allowedFields = ['first_name','last_name','password','email','mobile_no','alternate_mobile_no','date_of_birth','address','gender','postal_code','country_code','employee_code','role_id','department_id','group_id','first_approver','first_approver_email','second_approver','second_approver_email','third_approver','third_approver_email','fourth_approver','fourth_approver_email','user_type','passport_number','place_of_issue','passport_document','passport_firstname','passport_middlename','passport_lastname','nationality','date_of_issue','date_of_expiry','created_on','created_by','updated_on','updated_by','is_active','org_id', 'temp_password', 'forex_pre_paid_card_number', 'emergency_contact_number', 'd_seat_preference', 'd_meal_preference', 'i_seat_preference', 'i_meal_preference', 'agent_supported_service_ids', 'd_additonalInfo', 'i_additonalInfo', 'forex_expiry_date', 'delegated_to_user_id', 'delegation_start_date', 'delegation_end_date','last_login_at','company_name']; // Specify the return type of the results protected $returnType = 'array'; @@ -91,10 +91,9 @@ public function updateApprover(string $approverType, array $insertedIds) 'first' => ['column' => 'first_approver', 'email' => 'first_approver_email'], 'second' => ['column' => 'second_approver', 'email' => 'second_approver_email'], 'third' => ['column' => 'third_approver', 'email' => 'third_approver_email'], - 'exceptional' => ['column' => 'exceptional_approver', 'email' => 'exceptional_approver_email'] + 'fourth' => ['column' => 'fourth_approver', 'email' => 'fourth_approver_email'] ]; - $column = $validTypes[$approverType]['column']; $emailCol = $validTypes[$approverType]['email']; diff --git a/app/Views/pdf.php b/app/Views/pdf.php index d883991..0adb0bb 100644 --- a/app/Views/pdf.php +++ b/app/Views/pdf.php @@ -174,6 +174,7 @@ +
    @@ -340,6 +341,43 @@
    +Trip Status:'; + $statusHtml .= '
      '; + + foreach ($data['status_details'] as $key => $value) { + + if (isset($value['a1_status'])) { + $statusHtml .= '
    • Approver 1: ' . $value['a1_status'] . '
    • '; + } + + if (isset($value['a2_status'])) { + $statusHtml .= '
    • Approver 2: ' . $value['a2_status'] . '
    • '; + } + + if (isset($value['a3_status'])) { + $statusHtml .= '
    • Approver 3: ' . $value['a3_status'] . '
    • '; + } + + if (isset($value['a4_status'])) { + $statusHtml .= '
    • Approver 4: ' . $value['a4_status'] . '
    • '; + } + } + + $statusHtml .= '
    '; + + echo $statusHtml; +} ?> + + @@ -412,7 +450,6 @@
    Class
    -
    style="color: #960000;" >
    allowed for @@ -514,17 +551,10 @@
    City
    - +
    Hotel Name

    - -
    Category
    -
    - style="color: #960000;" >
    - allowed for -
    -
    Checkin Date & Time
    @@ -533,6 +563,13 @@
    Checkout Date & Time
    + +
    Category
    +
    + style="color: #960000;" >
    + allowed for +
    + diff --git a/public/assets/images/signature/1754653298_cd3ff596de561b50a8d4.jpg b/public/assets/images/signature/1754653298_cd3ff596de561b50a8d4.jpg new file mode 100644 index 0000000..a09617b Binary files /dev/null and b/public/assets/images/signature/1754653298_cd3ff596de561b50a8d4.jpg differ diff --git a/public/assets/images/signature/1754653531_c21e7c3dd83dd366060c.jpg b/public/assets/images/signature/1754653531_c21e7c3dd83dd366060c.jpg new file mode 100644 index 0000000..a09617b Binary files /dev/null and b/public/assets/images/signature/1754653531_c21e7c3dd83dd366060c.jpg differ diff --git a/writable/pdf/trips/trip_35.pdf b/writable/pdf/trips/trip_35.pdf new file mode 100644 index 0000000..957500c Binary files /dev/null and b/writable/pdf/trips/trip_35.pdf differ diff --git a/writable/uploads/template/user_file.xlsx b/writable/uploads/template/user_file.xlsx index f5c70cb..9e9fd36 100644 Binary files a/writable/uploads/template/user_file.xlsx and b/writable/uploads/template/user_file.xlsx differ