2568 lines
98 KiB
PHP
2568 lines
98 KiB
PHP
<?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 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());
|
||
}
|
||
|
||
return $this->respondCreated(['status' => 'success', 'id' => $this->leadModel->getInsertID()]);
|
||
} catch (\Exception $e) {
|
||
return $this->failServerError($e->getMessage());
|
||
}
|
||
}
|
||
|
||
// ==================== LEAD APIs ====================
|
||
|
||
/**
|
||
* Get all leads with pagination and filters
|
||
* GET /api/sales/leads
|
||
*/
|
||
public function getLeads()
|
||
{
|
||
try {
|
||
$limit = $this->request->getGet('limit') ?? 10;
|
||
$offset = $this->request->getGet('offset') ?? 0;
|
||
|
||
$filters = [
|
||
'status' => $this->request->getGet('status'),
|
||
'assigned_to' => $this->request->getGet('assigned_to'),
|
||
'search' => $this->request->getGet('search'),
|
||
'financial_year' => $this->request->getGet('financial_year'),
|
||
];
|
||
$filters = $this->applyFinancialYearDateRange($filters);
|
||
|
||
$result = $this->leadModel->getLeadsWithFilters($filters, $limit, $offset);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'data' => $result['data'],
|
||
'total' => $result['total'],
|
||
'limit' => $limit,
|
||
'offset' => $offset,
|
||
'counts' => $result['counts'],
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Export leads filtered by created_at date range.
|
||
* GET /sales/leads/export?from_date=YYYY-MM-DD&to_date=YYYY-MM-DD
|
||
*/
|
||
public function exportLeads()
|
||
{
|
||
try {
|
||
$range = $this->getExportDateRange();
|
||
} catch (\InvalidArgumentException $e) {
|
||
return $this->failValidationErrors($e->getMessage());
|
||
}
|
||
|
||
try {
|
||
$db = \Config\Database::connect();
|
||
$builder = $db->table('sales_actual_leads sal')
|
||
->select('
|
||
sal.company_name,
|
||
c.short_name,
|
||
sal.address,
|
||
sal.website,
|
||
sal.gst_number,
|
||
sal.email,
|
||
sal.phone,
|
||
sal.status,
|
||
sal.created_at,
|
||
up.first_name as assigned_to_name,
|
||
COUNT(DISTINCT sa.activity_id) as total_activity
|
||
')
|
||
->join('clients c', 'c.id = sal.client_id', 'left')
|
||
->join('user_profiles up', 'up.id = sal.assigned_to', 'left')
|
||
->join('sales_activities sa', 'sa.lead_id = sal.lead_id', 'left')
|
||
->where("sal.created_at BETWEEN {$db->escape($range['from'])} AND {$db->escape($range['to'])}", null, false);
|
||
|
||
$status = $this->request->getGet('status');
|
||
if (!empty($status)) {
|
||
$builder->where('sal.status', $status);
|
||
}
|
||
|
||
$assignedToIds = $this->getExportAssignedToIds();
|
||
if (!empty($assignedToIds)) {
|
||
$builder->whereIn('sal.assigned_to', $assignedToIds);
|
||
}
|
||
|
||
$search = $this->request->getGet('search');
|
||
if (!empty($search)) {
|
||
$builder->groupStart()
|
||
->like('sal.company_name', $search)
|
||
->orLike('c.short_name', $search)
|
||
->orLike('sal.address', $search)
|
||
->orLike('sal.website', $search)
|
||
->orLike('sal.gst_number', $search)
|
||
->orLike('sal.email', $search)
|
||
->orLike('sal.phone', $search)
|
||
->orLike('up.first_name', $search)
|
||
->groupEnd();
|
||
}
|
||
|
||
$leads = $builder
|
||
->groupBy('sal.lead_id, sal.company_name, c.short_name, sal.address, sal.website, sal.gst_number, sal.email, sal.phone, sal.status, sal.created_at, up.first_name')
|
||
->orderBy('sal.created_at', 'DESC')
|
||
->get()
|
||
->getResultArray();
|
||
|
||
if (empty($leads)) {
|
||
return $this->exportNotFoundResponse("Leads Not Found {$range['display']}");
|
||
}
|
||
|
||
$rows = [];
|
||
$serialNo = 1;
|
||
foreach ($leads as $lead) {
|
||
$rows[] = [
|
||
$serialNo++,
|
||
$lead['company_name'] ?? '',
|
||
$lead['short_name'] ?? '',
|
||
$lead['address'] ?? '',
|
||
$lead['website'] ?? '',
|
||
$lead['gst_number'] ?? '',
|
||
$lead['email'] ?? '',
|
||
$lead['phone'] ?? '',
|
||
$lead['status'] ?? '',
|
||
$lead['assigned_to_name'] ?? '',
|
||
$lead['total_activity'] ?? 0,
|
||
$this->formatExportDate($lead['created_at'] ?? ''),
|
||
];
|
||
}
|
||
|
||
return $this->streamCsvDownload(
|
||
"sales-leads-{$range['from_label']}-to-{$range['to_label']}.csv",
|
||
['S.No', 'Company', 'Company Short Name', 'Address', 'Website', 'GST', 'Email', 'Phone', 'Status', 'Assigned To', 'Total Activity', 'Created At'],
|
||
$rows
|
||
);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get single lead with complete details
|
||
* GET /api/sales/leads/{id}
|
||
*/
|
||
public function getLead($id)
|
||
{
|
||
try {
|
||
$lead = $this->leadModel->getLeadComplete($id);
|
||
|
||
if (!$lead) {
|
||
return $this->failNotFound('Lead not found');
|
||
}
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'data' => $lead
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Update lead
|
||
* PUT /api/sales/leads/{id}
|
||
*/
|
||
public function updateLead($id)
|
||
{
|
||
try {
|
||
if (!$this->leadModel->find((int)$id)) {
|
||
return $this->failNotFound('Lead not found');
|
||
}
|
||
|
||
$data = $this->request->getJSON(true);
|
||
$data['updated_by'] = $this->getUserId();
|
||
|
||
// ── Client logic — only runs if client_id key exists in payload ──
|
||
if (array_key_exists('client_id', $data)) {
|
||
|
||
$clientId = !empty($data['client_id']) ? (int)$data['client_id'] : null;
|
||
|
||
$clientData = array_filter([
|
||
'client_name' => $data['company_name'] ?? null,
|
||
'short_name' => $data['short_name'] ?? null,
|
||
'email' => $data['email'] ?? null,
|
||
'phone' => $data['phone'] ?? null,
|
||
], fn($v) => $v !== null && $v !== '');
|
||
|
||
if ($clientId) {
|
||
// Existing client — update if we have data
|
||
if (!empty($clientData)) {
|
||
$clientData['updated_by'] = $this->getUserId();
|
||
$this->clientModel->update($clientId, $clientData);
|
||
}
|
||
} else {
|
||
// New client — insert and get ID
|
||
$clientData['created_by'] = $this->getUserId();
|
||
$clientData['is_active'] = 1;
|
||
$this->clientModel->insert($clientData);
|
||
$clientId = $this->clientModel->getInsertID();
|
||
}
|
||
|
||
$data['client_id'] = $clientId;
|
||
}
|
||
|
||
// ── Clean up UI-only fields before inserting lead ────────────────
|
||
unset($data['short_name']);
|
||
|
||
if (!$this->leadModel->update($id, $data)) {
|
||
return $this->fail($this->leadModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
|
||
}
|
||
|
||
$lead = $this->leadModel->getLeadComplete($id);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'message' => 'Lead updated successfully',
|
||
'data' => $lead
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Delete lead
|
||
* DELETE /api/sales/leads/{id}
|
||
*/
|
||
public function deleteLead($id)
|
||
{
|
||
try {
|
||
if (!$this->leadModel->find((int)$id)) {
|
||
return $this->failNotFound('Lead not found');
|
||
}
|
||
|
||
$this->leadModel->delete($id);
|
||
|
||
return $this->respondDeleted([
|
||
'status' => 'success',
|
||
'message' => 'Lead deleted successfully'
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get lead statistics
|
||
* GET /api/sales/leads/stats
|
||
*/
|
||
public function getLeadStats()
|
||
{
|
||
try {
|
||
$userId = $this->request->getGet('user_id');
|
||
$stats = $this->leadModel->getLeadStats($userId);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'data' => $stats
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* GET /sales/searchClients?q=tech
|
||
*/
|
||
public function searchClients()
|
||
{
|
||
$q = trim($this->request->getGet('q') ?? '');
|
||
|
||
if (strlen($q) < 1) {
|
||
return $this->response
|
||
->setContentType('application/json')
|
||
->setBody(json_encode(['data' => []]));
|
||
}
|
||
|
||
$db = \Config\Database::connect();
|
||
|
||
$results = $db->table('clients')
|
||
->select('id, client_name, short_name, email, phone')
|
||
->like('client_name', $q)
|
||
->where('client_type', 1)
|
||
->where('is_active', 1)
|
||
->limit(10)
|
||
->get()
|
||
->getResultArray();
|
||
|
||
return $this->response
|
||
->setContentType('application/json')
|
||
->setBody(json_encode(['data' => $results]));
|
||
}
|
||
/**
|
||
* GET /sales/checkDuplicate?table=clients&field=short_name&value=TECH
|
||
*/
|
||
public function checkDuplicate()
|
||
{
|
||
if ($this->request->getMethod() !== 'get') {
|
||
return $this->response
|
||
->setStatusCode(405)
|
||
->setContentType('application/json')
|
||
->setBody(json_encode(['exists' => false]));
|
||
}
|
||
|
||
$table = $this->request->getGet('table');
|
||
$field = $this->request->getGet('field');
|
||
$value = trim($this->request->getGet('value') ?? '');
|
||
$exclude_id = $this->request->getGet('exclude_id');
|
||
|
||
// ── Whitelist (table → model, pk, allowed fields) ─────────
|
||
$allowed = [
|
||
'clients' => [
|
||
'model' => $this->clientModel,
|
||
'pk' => 'id',
|
||
'fields' => ['short_name', 'client_name'],
|
||
],
|
||
'sales_actual_leads' => [
|
||
'model' => $this->leadModel,
|
||
'pk' => 'lead_id',
|
||
'fields' => ['company_name'],
|
||
],
|
||
];
|
||
|
||
if (! isset($allowed[$table]) || ! in_array($field, $allowed[$table]['fields'], true)) {
|
||
return $this->response
|
||
->setStatusCode(400)
|
||
->setContentType('application/json')
|
||
->setBody(json_encode(['exists' => false, 'error' => 'Invalid table or field']));
|
||
}
|
||
|
||
if (empty($value)) {
|
||
return $this->response
|
||
->setContentType('application/json')
|
||
->setBody(json_encode(['exists' => false]));
|
||
}
|
||
|
||
$model = $allowed[$table]['model'];
|
||
$pk = $allowed[$table]['pk'];
|
||
|
||
// ── Case-insensitive match ────────────────────────────────
|
||
$model->where("LOWER({$field})", strtolower($value));
|
||
|
||
if (!empty($exclude_id)) {
|
||
$model->where("{$pk} !=", (int)$exclude_id);
|
||
}
|
||
|
||
$count = $model->countAllResults();
|
||
|
||
return $this->response
|
||
->setContentType('application/json')
|
||
->setBody(json_encode(['exists' => $count > 0]));
|
||
}
|
||
|
||
// ==================== CONTACT PERSON APIs ====================
|
||
|
||
/**
|
||
* Get contacts for a lead
|
||
* GET /api/sales/leads/{leadId}/contacts
|
||
*/
|
||
public function getContacts($leadId)
|
||
{
|
||
try {
|
||
$contacts = $this->contactModel->getContactsByLead($leadId);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'data' => $contacts
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Create contact person
|
||
* POST /api/sales/contacts
|
||
*/
|
||
public function createContact()
|
||
{
|
||
try {
|
||
$data = $this->request->getJSON(true);
|
||
$data['created_by'] = $this->getUserId();
|
||
$data['updated_by'] = $this->getUserId();
|
||
|
||
if (!$this->contactModel->insert($data)) {
|
||
return $this->fail($this->contactModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
|
||
}
|
||
|
||
$contactId = $this->contactModel->getInsertID();
|
||
$contact = $this->contactModel->find((int)$contactId);
|
||
|
||
return $this->respondCreated([
|
||
'status' => 'success',
|
||
'message' => 'Contact created successfully',
|
||
'data' => $contact
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Update contact person
|
||
* PUT /api/sales/contacts/{id}
|
||
*/
|
||
public function updateContact($id)
|
||
{
|
||
try {
|
||
if (!$this->contactModel->find((int)$id)) {
|
||
return $this->failNotFound('Contact not found');
|
||
}
|
||
|
||
$data = $this->request->getJSON(true);
|
||
$data['updated_by'] = $this->getUserId();
|
||
|
||
$contact = $this->contactModel->find($id);
|
||
|
||
if (isset($data['is_primary']) && $data['is_primary'] == 1) {
|
||
// Reset all contacts for this lead to 0 primary
|
||
$this->contactModel->where('lead_id', $contact['lead_id'])
|
||
->set(['is_primary' => 0])
|
||
->update();
|
||
}
|
||
|
||
if (!$this->contactModel->update((int)$id, $data)) {
|
||
return $this->fail($this->contactModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
|
||
}
|
||
|
||
$contact = $this->contactModel->find((int)$id);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'message' => 'Contact updated successfully',
|
||
'data' => $contact
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Delete contact person
|
||
* DELETE /api/sales/contacts/{id}
|
||
*/
|
||
public function deleteContact($id)
|
||
{
|
||
try {
|
||
if (!$this->contactModel->find((int)$id)) {
|
||
return $this->failNotFound('Contact not found');
|
||
}
|
||
|
||
$this->contactModel->delete((int)$id);
|
||
|
||
return $this->respondDeleted([
|
||
'status' => 'success',
|
||
'message' => 'Contact deleted successfully'
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Set primary contact
|
||
* PUT /api/sales/contacts/{id}/set-primary
|
||
*/
|
||
public function setPrimaryContact($id)
|
||
{
|
||
try {
|
||
$contact = $this->contactModel->find((int)$id);
|
||
|
||
if (!$contact) {
|
||
return $this->failNotFound('Contact not found');
|
||
}
|
||
|
||
$this->contactModel->setPrimaryContact($id, $contact['lead_id']);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'message' => 'Primary contact updated successfully'
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
// ==================== ACTIVITY APIs ====================
|
||
|
||
/**
|
||
* Get all activities with filters
|
||
* GET /api/sales/activities
|
||
*/
|
||
public function getActivities()
|
||
{
|
||
try {
|
||
$limit = $this->request->getGet('limit') ?? 10;
|
||
$offset = $this->request->getGet('offset') ?? 0;
|
||
|
||
$filters = [
|
||
'status' => $this->request->getGet('status'),
|
||
'activity_type' => $this->request->getGet('activity_type'),
|
||
'assigned_to' => $this->request->getGet('assigned_to'),
|
||
'date_from' => $this->request->getGet('date_from'),
|
||
'date_to' => $this->request->getGet('date_to'),
|
||
'financial_year' => $this->request->getGet('financial_year'),
|
||
'search' => $this->request->getGet('search'), // ← ADD THIS
|
||
];
|
||
$filters = $this->applyFinancialYearDateRange($filters);
|
||
|
||
$result = $this->activityModel->getActivitiesWithFilters($filters, $limit, $offset);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'data' => $result['data'],
|
||
'total' => $result['total'],
|
||
'limit' => $limit,
|
||
'offset' => $offset,
|
||
'counts' => $result['counts'],
|
||
], 200);
|
||
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Export activities filtered by created_at date range.
|
||
* GET /sales/activities/export?from_date=YYYY-MM-DD&to_date=YYYY-MM-DD
|
||
*/
|
||
public function exportActivities()
|
||
{
|
||
try {
|
||
$range = $this->getExportDateRange();
|
||
} catch (\InvalidArgumentException $e) {
|
||
return $this->failValidationErrors($e->getMessage());
|
||
}
|
||
|
||
try {
|
||
$db = \Config\Database::connect();
|
||
$builder = $db->table('sales_activities sa')
|
||
->select("
|
||
sa.activity_type,
|
||
sa.status,
|
||
sa.scheduled_date,
|
||
sa.created_at,
|
||
sa.notes,
|
||
sa.completion_notes,
|
||
sal.company_name,
|
||
c.short_name,
|
||
up1.first_name as assigned_to_name,
|
||
GROUP_CONCAT(DISTINCT up2.first_name SEPARATOR ', ') as additional_assigned_names
|
||
")
|
||
->join('sales_actual_leads sal', 'sal.lead_id = sa.lead_id', 'left')
|
||
->join('clients c', 'c.id = sal.client_id', 'left')
|
||
->join('user_profiles up1', 'up1.id = sa.assigned_to', 'left')
|
||
->join('user_profiles up2', "
|
||
sa.additional_assigned_ids IS NOT NULL
|
||
AND sa.additional_assigned_ids != ''
|
||
AND sa.additional_assigned_ids != '[]'
|
||
AND JSON_VALID(sa.additional_assigned_ids)
|
||
AND JSON_CONTAINS(sa.additional_assigned_ids, JSON_QUOTE(CAST(up2.id AS CHAR)))
|
||
", 'left')
|
||
->where("sa.created_at BETWEEN {$db->escape($range['from'])} AND {$db->escape($range['to'])}", null, false);
|
||
|
||
$status = $this->request->getGet('status');
|
||
if (!empty($status)) {
|
||
$builder->where('sa.status', $status);
|
||
}
|
||
|
||
$assignedToIds = $this->getExportAssignedToIds();
|
||
if (!empty($assignedToIds)) {
|
||
$builder->whereIn('sa.assigned_to', $assignedToIds);
|
||
}
|
||
|
||
$search = $this->request->getGet('search');
|
||
if (!empty($search)) {
|
||
$builder->groupStart()
|
||
->like('sal.company_name', $search)
|
||
->orLike('c.short_name', $search)
|
||
->orLike('sa.status', $search)
|
||
->orLike('sa.activity_type', $search)
|
||
->orLike('up1.first_name', $search)
|
||
->orLike('up2.first_name', $search)
|
||
->groupEnd();
|
||
}
|
||
|
||
$activities = $builder
|
||
->groupBy('sa.activity_id')
|
||
->orderBy('sa.created_at', 'DESC')
|
||
->get()
|
||
->getResultArray();
|
||
|
||
if (empty($activities)) {
|
||
return $this->exportNotFoundResponse("Activities not Found {$range['display']}");
|
||
}
|
||
|
||
$rows = [];
|
||
$serialNo = 1;
|
||
foreach ($activities as $activity) {
|
||
$rows[] = [
|
||
$serialNo++,
|
||
$activity['company_name'] ?? '',
|
||
$activity['short_name'] ?? '',
|
||
$activity['activity_type'] ?? '',
|
||
ucfirst((string) ($activity['status'] ?? '')),
|
||
$activity['assigned_to_name'] ?? '',
|
||
$activity['additional_assigned_names'] ?? '',
|
||
$this->formatExportDate($activity['scheduled_date'] ?? ''),
|
||
$this->formatExportDate($activity['created_at'] ?? ''),
|
||
$activity['notes'] ?: ($activity['completion_notes'] ?? ''),
|
||
];
|
||
}
|
||
|
||
return $this->streamCsvDownload(
|
||
"sales-activities-{$range['from_label']}-to-{$range['to_label']}.csv",
|
||
['S.No', 'Company', 'Company Short Name', 'Activity Type', 'Status', 'Assigned To', 'Additional Members', 'Scheduled Date', 'Created At', 'Notes'],
|
||
$rows
|
||
);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get activities for a specific lead
|
||
* GET /api/sales/leads/{leadId}/activities
|
||
*/
|
||
public function getLeadActivities($leadId)
|
||
{
|
||
try {
|
||
$status = $this->request->getGet('status');
|
||
$activities = $this->activityModel->getActivitiesByLead($leadId, $status);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'data' => $activities
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get activity timeline for a lead
|
||
* GET /api/sales/leads/{leadId}/timeline
|
||
*/
|
||
public function getActivityTimeline($leadId)
|
||
{
|
||
try {
|
||
$timeline = $this->activityModel->getActivityTimeline($leadId);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'data' => $timeline
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get upcoming activities for user
|
||
* GET /api/sales/activities/upcoming
|
||
*/
|
||
public function getUpcomingActivities()
|
||
{
|
||
try {
|
||
$userId = $this->getUserId();
|
||
$days = $this->request->getGet('days') ?? 7;
|
||
$limit = $this->request->getGet('limit') ?? 10;
|
||
|
||
$activities = $this->activityModel->getUpcomingActivities($userId, $days, $limit);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'data' => $activities
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Create activity
|
||
* POST /api/sales/activities
|
||
*/
|
||
public function createActivity()
|
||
{
|
||
try {
|
||
$data = $this->request->getJSON(true);
|
||
$data['created_by'] = $this->getUserId();
|
||
$data['updated_by'] = $this->getUserId();
|
||
|
||
// FIX: Convert the array to a JSON string so it fits in the VARCHAR column
|
||
if (isset($data['additional_assigned_ids']) && is_array($data['additional_assigned_ids'])) {
|
||
$data['additional_assigned_ids'] = json_encode($data['additional_assigned_ids']);
|
||
}
|
||
|
||
$activityId = $this->activityModel->insert($data);
|
||
if (!$activityId) {
|
||
return $this->fail($this->activityModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
|
||
}
|
||
|
||
$data['activity_id'] = $activityId;
|
||
|
||
// add google calender event
|
||
$response = $this->addCalenderEvent($data);
|
||
log_message("error", '[GOOGLE_CALENDER] addCalenderEvent response: ' . json_encode($response));
|
||
|
||
$activityId = $this->activityModel->getInsertID();
|
||
$activity = $this->activityModel->find((int)$activityId);
|
||
|
||
// OPTIONAL: Decode it back to an array for the API response so the frontend gets a clean array
|
||
if (isset($activity['additional_assigned_ids'])) {
|
||
$activity['additional_assigned_ids'] = json_decode($activity['additional_assigned_ids'], true);
|
||
}
|
||
|
||
return $this->respondCreated([
|
||
'status' => 'success',
|
||
'message' => 'Activity created successfully',
|
||
'data' => $activity,
|
||
'google_calender_response' => $response
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Update activity
|
||
* PUT /api/sales/activities/{id}
|
||
*/
|
||
public function updateActivity($id)
|
||
{
|
||
try {
|
||
if (!$this->activityModel->find((int)$id)) {
|
||
return $this->failNotFound('Activity not found');
|
||
}
|
||
|
||
$data = $this->request->getJSON(true);
|
||
$data['updated_by'] = $this->getUserId();
|
||
|
||
// FIX: Convert the array to a JSON string for updating
|
||
if (isset($data['additional_assigned_ids']) && is_array($data['additional_assigned_ids'])) {
|
||
$data['additional_assigned_ids'] = json_encode($data['additional_assigned_ids']);
|
||
}
|
||
|
||
if (!$this->activityModel->update($id, $data)) {
|
||
return $this->fail($this->activityModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
|
||
}
|
||
|
||
$activity = $this->activityModel->find((int)$id);
|
||
|
||
// OPTIONAL: Decode it back to an array for the API response
|
||
if (isset($activity['additional_assigned_ids'])) {
|
||
$activity['additional_assigned_ids'] = json_decode($activity['additional_assigned_ids'], true);
|
||
}
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'message' => 'Activity updated successfully',
|
||
'data' => $activity
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
* Delete activity
|
||
* DELETE /api/sales/activities/{id}
|
||
*/
|
||
public function deleteActivity($id)
|
||
{
|
||
try {
|
||
if (!$this->activityModel->find((int)$id)) {
|
||
return $this->failNotFound('Activity not found');
|
||
}
|
||
|
||
$this->activityModel->delete((int)$id);
|
||
|
||
return $this->respondDeleted([
|
||
'status' => 'success',
|
||
'message' => 'Activity deleted successfully'
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get pending activities count
|
||
* GET /api/sales/activities/pending-count
|
||
*/
|
||
public function getPendingCount()
|
||
{
|
||
try {
|
||
$userId = $this->getUserId();
|
||
$count = $this->activityModel->getPendingActivitiesCount($userId);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'data' => ['count' => $count]
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
// ==================== LEAD NOTES APIs ====================
|
||
|
||
/**
|
||
* Get notes for a lead
|
||
* GET /api/sales/leads/{leadId}/notes
|
||
*/
|
||
public function getLeadNotes($leadId)
|
||
{
|
||
try {
|
||
$notes = $this->noteModel->getNotesByLead($leadId);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'data' => $notes
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Create note
|
||
* POST /api/sales/notes
|
||
*/
|
||
public function createNote()
|
||
{
|
||
try {
|
||
$data = $this->request->getJSON(true);
|
||
$data['user_id'] = $this->getUserId();
|
||
$data['created_by'] = $this->getUserId();
|
||
$data['updated_by'] = $this->getUserId();
|
||
|
||
if (!$this->noteModel->insert($data)) {
|
||
return $this->fail($this->noteModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
|
||
}
|
||
|
||
$noteId = $this->noteModel->getInsertID();
|
||
$note = $this->noteModel->find((int)$noteId);
|
||
|
||
return $this->respondCreated([
|
||
'status' => 'success',
|
||
'message' => 'Note created successfully',
|
||
'data' => $note
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Update note
|
||
* PUT /api/sales/notes/{id}
|
||
*/
|
||
public function updateNote($id)
|
||
{
|
||
try {
|
||
$note = $this->noteModel->find((int)$id);
|
||
|
||
if (!$note) {
|
||
return $this->failNotFound('Note not found');
|
||
}
|
||
|
||
// Check if user owns the note
|
||
if ($note['user_id'] != $this->getUserId()) {
|
||
return $this->failUnauthorized('You are not authorized to update this note');
|
||
}
|
||
|
||
$data = $this->request->getJSON(true);
|
||
$data['updated_by'] = $this->getUserId();
|
||
|
||
if (!$this->noteModel->update($id, $data)) {
|
||
return $this->fail($this->noteModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
|
||
}
|
||
|
||
$updatedNote = $this->noteModel->find((int)$id);
|
||
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'message' => 'Note updated successfully',
|
||
'data' => $updatedNote
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Delete note
|
||
* DELETE /api/sales/notes/{id}
|
||
*/
|
||
public function deleteNote($id)
|
||
{
|
||
try {
|
||
$note = $this->noteModel->find((int)$id);
|
||
|
||
if (!$note) {
|
||
return $this->failNotFound('Note not found');
|
||
}
|
||
|
||
// Check if user owns the note
|
||
if ($note['user_id'] != $this->getUserId()) {
|
||
return $this->failUnauthorized('You are not authorized to delete this note');
|
||
}
|
||
|
||
$this->noteModel->delete((int)$id);
|
||
|
||
return $this->respondDeleted([
|
||
'status' => 'success',
|
||
'message' => 'Note deleted successfully'
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
// ==================== SALES TARGET APIs ====================
|
||
/**
|
||
* Get all sales targets
|
||
* GET /api/sales/targets
|
||
*/
|
||
public function getTargets()
|
||
{
|
||
try {
|
||
$targets = $this->targetModel->findAll();
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'data' => $targets
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get sales target by user
|
||
* GET /api/sales/targets/user/{userId}
|
||
*/
|
||
public function getTargetByUser($userId)
|
||
{
|
||
try {
|
||
$targets = $this->targetModel->where('user_id', $userId)->findAll();
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'data' => $targets
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get sales target by FY year
|
||
* GET /api/sales/targets/fy/{fyYear}
|
||
*/
|
||
public function getTargetByFY($fyYear)
|
||
{
|
||
try {
|
||
$targets = $this->targetModel->where('fy_year', $fyYear)->findAll();
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'data' => $targets
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Create sales target
|
||
* POST /api/sales/targets
|
||
*/
|
||
public function createTarget()
|
||
{
|
||
try {
|
||
$data = $this->request->getJSON(true);
|
||
$data['created_by'] = $this->getUserId();
|
||
$data['updated_by'] = $this->getUserId();
|
||
|
||
if (!$this->targetModel->insert($data)) {
|
||
return $this->fail($this->targetModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
|
||
}
|
||
|
||
$targetId = $this->targetModel->getInsertID();
|
||
$target = $this->targetModel->find((int)$targetId);
|
||
|
||
return $this->respondCreated([
|
||
'status' => 'success',
|
||
'message' => 'Sales target created successfully',
|
||
'data' => $target
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Update sales target
|
||
* PUT /api/sales/targets/{id}
|
||
*/
|
||
public function updateTarget($id)
|
||
{
|
||
try {
|
||
$target = $this->targetModel->find((int)$id);
|
||
if (!$target) {
|
||
return $this->failNotFound('Sales target not found');
|
||
}
|
||
|
||
$data = $this->request->getJSON(true);
|
||
$data['updated_by'] = $this->getUserId();
|
||
|
||
if (!$this->targetModel->update($id, $data)) {
|
||
return $this->fail($this->targetModel->errors(), ResponseInterface::HTTP_BAD_REQUEST);
|
||
}
|
||
|
||
$updatedTarget = $this->targetModel->find((int)$id);
|
||
return $this->respond([
|
||
'status' => 'success',
|
||
'message' => 'Sales target updated successfully',
|
||
'data' => $updatedTarget
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Delete sales target
|
||
* DELETE /api/sales/targets/{id}
|
||
*/
|
||
public function deleteTarget($id)
|
||
{
|
||
try {
|
||
$target = $this->targetModel->find((int)$id);
|
||
if (!$target) {
|
||
return $this->failNotFound('Sales target not found');
|
||
}
|
||
|
||
$this->targetModel->delete((int)$id);
|
||
return $this->respondDeleted([
|
||
'status' => 'success',
|
||
'message' => 'Sales target deleted successfully'
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR);
|
||
}
|
||
}
|
||
|
||
// ==================== HELPER METHODS ====================
|
||
|
||
/**
|
||
* Get current user ID from session or JWT token
|
||
* You'll need to implement this based on your authentication system
|
||
*/
|
||
private function getUserId()
|
||
{
|
||
// If using session
|
||
if (session()->has('user_id')) {
|
||
return session()->get('user_id');
|
||
}
|
||
|
||
// If using JWT, decode token and get user_id
|
||
// Example: return $this->decodeJWT()['user_id'];
|
||
|
||
// For development/testing, you can return a default value
|
||
return 1;
|
||
}
|
||
|
||
private function getSalesFilterYears(): array
|
||
{
|
||
$currentFinYear = getCurrentFinancialYear();
|
||
$db = \Config\Database::connect();
|
||
|
||
$finYearsRaw = $db->table('sales_target')
|
||
->select('fy_year', false)
|
||
->distinct()
|
||
->orderBy('fy_year', 'DESC')
|
||
->get()
|
||
->getResultArray();
|
||
|
||
$finYears = array_column($finYearsRaw, 'fy_year');
|
||
if (empty($finYears)) {
|
||
$finYears[] = $currentFinYear;
|
||
}
|
||
|
||
if (!in_array($currentFinYear, $finYears, true)) {
|
||
array_unshift($finYears, $currentFinYear);
|
||
}
|
||
|
||
return [
|
||
'fin_years' => $finYears,
|
||
'current_fin_year' => $currentFinYear,
|
||
];
|
||
}
|
||
|
||
private function applyFinancialYearDateRange(array $filters): array
|
||
{
|
||
if (empty($filters['financial_year'])) {
|
||
return $filters;
|
||
}
|
||
|
||
$fyRange = $this->getFYDateRange((string) $filters['financial_year']);
|
||
$filters['date_from'] = $fyRange['start'];
|
||
$filters['date_to'] = $fyRange['end'];
|
||
|
||
return $filters;
|
||
}
|
||
|
||
private function getExportDateRange(): array
|
||
{
|
||
$fromDate = trim((string) $this->request->getGet('from_date'));
|
||
$toDate = trim((string) $this->request->getGet('to_date'));
|
||
|
||
if ($fromDate === '' || $toDate === '') {
|
||
throw new \InvalidArgumentException('Please select from date and to date.');
|
||
}
|
||
|
||
$from = $this->normalizeExportDate($fromDate, '00:00:00');
|
||
$to = $this->normalizeExportDate($toDate, '23:59:59');
|
||
|
||
if ($from === null || $to === null) {
|
||
throw new \InvalidArgumentException('Invalid date range. Use YYYY-MM-DD format.');
|
||
}
|
||
|
||
if (strtotime($from) > strtotime($to)) {
|
||
throw new \InvalidArgumentException('From date cannot be after to date.');
|
||
}
|
||
|
||
return [
|
||
'from' => $from,
|
||
'to' => $to,
|
||
'from_label' => date('Y-m-d', strtotime($from)),
|
||
'to_label' => date('Y-m-d', strtotime($to)),
|
||
'display' => date('d-m-Y', strtotime($from)) . ' to ' . date('d-m-Y', strtotime($to)),
|
||
];
|
||
}
|
||
|
||
private function normalizeExportDate(string $date, string $time): ?string
|
||
{
|
||
$dateTime = \DateTime::createFromFormat('Y-m-d H:i:s', $date . ' ' . $time);
|
||
|
||
if (!$dateTime || $dateTime->format('Y-m-d') !== $date) {
|
||
return null;
|
||
}
|
||
|
||
return $dateTime->format('Y-m-d H:i:s');
|
||
}
|
||
|
||
private function getExportAssignedToIds(): array
|
||
{
|
||
$assignedTo = (string) $this->request->getGet('assigned_to');
|
||
|
||
if ($assignedTo === '') {
|
||
return [];
|
||
}
|
||
|
||
return array_values(array_filter(array_map('trim', explode(',', $assignedTo)), static function ($id) {
|
||
return ctype_digit($id);
|
||
}));
|
||
}
|
||
|
||
private function streamCsvDownload(string $filename, array $headers, array $rows)
|
||
{
|
||
$handle = fopen('php://temp', 'r+');
|
||
fwrite($handle, "\xEF\xBB\xBF");
|
||
fputcsv($handle, $headers);
|
||
|
||
foreach ($rows as $row) {
|
||
fputcsv($handle, $row);
|
||
}
|
||
|
||
rewind($handle);
|
||
$csv = stream_get_contents($handle);
|
||
fclose($handle);
|
||
|
||
return $this->response
|
||
->setHeader('Content-Type', 'text/csv; charset=UTF-8')
|
||
->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"')
|
||
->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate')
|
||
->setBody($csv);
|
||
}
|
||
|
||
private function exportNotFoundResponse(string $message)
|
||
{
|
||
return $this->response
|
||
->setStatusCode(ResponseInterface::HTTP_NOT_FOUND)
|
||
->setJSON([
|
||
'status' => 'error',
|
||
'message' => $message,
|
||
]);
|
||
}
|
||
|
||
private function formatExportDate(?string $value): string
|
||
{
|
||
if (empty($value)) {
|
||
return '';
|
||
}
|
||
|
||
$timestamp = strtotime($value);
|
||
if ($timestamp === false) {
|
||
return $value;
|
||
}
|
||
|
||
return date('d/m/Y h:i A', $timestamp);
|
||
}
|
||
|
||
|
||
// ==================== Dashboard ====================
|
||
|
||
// ─────────────────────────────────────────────
|
||
// HELPER: Build FY date range from fy_year string
|
||
// e.g. "2024-2025" → ['2024-04-01 00:00:00', '2025-03-31 23:59:59']
|
||
// ─────────────────────────────────────────────
|
||
private function getFYDateRange(string $financialYear): array
|
||
{
|
||
// Format: "2025-2026" — split on last hyphen to get start=2025, end=2026
|
||
$pos = strrpos($financialYear, '-');
|
||
$startYear = substr($financialYear, 0, $pos); // "2025"
|
||
$endYear = substr($financialYear, $pos + 1); // "2026"
|
||
|
||
return [
|
||
'start' => $startYear . '-04-01 00:00:00', // 2025-04-01 00:00:00
|
||
'end' => $endYear . '-03-31 23:59:59', // 2026-03-31 23:59:59
|
||
];
|
||
}
|
||
|
||
// ─────────────────────────────────────────────
|
||
// HELPER: FY quarters (Apr-Jun / Jul-Sep / Oct-Dec / Jan-Mar)
|
||
// ─────────────────────────────────────────────
|
||
private function getFYQuarters(string $financialYear): array
|
||
{
|
||
$pos = strrpos($financialYear, '-');
|
||
$sy = (int)substr($financialYear, 0, $pos); // 2025
|
||
$ey = (int)substr($financialYear, $pos + 1); // 2026
|
||
|
||
return [
|
||
['name' => 'Q1', 'label' => "Q1 (Apr–Jun {$sy})", 'start' => "{$sy}-04-01", 'end' => "{$sy}-06-30"],
|
||
['name' => 'Q2', 'label' => "Q2 (Jul–Sep {$sy})", 'start' => "{$sy}-07-01", 'end' => "{$sy}-09-30"],
|
||
['name' => 'Q3', 'label' => "Q3 (Oct–Dec {$sy})", 'start' => "{$sy}-10-01", 'end' => "{$sy}-12-31"],
|
||
['name' => 'Q4', 'label' => "Q4 (Jan–Mar {$ey})", 'start' => "{$ey}-01-01", 'end' => "{$ey}-03-31"],
|
||
];
|
||
}
|
||
|
||
// ─────────────────────────────────────────────
|
||
// dashboard() — entry point
|
||
// ─────────────────────────────────────────────
|
||
public function dashboard()
|
||
{
|
||
$payload = $this->request->getGet();
|
||
$base = $this->getSalesStaffData();
|
||
$salesRole = $base['sales_role'];
|
||
$salesManagerIds = $base['sales_manager_ids'];
|
||
$salesHeadManagerIds = $base['sales_manager_with_head'];
|
||
$userId = get_session_userid();
|
||
|
||
// Get branch id
|
||
$nhanceBranchId = $base['sales_manager'][0]['nhance_branch_id'] ?? null;
|
||
|
||
$current_fin_year = $payload['fy'] ?? getCurrentFinancialYear();
|
||
|
||
$db = \Config\Database::connect();
|
||
|
||
// Available FY years for dropdown
|
||
$fin_years_raw = $db->table('sales_target')
|
||
->select('fy_year', false) // false = no backtick escaping
|
||
->distinct()
|
||
->orderBy('fy_year', 'DESC')
|
||
->get()
|
||
->getResultArray();
|
||
|
||
$fin_years = array_column($fin_years_raw, 'fy_year');
|
||
|
||
if (empty($fin_years)) {
|
||
$fin_years[] = $current_fin_year;
|
||
}
|
||
|
||
// Ensure current FY is available in list
|
||
if (!in_array($current_fin_year, $fin_years)) {
|
||
array_unshift($fin_years, $current_fin_year);
|
||
}
|
||
|
||
// Route by role
|
||
if ($salesRole === 'Sales Head') {
|
||
$this->branchLevelDashboard($nhanceBranchId, $salesHeadManagerIds, $salesManagerIds, $current_fin_year, $fin_years);
|
||
} elseif ($salesRole === 'Sales Manager') {
|
||
$this->salesManagerLevelDashboard($userId, $current_fin_year, $fin_years);
|
||
}
|
||
}
|
||
|
||
// ─────────────────────────────────────────────
|
||
// branchLevelDashboard()
|
||
// ─────────────────────────────────────────────
|
||
public function branchLevelDashboard($branchId, $branchwise_all_sales_team_ids, $sales_manager_ids, $current_fin_year, $fin_years)
|
||
{
|
||
|
||
try {
|
||
$sales_manager_ids = array_values(array_filter(array_map('intval', $sales_manager_ids)));
|
||
$branchwise_all_sales_team_ids = array_column($branchwise_all_sales_team_ids, 'id');
|
||
|
||
|
||
$db = \Config\Database::connect();
|
||
$fyRange = $this->getFYDateRange($current_fin_year);
|
||
$fyStart = $fyRange['start'];
|
||
$fyEnd = $fyRange['end'];
|
||
|
||
if (empty($branchwise_all_sales_team_ids)) {
|
||
// ── No team members — return empty dashboard ──
|
||
$data = [
|
||
'total_leads' => 0,
|
||
'total_acts' => 0,
|
||
'total_completed_acts' => 0,
|
||
'total_pending_acts' => 0,
|
||
'team' => [],
|
||
'pending_acts' => [],
|
||
'leads_overview' => [],
|
||
'activity_breakdown' => [],
|
||
'team_achievement' => [],
|
||
'opp_achievement' => [],
|
||
'fin_years' => $fin_years,
|
||
'current_fin_year' => $current_fin_year,
|
||
'tab_name' => 'Sales Dashboard',
|
||
'page_name' => 'Sales Dashboard',
|
||
];
|
||
$this->loadLayout('sales/branch_level_dashboard_view', $data);
|
||
return;
|
||
}
|
||
|
||
// 1. Lead count — FY filtered by created_at
|
||
$total_leads = $this->leadModel
|
||
->whereIn('assigned_to', $branchwise_all_sales_team_ids)
|
||
->where('created_at >=', $fyStart)
|
||
->where('created_at <=', $fyEnd)
|
||
->countAllResults();
|
||
// 2. Total activities — FY filtered by scheduled_date
|
||
$total_activity = $this->activityModel
|
||
->whereIn('assigned_to', $branchwise_all_sales_team_ids)
|
||
->where('scheduled_date >=', $fyStart)
|
||
->where('scheduled_date <=', $fyEnd)
|
||
->countAllResults();
|
||
|
||
// 3. Completed activities — FY filtered
|
||
$total_completed_activity = $this->activityModel
|
||
->whereIn('assigned_to', $branchwise_all_sales_team_ids)
|
||
->where('status', 'completed')
|
||
->where('scheduled_date >=', $fyStart)
|
||
->where('scheduled_date <=', $fyEnd)
|
||
->countAllResults();
|
||
|
||
// 4. Pending activities — FY filtered
|
||
$total_pending_activity = $this->activityModel
|
||
->whereIn('assigned_to', $branchwise_all_sales_team_ids)
|
||
->where('status', 'pending')
|
||
->where('scheduled_date >=', $fyStart)
|
||
->where('scheduled_date <=', $fyEnd)
|
||
->countAllResults();
|
||
|
||
// 5. Team Performance — subqueries FY filtered by scheduled_date
|
||
$teamPerformance = $db->table('user_profiles as u')
|
||
->select("u.id, u.first_name, u.last_name, r.role,
|
||
(SELECT COUNT(*) FROM sales_activities
|
||
WHERE assigned_to = u.id
|
||
AND scheduled_date >= '{$fyStart}'
|
||
AND scheduled_date <= '{$fyEnd}') as total_acts,
|
||
(SELECT COUNT(*) FROM sales_activities
|
||
WHERE assigned_to = u.id AND status = 'completed'
|
||
AND scheduled_date >= '{$fyStart}'
|
||
AND scheduled_date <= '{$fyEnd}') as done_acts", false)
|
||
->join('roles r', 'r.id = u.role')
|
||
->where('u.nhance_branch_id', $branchId)
|
||
->whereIn('u.id', $branchwise_all_sales_team_ids)
|
||
->where('u.is_active', 1)
|
||
->get()
|
||
->getResultArray();
|
||
|
||
// 6. Pending Activities list — FY filtered by scheduled_date
|
||
$pending_activities = $db->table('sales_activities sa')
|
||
->select('sa.activity_id, sa.lead_id, sa.activity_type, sa.scheduled_date,
|
||
sa.status, sa.assigned_to, sal.company_name,
|
||
up.first_name AS assigned_to_name, sa.notes')
|
||
->join('user_profiles up', 'up.id = sa.assigned_to', 'left')
|
||
->join('sales_actual_leads sal', 'sal.lead_id = sa.lead_id', 'left')
|
||
->whereIn('sa.assigned_to', $branchwise_all_sales_team_ids)
|
||
->where('sa.status', 'pending')
|
||
->where('sa.scheduled_date >=', $fyStart)
|
||
->where('sa.scheduled_date <=', $fyEnd)
|
||
->orderBy('sa.scheduled_date', 'DESC')
|
||
->get()
|
||
->getResultArray();
|
||
|
||
// 7. All Leads Overview — FY filtered by sal.created_at
|
||
// Activities & opportunities also scoped to FY via CASE WHEN
|
||
$leadsOverview = $db->table('sales_actual_leads sal')
|
||
->select("sal.lead_id, sal.company_name, sal.status,
|
||
sal.assigned_to AS assigned_to_id,
|
||
up.first_name AS assigned_to,
|
||
COUNT(DISTINCT CASE WHEN sa.scheduled_date >= '{$fyStart}'
|
||
AND sa.scheduled_date <= '{$fyEnd}' THEN sa.activity_id END) AS activities,
|
||
COUNT(DISTINCT CASE WHEN l.updated_at >= '{$fyStart}'
|
||
AND l.updated_at <= '{$fyEnd}' THEN l.id END) AS opportunities", false)
|
||
->join('user_profiles up', 'up.id = sal.assigned_to', 'left')
|
||
->join('sales_activities sa', 'sa.lead_id = sal.lead_id', 'left')
|
||
->join('leads l', 'l.actual_lead_id = sal.lead_id', 'left')
|
||
->whereIn('sal.assigned_to', $branchwise_all_sales_team_ids)
|
||
->where('sal.created_at >=', $fyStart)
|
||
->where('sal.created_at <=', $fyEnd)
|
||
->groupBy('sal.lead_id, sal.company_name, sal.status, sal.assigned_to, up.first_name')
|
||
->orderBy('sal.created_at', 'DESC')
|
||
->get()
|
||
->getResultArray();
|
||
|
||
// Protect against division by zero in query #8
|
||
$total_activity_safe = $total_activity > 0 ? $total_activity : 1;
|
||
|
||
// 8. Activity Breakdown — FY filtered by scheduled_date
|
||
$activityBreakdown = $db->table('sales_activities')
|
||
->select("activity_type,
|
||
COUNT(*) AS total,
|
||
TRUNCATE(COUNT(*) * 100.0 / {$total_activity_safe}, 2) AS percentage_accuracy,
|
||
ROUND(COUNT(*) * 100.0 / {$total_activity_safe}, 0) AS percentage", false)
|
||
->whereIn('assigned_to', $branchwise_all_sales_team_ids)
|
||
->where('scheduled_date >=', $fyStart)
|
||
->where('scheduled_date <=', $fyEnd)
|
||
->groupBy('activity_type')
|
||
->orderBy('total', 'DESC')
|
||
->get()
|
||
->getResultArray();
|
||
|
||
// 9. Team Achievement (for achievement list + modal)
|
||
$teamAchievement = $this->buildTeamAchievement(
|
||
$db, $sales_manager_ids, $branchId, $current_fin_year, $fyStart, $fyEnd
|
||
);
|
||
|
||
// 10. Opportunities Achievement per member
|
||
$oppAchievement = $this->buildOppAchievement(
|
||
$db, $sales_manager_ids, $branchId, $current_fin_year, $fyStart, $fyEnd
|
||
);
|
||
|
||
$data = [
|
||
'total_leads' => $total_leads,
|
||
'total_acts' => $total_activity,
|
||
'total_completed_acts' => $total_completed_activity,
|
||
'total_pending_acts' => $total_pending_activity,
|
||
'team' => $teamPerformance,
|
||
'pending_acts' => $pending_activities,
|
||
'leads_overview' => $leadsOverview,
|
||
'activity_breakdown' => $activityBreakdown,
|
||
'team_achievement' => $teamAchievement, // used by JS TEAM constant
|
||
'opp_achievement' => $oppAchievement, // used by JS OPP_DATA constant
|
||
'fin_years' => $fin_years,
|
||
'current_fin_year' => $current_fin_year,
|
||
'tab_name' => 'Sales Dashboard',
|
||
'page_name' => 'Sales Dashboard',
|
||
];
|
||
|
||
$this->loadLayout('sales/branch_level_dashboard_view', $data);
|
||
|
||
} catch (\Exception $e) {
|
||
return $this->failServerError($e->getMessage());
|
||
}
|
||
}
|
||
|
||
// ─────────────────────────────────────────────
|
||
// buildTeamAchievement()
|
||
// Builds the TEAM array for the achievement list
|
||
// ─────────────────────────────────────────────
|
||
private function buildTeamAchievement($db, array $sales_manager_ids, $branchId, string $fy, string $fyStart, string $fyEnd): array
|
||
{
|
||
$quarters = $this->getFYQuarters($fy);
|
||
|
||
// Gradient palette (cycles)
|
||
$gradients = [
|
||
['grad' => 'linear-gradient(135deg,#10b981,#34d399)', 'color' => '#10b981'],
|
||
['grad' => 'linear-gradient(135deg,#06b6d4,#67e8f9)', 'color' => '#06b6d4'],
|
||
['grad' => 'linear-gradient(135deg,#4f46e5,#818cf8)', 'color' => '#4f46e5'],
|
||
['grad' => 'linear-gradient(135deg,#ec4899,#f9a8d4)', 'color' => '#ec4899'],
|
||
['grad' => 'linear-gradient(135deg,#f97316,#fbbf24)', 'color' => '#f97316'],
|
||
];
|
||
|
||
$members = $db->table('user_profiles as u')
|
||
->select('u.id, u.first_name, u.last_name, r.role')
|
||
->join('roles r', 'r.id = u.role')
|
||
->where('u.nhance_branch_id', $branchId)
|
||
->whereIn('u.id', $sales_manager_ids)
|
||
->where('u.is_active', 1)
|
||
->get()
|
||
->getResultArray();
|
||
|
||
$result = [];
|
||
|
||
foreach ($members as $idx => $m) {
|
||
$uid = (int)$m['id'];
|
||
|
||
// Target from sales_target
|
||
$targetRow = $db->table('sales_target')
|
||
->where('user_id', $uid)
|
||
->where('fy_year', $fy)
|
||
->get()
|
||
->getRowArray();
|
||
$targetAmt = (float)($targetRow['target_amount'] ?? 0);
|
||
$targetId = $targetRow['id'] ?? null;
|
||
|
||
// Achieved (won leads in FY)
|
||
$achievedAmt = (float)$this->getUserAchievedAmount($fy, $uid);
|
||
|
||
// Activities — FY filtered by scheduled_date
|
||
$totalActs = $db->table('sales_activities')
|
||
->where('assigned_to', $uid)
|
||
->where('scheduled_date >=', $fyStart)
|
||
->where('scheduled_date <=', $fyEnd)
|
||
->countAllResults();
|
||
$doneActs = $db->table('sales_activities')
|
||
->where('assigned_to', $uid)
|
||
->where('status', 'completed')
|
||
->where('scheduled_date >=', $fyStart)
|
||
->where('scheduled_date <=', $fyEnd)
|
||
->countAllResults();
|
||
|
||
// Activity breakdown — FY filtered
|
||
$actRows = $db->table('sales_activities')
|
||
->select('activity_type, COUNT(*) as cnt')
|
||
->where('assigned_to', $uid)
|
||
->where('scheduled_date >=', $fyStart)
|
||
->where('scheduled_date <=', $fyEnd)
|
||
->groupBy('activity_type')
|
||
->get()->getResultArray();
|
||
$activities = [];
|
||
foreach ($actRows as $ar) {
|
||
$activities[$ar['activity_type']] = (int)$ar['cnt'];
|
||
}
|
||
|
||
// Quarter splits
|
||
$splits = [];
|
||
foreach ($quarters as $q) {
|
||
$qStart = $q['start'] . ' 00:00:00';
|
||
$qEnd = $q['end'] . ' 23:59:59';
|
||
|
||
// Achievement = SUM(exp_amt) for won policies in this quarter
|
||
$qAchievedRow = $db->query("
|
||
SELECT COALESCE(SUM(ptcs.exp_amt), 0) AS total
|
||
FROM policy_transaction pt
|
||
LEFT JOIN pt_co_share_details ptcs
|
||
ON ptcs.pt_id = pt.id
|
||
AND ptcs.is_active = 1
|
||
WHERE pt.sales_generated_by = ?
|
||
AND pt.issuer_branch = ?
|
||
AND pt.created_at >= ?
|
||
AND pt.created_at <= ?
|
||
", [$uid, $branchId, $qStart, $qEnd])->getRowArray();
|
||
$qAchieved = (float)($qAchievedRow['total'] ?? 0);
|
||
|
||
$qTarget = $targetAmt > 0 ? round($targetAmt / 4, 2) : 0;
|
||
|
||
$qActs = $db->table('sales_activities')
|
||
->where('assigned_to', $uid)
|
||
->where('scheduled_date >=', $qStart)
|
||
->where('scheduled_date <=', $qEnd)
|
||
->countAllResults();
|
||
|
||
$qDone = $db->table('sales_activities')
|
||
->where('assigned_to', $uid)
|
||
->where('status', 'completed')
|
||
->where('scheduled_date >=', $qStart)
|
||
->where('scheduled_date <=', $qEnd)
|
||
->countAllResults();
|
||
|
||
$qLeads = $db->table('sales_actual_leads')
|
||
->where('assigned_to', $uid)
|
||
->where('created_at >=', $qStart)
|
||
->where('created_at <=', $qEnd)
|
||
->countAllResults();
|
||
|
||
$splits[] = [
|
||
'name' => $q['label'],
|
||
'start' => date('M Y', strtotime($q['start'])),
|
||
'end' => date('M Y', strtotime($q['end'])),
|
||
'target' => $qTarget,
|
||
'achieved' => $qAchieved,
|
||
'acts' => $qActs,
|
||
'done' => $qDone,
|
||
'leads' => $qLeads,
|
||
];
|
||
}
|
||
|
||
$palette = $gradients[$idx % count($gradients)];
|
||
|
||
$result[] = [
|
||
'id' => $uid,
|
||
'first_name' => $m['first_name'],
|
||
'last_name' => $m['last_name'],
|
||
'role' => $m['role'],
|
||
'total_acts' => $totalActs,
|
||
'done_acts' => $doneActs,
|
||
'target_id' => $targetId,
|
||
'target_amt' => $targetAmt,
|
||
'achieved_amt' => $achievedAmt,
|
||
'grad' => $palette['grad'],
|
||
'color' => $palette['color'],
|
||
'splits' => $splits,
|
||
'activities' => $activities,
|
||
];
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
// ─────────────────────────────────────────────
|
||
// buildOppAchievement()
|
||
// Opportunities via policy_transaction + pt_co_share_details
|
||
// Returns per-member: totals + flat policy list (no quarterly grouping)
|
||
// ─────────────────────────────────────────────
|
||
private function buildOppAchievement($db, array $sales_manager_ids, $branchId, string $fy, string $fyStart, string $fyEnd): array
|
||
{
|
||
$result = [];
|
||
|
||
foreach ($sales_manager_ids as $uid) {
|
||
|
||
// ── Target ──
|
||
$targetRow = $db->table('sales_target')
|
||
->where('user_id', $uid)
|
||
->where('fy_year', $fy)
|
||
->get()
|
||
->getRowArray();
|
||
$targetAmt = (float)($targetRow['target_amount'] ?? 0);
|
||
|
||
// ── All policy rows for this user in FY ──
|
||
// policy_no, issue_date (from pt), amount (exp_amt from child), created_at
|
||
// If no matching pt_co_share_details row exists, exp_amt = 0
|
||
// Policy list for Tab 2: policy_transaction + exp_amt from pt_co_share_details
|
||
$policyRows = $db->query("
|
||
SELECT
|
||
pt.id,
|
||
pt.policy_no,
|
||
pt.created_at AS issue_date,
|
||
COALESCE(ptcs.exp_amt, 0) AS amount,
|
||
pt.created_at AS created_at
|
||
FROM policy_transaction pt
|
||
LEFT JOIN pt_co_share_details ptcs
|
||
ON ptcs.pt_id = pt.id
|
||
AND ptcs.is_active = 1
|
||
WHERE pt.sales_generated_by = ?
|
||
AND pt.issuer_branch = ?
|
||
AND pt.created_at >= ?
|
||
AND pt.created_at <= ?
|
||
ORDER BY pt.created_at DESC
|
||
", [$uid, $branchId, $fyStart, $fyEnd])->getResultArray();
|
||
|
||
// ── Totals derived from policy_transaction ──
|
||
$totalPolicies = count($policyRows);
|
||
$totalExpAmt = array_sum(array_column($policyRows, 'amount'));
|
||
|
||
// Clean policy list for JS
|
||
$policies = array_map(function($row) {
|
||
return [
|
||
'policy_no' => $row['policy_no'],
|
||
'issue_date' => $row['issue_date'],
|
||
'amount' => (float)$row['amount'],
|
||
'created_at' => $row['created_at'],
|
||
];
|
||
}, $policyRows);
|
||
|
||
// ── Won Leads for this user in FY (Table 2 in modal) ──
|
||
// leads.actual_lead_id maps to sales_actual_leads.id
|
||
// leads.lead_form_type: 1 = EB, else = Non-EB
|
||
// leads.lead_type: 1 = Fresh, 2 = Renewal, 3 = Roll Over
|
||
$wonLeads = $db->query("
|
||
SELECT
|
||
l.id AS opportunities_id,
|
||
l.actual_lead_id,
|
||
COALESCE(l.lead_form_type, 1) AS lead_form_type_id,
|
||
l.lead_type AS lead_type_id,
|
||
sal.company_name AS company,
|
||
l.client_name AS client_name,
|
||
CASE WHEN COALESCE(l.lead_form_type, 1) = 1 THEN 'EB' ELSE 'Non-EB' END AS lead_form_type,
|
||
CASE
|
||
WHEN l.lead_type = 1 THEN 'Fresh'
|
||
WHEN l.lead_type = 2 THEN 'Renewal'
|
||
WHEN l.lead_type = 3 THEN 'Roll Over'
|
||
ELSE ''
|
||
END AS lead_type,
|
||
l.created_at AS created_at,
|
||
l.status
|
||
FROM leads l
|
||
INNER JOIN sales_actual_leads sal
|
||
ON sal.lead_id = l.actual_lead_id
|
||
WHERE l.status = 'won'
|
||
AND sal.assigned_to = ?
|
||
AND l.created_at >= ?
|
||
AND l.created_at <= ?
|
||
ORDER BY l.created_at DESC
|
||
", [$uid, $fyStart, $fyEnd])->getResultArray();
|
||
|
||
// -- 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
|
||
];
|
||
}
|
||
|
||
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()
|
||
];
|
||
}
|
||
}
|
||
} |