nhance/app/Controllers/SalesController.php

2730 lines
105 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use App\Models\SalesActualLeadModel;
use App\Models\SalesContactPersonModel;
use App\Models\SalesActivityModel;
use App\Models\SalesLeadNoteModel;
use App\Models\SalesTargetModel;
use App\Models\UserModel;
use App\Models\ClientModel;
use App\Helpers\MailHelper;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\API\ResponseTrait;
class SalesController extends BaseController
{
use ResponseTrait;
protected $leadModel;
protected $contactModel;
protected $activityModel;
protected $noteModel;
protected $userModel;
protected $targetModel;
protected $clientModel;
public function __construct()
{
$this->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 '<div class="col-12">
<div class="card text-center">
<div class="card-body">
<h3>Oops! ' . esc($page_type) . ' is under construction.</h3>
<img src="https://omjsblog.files.wordpress.com/2023/07/errorimg.png" alt="Error Image" style="max-width: 100%; height: auto;">
</div>
</div>
</div>';
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 (AprJun {$sy})", 'start' => "{$sy}-04-01", 'end' => "{$sy}-06-30"],
['name' => 'Q2', 'label' => "Q2 (JulSep {$sy})", 'start' => "{$sy}-07-01", 'end' => "{$sy}-09-30"],
['name' => 'Q3', 'label' => "Q3 (OctDec {$sy})", 'start' => "{$sy}-10-01", 'end' => "{$sy}-12-31"],
['name' => 'Q4', 'label' => "Q4 (JanMar {$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 '<p>Hello Team,</p>'
. '<p>A new lead has been created</p>'
. '<h3>Lead Details</h3>'
. '<p><strong>Company Name :</strong> ' . $e($companyName) . '</p>'
. '<p><strong>Email :</strong> ' . $e($leadEmail) . '</p>'
. '<p><strong>Mobile :</strong> ' . $e($leadMobile) . '</p>'
. '<p><strong>Status :</strong> ' . $e($leadStatus) . '</p>'
. '<h3>Created By</h3>'
. '<p><strong>Name :</strong> ' . $e($createdByName) . '</p>'
. '<p><strong>Email :</strong> ' . $e($createdByEmail) . '</p>'
. '<p><strong>Branch :</strong> ' . $e($createdByBranch) . '</p>'
. '<h3>Assigned To</h3>'
. '<p><strong>Name :</strong> ' . $e($assignedUserName) . '</p>'
. '<p><strong>Email :</strong> ' . $e($assignedUserEmail) . '</p>'
. '<h3>Created On</h3>'
. '<p><strong>Date &amp; Time :</strong> ' . $e($createdDateTime) . '</p>'
. '<p>Regards,<br>NHANCE INDIA INSURANCE BROKING PRIVATE LIMITED</p>';
}
}