diff --git a/app/Config/Routes.php b/app/Config/Routes.php index b0aded13..c3800032 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -776,7 +776,7 @@ $routes->get('testTracelog','TestBusinessController::a'); $routes->get("claimView", "EmployeeRestController::claimView"); // General Tickets -$routes->post("ticketSave", "ThzController::ticketSave"); +$routes->post("ticketSave", "ThzController::ticketSave"); // $routes->get("ticketList", "ThzController::ticketList"); $routes->post("ticketConversationSave", "ThzController::ticketConversationSave"); $routes->get("ticketConversationList", "ThzController::ticketConversationList"); @@ -852,3 +852,89 @@ $routes->group('logs', function($routes) { $routes->get('delete/(:segment)', 'LogController::delete/$1'); $routes->get('clearAll', 'LogController::clearAll'); }); + +// ----------------------------------------------------------------------------------------------------------------- +$routes->group('sales', function($routes) { + + // ==================== LEAD ROUTES ==================== + + // Get all leads with filters + $routes->get('leads', 'SalesController::getLeads'); + + // Get lead statistics + $routes->get('leads/stats', 'SalesController::getLeadStats'); + + // Get single lead with complete details + $routes->get('leads/(:num)', 'SalesController::getLead/$1'); + + // Create new lead + $routes->post('leads', 'SalesController::createLead'); + + // Update lead + $routes->put('leads/(:num)', 'SalesController::updateLead/$1'); + + // Delete lead + $routes->delete('leads/(:num)', 'SalesController::deleteLead/$1'); + + // Get contacts for a lead + $routes->get('leads/(:num)/contacts', 'SalesController::getContacts/$1'); + + // Get activities for a lead + $routes->get('leads/(:num)/activities', 'SalesController::getLeadActivities/$1'); + + // Get activity timeline for a lead + $routes->get('leads/(:num)/timeline', 'SalesController::getActivityTimeline/$1'); + + // Get notes for a lead + $routes->get('leads/(:num)/notes', 'SalesController::getLeadNotes/$1'); + + // ==================== CONTACT PERSON ROUTES ==================== + + // Create contact person + $routes->post('contacts', 'SalesController::createContact'); + + // Update contact person + $routes->put('contacts/(:num)', 'SalesController::updateContact/$1'); + + // Delete contact person + $routes->delete('contacts/(:num)', 'SalesController::deleteContact/$1'); + + // Set primary contact + $routes->put('contacts/(:num)/set-primary', 'SalesController::setPrimaryContact/$1'); + + // ==================== ACTIVITY ROUTES ==================== + + // Get all activities with filters + $routes->get('activities', 'SalesController::getActivities'); + + // Get upcoming activities + $routes->get('activities/upcoming', 'SalesController::getUpcomingActivities'); + + // Get pending activities count + $routes->get('activities/pending-count', 'SalesController::getPendingCount'); + + // Create activity + $routes->post('activities', 'SalesController::createActivity'); + + // Update activity + $routes->put('activities/(:num)', 'SalesController::updateActivity/$1'); + + // Complete activity + $routes->post('activities/(:num)/complete', 'SalesController::completeActivity/$1'); + + // Delete activity + $routes->delete('activities/(:num)', 'SalesController::deleteActivity/$1'); + + // ==================== NOTE ROUTES ==================== + + // Create note + $routes->post('notes', 'SalesController::createNote'); + + // Update note + $routes->put('notes/(:num)', 'SalesController::updateNote/$1'); + + // Delete note + $routes->delete('notes/(:num)', 'SalesController::deleteNote/$1'); +}); + + diff --git a/app/Controllers/SalesController.php b/app/Controllers/SalesController.php new file mode 100644 index 00000000..17a60b0e --- /dev/null +++ b/app/Controllers/SalesController.php @@ -0,0 +1,695 @@ +leadModel = new SalesActualLeadModel(); + $this->contactModel = new SalesContactPersonModel(); + $this->activityModel = new SalesActivityModel(); + $this->noteModel = new SalesLeadNoteModel(); + } + + // ==================== 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'), + ]; + + $result = $this->leadModel->getLeadsWithFilters($filters, $limit, $offset); + + return $this->respond([ + 'status' => 'success', + 'data' => $result['data'], + 'total' => $result['total'], + 'limit' => $limit, + 'offset' => $offset + ]); + } 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); + } + } + + /** + * Create new lead + * POST /api/sales/leads + */ + public function createLead() + { + try { + $data = $this->request->getJSON(true); + + // Set created_by and updated_by from authenticated user + $data['created_by'] = $this->getUserId(); + $data['updated_by'] = $this->getUserId(); + + if (!$this->leadModel->insert($data)) { + return $this->fail($this->leadModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); + } + + $leadId = $this->leadModel->getInsertID(); + + // Insert contact persons if provided + if (!empty($data['contact_persons'])) { + foreach ($data['contact_persons'] as $contact) { + $contact['lead_id'] = $leadId; + $contact['created_by'] = $this->getUserId(); + $this->contactModel->insert($contact); + } + } + + $lead = $this->leadModel->getLeadComplete($leadId); + + return $this->respondCreated([ + 'status' => 'success', + 'message' => 'Lead created successfully', + '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($id)) { + return $this->failNotFound('Lead not found'); + } + + $data = $this->request->getJSON(true); + $data['updated_by'] = $this->getUserId(); + + 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($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); + } + } + + // ==================== 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($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($id)) { + return $this->failNotFound('Contact not found'); + } + + $data = $this->request->getJSON(true); + $data['updated_by'] = $this->getUserId(); + + if (!$this->contactModel->update($id, $data)) { + return $this->fail($this->contactModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); + } + + $contact = $this->contactModel->find($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($id)) { + return $this->failNotFound('Contact not found'); + } + + $this->contactModel->delete($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($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'), + ]; + + $result = $this->activityModel->getActivitiesWithFilters($filters, $limit, $offset); + + return $this->respond([ + 'status' => 'success', + 'data' => $result['data'], + 'total' => $result['total'], + 'limit' => $limit, + 'offset' => $offset + ]); + } 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(); + + if (!$this->activityModel->insert($data)) { + return $this->fail($this->activityModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); + } + + $activityId = $this->activityModel->getInsertID(); + $activity = $this->activityModel->find($activityId); + + return $this->respondCreated([ + 'status' => 'success', + 'message' => 'Activity created successfully', + 'data' => $activity + ]); + } 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($id)) { + return $this->failNotFound('Activity not found'); + } + + $data = $this->request->getJSON(true); + $data['updated_by'] = $this->getUserId(); + + if (!$this->activityModel->update($id, $data)) { + return $this->fail($this->activityModel->errors(), ResponseInterface::HTTP_BAD_REQUEST); + } + + $activity = $this->activityModel->find($id); + + return $this->respond([ + 'status' => 'success', + 'message' => 'Activity updated successfully', + 'data' => $activity + ]); + } catch (\Exception $e) { + return $this->fail($e->getMessage(), ResponseInterface::HTTP_INTERNAL_SERVER_ERROR); + } + } + + /** + * Complete activity + * POST /api/sales/activities/{id}/complete + */ + public function completeActivity($id) + { + try { + $activity = $this->activityModel->find($id); + + if (!$activity) { + return $this->failNotFound('Activity not found'); + } + + if ($activity['status'] === 'completed') { + return $this->fail('Activity is already completed', ResponseInterface::HTTP_BAD_REQUEST); + } + + $data = $this->request->getJSON(true); + $data['updated_by'] = $this->getUserId(); + + $this->activityModel->completeActivity($id, $data); + + // Create follow-up activity if requested + if (!empty($data['create_followup']) && $data['create_followup'] === true) { + $followupData = [ + 'lead_id' => $activity['lead_id'], + 'activity_type' => $data['followup_type'] ?? 'Call', + 'notes' => $data['followup_notes'] ?? '', + 'scheduled_date' => $data['followup_date'] ?? null, + 'assigned_to' => $activity['assigned_to'], + 'parent_activity_id' => $id, + 'created_by' => $this->getUserId(), + 'updated_by' => $this->getUserId(), + ]; + + $this->activityModel->insert($followupData); + } + + $updatedActivity = $this->activityModel->find($id); + + return $this->respond([ + 'status' => 'success', + 'message' => 'Activity completed successfully', + 'data' => $updatedActivity + ]); + } 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($id)) { + return $this->failNotFound('Activity not found'); + } + + $this->activityModel->delete($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($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($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($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($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($id); + + return $this->respondDeleted([ + 'status' => 'success', + 'message' => 'Note 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; + } +} \ No newline at end of file diff --git a/app/Models/SalesActivityModel.php b/app/Models/SalesActivityModel.php new file mode 100644 index 00000000..dc70086c --- /dev/null +++ b/app/Models/SalesActivityModel.php @@ -0,0 +1,166 @@ + 'required|integer', + 'activity_type' => 'required|in_list[Call,Email,Meeting,Demo,Share,Todo]', + 'notes' => 'required', + 'scheduled_date' => 'required|valid_date', + 'assigned_to' => 'required|integer', + 'status' => 'in_list[pending,completed]', + ]; + + protected $validationMessages = [ + 'lead_id' => [ + 'required' => 'Lead ID is required', + ], + 'activity_type' => [ + 'required' => 'Activity type is required', + ], + 'notes' => [ + 'required' => 'Activity notes are required', + ], + ]; + + protected $skipValidation = false; + + /** + * Get activities by lead with user details + */ + public function getActivitiesByLead($leadId, $status = null) + { + $builder = $this->select('activities.*, user_profiles.username as assigned_to_name') + ->join('user_profiles', 'user_profiles.id = activities.assigned_to', 'left') + ->where('activities.lead_id', $leadId); + + if ($status) { + $builder->where('activities.status', $status); + } + + return $builder->orderBy('activities.scheduled_date', 'DESC')->findAll(); + } + + /** + * Get all activities with filters + */ + public function getActivitiesWithFilters($filters = [], $limit = 10, $offset = 0) + { + $builder = $this->select('activities.*, actual_leads.company_name, user_profiles.username as assigned_to_name') + ->join('actual_leads', 'actual_leads.lead_id = activities.lead_id', 'left') + ->join('user_profiles', 'user_profiles.id = activities.assigned_to', 'left'); + + if (!empty($filters['status'])) { + $builder->where('activities.status', $filters['status']); + } + + if (!empty($filters['activity_type'])) { + $builder->where('activities.activity_type', $filters['activity_type']); + } + + if (!empty($filters['assigned_to'])) { + $builder->where('activities.assigned_to', $filters['assigned_to']); + } + + if (!empty($filters['date_from'])) { + $builder->where('activities.scheduled_date >=', $filters['date_from']); + } + + if (!empty($filters['date_to'])) { + $builder->where('activities.scheduled_date <=', $filters['date_to']); + } + + return [ + 'data' => $builder->orderBy('activities.scheduled_date', 'DESC') + ->limit($limit, $offset)->findAll(), + 'total' => $builder->countAllResults(false) + ]; + } + + /** + * Complete an activity + */ + public function completeActivity($activityId, $completionData) + { + return $this->update($activityId, [ + 'status' => 'completed', + 'completion_notes' => $completionData['completion_notes'], + 'completed_date' => date('Y-m-d H:i:s'), + 'updated_by' => $completionData['updated_by'] ?? null + ]); + } + + /** + * Get pending activities count by user + */ + public function getPendingActivitiesCount($userId) + { + return $this->where('assigned_to', $userId) + ->where('status', 'pending') + ->where('scheduled_date <=', date('Y-m-d H:i:s')) + ->countAllResults(); + } + + /** + * Get upcoming activities for a user + */ + public function getUpcomingActivities($userId, $days = 7, $limit = 10) + { + $endDate = date('Y-m-d H:i:s', strtotime("+{$days} days")); + + return $this->select('activities.*, actual_leads.company_name') + ->join('actual_leads', 'actual_leads.lead_id = activities.lead_id', 'left') + ->where('activities.assigned_to', $userId) + ->where('activities.status', 'pending') + ->where('activities.scheduled_date <=', $endDate) + ->orderBy('activities.scheduled_date', 'ASC') + ->limit($limit) + ->findAll(); + } + + /** + * Get activity timeline for a lead + */ + public function getActivityTimeline($leadId) + { + return $this->select('activities.*, user_profiles.username as assigned_to_name') + ->join('user_profiles', 'user_profiles.id = activities.assigned_to', 'left') + ->where('activities.lead_id', $leadId) + ->orderBy('activities.scheduled_date', 'DESC') + ->findAll(); + } +} \ No newline at end of file diff --git a/app/Models/SalesActualLeadModel.php b/app/Models/SalesActualLeadModel.php new file mode 100644 index 00000000..6b8aee8f --- /dev/null +++ b/app/Models/SalesActualLeadModel.php @@ -0,0 +1,145 @@ + 'required|min_length[2]|max_length[255]', + 'email' => 'required|valid_email|max_length[255]', + 'phone' => 'required|min_length[10]|max_length[20]', + 'status' => 'in_list[New,Potential,Prospects,Non prospects]', + ]; + + protected $validationMessages = [ + 'company_name' => [ + 'required' => 'Company name is required', + ], + 'email' => [ + 'required' => 'Email is required', + 'valid_email' => 'Please provide a valid email address', + ], + 'phone' => [ + 'required' => 'Phone number is required', + ], + ]; + + protected $skipValidation = false; + + /** + * Get lead with assigned user details + */ + public function getLeadWithUser($leadId) + { + return $this->select('actual_leads.*, user_profiles.username as assigned_to_name, user_profiles.email as assigned_to_email') + ->join('user_profiles', 'user_profiles.id = actual_leads.assigned_to', 'left') + ->where('actual_leads.lead_id', $leadId) + ->first(); + } + + /** + * Get all leads with pagination and filters + */ + public function getLeadsWithFilters($filters = [], $limit = 10, $offset = 0) + { + $builder = $this->select('actual_leads.*, user_profiles.username as assigned_to_name') + ->join('user_profiles', 'user_profiles.id = actual_leads.assigned_to', 'left'); + + if (!empty($filters['status'])) { + $builder->where('actual_leads.status', $filters['status']); + } + + if (!empty($filters['assigned_to'])) { + $builder->where('actual_leads.assigned_to', $filters['assigned_to']); + } + + if (!empty($filters['search'])) { + $builder->groupStart() + ->like('actual_leads.company_name', $filters['search']) + ->orLike('actual_leads.email', $filters['search']) + ->orLike('actual_leads.phone', $filters['search']) + ->groupEnd(); + } + + return [ + 'data' => $builder->limit($limit, $offset)->findAll(), + 'total' => $builder->countAllResults(false) + ]; + } + + /** + * Get lead with all related data (contacts, activities, notes) + */ + public function getLeadComplete($leadId) + { + $lead = $this->getLeadWithUser($leadId); + + if (!$lead) { + return null; + } + + $contactModel = new SalesContactPersonModel(); + $activityModel = new SalesActivityModel(); + $noteModel = new SalesLeadNoteModel(); + + $lead['contact_persons'] = $contactModel->where('lead_id', $leadId)->findAll(); + $lead['activities'] = $activityModel->getActivitiesByLead($leadId); + $lead['notes'] = $noteModel->getNotesByLead($leadId); + + return $lead; + } + + /** + * Get leads statistics + */ + public function getLeadStats($userId = null) + { + $builder = $this->builder(); + + if ($userId) { + $builder->where('assigned_to', $userId); + } + + $stats = [ + 'total' => $builder->countAllResults(false), + 'new' => $builder->where('status', 'New')->countAllResults(false), + 'potential' => $builder->where('status', 'Potential')->countAllResults(false), + 'prospects' => $builder->where('status', 'Prospects')->countAllResults(false), + 'non_prospects' => $builder->where('status', 'Non prospects')->countAllResults(false), + ]; + + return $stats; + } +} \ No newline at end of file diff --git a/app/Models/SalesContactPersonModel.php b/app/Models/SalesContactPersonModel.php new file mode 100644 index 00000000..3a7073bb --- /dev/null +++ b/app/Models/SalesContactPersonModel.php @@ -0,0 +1,95 @@ + 'required|integer', + 'name' => 'required|min_length[2]|max_length[100]', + 'mobile' => 'required|min_length[10]|max_length[20]', + ]; + + protected $validationMessages = [ + 'lead_id' => [ + 'required' => 'Lead ID is required', + ], + 'name' => [ + 'required' => 'Contact person name is required', + ], + 'mobile' => [ + 'required' => 'Mobile number is required', + ], + ]; + + protected $skipValidation = false; + + /** + * Get all contacts for a lead + */ + public function getContactsByLead($leadId) + { + return $this->where('lead_id', $leadId) + ->orderBy('is_primary', 'DESC') + ->findAll(); + } + + /** + * Get primary contact for a lead + */ + public function getPrimaryContact($leadId) + { + return $this->where('lead_id', $leadId) + ->where('is_primary', 1) + ->first(); + } + + /** + * Set a contact as primary (unset others) + */ + public function setPrimaryContact($contactId, $leadId) + { + $this->db->transStart(); + + // Unset all primary contacts for this lead + $this->where('lead_id', $leadId) + ->set(['is_primary' => 0]) + ->update(); + + // Set the specified contact as primary + $this->update($contactId, ['is_primary' => 1]); + + $this->db->transComplete(); + + return $this->db->transStatus(); + } +} \ No newline at end of file diff --git a/app/Models/SalesLeadNoteModel.php b/app/Models/SalesLeadNoteModel.php new file mode 100644 index 00000000..c8d9654f --- /dev/null +++ b/app/Models/SalesLeadNoteModel.php @@ -0,0 +1,75 @@ + 'required|integer', + 'user_id' => 'required|integer', + 'note_text' => 'required|min_length[3]', + ]; + + protected $validationMessages = [ + 'lead_id' => [ + 'required' => 'Lead ID is required', + ], + 'note_text' => [ + 'required' => 'Note text is required', + ], + ]; + + protected $skipValidation = false; + + /** + * Get notes by lead with user details + */ + public function getNotesByLead($leadId) + { + return $this->select('lead_notes.*, user_profiles.username') + ->join('user_profiles', 'user_profiles.id = lead_notes.user_id', 'left') + ->where('lead_notes.lead_id', $leadId) + ->orderBy('lead_notes.created_at', 'DESC') + ->findAll(); + } + + /** + * Get notes by user + */ + public function getNotesByUser($userId, $limit = 20, $offset = 0) + { + return $this->select('lead_notes.*, actual_leads.company_name') + ->join('actual_leads', 'actual_leads.lead_id = lead_notes.lead_id', 'left') + ->where('lead_notes.user_id', $userId) + ->orderBy('lead_notes.created_at', 'DESC') + ->limit($limit, $offset) + ->findAll(); + } +} \ No newline at end of file