diff --git a/app/Controllers/LeadsController.php b/app/Controllers/LeadsController.php index dfd99a43..2d3988ac 100644 --- a/app/Controllers/LeadsController.php +++ b/app/Controllers/LeadsController.php @@ -32,6 +32,8 @@ use App\Models\OccupancyMasterModel; use App\Models\LeadFilesModel; use App\Models\LeadInstallmentPaymentDetails; use App\Models\GmailSentHistoryModel; +use App\Models\SalesActualLeadModel; +use App\Models\SalesContactPersonModel; use App\Helpers\MailHelper; use App\Helpers\ExcelMergeHelper; @@ -83,6 +85,8 @@ class LeadsController extends BaseController protected $buisnessType; protected $member_data_excel_columns; protected $general_relationships; + protected $leadModel; + protected $contactModel; public function __construct() @@ -107,6 +111,8 @@ class LeadsController extends BaseController $this->leadFilesModel = new LeadFilesModel(); $this->leadInstallmentPaymentDetails = new LeadInstallmentPaymentDetails(); $this->gmailSentHistoryModel = new GmailSentHistoryModel(); + $this->leadModel = new SalesActualLeadModel(); + $this->contactModel = new SalesContactPersonModel(); $this->issuer = [1 => 'JIBS', 2 => 'Nhance']; $this->clientType = [1 => 'Group', 2 => 'Individual']; @@ -627,6 +633,7 @@ class LeadsController extends BaseController { $request_data = $this->request->getPost(); + print_r($request_data);die; $data = sanitizeInputArrayAdvanced($request_data); $data['client_type'] = 1; @@ -825,6 +832,7 @@ class LeadsController extends BaseController $last_3_years_claims = $data['finyear']; $processedData[] = [ + 'lost_reason' => $data['lost_reason'] ?? null, 'actual_lead_id' => $data['actual_lead_id'] ?? null, 'lead_type' => $data['lead_type'], 'issuer' => $data['issuer'], @@ -4372,6 +4380,28 @@ class LeadsController extends BaseController 'actual_lead_id' => $actual_lead_id, ]; + if ($actual_lead_id > 0) { + + // πŸ”Ή Client Details (Single Row) + $data['actual_lead_client_details'] = $this->leadModel + ->select('company_name, email, phone, address, website, gst_number, status, assigned_to') + ->where('lead_id', $actual_lead_id) + ->first(); // first row only + + + // πŸ”Ή Contact Person Details (First Row Only) + $data['actual_lead_contact_person_details'] = $this->contactModel + ->select('contact_id, name, mobile, designation, email, is_primary') + ->where('lead_id', $actual_lead_id) + ->where('is_primary',1) + ->orderBy('is_primary', 'DESC') // optional (primary first) + ->first(); // only first row + } + else { + $data['actual_lead_client_details'] = null; + $data['actual_lead_contact_person_details'] = null; + } + // Fetch sales team members who are active in team 5 $data['salse_team'] = $this->userModel ->select('user_profiles.*') diff --git a/app/Controllers/SalesController.php b/app/Controllers/SalesController.php index 164444e9..39a0f672 100644 --- a/app/Controllers/SalesController.php +++ b/app/Controllers/SalesController.php @@ -53,7 +53,6 @@ class SalesController extends BaseController ->orderBy('lead_id', 'DESC') ->findAll(); - return $this->loadLayout('sales/activity_view', $data); } @@ -66,9 +65,14 @@ class SalesController extends BaseController return $this->loadLayout('sales/target_view', $data); } - /** - * HELPER: Fetches Sales Managers based on the logged-in user's role and branch - */ + /** + * 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(); @@ -76,54 +80,118 @@ class SalesController extends BaseController $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 = [ - 'users' => [], - 'sales_manager_ids'=> [], - 'sales_role' => '', - 'nhance_branch_id' => null, - 'assigned_ids' => [], + 'sales_role' => '', + 'sales_manager' => [], + 'sales_manager_ids' => [], + 'sales_manager_with_head' => [], + 'sales_manager_with_head_ids' => [], + 'sales_team' => [], + 'nhance_branch_id' => $nhance_branch_id, ]; - $row = $db->table('user_profiles')->select('*') - ->where('is_active', 1)->where('id', $logged_user_id) - ->get()->getRow(); + // ================================================================ + // 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(); - $nhance_branch_id = $row ? $row->nhance_branch_id : null; - $data['nhance_branch_id']= $nhance_branch_id; + // ================================================================ + // 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(); - // ── Sales Manager (Role 4, Team 5) ────────────────────────── + // Add "(Head)" label to heads so dropdown is clear + foreach ($branch_heads as &$head) { + $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)" label to all heads + foreach ($all_heads as &$head) { + $head['first_name'] = $head['first_name'] . ' (Head)'; + } + unset($head); + + // ================================================================ + // 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]; - $data['assigned_ids'] = [$logged_user_id]; - $data['users'] = [ - [ - 'id' => $row->id, - 'first_name' => $row->first_name, - 'last_name' => $row->last_name ?? '', - 'nhance_branch_id' => $nhance_branch_id, - ] - ]; - // ── Sales Head (Role 1 or 5) ───────────────────────────────── + // ── Sales Head (Role 1 or 5) ───────────────────────────────────── } elseif (in_array($role, [1, 5])) { - $data['sales_role'] = 'Sales Head'; - $data['users'] = $db->table('user_profiles up') - ->select('up.id, up.first_name, up.last_name, 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) - ->where('ut.team_id', 5) - ->where('up.nhance_branch_id', $nhance_branch_id) - ->get() - ->getResultArray(); - - $ids = array_column($data['users'], 'id'); - $data['sales_manager_ids'] = $ids; - $data['assigned_ids'] = $ids; // same value, both available + $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; @@ -149,6 +217,9 @@ class SalesController extends BaseController echo view('layout/footer', $data); } + /** + * GET /api/sales/activities/(:num)/complete + */ public function completeActivity($id) { try { $data = $this->request->getJSON(true); @@ -236,7 +307,8 @@ class SalesController extends BaseController 'data' => $result['data'], 'total' => $result['total'], 'limit' => $limit, - 'offset' => $offset + 'offset' => $offset, + 'counts' => $result['counts'], ]); } catch (\Exception $e) { return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); @@ -398,6 +470,15 @@ class SalesController extends BaseController $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); } @@ -487,7 +568,8 @@ class SalesController extends BaseController 'data' => $result['data'], 'total' => $result['total'], 'limit' => $limit, - 'offset' => $offset + 'offset' => $offset, + 'counts' => $result['counts'], ], 200); } catch (\Exception $e) { @@ -565,6 +647,11 @@ class SalesController extends BaseController $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']); + } + if (!$this->activityModel->insert($data)) { return $this->fail($this->activityModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); } @@ -575,6 +662,11 @@ class SalesController extends BaseController $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', @@ -600,12 +692,22 @@ class SalesController extends BaseController $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', @@ -927,252 +1029,896 @@ class SalesController extends BaseController // ==================== Dashboard ==================== - 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; +// ───────────────────────────────────────────── +// 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" - if ($salesRole === 'Sales Head') { - $this->branchLevelDashboard($nhanceBranchId, $salesManagerIds); - } elseif ($salesRole === 'Sales Manager') { - $this->salesManagerLevelDashboard($userId, $salesManagerIds,$payload); - } - } + 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 + ]; +} - public function branchLevelDashboard($branchId,$sales_manager_ids) - { - // Hardcoded branch ID as requested - // $branchId = 1; +// ───────────────────────────────────────────── +// 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 - 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 { + 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"], + ]; +} - 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(); +// ───────────────────────────────────────────── +// dashboard() β€” entry point +// ───────────────────────────────────────────── +public function dashboard() +{ + $payload = $this->request->getGet(); + $base = $this->getSalesStaffData(); + $salesRole = $base['sales_role']; + $salesManagerIds = $base['sales_manager_ids']; + $userId = get_session_userid(); - // 3. Completed activity - $total_completed_activity = $this->activityModel->whereIn('assigned_to', $sales_manager_ids)->where('status', 'completed')->countAllResults(); + // Get branch id + $nhanceBranchId = $base['sales_manager'][0]['nhance_branch_id'] ?? null; - // 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(); + $current_fin_year = $payload['fy'] ?? getCurrentFinancialYear(); - // 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(); + $db = \Config\Database::connect(); - // 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(); - } + // 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, $salesManagerIds, $current_fin_year, $fin_years); + } elseif ($salesRole === 'Sales Manager') { + $this->salesManagerLevelDashboard($userId, $current_fin_year, $fin_years); + } +} + +// ───────────────────────────────────────────── +// branchLevelDashboard() +// ───────────────────────────────────────────── +public function branchLevelDashboard($branchId, $sales_manager_ids, $current_fin_year, $fin_years) +{ + try { + $sales_manager_ids = array_values(array_filter(array_map('intval', $sales_manager_ids))); + + $db = \Config\Database::connect(); + $fyRange = $this->getFYDateRange($current_fin_year); + $fyStart = $fyRange['start']; + $fyEnd = $fyRange['end']; + + if (empty($sales_manager_ids)) { + // ── No team members β€” return empty dashboard ── $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] - 'team' => $teamPerformance, - 'recent_acts' => $recent_activities, - 'pending_acts' => $pending_activities, - 'leads_overview' => $leadsOverview, - 'activity_breakdown'=> $activityBreakdown, - 'tab_name' => "Sales Dashboard", - 'page_name' => "Sales Dashboard" + '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', ]; - - // dd($data); - $this->loadLayout('sales/branch_level_dashboard_view', $data); - - // return view('sales/dashboard_view', $data); - - } catch (\Exception $e) { - return $this->failServerError($e->getMessage()); + return; } + + // 1. Lead count β€” FY filtered by created_at + $total_leads = $this->leadModel + ->whereIn('assigned_to', $sales_manager_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', $sales_manager_ids) + ->where('scheduled_date >=', $fyStart) + ->where('scheduled_date <=', $fyEnd) + ->countAllResults(); + + // 3. Completed activities β€” FY filtered + $total_completed_activity = $this->activityModel + ->whereIn('assigned_to', $sales_manager_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', $sales_manager_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', $sales_manager_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', $sales_manager_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, + 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', $sales_manager_ids) + ->where('sal.created_at >=', $fyStart) + ->where('sal.created_at <=', $fyEnd) + ->groupBy('sal.lead_id, sal.company_name, sal.status, 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, + ROUND(COUNT(*) * 100.0 / {$total_activity_safe}, 0) AS percentage", false) + ->whereIn('assigned_to', $sales_manager_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); + + // 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_amt' => $targetAmt, + 'achieved_amt' => $achievedAmt, + 'grad' => $palette['grad'], + 'color' => $palette['color'], + 'splits' => $splits, + 'activities' => $activities, + ]; } - public function salesManagerLevelDashboard($userId,$sales_manager_ids, $payload = []) - { - // $userId = get_session_userid(); - // $userId = 1; - $db = \Config\Database::connect(); + return $result; +} - try { +// ───────────────────────────────────────────── +// 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 = []; - // $payload = $this->request->getGet(); - $current_fin_year = $payload['fy'] ?? getCurrentFinancialYear(); + foreach ($sales_manager_ids as $uid) { - - $fin_years = $db->table('sales_target') - ->select('fy_year') - ->where('user_id', $userId) - ->orderBy('fy_year', 'desc') - ->get() - ->getResultArray(); + // ── Target ── + $targetRow = $db->table('sales_target') + ->where('user_id', $uid) + ->where('fy_year', $fy) + ->get() + ->getRowArray(); + $targetAmt = (float)($targetRow['target_amount'] ?? 0); - $fin_years = array_column($fin_years, 'fy_year'); + // ── 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(); - if(empty($fin_years)){ - $fin_years[] = $current_fin_year; - } + // ── Totals derived from policy_transaction ── + $totalPolicies = count($policyRows); + $totalExpAmt = array_sum(array_column($policyRows, 'amount')); - $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(), + // 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); - $myLeadsCount = $this->leadModel->where('assigned_to', $userId)->countAllResults(); + // ── Won Leads for this user in FY (Table 2 in modal) ── + // leads.actual_lead_id maps to sales_actual_leads.id + // leads.type: 1 = EB, else = Non-EB + $wonLeads = $db->query(" + SELECT + sal.lead_id, + sal.company_name AS company, + CASE WHEN l.lead_type = 1 THEN 'EB' ELSE 'Non-EB' 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(); - $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" - ]; - - // 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()); - } + $result[$uid] = [ + 'total_policies' => $totalPolicies, + 'total_exp_amt' => $totalExpAmt, + 'target_amt' => $targetAmt, + 'policies' => $policies, + 'won_leads' => $wonLeads, // for Table 2 in modal Tab 2 + ]; } - public function getUserAchievedAmount($financialYear, $userId) - { - // Split the string into two years - $years = explode('-', $financialYear); - $startYear = $years[0]; // 2025 - $endYear = $years[1]; // 2026 + return $result; +} - // Create the timestamps - $startFY = $startYear . '-04-01 00:00:00'; - $endFY = $endYear . '-03-31 23:59:59'; +// ───────────────────────────────────────────── +// 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" - $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) + $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(); - return $achievedAmountData[0]['achieved_amount'] ?? 0.00; + // ------------------------------- + // 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) { diff --git a/app/Models/LeadsModel.php b/app/Models/LeadsModel.php index 72ac2f59..3ba0e2bd 100644 --- a/app/Models/LeadsModel.php +++ b/app/Models/LeadsModel.php @@ -47,6 +47,7 @@ class LeadsModel extends Model 'proposel_data', 'status', 'notes', + 'lost_reason', 'created_at', 'created_by', 'updated_at', diff --git a/app/Models/SalesActivityModel.php b/app/Models/SalesActivityModel.php index e9a3801d..a51713dd 100644 --- a/app/Models/SalesActivityModel.php +++ b/app/Models/SalesActivityModel.php @@ -25,6 +25,7 @@ class SalesActivityModel extends Model 'completion_notes', 'completed_date', 'parent_activity_id', + 'additional_assigned_ids', 'created_by', 'updated_by' ]; @@ -64,8 +65,14 @@ class SalesActivityModel extends Model */ public function getActivitiesByLead($leadId, $status = null) { - $builder = $this->select('sales_activities.*, user_profiles.first_name as assigned_to_name') - ->join('user_profiles', 'user_profiles.id = sales_activities.assigned_to', 'left') + // Convert the INT id to a string, then wrap it in JSON quotes to match ["5", "11"] + $subQuery = "(SELECT GROUP_CONCAT(up2.first_name SEPARATOR ', ') + FROM user_profiles up2 + WHERE JSON_CONTAINS(sales_activities.additional_assigned_ids, JSON_QUOTE(CAST(up2.id AS CHAR))) + ) as additional_assigned_names"; + + $builder = $this->select("sales_activities.*, up1.first_name as assigned_to_name, $subQuery") + ->join('user_profiles as up1', 'up1.id = sales_activities.assigned_to', 'left') ->where('sales_activities.lead_id', $leadId); if ($status) { @@ -78,7 +85,7 @@ class SalesActivityModel extends Model /** * Get all sales_activities with filters */ - public function getActivitiesWithFilters($filters = [], $limit = 10, $offset = 0) + public function getActivitiesWithFiltersOLD($filters = [], $limit = 10, $offset = 0) { $this->select('sales_activities.*, sales_actual_leads.company_name, user_profiles.first_name as assigned_to_name') ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id', 'left') @@ -139,6 +146,90 @@ class SalesActivityModel extends Model // ]; } + public function getActivitiesWithFilters($filters = [], $limit = 10, $offset = 0) + { + // 1. Initial Selection + $builder = $this->select(" + sales_activities.*, + sales_actual_leads.company_name, + up1.first_name as assigned_to_name, + GROUP_CONCAT(DISTINCT up2.first_name SEPARATOR ', ') as additional_assigned_names + ") + ->join('sales_actual_leads', 'sales_actual_leads.lead_id = sales_activities.lead_id', 'left') + ->join('user_profiles as up1', 'up1.id = sales_activities.assigned_to', 'left'); + + // 2. The JSON Join for additional names + // Only attempts join if the string looks like a JSON array + $builder->join('user_profiles as up2', " + sales_activities.additional_assigned_ids IS NOT NULL + AND sales_activities.additional_assigned_ids != '' + AND sales_activities.additional_assigned_ids != '[]' + AND JSON_VALID(sales_activities.additional_assigned_ids) + AND JSON_CONTAINS(sales_activities.additional_assigned_ids, JSON_QUOTE(CAST(up2.id AS CHAR))) + ", 'left'); + + // 3. Apply Filters + if (!empty($filters['status'])) { + $builder->where('sales_activities.status', $filters['status']); + } + + if (!empty($filters['activity_type'])) { + $builder->where('sales_activities.activity_type', $filters['activity_type']); + } + + if (!empty($filters['assigned_to'])) { + $assignedToIds = is_array($filters['assigned_to']) ? $filters['assigned_to'] : explode(',', $filters['assigned_to']); + $builder->whereIn('sales_activities.assigned_to', $assignedToIds); + } + + if (!empty($filters['search'])) { + $builder->groupStart() + ->like('sales_actual_leads.company_name', $filters['search']) + ->orLike('up1.first_name', $filters['search']) + ->groupEnd(); + } + + // 4. Grouping & Ordering + $builder->groupBy('sales_activities.activity_id'); + $builder->orderBy('sales_activities.created_at', 'DESC'); + + // 5. Calculate Counts (using a clean builder to avoid the syntax error) + $counts = $this->getActivityStatusCounts($filters); + + // 6. Get Data and Total + // Use true for countAllResults to get an accurate count of grouped rows + $totalCountQuery = clone $builder; + $total = $totalCountQuery->countAllResults(false); + + $data = $builder->findAll($limit, $offset); + + return [ + 'data' => $data, + 'total' => $total, + 'counts' => $counts + ]; + } + + /** + * Helper function to get counts without breaking the main query syntax + */ + private function getActivityStatusCounts($filters) + { + $validStatuses = ['pending', 'completed']; + $counts = ['all' => 0, 'pending' => 0, 'completed' => 0]; + + foreach ($validStatuses as $status) { + $query = $this->db->table('sales_activities')->where('status', $status); + if (!empty($filters['assigned_to'])) { + $ids = is_array($filters['assigned_to']) ? $filters['assigned_to'] : explode(',', $filters['assigned_to']); + $query->whereIn('assigned_to', $ids); + } + $counts[$status] = $query->countAllResults(); + } + $counts['all'] = $counts['pending'] + $counts['completed']; + return $counts; + } + /** * Complete an activity */ diff --git a/app/Models/SalesActualLeadModel.php b/app/Models/SalesActualLeadModel.php index 22db6a2a..a5ca1026 100644 --- a/app/Models/SalesActualLeadModel.php +++ b/app/Models/SalesActualLeadModel.php @@ -123,7 +123,38 @@ class SalesActualLeadModel extends Model $data = $this->findAll($limit, $offset); - return ['data' => $data,'total' => $total]; + $counts = $this->getLeadStatusCounts($filters); + + return ['data' => $data,'total' => $total,'counts' => $counts]; + } + + /** + * Helper function to get counts without breaking the main query syntax + */ + private function getLeadStatusCounts($filters) + { + + $validStatuses = ['New', 'Potential', 'Prospects', 'Not a Prospects']; + $counts = ['all' => 0, 'New' => 0, 'Potential' => 0, 'Prospects' => 0, 'Not a Prospects' => 0]; + + foreach ($validStatuses as $status) { + $countQuery = $this->db->table('sales_actual_leads') + ->whereIn('status', $validStatuses); + + // Apply assigned_to filter to counts too + if (!empty($filters['assigned_to'])) { + $assignedToIds = is_array($filters['assigned_to']) + ? $filters['assigned_to'] + : explode(',', $filters['assigned_to']); + $countQuery->whereIn('assigned_to', $assignedToIds); + } + + $counts[$status] = $countQuery->where('status', $status)->countAllResults(); + } + + $counts['all'] = array_sum($counts); + + return $counts; } /** diff --git a/app/Views/leads_form.php b/app/Views/leads_form.php index eeef3d1b..089bff40 100644 --- a/app/Views/leads_form.php +++ b/app/Views/leads_form.php @@ -401,6 +401,12 @@ +
@@ -778,7 +784,8 @@ $('#client_name').val(res.data.client_name); $('#client_short_name').val(res.data.client_short_name); $('#entity_type_id').val(res.data.entity_type_id); - $('#lead_status').val(res.data.status); + $('#lead_status').val(res.data.status).trigger('change'); + $('#lost_reason').val(res.data.lost_reason || ''); $('#notes').val(res.data.notes); setTimeout(function() { @@ -2431,5 +2438,66 @@ }); + $(document).ready(function() { + + var actual_lead_client_details = ; + var actual_lead_contact_person_details = ; + + // ------------------------------- + // CLIENT DETAILS AUTO FILL + // ------------------------------- + if (actual_lead_client_details) { + + $('#client_name').val(actual_lead_client_details.company_name || ''); + + $('#gst').val(actual_lead_client_details.gst_number || ''); + + // πŸ”₯ Important: + // Only auto-generate short name IF empty (avoid overwrite in edit) + if (!$('#client_short_name').val()) { + $('#client_name').trigger('input'); + } else { + // Run duplicate validation once + validateInput($('#client_short_name')[0], "clients", "short_name"); + } + } + + // ------------------------------- + // CONTACT PERSON AUTO FILL + // ------------------------------- + if (actual_lead_contact_person_details) { + + $('#contact_person_name').val(actual_lead_contact_person_details.name || ''); + + $('#contact_person_mobile').val(actual_lead_contact_person_details.mobile || ''); + + $('#contact_person_email').val(actual_lead_contact_person_details.email || ''); + } + + + $('#lead_status').change(function() { + if ($(this).val() === 'lost') { + $('#lost_reason_div').show(); + $('#lost_reason').attr('required', 'required'); + } else { + $('#lost_reason_div').hide(); + $('#lost_reason').val(""); + $('#lost_reason').removeAttr('required'); + } + }); + + // Check on page load + if ($('#lead_status').val() === 'lost') { + $('#lost_reason_div').show(); + $('#lost_reason').attr('required', 'required'); + } else { + // Ensure it's hidden and not required if the initial value is not 'lost' + $('#lost_reason_div').hide(); + $('#lost_reason').val(""); + $('#lost_reason').removeAttr('required'); + } + }); + + //----------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/app/Views/leads_non_eb.php b/app/Views/leads_non_eb.php index c6749816..c4caca5f 100644 --- a/app/Views/leads_non_eb.php +++ b/app/Views/leads_non_eb.php @@ -410,6 +410,12 @@
+
@@ -1258,4 +1264,63 @@ } }); + $(document).ready(function() { + + var actual_lead_client_details = ; + var actual_lead_contact_person_details = ; + + // ------------------------------- + // CLIENT DETAILS AUTO FILL + // ------------------------------- + if (actual_lead_client_details) { + + $('#client_name').val(actual_lead_client_details.company_name || ''); + + $('#gst').val(actual_lead_client_details.gst_number || ''); + + // πŸ”₯ Important: + // Only auto-generate short name IF empty (avoid overwrite in edit) + if (!$('#client_short_name').val()) { + $('#client_name').trigger('input'); + } else { + // Run duplicate validation once + validateInput($('#client_short_name')[0], "clients", "short_name"); + } + } + + // ------------------------------- + // CONTACT PERSON AUTO FILL + // ------------------------------- + if (actual_lead_contact_person_details) { + + $('#contact_person_name').val(actual_lead_contact_person_details.name || ''); + + $('#contact_person_mobile').val(actual_lead_contact_person_details.mobile || ''); + + $('#contact_person_email').val(actual_lead_contact_person_details.email || ''); + } + + + $('#lead_status').change(function() { + if ($(this).val() === 'lost') { + $('#lost_reason_div').show(); + $('#lost_reason').attr('required', 'required'); + } else { + $('#lost_reason_div').hide(); + $('#lost_reason').val(""); + $('#lost_reason').removeAttr('required'); + } + }); + + // Check on page load + if ($('#lead_status').val() === 'lost') { + $('#lost_reason_div').show(); + $('#lost_reason').attr('required', 'required'); + } else { + // Ensure it's hidden and not required if the initial value is not 'lost' + $('#lost_reason_div').hide(); + $('#lost_reason').val(""); + $('#lost_reason').removeAttr('required'); + } + }); \ No newline at end of file diff --git a/app/Views/sales/activity_view.php b/app/Views/sales/activity_view.php index 3edb0181..da457b03 100644 --- a/app/Views/sales/activity_view.php +++ b/app/Views/sales/activity_view.php @@ -10,6 +10,8 @@ .btn-complete:hover { background: #4caf50; transform: translateY(-1px); } .btn-view { background: #f0f0f0; color: #666; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s;} .btn-view:hover { background: #f0f0f0; transform: translateY(-1px); } + .btn-close { background: none; border: none; font-size: 22px; cursor: pointer; color: #888; width: 32px; height: 32px; border-radius: 6px; display: flex; align-items: center; justify-content: center; transition: background .2s; } + .btn-close:hover { background: #f0f0f0; color: #333; } /* Filter Tabs */ .filter-tabs { display: flex; gap: 10px; padding: 20px 30px; } @@ -68,6 +70,7 @@ .activity-meta { display: flex; gap: 20px; font-size: 13px; color: #999; margin-top: 10px;} .lead-status { display: inline-block; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 500; margin-top: 5px; } + .opportunity-status-badge { padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 500;} .status-new { background: #e3f2fd; color: #1976d2; } .status-potential { background: #fff3e0; color: #f57c00; } @@ -76,6 +79,10 @@ .status-pending { background: #fff3e0; color: #f57c00; } .status-completed { background: #e8f5e9; color: #388e3c; } .status-unknown { background: #000; color: #fff; } + .status-text-unknown { color: #000; font-weight: bold; } + .status-text-lost { color: #d32f2f; font-weight: bold; } + .status-text-won { color: #388e3c; font-weight: bold; } + /* Modals */ .modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 2000; align-items: center; justify-content: center; } @@ -124,6 +131,9 @@ .opportunity-details { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 15px;} .opportunity-detail-item { font-size: 13px; color: #666;} .opportunity-footer { margin-top: 15px; padding-top: 15px; border-top: 1px solid #f0f0f0; font-size: 13px; color: #666; } + .lost-reason { margin-bottom: 8px; color: #c0392b; /* soft red for lost */ } + .footer-divider { border-top: 1px dashed #ddd; margin: 8px 0; } + .notes { color: #555; } /* Base style for both tabs */ .tab-item { cursor: pointer; padding-bottom: 10px; margin: 0; font-size: 16px; color: #999; /* Default grey for unselected */ border-bottom: 2px solid transparent; transition: all 0.2s ease; } @@ -142,6 +152,21 @@ outline: none; } +.select2-container--default +.select2-selection--multiple +.select2-selection__choice { + background-color: #02a8b5 !important; + border: none !important; + border-color: #fff !important; + color: #fff !important; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice__remove { + color: #fff !important; +} +.modal .select2-container--default .select2-selection--multiple { + background-color: #fff !important; +} + @@ -159,7 +184,7 @@
+ placeholder="Search activities..." onkeyup="fetchActivities(false)" style="width: 300px !important;"> @@ -191,7 +216,7 @@

Lead Detail

- +

@@ -368,7 +409,7 @@
- @@ -133,6 +133,9 @@
--> + + + 'πŸ“ž', 'Email' => 'βœ‰οΈ', 'Meeting' => 'πŸ“…', 'Visit' => 'πŸš—', 'Demo' => 'πŸ–₯️', 'Share Docs' => 'πŸ“„', 'To Do' => 'βœ“' ]; $icon = $activityIcons[$u['activity_type']] ?? 'πŸ“Œ'; @@ -160,11 +163,19 @@
+ + +
+ No Upcoming Activities for this Financial Year. +
+ +

My Recent Leads

+
+ placeholder="Search leads..." onkeyup="fetchLeads(false)" style="width: 300px !important;"> @@ -166,7 +189,7 @@ @@ -228,7 +251,7 @@

Lead Detail

- +