leadModel = new SalesActualLeadModel(); $this->contactModel = new SalesContactPersonModel(); $this->activityModel = new SalesActivityModel(); $this->noteModel = new SalesLeadNoteModel(); $this->userModel = new UserModel(); $this->targetModel = new SalesTargetModel(); $this->clientModel = new ClientModel(); } public function index() { $data = $this->getSalesStaffData(); $data = array_merge($data, $this->getSalesFilterYears()); $data['tab_name'] = 'Leads'; $data['page_name'] = 'Leads'; return $this->loadLayout('sales/tracker_view', $data); } public function loadactivities(){ $data = $this->getSalesStaffData(); $data = array_merge($data, $this->getSalesFilterYears()); $data['tab_name'] = 'Activities'; $data['page_name'] = 'Activities'; $data['leads'] = $this->leadModel->select('lead_id, company_name') ->orderBy('lead_id', 'DESC') ->findAll(); return $this->loadLayout('sales/activity_view', $data); } public function loadtargets(){ $data = $this->getSalesStaffData(); $data['tab_name'] = 'Sales Team Targets'; $data['page_name'] = 'Sales Team Targets'; return $this->loadLayout('sales/target_view', $data); } /** * HELPER: Fetches Sales Managers based on the logged-in user's role and branch * sales_manager Current branch | Role 4 + Team 5 | Assign To dropdown * sales_manager_ids Current branch | Role 4 + Team 5 | Query filter IDs * sales_manager_with_head Current branch | Role 1,4,5 | Branch reporting dropdown * sales_manager_with_head_ids Current branch | Role 1,4,5 | Branch reporting filter * sales_team All branches | Role 1,4,5 | Admin/global reporting */ private function getSalesStaffData(): array { $db = \Config\Database::connect(); $logged_user_id = get_session_userid(); $role = get_role_id(); $team_id = user_team(); // ── Get logged-in user's profile ──────────────────────────────── $row = $db->table('user_profiles') ->where('is_active', 1) ->where('id', $logged_user_id) ->get()->getRow(); $nhance_branch_id = $row ? $row->nhance_branch_id : null; // ── Base result structure ──────────────────────────────────────── $data = [ 'sales_role' => '', 'sales_manager' => [], 'sales_manager_ids' => [], 'sales_manager_with_head' => [], 'sales_manager_with_head_ids' => [], 'sales_team' => [], 'nhance_branch_id' => $nhance_branch_id, ]; // ================================================================ // QUERY 1: Get all MANAGERS in current branch // Role = 4 AND Team = 5 AND same branch // ================================================================ $branch_managers = $db->table('user_profiles up') ->select('up.id, up.first_name, up.role, up.nhance_branch_id') ->join('user_teams ut', 'ut.user_id = up.id') ->where('up.is_active', 1) ->where('ut.is_active', 1) ->where('up.role', 4) // Sales Manager role ->where('ut.team_id', 5) // Sales team ->where('up.nhance_branch_id', $nhance_branch_id) // same branch ->groupBy('up.id') ->get()->getResultArray(); // ================================================================ // QUERY 2: Get all HEADS in current branch // Role = 1 or 5 AND same branch // ================================================================ $branch_heads = $db->table('user_profiles') ->select('id, first_name, role, nhance_branch_id') ->where('is_active', 1) ->whereIn('role', [1, 5]) // Sales Head roles ->where('nhance_branch_id', $nhance_branch_id) // same branch ->get()->getResultArray(); // Add "(Head) (Admin)" label foreach ($branch_heads as &$head) { if ($head['role'] == 1) { $head['first_name'] = $head['first_name'] . ' (Admin)'; } elseif ($head['role'] == 5) { $head['first_name'] = $head['first_name'] . ' (Head)'; } } unset($head); // ================================================================ // QUERY 3: Get ALL MANAGERS across ALL branches // Role = 4 AND Team = 5 (no branch filter) // ================================================================ $all_managers = $db->table('user_profiles up') ->select('up.id, up.first_name, up.role, up.nhance_branch_id') ->join('user_teams ut', 'ut.user_id = up.id') ->where('up.is_active', 1) ->where('ut.is_active', 1) ->where('up.role', 4) // Sales Manager role ->where('ut.team_id', 5) // Sales team ->groupBy('up.id') ->get()->getResultArray(); // ================================================================ // QUERY 4: Get ALL HEADS across ALL branches // Role = 1 or 5 (no branch filter) // ================================================================ $all_heads = $db->table('user_profiles') ->select('id, first_name, role, nhance_branch_id') ->where('is_active', 1) ->whereIn('role', [1, 5]) // Sales Head roles ->get()->getResultArray(); // Add "(Head) (Admin)" label foreach ($all_heads as &$head) { if ($head['role'] == 1) { $head['first_name'] = $head['first_name'] . ' (Admin)'; } elseif ($head['role'] == 5) { $head['first_name'] = $head['first_name'] . ' (Head)'; } } unset($head); // ================================================================ // QUERY 5: Get ALL clients // ================================================================ $all_clients = $db->table('clients') ->select('id, client_name,short_name,email,phone') ->where('is_active', 1) ->get()->getResultArray(); $data['$all_clients'] = $all_clients; // ================================================================ // BUILD: sales_manager_with_head = branch heads + branch managers // ================================================================ $data['sales_manager_with_head'] = array_merge($branch_heads, $branch_managers); $data['sales_manager_with_head_ids'] = array_column($data['sales_manager_with_head'], 'id'); // ================================================================ // BUILD: sales_team = all heads + all managers (every branch) // ================================================================ $data['sales_team'] = array_merge($all_heads, $all_managers); // ── Sales Manager (Role 4, Team 5) ────────────────────────────── if ($role == 4 && in_array(5, $team_id)) { $data['sales_role'] = 'Sales Manager'; $data['sales_manager'] = [[ // only himself 'id' => $row->id, 'first_name' => $row->first_name, 'role' => $role, 'nhance_branch_id' => $nhance_branch_id, ]]; $data['sales_manager_ids'] = [$logged_user_id]; // ── Sales Head (Role 1 or 5) ───────────────────────────────────── } elseif (in_array($role, [1, 5])) { $data['sales_role'] = 'Sales Head'; $data['sales_manager'] = $branch_managers; // reuse QUERY 1 result $data['sales_manager_ids'] = array_column($branch_managers, 'id'); $data['sales_manager_with_head'] = array_merge($branch_heads, $branch_managers); // reuse $data['sales_manager_with_head_ids'] = array_column($data['sales_manager_with_head'], 'id'); } return $data; } public function noPage($type = null) { $page_type = $type ?? 'Page'; $data['tab_name'] = 'Sales ' . $page_type; $data['page_name'] = 'Sales ' . $page_type; echo view('layout/header', $data); echo '

Oops! ' . esc($page_type) . ' is under construction.

Error Image
'; echo view('layout/footer', $data); } /** * GET /api/sales/activities/(:num)/complete */ public function completeActivity($id) { try { $data = $this->request->getJSON(true); $data['updated_by'] = $this->getUserId(); // 1. Mark current activity as completed $this->activityModel->completeActivity((int)$id, [ 'completion_notes' => $data['completion_notes'], 'updated_by' => $data['updated_by'] ]); // 2. Handle follow-up if requested $google_calender_response = []; if (!empty($data['schedule_followup']) && $data['schedule_followup'] === 'yes') { $activity = $this->activityModel->find($id); $activity_data = [ 'lead_id' => $activity['lead_id'], 'activity_type' => $data['followup_type'], 'notes' => $data['followup_notes'], 'scheduled_date' => $data['followup_schedule'], 'assigned_to' => $activity['assigned_to'], 'status' => 'pending', 'created_by' => $this->getUserId() ]; $this->activityModel->insert($activity_data); // add google calender event $google_calender_response = $this->addCalenderEvent($activity_data); } return $this->respond(['status' => 'success', 'message' => 'Activity updated', 'google_calender_response' => $google_calender_response]); } catch (\Exception $e) { return $this->failServerError($e->getMessage()); } } /** * Corrected createLead to handle assigned_to as ID */ public function createLead() { try { $data = $this->request->getJSON(true); $data['created_by'] = $this->getUserId(); // Ensure assigned_to is a valid integer from user_profiles if (empty($data['assigned_to'])) { return $this->fail('Please assign this lead to a user.'); } // ── Client logic — only runs if client_id key exists in payload ── if (array_key_exists('client_id', $data)) { $clientId = !empty($data['client_id']) ? (int)$data['client_id'] : null; $clientData = array_filter([ 'client_name' => $data['company_name'] ?? null, 'short_name' => $data['short_name'] ?? null, 'email' => $data['email'] ?? null, 'phone' => $data['phone'] ?? null, ], fn($v) => $v !== null && $v !== ''); if ($clientId) { // Existing client — update name/short_name/email/phone if provided if (!empty($clientData)) { $clientData['updated_by'] = $this->getUserId(); $this->clientModel->update($clientId, $clientData); } } else { // New client — insert and get ID $clientData['created_by'] = $this->getUserId(); $clientData['is_active'] = 1; $this->clientModel->insert($clientData); $clientId = $this->clientModel->getInsertID(); } $data['client_id'] = $clientId; } // ── Clean up UI-only fields before inserting lead ──────────────── unset($data['short_name']); if (!$this->leadModel->insert($data)) { return $this->fail($this->leadModel->errors()); } $leadId = $this->leadModel->getInsertID(); $this->sendLeadCreateMail($leadId, $data); return $this->respondCreated(['status' => 'success', 'id' => $leadId]); } catch (\Exception $e) { return $this->failServerError($e->getMessage()); } } // ==================== LEAD APIs ==================== /** * Get all leads with pagination and filters * GET /api/sales/leads */ public function getLeads() { try { $limit = $this->request->getGet('limit') ?? 10; $offset = $this->request->getGet('offset') ?? 0; $filters = [ 'status' => $this->request->getGet('status'), 'assigned_to' => $this->request->getGet('assigned_to'), 'search' => $this->request->getGet('search'), 'financial_year' => $this->request->getGet('financial_year'), ]; $filters = $this->applyFinancialYearDateRange($filters); $result = $this->leadModel->getLeadsWithFilters($filters, $limit, $offset); return $this->respond([ 'status' => 'success', 'data' => $result['data'], 'total' => $result['total'], 'limit' => $limit, 'offset' => $offset, 'counts' => $result['counts'], ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Export leads filtered by created_at date range. * GET /sales/leads/export?from_date=YYYY-MM-DD&to_date=YYYY-MM-DD */ public function exportLeads() { try { $range = $this->getExportDateRange(); } catch (\InvalidArgumentException $e) { return $this->failValidationErrors($e->getMessage()); } try { $db = \Config\Database::connect(); $builder = $db->table('sales_actual_leads sal') ->select(' sal.company_name, c.short_name, sal.address, sal.website, sal.gst_number, sal.email, sal.phone, sal.status, sal.created_at, up.first_name as assigned_to_name, COUNT(DISTINCT sa.activity_id) as total_activity ') ->join('clients c', 'c.id = sal.client_id', 'left') ->join('user_profiles up', 'up.id = sal.assigned_to', 'left') ->join('sales_activities sa', 'sa.lead_id = sal.lead_id', 'left') ->where("sal.created_at BETWEEN {$db->escape($range['from'])} AND {$db->escape($range['to'])}", null, false); $status = $this->request->getGet('status'); if (!empty($status)) { $builder->where('sal.status', $status); } $assignedToIds = $this->getExportAssignedToIds(); if (!empty($assignedToIds)) { $builder->whereIn('sal.assigned_to', $assignedToIds); } $search = $this->request->getGet('search'); if (!empty($search)) { $builder->groupStart() ->like('sal.company_name', $search) ->orLike('c.short_name', $search) ->orLike('sal.address', $search) ->orLike('sal.website', $search) ->orLike('sal.gst_number', $search) ->orLike('sal.email', $search) ->orLike('sal.phone', $search) ->orLike('up.first_name', $search) ->groupEnd(); } $leads = $builder ->groupBy('sal.lead_id, sal.company_name, c.short_name, sal.address, sal.website, sal.gst_number, sal.email, sal.phone, sal.status, sal.created_at, up.first_name') ->orderBy('sal.created_at', 'DESC') ->get() ->getResultArray(); if (empty($leads)) { return $this->exportNotFoundResponse("Leads Not Found {$range['display']}"); } $rows = []; $serialNo = 1; foreach ($leads as $lead) { $rows[] = [ $serialNo++, $lead['company_name'] ?? '', $lead['short_name'] ?? '', $lead['address'] ?? '', $lead['website'] ?? '', $lead['gst_number'] ?? '', $lead['email'] ?? '', $lead['phone'] ?? '', $lead['status'] ?? '', $lead['assigned_to_name'] ?? '', $lead['total_activity'] ?? 0, $this->formatExportDate($lead['created_at'] ?? ''), ]; } return $this->streamCsvDownload( "sales-leads-{$range['from_label']}-to-{$range['to_label']}.csv", ['S.No', 'Company', 'Company Short Name', 'Address', 'Website', 'GST', 'Email', 'Phone', 'Status', 'Assigned To', 'Total Activity', 'Created At'], $rows ); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Get single lead with complete details * GET /api/sales/leads/{id} */ public function getLead($id) { try { $lead = $this->leadModel->getLeadComplete($id); if (!$lead) { return $this->failNotFound('Lead not found'); } return $this->respond([ 'status' => 'success', 'data' => $lead ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Update lead * PUT /api/sales/leads/{id} */ public function updateLead($id) { try { if (!$this->leadModel->find((int)$id)) { return $this->failNotFound('Lead not found'); } $data = $this->request->getJSON(true); $data['updated_by'] = $this->getUserId(); // ── Client logic — only runs if client_id key exists in payload ── if (array_key_exists('client_id', $data)) { $clientId = !empty($data['client_id']) ? (int)$data['client_id'] : null; $clientData = array_filter([ 'client_name' => $data['company_name'] ?? null, 'short_name' => $data['short_name'] ?? null, 'email' => $data['email'] ?? null, 'phone' => $data['phone'] ?? null, ], fn($v) => $v !== null && $v !== ''); if ($clientId) { // Existing client — update if we have data if (!empty($clientData)) { $clientData['updated_by'] = $this->getUserId(); $this->clientModel->update($clientId, $clientData); } } else { // New client — insert and get ID $clientData['created_by'] = $this->getUserId(); $clientData['is_active'] = 1; $this->clientModel->insert($clientData); $clientId = $this->clientModel->getInsertID(); } $data['client_id'] = $clientId; } // ── Clean up UI-only fields before inserting lead ──────────────── unset($data['short_name']); if (!$this->leadModel->update($id, $data)) { return $this->fail($this->leadModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); } $lead = $this->leadModel->getLeadComplete($id); return $this->respond([ 'status' => 'success', 'message' => 'Lead updated successfully', 'data' => $lead ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Delete lead * DELETE /api/sales/leads/{id} */ public function deleteLead($id) { try { if (!$this->leadModel->find((int)$id)) { return $this->failNotFound('Lead not found'); } $this->leadModel->delete($id); return $this->respondDeleted([ 'status' => 'success', 'message' => 'Lead deleted successfully' ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Get lead statistics * GET /api/sales/leads/stats */ public function getLeadStats() { try { $userId = $this->request->getGet('user_id'); $stats = $this->leadModel->getLeadStats($userId); return $this->respond([ 'status' => 'success', 'data' => $stats ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * GET /sales/searchClients?q=tech */ public function searchClients() { $q = trim($this->request->getGet('q') ?? ''); if (strlen($q) < 1) { return $this->response ->setContentType('application/json') ->setBody(json_encode(['data' => []])); } $db = \Config\Database::connect(); $results = $db->table('clients') ->select('id, client_name, short_name, email, phone') ->like('client_name', $q) ->where('client_type', 1) ->where('is_active', 1) ->limit(10) ->get() ->getResultArray(); return $this->response ->setContentType('application/json') ->setBody(json_encode(['data' => $results])); } /** * GET /sales/checkDuplicate?table=clients&field=short_name&value=TECH */ public function checkDuplicate() { if ($this->request->getMethod() !== 'get') { return $this->response ->setStatusCode(405) ->setContentType('application/json') ->setBody(json_encode(['exists' => false])); } $table = $this->request->getGet('table'); $field = $this->request->getGet('field'); $value = trim($this->request->getGet('value') ?? ''); $exclude_id = $this->request->getGet('exclude_id'); // ── Whitelist (table → model, pk, allowed fields) ───────── $allowed = [ 'clients' => [ 'model' => $this->clientModel, 'pk' => 'id', 'fields' => ['short_name', 'client_name'], ], 'sales_actual_leads' => [ 'model' => $this->leadModel, 'pk' => 'lead_id', 'fields' => ['company_name'], ], ]; if (! isset($allowed[$table]) || ! in_array($field, $allowed[$table]['fields'], true)) { return $this->response ->setStatusCode(400) ->setContentType('application/json') ->setBody(json_encode(['exists' => false, 'error' => 'Invalid table or field'])); } if (empty($value)) { return $this->response ->setContentType('application/json') ->setBody(json_encode(['exists' => false])); } $model = $allowed[$table]['model']; $pk = $allowed[$table]['pk']; // ── Case-insensitive match ──────────────────────────────── $model->where("LOWER({$field})", strtolower($value)); if (!empty($exclude_id)) { $model->where("{$pk} !=", (int)$exclude_id); } $count = $model->countAllResults(); return $this->response ->setContentType('application/json') ->setBody(json_encode(['exists' => $count > 0])); } // ==================== CONTACT PERSON APIs ==================== /** * Get contacts for a lead * GET /api/sales/leads/{leadId}/contacts */ public function getContacts($leadId) { try { $contacts = $this->contactModel->getContactsByLead($leadId); return $this->respond([ 'status' => 'success', 'data' => $contacts ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Create contact person * POST /api/sales/contacts */ public function createContact() { try { $data = $this->request->getJSON(true); $data['created_by'] = $this->getUserId(); $data['updated_by'] = $this->getUserId(); if (!$this->contactModel->insert($data)) { return $this->fail($this->contactModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); } $contactId = $this->contactModel->getInsertID(); $contact = $this->contactModel->find((int)$contactId); return $this->respondCreated([ 'status' => 'success', 'message' => 'Contact created successfully', 'data' => $contact ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Update contact person * PUT /api/sales/contacts/{id} */ public function updateContact($id) { try { if (!$this->contactModel->find((int)$id)) { return $this->failNotFound('Contact not found'); } $data = $this->request->getJSON(true); $data['updated_by'] = $this->getUserId(); $contact = $this->contactModel->find($id); if (isset($data['is_primary']) && $data['is_primary'] == 1) { // Reset all contacts for this lead to 0 primary $this->contactModel->where('lead_id', $contact['lead_id']) ->set(['is_primary' => 0]) ->update(); } if (!$this->contactModel->update((int)$id, $data)) { return $this->fail($this->contactModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); } $contact = $this->contactModel->find((int)$id); return $this->respond([ 'status' => 'success', 'message' => 'Contact updated successfully', 'data' => $contact ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Delete contact person * DELETE /api/sales/contacts/{id} */ public function deleteContact($id) { try { if (!$this->contactModel->find((int)$id)) { return $this->failNotFound('Contact not found'); } $this->contactModel->delete((int)$id); return $this->respondDeleted([ 'status' => 'success', 'message' => 'Contact deleted successfully' ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Set primary contact * PUT /api/sales/contacts/{id}/set-primary */ public function setPrimaryContact($id) { try { $contact = $this->contactModel->find((int)$id); if (!$contact) { return $this->failNotFound('Contact not found'); } $this->contactModel->setPrimaryContact($id, $contact['lead_id']); return $this->respond([ 'status' => 'success', 'message' => 'Primary contact updated successfully' ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } // ==================== ACTIVITY APIs ==================== /** * Get all activities with filters * GET /api/sales/activities */ public function getActivities() { try { $limit = $this->request->getGet('limit') ?? 10; $offset = $this->request->getGet('offset') ?? 0; $filters = [ 'status' => $this->request->getGet('status'), 'activity_type' => $this->request->getGet('activity_type'), 'assigned_to' => $this->request->getGet('assigned_to'), 'date_from' => $this->request->getGet('date_from'), 'date_to' => $this->request->getGet('date_to'), 'financial_year' => $this->request->getGet('financial_year'), 'search' => $this->request->getGet('search'), // ← ADD THIS ]; $filters = $this->applyFinancialYearDateRange($filters); $result = $this->activityModel->getActivitiesWithFilters($filters, $limit, $offset); return $this->respond([ 'status' => 'success', 'data' => $result['data'], 'total' => $result['total'], 'limit' => $limit, 'offset' => $offset, 'counts' => $result['counts'], ], 200); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Export activities filtered by created_at date range. * GET /sales/activities/export?from_date=YYYY-MM-DD&to_date=YYYY-MM-DD */ public function exportActivities() { try { $range = $this->getExportDateRange(); } catch (\InvalidArgumentException $e) { return $this->failValidationErrors($e->getMessage()); } try { $db = \Config\Database::connect(); $builder = $db->table('sales_activities sa') ->select(" sa.activity_type, sa.status, sa.scheduled_date, sa.created_at, sa.notes, sa.completion_notes, sal.company_name, c.short_name, up1.first_name as assigned_to_name, GROUP_CONCAT(DISTINCT up2.first_name SEPARATOR ', ') as additional_assigned_names ") ->join('sales_actual_leads sal', 'sal.lead_id = sa.lead_id', 'left') ->join('clients c', 'c.id = sal.client_id', 'left') ->join('user_profiles up1', 'up1.id = sa.assigned_to', 'left') ->join('user_profiles up2', " sa.additional_assigned_ids IS NOT NULL AND sa.additional_assigned_ids != '' AND sa.additional_assigned_ids != '[]' AND JSON_VALID(sa.additional_assigned_ids) AND JSON_CONTAINS(sa.additional_assigned_ids, JSON_QUOTE(CAST(up2.id AS CHAR))) ", 'left') ->where("sa.created_at BETWEEN {$db->escape($range['from'])} AND {$db->escape($range['to'])}", null, false); $status = $this->request->getGet('status'); if (!empty($status)) { $builder->where('sa.status', $status); } $assignedToIds = $this->getExportAssignedToIds(); if (!empty($assignedToIds)) { $builder->whereIn('sa.assigned_to', $assignedToIds); } $search = $this->request->getGet('search'); if (!empty($search)) { $builder->groupStart() ->like('sal.company_name', $search) ->orLike('c.short_name', $search) ->orLike('sa.status', $search) ->orLike('sa.activity_type', $search) ->orLike('up1.first_name', $search) ->orLike('up2.first_name', $search) ->groupEnd(); } $activities = $builder ->groupBy('sa.activity_id') ->orderBy('sa.created_at', 'DESC') ->get() ->getResultArray(); if (empty($activities)) { return $this->exportNotFoundResponse("Activities not Found {$range['display']}"); } $rows = []; $serialNo = 1; foreach ($activities as $activity) { $rows[] = [ $serialNo++, $activity['company_name'] ?? '', $activity['short_name'] ?? '', $activity['activity_type'] ?? '', ucfirst((string) ($activity['status'] ?? '')), $activity['assigned_to_name'] ?? '', $activity['additional_assigned_names'] ?? '', $this->formatExportDate($activity['scheduled_date'] ?? ''), $this->formatExportDate($activity['created_at'] ?? ''), $activity['notes'] ?: ($activity['completion_notes'] ?? ''), ]; } return $this->streamCsvDownload( "sales-activities-{$range['from_label']}-to-{$range['to_label']}.csv", ['S.No', 'Company', 'Company Short Name', 'Activity Type', 'Status', 'Assigned To', 'Additional Members', 'Scheduled Date', 'Created At', 'Notes'], $rows ); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Get activities for a specific lead * GET /api/sales/leads/{leadId}/activities */ public function getLeadActivities($leadId) { try { $status = $this->request->getGet('status'); $activities = $this->activityModel->getActivitiesByLead($leadId, $status); return $this->respond([ 'status' => 'success', 'data' => $activities ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Get activity timeline for a lead * GET /api/sales/leads/{leadId}/timeline */ public function getActivityTimeline($leadId) { try { $timeline = $this->activityModel->getActivityTimeline($leadId); return $this->respond([ 'status' => 'success', 'data' => $timeline ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Get upcoming activities for user * GET /api/sales/activities/upcoming */ public function getUpcomingActivities() { try { $userId = $this->getUserId(); $days = $this->request->getGet('days') ?? 7; $limit = $this->request->getGet('limit') ?? 10; $activities = $this->activityModel->getUpcomingActivities($userId, $days, $limit); return $this->respond([ 'status' => 'success', 'data' => $activities ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Create activity * POST /api/sales/activities */ public function createActivity() { try { $data = $this->request->getJSON(true); $data['created_by'] = $this->getUserId(); $data['updated_by'] = $this->getUserId(); // FIX: Convert the array to a JSON string so it fits in the VARCHAR column if (isset($data['additional_assigned_ids']) && is_array($data['additional_assigned_ids'])) { $data['additional_assigned_ids'] = json_encode($data['additional_assigned_ids']); } $activityId = $this->activityModel->insert($data); if (!$activityId) { return $this->fail($this->activityModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); } $data['activity_id'] = $activityId; // add google calender event $response = $this->addCalenderEvent($data); log_message("error", '[GOOGLE_CALENDER] addCalenderEvent response: ' . json_encode($response)); $activityId = $this->activityModel->getInsertID(); $activity = $this->activityModel->find((int)$activityId); // OPTIONAL: Decode it back to an array for the API response so the frontend gets a clean array if (isset($activity['additional_assigned_ids'])) { $activity['additional_assigned_ids'] = json_decode($activity['additional_assigned_ids'], true); } return $this->respondCreated([ 'status' => 'success', 'message' => 'Activity created successfully', 'data' => $activity, 'google_calender_response' => $response ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Update activity * PUT /api/sales/activities/{id} */ public function updateActivity($id) { try { if (!$this->activityModel->find((int)$id)) { return $this->failNotFound('Activity not found'); } $data = $this->request->getJSON(true); $data['updated_by'] = $this->getUserId(); // FIX: Convert the array to a JSON string for updating if (isset($data['additional_assigned_ids']) && is_array($data['additional_assigned_ids'])) { $data['additional_assigned_ids'] = json_encode($data['additional_assigned_ids']); } if (!$this->activityModel->update($id, $data)) { return $this->fail($this->activityModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); } $activity = $this->activityModel->find((int)$id); // OPTIONAL: Decode it back to an array for the API response if (isset($activity['additional_assigned_ids'])) { $activity['additional_assigned_ids'] = json_decode($activity['additional_assigned_ids'], true); } return $this->respond([ 'status' => 'success', 'message' => 'Activity updated successfully', 'data' => $activity ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Delete activity * DELETE /api/sales/activities/{id} */ public function deleteActivity($id) { try { if (!$this->activityModel->find((int)$id)) { return $this->failNotFound('Activity not found'); } $this->activityModel->delete((int)$id); return $this->respondDeleted([ 'status' => 'success', 'message' => 'Activity deleted successfully' ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Get pending activities count * GET /api/sales/activities/pending-count */ public function getPendingCount() { try { $userId = $this->getUserId(); $count = $this->activityModel->getPendingActivitiesCount($userId); return $this->respond([ 'status' => 'success', 'data' => ['count' => $count] ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } // ==================== LEAD NOTES APIs ==================== /** * Get notes for a lead * GET /api/sales/leads/{leadId}/notes */ public function getLeadNotes($leadId) { try { $notes = $this->noteModel->getNotesByLead($leadId); return $this->respond([ 'status' => 'success', 'data' => $notes ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Create note * POST /api/sales/notes */ public function createNote() { try { $data = $this->request->getJSON(true); $data['user_id'] = $this->getUserId(); $data['created_by'] = $this->getUserId(); $data['updated_by'] = $this->getUserId(); if (!$this->noteModel->insert($data)) { return $this->fail($this->noteModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); } $noteId = $this->noteModel->getInsertID(); $note = $this->noteModel->find((int)$noteId); return $this->respondCreated([ 'status' => 'success', 'message' => 'Note created successfully', 'data' => $note ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Update note * PUT /api/sales/notes/{id} */ public function updateNote($id) { try { $note = $this->noteModel->find((int)$id); if (!$note) { return $this->failNotFound('Note not found'); } // Check if user owns the note if ($note['user_id'] != $this->getUserId()) { return $this->failUnauthorized('You are not authorized to update this note'); } $data = $this->request->getJSON(true); $data['updated_by'] = $this->getUserId(); if (!$this->noteModel->update($id, $data)) { return $this->fail($this->noteModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); } $updatedNote = $this->noteModel->find((int)$id); return $this->respond([ 'status' => 'success', 'message' => 'Note updated successfully', 'data' => $updatedNote ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Delete note * DELETE /api/sales/notes/{id} */ public function deleteNote($id) { try { $note = $this->noteModel->find((int)$id); if (!$note) { return $this->failNotFound('Note not found'); } // Check if user owns the note if ($note['user_id'] != $this->getUserId()) { return $this->failUnauthorized('You are not authorized to delete this note'); } $this->noteModel->delete((int)$id); return $this->respondDeleted([ 'status' => 'success', 'message' => 'Note deleted successfully' ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } // ==================== SALES TARGET APIs ==================== /** * Get all sales targets * GET /api/sales/targets */ public function getTargets() { try { $targets = $this->targetModel->findAll(); return $this->respond([ 'status' => 'success', 'data' => $targets ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Get sales target by user * GET /api/sales/targets/user/{userId} */ public function getTargetByUser($userId) { try { $targets = $this->targetModel->where('user_id', $userId)->findAll(); return $this->respond([ 'status' => 'success', 'data' => $targets ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Get sales target by FY year * GET /api/sales/targets/fy/{fyYear} */ public function getTargetByFY($fyYear) { try { $targets = $this->targetModel->where('fy_year', $fyYear)->findAll(); return $this->respond([ 'status' => 'success', 'data' => $targets ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Create sales target * POST /api/sales/targets */ public function createTarget() { try { $data = $this->request->getJSON(true); $data['created_by'] = $this->getUserId(); $data['updated_by'] = $this->getUserId(); if (!$this->targetModel->insert($data)) { return $this->fail($this->targetModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); } $targetId = $this->targetModel->getInsertID(); $target = $this->targetModel->find((int)$targetId); return $this->respondCreated([ 'status' => 'success', 'message' => 'Sales target created successfully', 'data' => $target ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Update sales target * PUT /api/sales/targets/{id} */ public function updateTarget($id) { try { $target = $this->targetModel->find((int)$id); if (!$target) { return $this->failNotFound('Sales target not found'); } $data = $this->request->getJSON(true); $data['updated_by'] = $this->getUserId(); if (!$this->targetModel->update($id, $data)) { return $this->fail($this->targetModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); } $updatedTarget = $this->targetModel->find((int)$id); return $this->respond([ 'status' => 'success', 'message' => 'Sales target updated successfully', 'data' => $updatedTarget ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } /** * Delete sales target * DELETE /api/sales/targets/{id} */ public function deleteTarget($id) { try { $target = $this->targetModel->find((int)$id); if (!$target) { return $this->failNotFound('Sales target not found'); } $this->targetModel->delete((int)$id); return $this->respondDeleted([ 'status' => 'success', 'message' => 'Sales target deleted successfully' ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); } } // ==================== HELPER METHODS ==================== /** * Get current user ID from session or JWT token * You'll need to implement this based on your authentication system */ private function getUserId() { // If using session if (session()->has('user_id')) { return session()->get('user_id'); } // If using JWT, decode token and get user_id // Example: return $this->decodeJWT()['user_id']; // For development/testing, you can return a default value return 1; } private function getSalesFilterYears(): array { $currentFinYear = getCurrentFinancialYear(); $db = \Config\Database::connect(); $finYearsRaw = $db->table('sales_target') ->select('fy_year', false) ->distinct() ->orderBy('fy_year', 'DESC') ->get() ->getResultArray(); $finYears = array_column($finYearsRaw, 'fy_year'); if (empty($finYears)) { $finYears[] = $currentFinYear; } if (!in_array($currentFinYear, $finYears, true)) { array_unshift($finYears, $currentFinYear); } return [ 'fin_years' => $finYears, 'current_fin_year' => $currentFinYear, ]; } private function applyFinancialYearDateRange(array $filters): array { if (empty($filters['financial_year'])) { return $filters; } $fyRange = $this->getFYDateRange((string) $filters['financial_year']); $filters['date_from'] = $fyRange['start']; $filters['date_to'] = $fyRange['end']; return $filters; } private function getExportDateRange(): array { $fromDate = trim((string) $this->request->getGet('from_date')); $toDate = trim((string) $this->request->getGet('to_date')); if ($fromDate === '' || $toDate === '') { throw new \InvalidArgumentException('Please select from date and to date.'); } $from = $this->normalizeExportDate($fromDate, '00:00:00'); $to = $this->normalizeExportDate($toDate, '23:59:59'); if ($from === null || $to === null) { throw new \InvalidArgumentException('Invalid date range. Use YYYY-MM-DD format.'); } if (strtotime($from) > strtotime($to)) { throw new \InvalidArgumentException('From date cannot be after to date.'); } return [ 'from' => $from, 'to' => $to, 'from_label' => date('Y-m-d', strtotime($from)), 'to_label' => date('Y-m-d', strtotime($to)), 'display' => date('d-m-Y', strtotime($from)) . ' to ' . date('d-m-Y', strtotime($to)), ]; } private function normalizeExportDate(string $date, string $time): ?string { $dateTime = \DateTime::createFromFormat('Y-m-d H:i:s', $date . ' ' . $time); if (!$dateTime || $dateTime->format('Y-m-d') !== $date) { return null; } return $dateTime->format('Y-m-d H:i:s'); } private function getExportAssignedToIds(): array { $assignedTo = (string) $this->request->getGet('assigned_to'); if ($assignedTo === '') { return []; } return array_values(array_filter(array_map('trim', explode(',', $assignedTo)), static function ($id) { return ctype_digit($id); })); } private function streamCsvDownload(string $filename, array $headers, array $rows) { $handle = fopen('php://temp', 'r+'); fwrite($handle, "\xEF\xBB\xBF"); fputcsv($handle, $headers); foreach ($rows as $row) { fputcsv($handle, $row); } rewind($handle); $csv = stream_get_contents($handle); fclose($handle); return $this->response ->setHeader('Content-Type', 'text/csv; charset=UTF-8') ->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"') ->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate') ->setBody($csv); } private function exportNotFoundResponse(string $message) { return $this->response ->setStatusCode(ResponseInterface::HTTP_NOT_FOUND) ->setJSON([ 'status' => 'error', 'message' => $message, ]); } private function formatExportDate(?string $value): string { if (empty($value)) { return ''; } $timestamp = strtotime($value); if ($timestamp === false) { return $value; } return date('d/m/Y h:i A', $timestamp); } // ==================== Dashboard ==================== // ───────────────────────────────────────────── // HELPER: Build FY date range from fy_year string // e.g. "2024-2025" → ['2024-04-01 00:00:00', '2025-03-31 23:59:59'] // ───────────────────────────────────────────── private function getFYDateRange(string $financialYear): array { // Format: "2025-2026" — split on last hyphen to get start=2025, end=2026 $pos = strrpos($financialYear, '-'); $startYear = substr($financialYear, 0, $pos); // "2025" $endYear = substr($financialYear, $pos + 1); // "2026" return [ 'start' => $startYear . '-04-01 00:00:00', // 2025-04-01 00:00:00 'end' => $endYear . '-03-31 23:59:59', // 2026-03-31 23:59:59 ]; } // ───────────────────────────────────────────── // HELPER: FY quarters (Apr-Jun / Jul-Sep / Oct-Dec / Jan-Mar) // ───────────────────────────────────────────── private function getFYQuarters(string $financialYear): array { $pos = strrpos($financialYear, '-'); $sy = (int)substr($financialYear, 0, $pos); // 2025 $ey = (int)substr($financialYear, $pos + 1); // 2026 return [ ['name' => 'Q1', 'label' => "Q1 (Apr–Jun {$sy})", 'start' => "{$sy}-04-01", 'end' => "{$sy}-06-30"], ['name' => 'Q2', 'label' => "Q2 (Jul–Sep {$sy})", 'start' => "{$sy}-07-01", 'end' => "{$sy}-09-30"], ['name' => 'Q3', 'label' => "Q3 (Oct–Dec {$sy})", 'start' => "{$sy}-10-01", 'end' => "{$sy}-12-31"], ['name' => 'Q4', 'label' => "Q4 (Jan–Mar {$ey})", 'start' => "{$ey}-01-01", 'end' => "{$ey}-03-31"], ]; } // ───────────────────────────────────────────── // dashboard() — entry point // ───────────────────────────────────────────── public function dashboard() { $payload = $this->request->getGet(); $base = $this->getSalesStaffData(); $salesRole = $base['sales_role']; $salesManagerIds = $base['sales_manager_ids']; $salesHeadManagerIds = $base['sales_manager_with_head']; $userId = get_session_userid(); // Get branch id $nhanceBranchId = $base['sales_manager'][0]['nhance_branch_id'] ?? null; $current_fin_year = $payload['fy'] ?? getCurrentFinancialYear(); $db = \Config\Database::connect(); // Available FY years for dropdown $fin_years_raw = $db->table('sales_target') ->select('fy_year', false) // false = no backtick escaping ->distinct() ->orderBy('fy_year', 'DESC') ->get() ->getResultArray(); $fin_years = array_column($fin_years_raw, 'fy_year'); if (empty($fin_years)) { $fin_years[] = $current_fin_year; } // Ensure current FY is available in list if (!in_array($current_fin_year, $fin_years)) { array_unshift($fin_years, $current_fin_year); } // Route by role if ($salesRole === 'Sales Head') { $this->branchLevelDashboard($nhanceBranchId, $salesHeadManagerIds, $salesManagerIds, $current_fin_year, $fin_years); } elseif ($salesRole === 'Sales Manager') { $this->salesManagerLevelDashboard($userId, $current_fin_year, $fin_years); } } // ───────────────────────────────────────────── // branchLevelDashboard() // ───────────────────────────────────────────── public function branchLevelDashboard($branchId, $branchwise_all_sales_team_ids, $sales_manager_ids, $current_fin_year, $fin_years) { try { $sales_manager_ids = array_values(array_filter(array_map('intval', $sales_manager_ids))); $branchwise_all_sales_team_ids = array_column($branchwise_all_sales_team_ids, 'id'); $db = \Config\Database::connect(); $fyRange = $this->getFYDateRange($current_fin_year); $fyStart = $fyRange['start']; $fyEnd = $fyRange['end']; if (empty($branchwise_all_sales_team_ids)) { // ── No team members — return empty dashboard ── $data = [ 'total_leads' => 0, 'total_acts' => 0, 'total_completed_acts' => 0, 'total_pending_acts' => 0, 'team' => [], 'pending_acts' => [], 'leads_overview' => [], 'activity_breakdown' => [], 'team_achievement' => [], 'opp_achievement' => [], 'fin_years' => $fin_years, 'current_fin_year' => $current_fin_year, 'tab_name' => 'Sales Dashboard', 'page_name' => 'Sales Dashboard', ]; $this->loadLayout('sales/branch_level_dashboard_view', $data); return; } // 1. Lead count — FY filtered by created_at $total_leads = $this->leadModel ->whereIn('assigned_to', $branchwise_all_sales_team_ids) ->where('created_at >=', $fyStart) ->where('created_at <=', $fyEnd) ->countAllResults(); // 2. Total activities — FY filtered by scheduled_date $total_activity = $this->activityModel ->whereIn('assigned_to', $branchwise_all_sales_team_ids) ->where('scheduled_date >=', $fyStart) ->where('scheduled_date <=', $fyEnd) ->countAllResults(); // 3. Completed activities — FY filtered $total_completed_activity = $this->activityModel ->whereIn('assigned_to', $branchwise_all_sales_team_ids) ->where('status', 'completed') ->where('scheduled_date >=', $fyStart) ->where('scheduled_date <=', $fyEnd) ->countAllResults(); // 4. Pending activities — FY filtered $total_pending_activity = $this->activityModel ->whereIn('assigned_to', $branchwise_all_sales_team_ids) ->where('status', 'pending') ->where('scheduled_date >=', $fyStart) ->where('scheduled_date <=', $fyEnd) ->countAllResults(); // 5. Team Performance — subqueries FY filtered by scheduled_date $teamPerformance = $db->table('user_profiles as u') ->select("u.id, u.first_name, u.last_name, r.role, (SELECT COUNT(*) FROM sales_activities WHERE assigned_to = u.id AND scheduled_date >= '{$fyStart}' AND scheduled_date <= '{$fyEnd}') as total_acts, (SELECT COUNT(*) FROM sales_activities WHERE assigned_to = u.id AND status = 'completed' AND scheduled_date >= '{$fyStart}' AND scheduled_date <= '{$fyEnd}') as done_acts", false) ->join('roles r', 'r.id = u.role') ->where('u.nhance_branch_id', $branchId) ->whereIn('u.id', $branchwise_all_sales_team_ids) ->where('u.is_active', 1) ->get() ->getResultArray(); // 6. Pending Activities list — FY filtered by scheduled_date $pending_activities = $db->table('sales_activities sa') ->select('sa.activity_id, sa.lead_id, sa.activity_type, sa.scheduled_date, sa.status, sa.assigned_to, sal.company_name, up.first_name AS assigned_to_name, sa.notes') ->join('user_profiles up', 'up.id = sa.assigned_to', 'left') ->join('sales_actual_leads sal', 'sal.lead_id = sa.lead_id', 'left') ->whereIn('sa.assigned_to', $branchwise_all_sales_team_ids) ->where('sa.status', 'pending') ->where('sa.scheduled_date >=', $fyStart) ->where('sa.scheduled_date <=', $fyEnd) ->orderBy('sa.scheduled_date', 'DESC') ->get() ->getResultArray(); // 7. All Leads Overview — FY filtered by sal.created_at // Activities & opportunities also scoped to FY via CASE WHEN $leadsOverview = $db->table('sales_actual_leads sal') ->select("sal.lead_id, sal.company_name, sal.status, sal.assigned_to AS assigned_to_id, up.first_name AS assigned_to, COUNT(DISTINCT CASE WHEN sa.scheduled_date >= '{$fyStart}' AND sa.scheduled_date <= '{$fyEnd}' THEN sa.activity_id END) AS activities, COUNT(DISTINCT CASE WHEN l.updated_at >= '{$fyStart}' AND l.updated_at <= '{$fyEnd}' THEN l.id END) AS opportunities", false) ->join('user_profiles up', 'up.id = sal.assigned_to', 'left') ->join('sales_activities sa', 'sa.lead_id = sal.lead_id', 'left') ->join('leads l', 'l.actual_lead_id = sal.lead_id', 'left') ->whereIn('sal.assigned_to', $branchwise_all_sales_team_ids) ->where('sal.created_at >=', $fyStart) ->where('sal.created_at <=', $fyEnd) ->groupBy('sal.lead_id, sal.company_name, sal.status, sal.assigned_to, up.first_name') ->orderBy('sal.created_at', 'DESC') ->get() ->getResultArray(); // Protect against division by zero in query #8 $total_activity_safe = $total_activity > 0 ? $total_activity : 1; // 8. Activity Breakdown — FY filtered by scheduled_date $activityBreakdown = $db->table('sales_activities') ->select("activity_type, COUNT(*) AS total, TRUNCATE(COUNT(*) * 100.0 / {$total_activity_safe}, 2) AS percentage_accuracy, ROUND(COUNT(*) * 100.0 / {$total_activity_safe}, 0) AS percentage", false) ->whereIn('assigned_to', $branchwise_all_sales_team_ids) ->where('scheduled_date >=', $fyStart) ->where('scheduled_date <=', $fyEnd) ->groupBy('activity_type') ->orderBy('total', 'DESC') ->get() ->getResultArray(); // 9. Team Achievement (for achievement list + modal) $teamAchievement = $this->buildTeamAchievement( $db, $sales_manager_ids, $branchId, $current_fin_year, $fyStart, $fyEnd ); // 10. Opportunities Achievement per member $oppAchievement = $this->buildOppAchievement( $db, $sales_manager_ids, $branchId, $current_fin_year, $fyStart, $fyEnd ); $data = [ 'total_leads' => $total_leads, 'total_acts' => $total_activity, 'total_completed_acts' => $total_completed_activity, 'total_pending_acts' => $total_pending_activity, 'team' => $teamPerformance, 'pending_acts' => $pending_activities, 'leads_overview' => $leadsOverview, 'activity_breakdown' => $activityBreakdown, 'team_achievement' => $teamAchievement, // used by JS TEAM constant 'opp_achievement' => $oppAchievement, // used by JS OPP_DATA constant 'fin_years' => $fin_years, 'current_fin_year' => $current_fin_year, 'tab_name' => 'Sales Dashboard', 'page_name' => 'Sales Dashboard', ]; $this->loadLayout('sales/branch_level_dashboard_view', $data); } catch (\Exception $e) { return $this->failServerError($e->getMessage()); } } // ───────────────────────────────────────────── // buildTeamAchievement() // Builds the TEAM array for the achievement list // ───────────────────────────────────────────── private function buildTeamAchievement($db, array $sales_manager_ids, $branchId, string $fy, string $fyStart, string $fyEnd): array { $quarters = $this->getFYQuarters($fy); // Gradient palette (cycles) $gradients = [ ['grad' => 'linear-gradient(135deg,#10b981,#34d399)', 'color' => '#10b981'], ['grad' => 'linear-gradient(135deg,#06b6d4,#67e8f9)', 'color' => '#06b6d4'], ['grad' => 'linear-gradient(135deg,#4f46e5,#818cf8)', 'color' => '#4f46e5'], ['grad' => 'linear-gradient(135deg,#ec4899,#f9a8d4)', 'color' => '#ec4899'], ['grad' => 'linear-gradient(135deg,#f97316,#fbbf24)', 'color' => '#f97316'], ]; $members = $db->table('user_profiles as u') ->select('u.id, u.first_name, u.last_name, r.role') ->join('roles r', 'r.id = u.role') ->where('u.nhance_branch_id', $branchId) ->whereIn('u.id', $sales_manager_ids) ->where('u.is_active', 1) ->get() ->getResultArray(); $result = []; foreach ($members as $idx => $m) { $uid = (int)$m['id']; // Target from sales_target $targetRow = $db->table('sales_target') ->where('user_id', $uid) ->where('fy_year', $fy) ->get() ->getRowArray(); $targetAmt = (float)($targetRow['target_amount'] ?? 0); $targetId = $targetRow['id'] ?? null; // Achieved (won leads in FY) $achievedAmt = (float)$this->getUserAchievedAmount($fy, $uid); // Activities — FY filtered by scheduled_date $totalActs = $db->table('sales_activities') ->where('assigned_to', $uid) ->where('scheduled_date >=', $fyStart) ->where('scheduled_date <=', $fyEnd) ->countAllResults(); $doneActs = $db->table('sales_activities') ->where('assigned_to', $uid) ->where('status', 'completed') ->where('scheduled_date >=', $fyStart) ->where('scheduled_date <=', $fyEnd) ->countAllResults(); // Activity breakdown — FY filtered $actRows = $db->table('sales_activities') ->select('activity_type, COUNT(*) as cnt') ->where('assigned_to', $uid) ->where('scheduled_date >=', $fyStart) ->where('scheduled_date <=', $fyEnd) ->groupBy('activity_type') ->get()->getResultArray(); $activities = []; foreach ($actRows as $ar) { $activities[$ar['activity_type']] = (int)$ar['cnt']; } // Quarter splits $splits = []; foreach ($quarters as $q) { $qStart = $q['start'] . ' 00:00:00'; $qEnd = $q['end'] . ' 23:59:59'; // Achievement = SUM(exp_amt) for won policies in this quarter $qAchievedRow = $db->query(" SELECT COALESCE(SUM(ptcs.exp_amt), 0) AS total FROM policy_transaction pt LEFT JOIN pt_co_share_details ptcs ON ptcs.pt_id = pt.id AND ptcs.is_active = 1 WHERE pt.sales_generated_by = ? AND pt.issuer_branch = ? AND pt.created_at >= ? AND pt.created_at <= ? ", [$uid, $branchId, $qStart, $qEnd])->getRowArray(); $qAchieved = (float)($qAchievedRow['total'] ?? 0); $qTarget = $targetAmt > 0 ? round($targetAmt / 4, 2) : 0; $qActs = $db->table('sales_activities') ->where('assigned_to', $uid) ->where('scheduled_date >=', $qStart) ->where('scheduled_date <=', $qEnd) ->countAllResults(); $qDone = $db->table('sales_activities') ->where('assigned_to', $uid) ->where('status', 'completed') ->where('scheduled_date >=', $qStart) ->where('scheduled_date <=', $qEnd) ->countAllResults(); $qLeads = $db->table('sales_actual_leads') ->where('assigned_to', $uid) ->where('created_at >=', $qStart) ->where('created_at <=', $qEnd) ->countAllResults(); $splits[] = [ 'name' => $q['label'], 'start' => date('M Y', strtotime($q['start'])), 'end' => date('M Y', strtotime($q['end'])), 'target' => $qTarget, 'achieved' => $qAchieved, 'acts' => $qActs, 'done' => $qDone, 'leads' => $qLeads, ]; } $palette = $gradients[$idx % count($gradients)]; $result[] = [ 'id' => $uid, 'first_name' => $m['first_name'], 'last_name' => $m['last_name'], 'role' => $m['role'], 'total_acts' => $totalActs, 'done_acts' => $doneActs, 'target_id' => $targetId, 'target_amt' => $targetAmt, 'achieved_amt' => $achievedAmt, 'grad' => $palette['grad'], 'color' => $palette['color'], 'splits' => $splits, 'activities' => $activities, ]; } return $result; } // ───────────────────────────────────────────── // buildOppAchievement() // Opportunities via policy_transaction + pt_co_share_details // Returns per-member: totals + flat policy list (no quarterly grouping) // ───────────────────────────────────────────── private function buildOppAchievement($db, array $sales_manager_ids, $branchId, string $fy, string $fyStart, string $fyEnd): array { $result = []; foreach ($sales_manager_ids as $uid) { // ── Target ── $targetRow = $db->table('sales_target') ->where('user_id', $uid) ->where('fy_year', $fy) ->get() ->getRowArray(); $targetAmt = (float)($targetRow['target_amount'] ?? 0); // ── All policy rows for this user in FY ── // policy_no, issue_date (from pt), amount (exp_amt from child), created_at // If no matching pt_co_share_details row exists, exp_amt = 0 // Policy list for Tab 2: policy_transaction + exp_amt from pt_co_share_details $policyRows = $db->query(" SELECT pt.id, pt.policy_no, pt.created_at AS issue_date, COALESCE(ptcs.exp_amt, 0) AS amount, pt.created_at AS created_at FROM policy_transaction pt LEFT JOIN pt_co_share_details ptcs ON ptcs.pt_id = pt.id AND ptcs.is_active = 1 WHERE pt.sales_generated_by = ? AND pt.issuer_branch = ? AND pt.created_at >= ? AND pt.created_at <= ? ORDER BY pt.created_at DESC ", [$uid, $branchId, $fyStart, $fyEnd])->getResultArray(); // ── Totals derived from policy_transaction ── $totalPolicies = count($policyRows); $totalExpAmt = array_sum(array_column($policyRows, 'amount')); // Clean policy list for JS $policies = array_map(function($row) { return [ 'policy_no' => $row['policy_no'], 'issue_date' => $row['issue_date'], 'amount' => (float)$row['amount'], 'created_at' => $row['created_at'], ]; }, $policyRows); // ── Won Leads for this user in FY (Table 2 in modal) ── // leads.actual_lead_id maps to sales_actual_leads.id // leads.lead_form_type: 1 = EB, else = Non-EB // leads.lead_type: 1 = Fresh, 2 = Renewal, 3 = Roll Over $wonLeads = $db->query(" SELECT l.id AS opportunities_id, l.actual_lead_id, COALESCE(l.lead_form_type, 1) AS lead_form_type_id, l.lead_type AS lead_type_id, sal.company_name AS company, l.client_name AS client_name, CASE WHEN COALESCE(l.lead_form_type, 1) = 1 THEN 'EB' ELSE 'Non-EB' END AS lead_form_type, CASE WHEN l.lead_type = 1 THEN 'Fresh' WHEN l.lead_type = 2 THEN 'Renewal' WHEN l.lead_type = 3 THEN 'Roll Over' ELSE '' END AS lead_type, l.created_at AS created_at, l.status FROM leads l INNER JOIN sales_actual_leads sal ON sal.lead_id = l.actual_lead_id WHERE l.status = 'won' AND sal.assigned_to = ? AND l.created_at >= ? AND l.created_at <= ? ORDER BY l.created_at DESC ", [$uid, $fyStart, $fyEnd])->getResultArray(); // ── Lost Leads for this user in FY (Opportunities tab — toggle off) ── $lossLeads = $db->query(" SELECT l.id AS opportunities_id, l.actual_lead_id, COALESCE(l.lead_form_type, 1) AS lead_form_type_id, l.lead_type AS lead_type_id, sal.company_name AS company, l.client_name AS client_name, CASE WHEN COALESCE(l.lead_form_type, 1) = 1 THEN 'EB' ELSE 'Non-EB' END AS lead_form_type, CASE WHEN l.lead_type = 1 THEN 'Fresh' WHEN l.lead_type = 2 THEN 'Renewal' WHEN l.lead_type = 3 THEN 'Roll Over' ELSE '' END AS lead_type, l.created_at AS created_at, l.status, l.lost_reason AS lost_reason FROM leads l INNER JOIN sales_actual_leads sal ON sal.lead_id = l.actual_lead_id WHERE l.status = 'lost' AND sal.assigned_to = ? AND l.created_at >= ? AND l.created_at <= ? ORDER BY l.created_at DESC ", [$uid, $fyStart, $fyEnd])->getResultArray(); // -- NOTE: AND TRIM(sal.company_name) = TRIM(l.client_name) $result[$uid] = [ 'total_policies' => $totalPolicies, 'total_exp_amt' => $totalExpAmt, 'target_amt' => $targetAmt, 'policies' => $policies, 'won_leads' => $wonLeads, // for Table 2 in modal Tab 2 'loss_leads' => $lossLeads, ]; } return $result; } // ───────────────────────────────────────────── // getUserAchievedAmount() // ───────────────────────────────────────────── public function getUserAchievedAmount($financialYear, $userId) { // "2025-2026" → strrpos splits correctly into 2025 / 2026 $pos = strrpos($financialYear, '-'); $startYear = substr($financialYear, 0, $pos); // "2025" $endYear = substr($financialYear, $pos + 1); // "2026" $startFY = $startYear . '-04-01 00:00:00'; // 2025-04-01 $endFY = $endYear . '-03-31 23:59:59'; // 2026-03-31 // Achievement = SUM(exp_amt) from policy_transaction + pt_co_share_details $db = \Config\Database::connect(); $row = $db->query(" SELECT COALESCE(SUM(ptcs.exp_amt), 0) AS achieved_amount FROM policy_transaction pt LEFT JOIN pt_co_share_details ptcs ON ptcs.pt_id = pt.id AND ptcs.is_active = 1 WHERE pt.sales_generated_by = ? AND pt.created_at >= ? AND pt.created_at <= ? ", [$userId, $startFY, $endFY])->getRowArray(); return (float)($row['achieved_amount'] ?? 0.00); } // ───────────────────────────────────────────── // salesManagerLevelDashboard() // ───────────────────────────────────────────── public function salesManagerLevelDashboard($userId, $current_fin_year = null, $fin_years = []) { $db = \Config\Database::connect(); try { // ------------------------------- // Financial Year Handling // ------------------------------- if (empty($current_fin_year)) { $current_fin_year = getCurrentFinancialYear(); } $fyRange = $this->getFYDateRange($current_fin_year); $fyStart = $fyRange['start']; $fyEnd = $fyRange['end']; // ------------------------------- // Target (FY Based) // ------------------------------- $target = $db->table('sales_target') ->where('user_id', $userId) ->where('fy_year', $current_fin_year) ->get() ->getRowArray(); $targetAmount = (float)($target['target_amount'] ?? 0); // ------------------------------- // Achieved (FY Based) // ------------------------------- $achievedAmount = (float)$this->getUserAchievedAmount($current_fin_year, $userId); $remainingAmount = $targetAmount - $achievedAmount; $achievementPercent = ($targetAmount > 0) ? min(100, round(($achievedAmount / $targetAmount) * 100)) : 0; // ------------------------------- // Activity Summary (FY Based using created_at) // ------------------------------- $activitySummary = [ 'total' => $this->activityModel ->where('assigned_to', $userId) ->where('created_at >=', $fyStart) ->where('created_at <=', $fyEnd) ->countAllResults(), 'pending' => $this->activityModel ->where([ 'assigned_to' => $userId, 'status' => 'pending' ]) ->where('created_at >=', $fyStart) ->where('created_at <=', $fyEnd) ->countAllResults(), 'completed' => $this->activityModel ->where([ 'assigned_to' => $userId, 'status' => 'completed' ]) ->where('created_at >=', $fyStart) ->where('created_at <=', $fyEnd) ->countAllResults(), ]; // ------------------------------- // Leads Count (FY Based) // ------------------------------- $myLeadsCount = $this->leadModel ->where('assigned_to', $userId) ->where('created_at >=', $fyStart) ->where('created_at <=', $fyEnd) ->countAllResults(); // ------------------------------- // Upcoming Activities (FY Based) // ------------------------------- $upcomingActivities = $this->activityModel ->select('sales_activities.*, sales_actual_leads.company_name') ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id') ->where([ 'sales_activities.assigned_to' => $userId, 'sales_activities.status' => 'pending' ]) ->where('sales_activities.created_at >=', $fyStart) ->where('sales_activities.created_at <=', $fyEnd) ->orderBy('scheduled_date', 'ASC') ->limit(3) ->findAll(); // ------------------------------- // Recent Leads (FY Based) // ------------------------------- $recentLeads = $this->leadModel ->where('assigned_to', $userId) ->where('created_at >=', $fyStart) ->where('created_at <=', $fyEnd) ->orderBy('created_at', 'DESC') ->limit(5) ->findAll(); // ------------------------------- // Final Data // ------------------------------- $data = [ 'target_amt' => $targetAmount, 'achieved' => $achievedAmount, 'remaining' => $remainingAmount, 'percent' => $achievementPercent, 'acts' => $activitySummary, 'lead_count' => $myLeadsCount, 'upcoming' => $upcomingActivities, 'recent_leads' => $recentLeads, 'fin_years' => $fin_years, 'current_fin_year' => $current_fin_year, 'display_fin_years' => format_financial_year($current_fin_year), 'user_name' => get_session_userdata()->first_name ?? '', 'tab_name' => 'Sales Dashboard', 'page_name' => 'Sales Dashboard', ]; return $this->loadLayout('sales/sales_manager_level_dashboard', $data); } catch (\Exception $e) { return $this->failServerError($e->getMessage()); } } // public function dashboard() // { // $payload = $this->request->getGet(); // $base = $this->getSalesStaffData(); // $salesRole = $base['sales_role']; // $salesManagerIds = $base['sales_manager_ids']; // $userId = get_session_userid(); // // Get branch id from users array // $nhanceBranchId = $base['users'][0]['nhance_branch_id'] ?? null; // if ($salesRole === 'Sales Head') { // $this->branchLevelDashboard($nhanceBranchId, $salesManagerIds); // // $this->salesManagerLevelDashboard($userId, $salesManagerIds,$payload); // } elseif ($salesRole === 'Sales Manager') { // $this->salesManagerLevelDashboard($userId, $salesManagerIds,$payload); // } // } // public function branchLevelDashboard($branchId,$sales_manager_ids) // { // // Hardcoded branch ID as requested // // $branchId = 1; // try { // $sales_manager_ids = array_values(array_map('intval', $sales_manager_ids)); // // Final safe check // if (empty($sales_manager_ids)) { // // No valid IDs — skip queries or return empty // $total_leads = 0; // $total_activity = 0; // $total_completed_activity = 0; // $total_pending_activity = 0; // $pending_activities = []; // $recent_activities = []; // $teamPerformance = []; // $leadsOverview = []; // } else { // if (empty($sales_manager_ids) || !is_array($sales_manager_ids)) { // $sales_manager_ids = array_filter((array) $sales_manager_ids); // removes null, "", 0 // } // // 1. Lead Statistics // $total_leads = $this->leadModel->whereIn('assigned_to', $sales_manager_ids)->countAllResults(); // Use countAllResults, NOT countAll // // echo $this->leadModel->getLastQuery();die(); // // 2. Total activity // $total_activity = $this->activityModel->whereIn('assigned_to', $sales_manager_ids)->countAllResults(); // // 3. Completed activity // $total_completed_activity = $this->activityModel->whereIn('assigned_to', $sales_manager_ids)->where('status', 'completed')->countAllResults(); // // 4. Pending activity // $total_pending_activity = $this->activityModel->whereIn('assigned_to', $sales_manager_ids)->where('status', 'pending')->countAllResults(); // $db = \Config\Database::connect(); // // 5. Team Performance // $teamPerformance = $db->table('user_profiles as u') // ->select('u.first_name, u.last_name, r.role, // (SELECT COUNT(*) FROM sales_activities WHERE assigned_to = u.id) as total_acts, // (SELECT COUNT(*) FROM sales_activities WHERE assigned_to = u.id AND status = "completed") as done_acts') // ->join('roles r', 'r.id = u.role') // ->where('u.nhance_branch_id', $branchId) // ->whereIn('u.id', $sales_manager_ids) // ->where('u.is_active', 1) // ->get()->getResultArray(); // // 6. Recent Activities (Joining for Lead Names) // $recent_activities = $this->activityModel->select('sales_activities.*, sales_actual_leads.company_name') // ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id') // ->orderBy('sales_activities.scheduled_date', 'DESC') // ->limit(6) // ->findAll(); // // 7. Pending Activities (List) // $pending_activities = $db->table('sales_activities sa') // ->select('sa.activity_id,sa.lead_id,sa.activity_type,sa.scheduled_date,sa.status,sa.assigned_to,sal.company_name,up.first_name AS assigned_to_name,sa.notes') // ->join('user_profiles up', 'up.id = sa.assigned_to', 'left') // ->join('sales_actual_leads sal', 'sal.lead_id = sa.lead_id', 'left') // ✅ ADD THIS // ->orderBy('sa.scheduled_date', 'DESC') // ->whereIn('sa.assigned_to', $sales_manager_ids) // ->where('sa.status', 'pending') // ->get()->getResultArray(); // // 8. All Leads Overview // $leadsOverview = $db->table('sales_actual_leads sal') // ->select('sal.lead_id,sal.company_name,sal.status,up.first_name AS assigned_to, // COUNT(DISTINCT sa.activity_id) AS activities, // COUNT(DISTINCT l.id) AS opportunities // ') // ->join('user_profiles up', 'up.id = sal.assigned_to', 'left') // ->join('sales_activities sa', 'sa.lead_id = sal.lead_id', 'left') // ->join('leads l', 'l.actual_lead_id = sal.lead_id', 'left') // ->groupBy('sal.lead_id, sal.company_name, sal.status, up.first_name') // // ->having('COUNT(DISTINCT sa.activity_id) + COUNT(DISTINCT l.id) >', 0) // ← this line // ->orderBy('sal.created_at', 'DESC') // ->whereIn('sal.assigned_to', $sales_manager_ids) // ->get() // ->getResultArray(); // // 9. Activity BrakDown // $activityBreakdown = $db->table('sales_activities') // ->select("activity_type, COUNT(*) AS total, ROUND(COUNT(*) * 100.0 / {$total_activity}, 0) AS percentage", false) // ->whereIn('assigned_to', $sales_manager_ids) // ->groupBy('activity_type') // ->orderBy('total', 'DESC') // ->get() // ->getResultArray(); // } // $data = [ // 'total_leads' => $total_leads, // 'total_acts' => $total_activity, // 'total_completed_acts' => $total_completed_activity, // 'total_pending_acts'=> $total_pending_activity, // 'pipeline_value' => '15.0L', // Hardcoded placeholder from PDF [cite: 14] // 'display_fin_years' => format_financial_year($current_fin_year), // 'team' => $teamPerformance, // 'recent_acts' => $recent_activities, // 'pending_acts' => $pending_activities, // 'leads_overview' => $leadsOverview, // 'activity_breakdown'=> $activityBreakdown, // 'tab_name' => "Sales Dashboard", // 'page_name' => "Sales Dashboard" // ]; // // dd($data); // $this->loadLayout('sales/branch_level_dashboard_view', $data); // // return view('sales/dashboard_view', $data); // } catch (\Exception $e) { // return $this->failServerError($e->getMessage()); // } // } // public function salesManagerLevelDashboard($userId,$sales_manager_ids, $payload = []) // { // // $userId = get_session_userid(); // // $userId = 1; // $db = \Config\Database::connect(); // try { // // $payload = $this->request->getGet(); // $current_fin_year = $payload['fy'] ?? getCurrentFinancialYear(); // $fin_years = $db->table('sales_target') // ->select('fy_year') // ->where('user_id', $userId) // ->orderBy('fy_year', 'desc') // ->get() // ->getResultArray(); // $fin_years = array_column($fin_years, 'fy_year'); // if(empty($fin_years)){ // $fin_years[] = $current_fin_year; // } // $target = $db->table('sales_target') // ->where('user_id', $userId) // ->where('fy_year', $current_fin_year) // ->get() // ->getRowArray(); // $targetAmount = $target['target_amount'] ?? 0.00; // // get achieved amount from leads table // $achievedAmount = $this->getUserAchievedAmount($current_fin_year, $userId); // $remainingAmount = $targetAmount - $achievedAmount; // // $achievementPercent = ($targetAmount > 0) ? round(($achievedAmount / $targetAmount) * 100) : 0; // $achievementPercent = ($targetAmount > 0) ? min(100, round(($achievedAmount / $targetAmount) * 100)) : 0; // $activitySummary = [ // 'total' => $this->activityModel->where('assigned_to', $userId)->countAllResults(), // 'pending' => $this->activityModel->where(['assigned_to' => $userId, 'status' => 'pending'])->countAllResults(), // 'completed' => $this->activityModel->where(['assigned_to' => $userId, 'status' => 'completed'])->countAllResults(), // ]; // $myLeadsCount = $this->leadModel->where('assigned_to', $userId)->countAllResults(); // $upcomingActivities = $this->activityModel->select('sales_activities.*, sales_actual_leads.company_name') // ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id') // ->where(['sales_activities.assigned_to' => $userId, 'sales_activities.status' => 'pending']) // ->orderBy('scheduled_date', 'ASC') // ->limit(3) // ->findAll(); // $recentLeads = $this->leadModel->where('assigned_to', $userId) // ->orderBy('created_at', 'DESC') // ->limit(5) // ->findAll(); // $data = [ // 'target_amt' => $targetAmount, // 'achieved' => $achievedAmount, // 'remaining' => $remainingAmount, // 'percent' => $achievementPercent, // 'acts' => $activitySummary, // 'lead_count' => $myLeadsCount, // 'upcoming' => $upcomingActivities, // 'recent_leads' => $recentLeads, // 'fin_years' => $fin_years, // 'display_fin_years' => format_financial_year($current_fin_year), // 'user_name' => get_session_userdata()->first_namee ?? '', // 'tab_name' => "Sales Dashboard", // 'page_name' => "Sales Dashboard", // 'splits' => [], // 'activity_breakdown'=> [], // ]; // // dd($data); // // return view('sales/my_dashboard_view', $data); // $this->loadLayout('sales/sales_manager_level_dashboard', $data); // } catch (\Exception $e) { // return $this->failServerError($e->getMessage()); // } // } // public function getUserAchievedAmount($financialYear, $userId) // { // // Split the string into two years // $years = explode('-', $financialYear); // $startYear = $years[0]; // 2025 // $endYear = $years[1]; // 2026 // // Create the timestamps // $startFY = $startYear . '-04-01 00:00:00'; // $endFY = $endYear . '-03-31 23:59:59'; // $achievedAmountData = $this->leadModel // ->select('SUM(leads.premium_amount) as achieved_amount') // ->join('leads', 'sales_actual_leads.lead_id = leads.actual_lead_id') // ->where('sales_actual_leads.assigned_to', $userId) // ->where('leads.status', 'won') // ->where('leads.updated_at >=', $startFY) // ->where('leads.updated_at <=', $endFY) // ->findAll(); // return $achievedAmountData[0]['achieved_amount'] ?? 0.00; // } public function addCalenderEvent($input) { try { log_message('error', '[GOOGLE_CALENDER] Calendar Event Input: ' . json_encode($input)); /* ---------------- VALIDATION ---------------- */ if (empty($input['lead_id']) || empty($input['assigned_to']) || empty($input['scheduled_date'])) { log_message('error', '[GOOGLE_CALENDER] Calendar Event Missing Required Data'); return [ 'status' => false, 'message' => 'Required data missing' ]; } /* ---------------- FETCH DATA ---------------- */ $lead_data = $this->leadModel ->where('lead_id', $input['lead_id']) ->first(); if (!$lead_data) { log_message('error', '[GOOGLE_CALENDER] Lead Not Found: ' . $input['lead_id']); return [ 'status' => false, 'message' => 'Lead not found' ]; } $user_data = $this->userModel ->where('id', $input['assigned_to']) ->first(); if (!$user_data) { log_message('error', '[GOOGLE_CALENDER] User Not Found: ' . $input['assigned_to']); return [ 'status' => false, 'message' => 'Assigned user not found' ]; } $activity_data = $this->activityModel->where('activity_id', $input['activity_id'])->first(); $emails[] = $user_data['email']; if (isset($activity_data['additional_assigned_ids']) && !empty($activity_data['additional_assigned_ids'])) { $additional_assigned_ids = json_decode($activity_data['additional_assigned_ids'], true) ?? []; foreach ($additional_assigned_ids as $additional_assigned_id) { $additional_user_data = $this->userModel->where('id', $additional_assigned_id)->first(); if (!empty($additional_user_data['email'])) { $emails[] = $additional_user_data['email']; } } } log_message('error', '[GOOGLE_CALENDER] User emails: ' . json_encode($emails)); /* ---------------- SUMMARY ---------------- */ $activityType = ucfirst($input['activity_type']); $prepositionMap = [ 'Email' => 'to', 'Call' => 'with', 'Meeting' => 'with', 'Visit' => 'to', 'Demo' => 'with', 'Share Docs' => 'to', 'To Do' => 'for' ]; $preposition = $prepositionMap[$activityType] ?? 'with'; $summary = "Activity scheduled : {$activityType} {$preposition} {$lead_data['company_name']}"; /* ---------------- GOOGLE PAYLOAD ---------------- */ $eventData = [ 'summary' => $summary, 'meeting_date' => $input['scheduled_date'], 'description' => $input['notes'] ?? '', 'emails' => $emails, ]; log_message('error', '[GOOGLE_CALENDER] Google Calendar Payload: ' . json_encode($eventData)); /* ---------------- API CALL ---------------- */ $response = add_google_calender_event($eventData); log_message('error', '[GOOGLE_CALENDER] Google Calendar Response: ' . json_encode($response)); return $response; } catch (\Throwable $e) { log_message('error', '[GOOGLE_CALENDER] Calendar Event Exception: ' . $e->getMessage()); log_message('error', '[GOOGLE_CALENDER] Calendar Event Trace: ' . $e->getTraceAsString()); return [ 'status' => false, 'message' => 'Calendar event failed', 'error' => $e->getMessage() ]; } } /** * Send lead-create notification email when the logged-in user's branch is Chennai. */ private function sendLeadCreateMail(int $leadId, array $leadData): void { try { if (!$this->isLoggedInUserChennaiBranch()) { return; } $toMail = trim((string) getenv('LEAD_CREATE_MAIL')); if ($toMail === '') { log_message('error', 'sendLeadCreateMail: LEAD_CREATE_MAIL is not configured.'); return; } $recipientEmails = array_filter(array_map('trim', explode(',', $toMail))); if (empty($recipientEmails)) { log_message('error', 'sendLeadCreateMail: No valid recipients in LEAD_CREATE_MAIL.'); return; } $creator = $this->getLeadCreateMailCreatorDetails(); $assignedUser = $this->userModel->where('id', $leadData['assigned_to'] ?? 0)->first() ?? []; $companyName = $leadData['company_name'] ?? ''; $subject = 'New Lead Created - ' . $companyName; $message = $this->buildLeadCreateMailMessage( $companyName, $leadData['email'] ?? '', $leadData['phone'] ?? '', $leadData['status'] ?? '', $creator['name'] ?? '', $creator['email'] ?? '', $creator['branch'] ?? '', $assignedUser['first_name'] ?? '', $assignedUser['email'] ?? '', date('d/m/Y h:i A') ); $res = MailHelper::send_email([ 'mail' => $recipientEmails, 'subject' => $subject, 'message' => $message, 'common' => ['module' => 'sales', 'pk' => $leadId, 'mail_type' => 'lead_create'], ]); $resDecoded = is_string($res) ? json_decode($res, true) : $res; if (!isset($resDecoded['status']) || $resDecoded['status'] !== 'success') { log_message('error', 'sendLeadCreateMail: Email send failed - ' . json_encode($resDecoded)); } } catch (\Throwable $e) { log_message('error', 'sendLeadCreateMail: ' . $e->getMessage()); } } private function isLoggedInUserChennaiBranch(): bool { $userId = get_session_userid(); if (empty($userId)) { return false; } $row = $this->userModel ->select('id') ->where('id', $userId) ->where('nhance_branch_id', 1) ->first(); return !empty($row); } private function getLeadCreateMailCreatorDetails(): array { $userId = get_session_userid(); $db = \Config\Database::connect(); $row = $db->table('user_profiles up') ->select('up.first_name, up.email, nb.branch_name') ->join('nhance_branch nb', 'nb.id = up.nhance_branch_id', 'left') ->where('up.id', $userId) ->get() ->getRowArray(); if (!$row) { return ['name' => '', 'email' => '', 'branch' => '']; } return [ 'name' => $row['first_name'] ?? '', 'email' => $row['email'] ?? '', 'branch' => $row['branch_name'] ?? '', ]; } private function buildLeadCreateMailMessage( string $companyName, string $leadEmail, string $leadMobile, string $leadStatus, string $createdByName, string $createdByEmail, string $createdByBranch, string $assignedUserName, string $assignedUserEmail, string $createdDateTime ): string { $e = static fn (?string $value): string => htmlspecialchars((string) ($value ?? ''), ENT_QUOTES, 'UTF-8'); return '

Hello Team,

' . '

A new lead has been created

' . '

Lead Details

' . '

Company Name : ' . $e($companyName) . '

' . '

Email : ' . $e($leadEmail) . '

' . '

Mobile : ' . $e($leadMobile) . '

' . '

Status : ' . $e($leadStatus) . '

' . '

Created By

' . '

Name : ' . $e($createdByName) . '

' . '

Email : ' . $e($createdByEmail) . '

' . '

Branch : ' . $e($createdByBranch) . '

' . '

Assigned To

' . '

Name : ' . $e($assignedUserName) . '

' . '

Email : ' . $e($assignedUserEmail) . '

' . '

Created On

' . '

Date & Time : ' . $e($createdDateTime) . '

' . '

Regards,
NHANCE INDIA INSURANCE BROKING PRIVATE LIMITED

'; } }