Merge branch 'dev' of bitbucket.org:jubilian/nhance into dev
This commit is contained in:
commit
19492bbcec
@ -917,6 +917,8 @@ $routes->group('sales', function($routes) {
|
||||
|
||||
$routes->get('loadactivities', 'SalesController::loadactivities');
|
||||
|
||||
$routes->get('loadtargets', 'SalesController::loadtargets');
|
||||
|
||||
$routes->get('page/(:segment)', 'SalesController::noPage/$1');
|
||||
|
||||
// Get all leads with filters
|
||||
@ -997,6 +999,13 @@ $routes->group('sales', function($routes) {
|
||||
// Delete note
|
||||
$routes->delete('notes/(:num)', 'SalesController::deleteNote/$1');
|
||||
|
||||
// ==================== TARGETS ROUTES ====================
|
||||
$routes->get('targets', 'SalesController::getTargets');
|
||||
$routes->get('targets/user/(:num)', 'SalesController::getTargetByUser/$1');
|
||||
$routes->get('targets/fy/(:segment)','SalesController::getTargetByFY/$1');
|
||||
$routes->post('targets', 'SalesController::createTarget');
|
||||
$routes->put('targets/(:num)', 'SalesController::updateTarget/$1');
|
||||
$routes->delete('targets/(:num)', 'SalesController::deleteTarget/$1');
|
||||
|
||||
//Dashboard
|
||||
$routes->get('dashboard', 'SalesController::dashboard');
|
||||
|
||||
@ -62,32 +62,40 @@ class AppContentManagementController extends AdminController
|
||||
|
||||
$data = $this->request->getPost();
|
||||
$sanitized_post_data = sanitizeInputArrayAdvanced($data);
|
||||
$id = ((int) $sanitized_post_data['add_image_id']) ?? null;
|
||||
$id = (int)($sanitized_post_data['add_image_id'] ?? 0);
|
||||
|
||||
$rules = [
|
||||
'client_id' => [
|
||||
'rules' => 'required|integer',
|
||||
'add_image_id' => [
|
||||
'rules' => 'permit_empty|integer|is_natural',
|
||||
'errors' => [
|
||||
'required' => 'Client is required',
|
||||
'integer' => 'Invalid client selected'
|
||||
'integer' => 'Image ID must be a valid number',
|
||||
'is_natural' => 'Image ID must be a non-negative number'
|
||||
]
|
||||
],
|
||||
'client_id' => [
|
||||
'rules' => 'required|integer|is_natural_no_zero',
|
||||
'errors' => [
|
||||
'required' => 'Client is required',
|
||||
'integer' => 'Invalid client selected',
|
||||
'is_natural_no_zero' => 'Invalid client selected'
|
||||
]
|
||||
],
|
||||
'advertise_image' => [
|
||||
'rules' => ($id === 0 ? 'uploaded[advertise_image]|' : '')
|
||||
. 'is_image[advertise_image]'
|
||||
. '|mime_in[advertise_image,image/jpg,image/jpeg,image/png]'
|
||||
. '|max_size[advertise_image,200]'
|
||||
. '|min_dims[advertise_image,1640,664]'
|
||||
. '|max_dims[advertise_image,1640,664]',
|
||||
'errors' => [
|
||||
'uploaded' => 'Image is required',
|
||||
'is_image' => 'File must be an image',
|
||||
'mime_in' => 'Only JPG, JPEG, PNG allowed',
|
||||
'max_size' => 'Image size must not exceed 200 KB',
|
||||
'min_dims' => 'Image dimensions must be exactly 1640x664 pixels',
|
||||
'max_dims' => 'Image dimensions must be exactly 1640x664 pixels',
|
||||
]
|
||||
],
|
||||
];
|
||||
$rules['advertise_image'] = [
|
||||
'rules' => ($id === 0 ? 'uploaded[advertise_image]|' : '') // required only for ADD
|
||||
. 'is_image[advertise_image]'
|
||||
. '|mime_in[advertise_image,image/jpg,image/jpeg,image/png]'
|
||||
. '|max_size[advertise_image,200]'
|
||||
. '|min_dims[advertise_image,1640,664]'
|
||||
. '|max_dims[advertise_image,1640,664]',
|
||||
'errors' => [
|
||||
'uploaded' => 'Image is required',
|
||||
'is_image' => 'File must be an image',
|
||||
'mime_in' => 'Only JPG, JPEG, PNG allowed',
|
||||
'max_size' => 'Image size must not exceed 200 KB',
|
||||
'min_dims' => 'Image dimensions must be exactly 1640x664 pixels',
|
||||
'max_dims' => 'Image dimensions must be exactly 1640x664 pixels',
|
||||
]
|
||||
];
|
||||
if (!$this->validate($rules)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
@ -100,7 +108,12 @@ class AppContentManagementController extends AdminController
|
||||
|
||||
|
||||
$file = $this->request->getFile('advertise_image');
|
||||
$client_id = $sanitized_post_data['client_id'] ?? null;
|
||||
$client_id = (int)($sanitized_post_data['client_id'] ?? 0);
|
||||
|
||||
$clientExists = $this->clientModel->where('id', $client_id)->where('is_active', 1)->first();
|
||||
if (!$clientExists) {
|
||||
return $this->respond(['status' => false, 'message' => 'Invalid client selected.'], 400);
|
||||
}
|
||||
|
||||
if (!$file || !$file->isValid()) {
|
||||
return $this->respond(['status' => false, 'message' => 'No file uploaded or invalid file.'], 400);
|
||||
@ -119,12 +132,15 @@ class AppContentManagementController extends AdminController
|
||||
|
||||
$file->move($uploadPath, $fileName);
|
||||
|
||||
$id = $sanitized_post_data['add_image_id'] ?? null;
|
||||
$details = ['name' => $fileName,'client_id'=>$client_id];
|
||||
|
||||
if ($id == 0) {
|
||||
if ($id === 0) {
|
||||
$this->addImgModel->insert($details);
|
||||
} else {
|
||||
$existingRecord = $this->addImgModel->find($id);
|
||||
if (!$existingRecord) {
|
||||
return $this->respond(['status' => false, 'message' => 'Record not found for update.'], 404);
|
||||
}
|
||||
$this->addImgModel->update($id, $details);
|
||||
}
|
||||
|
||||
@ -138,14 +154,34 @@ class AppContentManagementController extends AdminController
|
||||
public function remove_advertise_image()
|
||||
{
|
||||
try {
|
||||
$id = $this->request->getPost('add_image_id');
|
||||
$rules = [
|
||||
'add_image_id' => [
|
||||
'rules' => 'required|integer|is_natural_no_zero',
|
||||
'errors' => [
|
||||
'required' => 'Image ID is required',
|
||||
'integer' => 'Image ID must be a valid number',
|
||||
'is_natural_no_zero' => 'Image ID must be a positive number'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
if (!$id) {
|
||||
return $this->respond(['status' => false, 'message' => 'ID missing'], 400);
|
||||
if (!$this->validate($rules)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Input validation failed',
|
||||
'code' => 400,
|
||||
'errors' => $this->validator->getErrors()
|
||||
]);
|
||||
}
|
||||
|
||||
$data = ['is_active' => 0];
|
||||
$this->addImgModel->update($id, $data);
|
||||
$id = (int)$this->request->getPost('add_image_id');
|
||||
|
||||
$existing = $this->addImgModel->find($id);
|
||||
if (!$existing) {
|
||||
return $this->respond(['status' => false, 'message' => 'Record not found'], 404);
|
||||
}
|
||||
|
||||
$this->addImgModel->update($id, ['is_active' => 0]);
|
||||
|
||||
return $this->respond(['status' => true, 'message' => 'Deleted successfully']);
|
||||
|
||||
@ -158,19 +194,27 @@ class AppContentManagementController extends AdminController
|
||||
// Preview image Went Edit.
|
||||
public function showAdvertiseImage($filename)
|
||||
{
|
||||
// $path = WRITEPATH . 'uploads/advertiseImage/' . $filename;
|
||||
$path = ROOTPATH . 'public/uploads/add_image_upload/' . $filename;
|
||||
$filename = basename($filename);
|
||||
|
||||
if (!file_exists($path)) {
|
||||
if (!preg_match('/^[a-zA-Z0-9_\-]+\.(jpg|jpeg|png|gif|webp)$/i', $filename)) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
// return $this->response->setStatusCode(404, 'File not found');
|
||||
}
|
||||
|
||||
$mime = mime_content_type($path);
|
||||
// header('Content-Type: ' . $mimeType);
|
||||
// readfile($path);
|
||||
// exit;
|
||||
return $this->response->setHeader('Content-Type', $mime)->setBody(file_get_contents($path));
|
||||
$path = ROOTPATH . 'public/uploads/add_image_upload/' . $filename;
|
||||
$realPath = realpath($path);
|
||||
$allowedDir = realpath(ROOTPATH . 'public/uploads/add_image_upload');
|
||||
|
||||
if (!$realPath || strpos($realPath, $allowedDir) !== 0 || !file_exists($realPath)) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
$mime = mime_content_type($realPath);
|
||||
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
if (!in_array($mime, $allowedMimes, true)) {
|
||||
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
|
||||
}
|
||||
|
||||
return $this->response->setHeader('Content-Type', $mime)->setBody(file_get_contents($realPath));
|
||||
}
|
||||
|
||||
|
||||
@ -183,39 +227,51 @@ class AppContentManagementController extends AdminController
|
||||
if ($this->request->getMethod() === 'post') {
|
||||
|
||||
$rules = [
|
||||
'type' => [
|
||||
'rules' => 'required|max_length[255]',
|
||||
'fe_id' => [
|
||||
'rules' => 'permit_empty|integer|is_natural',
|
||||
'errors' => [
|
||||
'required' => 'Type is required',
|
||||
'max_length' => 'Type cannot exceed 255 characters'
|
||||
'integer' => 'ID must be a valid number',
|
||||
'is_natural' => 'ID must be a non-negative number'
|
||||
]
|
||||
],
|
||||
'type' => [
|
||||
'rules' => 'required|max_length[255]|regex_match[/^[a-zA-Z0-9_ \-]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'Type is required',
|
||||
'max_length' => 'Type cannot exceed 255 characters',
|
||||
'regex_match' => 'Type contains invalid characters'
|
||||
]
|
||||
],
|
||||
'content_section' => [
|
||||
'rules' => 'required|max_length[255]',
|
||||
'rules' => 'required|max_length[255]|regex_match[/^[a-zA-Z0-9_ \-]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'Content Section is required',
|
||||
'max_length' => 'Content Section cannot exceed 255 characters'
|
||||
'required' => 'Content Section is required',
|
||||
'max_length' => 'Content Section cannot exceed 255 characters',
|
||||
'regex_match' => 'Content Section contains invalid characters'
|
||||
]
|
||||
],
|
||||
'heading' => [
|
||||
'rules' => 'required|max_length[255]',
|
||||
'rules' => 'required|max_length[255]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'Heading is required',
|
||||
'max_length' => 'Heading cannot exceed 255 characters'
|
||||
'required' => 'Heading is required',
|
||||
'max_length' => 'Heading cannot exceed 255 characters',
|
||||
'regex_match' => 'Heading contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed'
|
||||
]
|
||||
],
|
||||
'content' => [
|
||||
'rules' => 'required|max_length[5000]',
|
||||
'rules' => 'required|max_length[5000]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'Content is required',
|
||||
'max_length' => 'Content cannot exceed 5000 characters'
|
||||
'required' => 'Content is required',
|
||||
'max_length' => 'Content cannot exceed 5000 characters',
|
||||
'regex_match' => 'Content contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed'
|
||||
]
|
||||
],
|
||||
'notes' => [
|
||||
'rules' => 'required|max_length[1500]',
|
||||
'rules' => 'required|max_length[1500]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'Notes are required',
|
||||
'max_length' => 'Notes cannot exceed 1500 characters'
|
||||
'required' => 'Notes are required',
|
||||
'max_length' => 'Notes cannot exceed 1500 characters',
|
||||
'regex_match' => 'Notes contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed'
|
||||
]
|
||||
]
|
||||
];
|
||||
@ -230,8 +286,14 @@ class AppContentManagementController extends AdminController
|
||||
}
|
||||
$request_post_data = $this->request->getPost();
|
||||
$data = sanitizeInputArrayAdvanced($request_post_data);
|
||||
$id = $data['fe_id'];
|
||||
|
||||
$id = $data['fe_id'] ?? null;
|
||||
|
||||
if (!empty($id) && (!ctype_digit((string)$id) || (int)$id <= 0)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => false, 'message' => 'Invalid ID format', 'code' => 400
|
||||
]);
|
||||
}
|
||||
$id = !empty($id) ? (int)$id : null;
|
||||
|
||||
unset($data['fe_id']);
|
||||
|
||||
@ -244,6 +306,12 @@ class AppContentManagementController extends AdminController
|
||||
$status = $this->feContentModel->insert($data);
|
||||
$text = "Created";
|
||||
} else {
|
||||
$existing = $this->feContentModel->find($id);
|
||||
if (!$existing) {
|
||||
return $this->response->setStatusCode(404)->setJSON([
|
||||
'status' => false, 'message' => 'Record not found', 'code' => 404
|
||||
]);
|
||||
}
|
||||
$status = $this->feContentModel->update($id, $data);
|
||||
$text = "Updated";
|
||||
}
|
||||
@ -260,6 +328,13 @@ class AppContentManagementController extends AdminController
|
||||
$id = $this->request->getGet('fe_id') ?? null;
|
||||
|
||||
if (!empty($id)) {
|
||||
|
||||
if (!ctype_digit((string)$id) || (int)$id <= 0) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => false, 'code' => 400, 'message' => 'Invalid ID format'
|
||||
]);
|
||||
}
|
||||
$id = (int)$id;
|
||||
|
||||
$data['fe_list'] = $this->feContentModel->where('id', $id)->orderBy('id', 'DESC')->findAll();
|
||||
|
||||
@ -276,16 +351,24 @@ class AppContentManagementController extends AdminController
|
||||
|
||||
} elseif ($method === 'delete') {
|
||||
|
||||
// $input = $this->request->getRawInput();
|
||||
$id = $this->request->getGet('fe_id'); // ✅ THIS
|
||||
$id = $id ?? null;
|
||||
$id = $this->request->getGet('fe_id') ?? null;
|
||||
|
||||
if (empty($id)) {
|
||||
if (empty($id) || !ctype_digit((string)$id) || (int)$id <= 0) {
|
||||
return $this->respond([
|
||||
'status' => false,
|
||||
'code' => 400,
|
||||
'message' => 'Valid numeric ID is required for deletion'
|
||||
], 400);
|
||||
}
|
||||
$id = (int)$id;
|
||||
|
||||
$existing = $this->feContentModel->find($id);
|
||||
if (!$existing) {
|
||||
return $this->respond([
|
||||
'status' => false,
|
||||
'code' => 404,
|
||||
'message' => 'No ID provided for deletion'
|
||||
], 200);
|
||||
'message' => 'Record not found'
|
||||
], 404);
|
||||
}
|
||||
|
||||
$update_status = $this->feContentModel->where('id', $id)->set(['is_active' => 0])->update();
|
||||
@ -309,10 +392,15 @@ class AppContentManagementController extends AdminController
|
||||
}
|
||||
|
||||
|
||||
private const ALLOWED_RETURN_TYPES = ['api', 'web'];
|
||||
|
||||
public function FAQ()
|
||||
{
|
||||
$method = strtolower($this->request->getMethod());
|
||||
$returnType = strtolower($this->request->getGet('return_type') ?? 'api');
|
||||
if (!in_array($returnType, self::ALLOWED_RETURN_TYPES, true)) {
|
||||
$returnType = 'api';
|
||||
}
|
||||
$ref = ['timestamp' => date('Y-m-d H:i:s')];
|
||||
|
||||
try {
|
||||
@ -320,33 +408,64 @@ class AppContentManagementController extends AdminController
|
||||
if ($method === 'post') {
|
||||
$rules = [
|
||||
'category' => [
|
||||
'rules' => 'required',
|
||||
'rules' => 'required|max_length[100]|alpha_numeric_space',
|
||||
'errors' => [
|
||||
'required' => 'Category is required'
|
||||
'required' => 'Category is required',
|
||||
'max_length' => 'Category cannot exceed 100 characters',
|
||||
'alpha_numeric_space' => 'Category contains invalid characters'
|
||||
]
|
||||
],
|
||||
'question' => [
|
||||
'rules' => 'required',
|
||||
'rules' => 'required|max_length[1000]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'Question is required'
|
||||
'required' => 'Question is required',
|
||||
'max_length' => 'Question cannot exceed 1000 characters',
|
||||
'regex_match' => 'Question contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed'
|
||||
]
|
||||
],
|
||||
'answer' => [
|
||||
'rules' => 'required',
|
||||
'rules' => 'required|max_length[5000]|regex_match[/^[a-zA-Z0-9 _\-.,;:!?()&\/\r\n]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'Answer is required'
|
||||
'required' => 'Answer is required',
|
||||
'max_length' => 'Answer cannot exceed 5000 characters',
|
||||
'regex_match' => 'Answer contains invalid characters. Only letters, numbers, spaces and basic punctuation are allowed'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
if (!$this->validate($rules)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => 'error',
|
||||
'message' => 'Input validation failed',
|
||||
'code' => 400,
|
||||
'errors' => $this->validator->getErrors(),
|
||||
'ref' => $ref
|
||||
]);
|
||||
}
|
||||
|
||||
$request_post_data = $this->request->getPost();
|
||||
$sanitized_post_data = sanitizeInputArrayAdvanced($request_post_data);
|
||||
$data = array_filter($sanitized_post_data, fn($v) => $v !== '' && $v !== null);
|
||||
$id = $data['faq_id'];
|
||||
$id = $data['faq_id'] ?? null;
|
||||
|
||||
if (!empty($id) && (!ctype_digit((string)$id) || (int)$id <= 0)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => 'error', 'message' => 'Invalid FAQ ID format', 'code' => 400, 'ref' => $ref
|
||||
]);
|
||||
}
|
||||
$id = !empty($id) ? (int)$id : null;
|
||||
unset($data['faq_id']);
|
||||
|
||||
if (empty($id)) {
|
||||
$status = $this->faqModel->insert($data);
|
||||
$msg = "Created";
|
||||
} else {
|
||||
$existing = $this->faqModel->find($id);
|
||||
if (!$existing) {
|
||||
return $this->response->setStatusCode(404)->setJSON([
|
||||
'status' => 'error', 'message' => 'FAQ not found', 'code' => 404, 'ref' => $ref
|
||||
]);
|
||||
}
|
||||
$status = $this->faqModel->update($id, $data);
|
||||
$msg = "Updated";
|
||||
}
|
||||
@ -372,10 +491,17 @@ class AppContentManagementController extends AdminController
|
||||
$id = $this->request->getGet('faq_id');
|
||||
|
||||
if (!empty($id)) {
|
||||
if (!ctype_digit((string)$id) || (int)$id <= 0) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => 'error', 'message' => 'Invalid FAQ ID format', 'code' => 400, 'ref' => $ref
|
||||
]);
|
||||
}
|
||||
$id = (int)$id;
|
||||
|
||||
if($returnType === 'web'){
|
||||
$row = $this->faqModel->find((int)$id);
|
||||
$row = $this->faqModel->find($id);
|
||||
}else{
|
||||
$row = $this->faqModel->where('is_active', 1)->find((int)$id);
|
||||
$row = $this->faqModel->where('is_active', 1)->find($id);
|
||||
}
|
||||
|
||||
$data['faq_list'] = $row ? [$row] : [];
|
||||
@ -417,7 +543,15 @@ class AppContentManagementController extends AdminController
|
||||
// --- 3. DELETE: SOFT DELETE ---
|
||||
elseif ($method === 'delete') {
|
||||
$id = $this->request->getGet('faq_id');
|
||||
$status = (!empty($id)) ? $this->faqModel->update($id, ['is_active' => 0]) : false;
|
||||
|
||||
if (empty($id) || !ctype_digit((string)$id) || (int)$id <= 0) {
|
||||
return $this->response->setJSON([
|
||||
'status' => 'error', 'message' => 'Valid numeric FAQ ID is required', 'code' => 400, 'ref' => $ref
|
||||
])->setStatusCode(400);
|
||||
}
|
||||
$id = (int)$id;
|
||||
|
||||
$status = $this->faqModel->find($id) ? $this->faqModel->update($id, ['is_active' => 0]) : false;
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => $status ? ($returnType === 'web' ? true : 'success') : ($returnType === 'web' ? false : 'error'),
|
||||
@ -429,12 +563,12 @@ class AppContentManagementController extends AdminController
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$msg = $e->getMessage();
|
||||
log_message('error', 'FAQ error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
|
||||
return $this->response->setJSON([
|
||||
'status' => $returnType === 'web' ? false : 'error',
|
||||
'code' => 500,
|
||||
'message' => $msg,
|
||||
'ref' => ['file' => $e->getFile(), 'line' => $e->getLine()]
|
||||
'message' => 'An internal error occurred. Please try again later.',
|
||||
'ref' => $ref
|
||||
])->setStatusCode(500);
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,24 +142,38 @@ class ClientController extends AdminController
|
||||
return $this->response->setJSON(['isDuplicate' => $isDuplicate]);
|
||||
}
|
||||
|
||||
private const ALLOWED_DUPLICATE_CHECK_TABLES = [
|
||||
'clients', 'client_branch', 'level_contacts', 'insurers', 'insurer_branch',
|
||||
'tpa', 'tpa_branch', 'policies', 'client_policy', 'user_profiles',
|
||||
];
|
||||
|
||||
public function checkDuplicateTableFieldValue()
|
||||
{
|
||||
$table = $this->request->getPost('table');
|
||||
$field = $this->request->getPost('field');
|
||||
$value = $this->request->getPost('value');
|
||||
|
||||
// Load the database if not already loaded
|
||||
$db = db_connect();
|
||||
if (!in_array($table, self::ALLOWED_DUPLICATE_CHECK_TABLES, true)) {
|
||||
return $this->response->setJSON(['isDuplicate' => false, 'error' => 'Invalid table']);
|
||||
}
|
||||
|
||||
$db = db_connect();
|
||||
$tableFields = $db->getFieldNames($table);
|
||||
|
||||
// Perform the query
|
||||
$builder = $db->table($table);
|
||||
if (is_array($value)) {
|
||||
$isDuplicate = $builder->where($value)->where('is_active', 1)->countAllResults() > 0;
|
||||
$filteredValue = array_intersect_key($value, array_flip($tableFields));
|
||||
if (empty($filteredValue)) {
|
||||
return $this->response->setJSON(['isDuplicate' => false, 'error' => 'Invalid fields']);
|
||||
}
|
||||
$isDuplicate = $builder->where($filteredValue)->where('is_active', 1)->countAllResults() > 0;
|
||||
} else {
|
||||
if (!in_array($field, $tableFields, true)) {
|
||||
return $this->response->setJSON(['isDuplicate' => false, 'error' => 'Invalid field']);
|
||||
}
|
||||
$isDuplicate = $builder->where($field, $value)->where('is_active', 1)->countAllResults() > 0;
|
||||
}
|
||||
|
||||
// Return the result
|
||||
return $this->response->setJSON(['isDuplicate' => $isDuplicate]);
|
||||
}
|
||||
|
||||
|
||||
@ -4360,13 +4360,15 @@ class EmpDataServiceController extends BaseController
|
||||
{
|
||||
$client_policy_id = $arrayData['client_policy_id'];
|
||||
$cd_ac_pk = $this->clientPolicyModel->select('cd_ac_pk')->where('id',$client_policy_id)->first();
|
||||
$sanitizedIds = array_map('intval', $arrayData['employeeIds']);
|
||||
$placeholders = implode(',', array_fill(0, count($sanitizedIds), '?'));
|
||||
$amount = $this->employeePolicyModel->query("
|
||||
SELECT SUM(rata_premimum + gst) AS total_sum
|
||||
FROM employee_polices
|
||||
JOIN employees ON employees.id = employee_polices.employee_id
|
||||
WHERE employees.unit = '$unit'
|
||||
AND employee_polices.id IN (" . implode(',', $arrayData['employeeIds']) . ")
|
||||
")->getRow();
|
||||
WHERE employees.unit = ?
|
||||
AND employee_polices.id IN ($placeholders)
|
||||
", array_merge([$unit], $sanitizedIds))->getRow();
|
||||
|
||||
if($amount){
|
||||
|
||||
@ -4625,11 +4627,14 @@ class EmpDataServiceController extends BaseController
|
||||
// AND employee_polices.id IN (" . implode(',', $arrayData['employeeIds']) . ")
|
||||
// ")->getRow();
|
||||
|
||||
$sanitizedIds = array_map('intval', $arrayData['employeeIds']);
|
||||
$placeholders = implode(',', array_fill(0, count($sanitizedIds), '?'));
|
||||
$safeAddOneDay = (int)$add_one_day;
|
||||
$amount = $this->employeePolicyModel->query("
|
||||
SELECT
|
||||
SUM(ROUND(
|
||||
((employee_polices.rata_premimum * (DATEDIFF(employee_polices.policy_end_date, emp_endorsement.new_value) + '$add_one_day')) / 365) +
|
||||
(((employee_polices.rata_premimum * (DATEDIFF(employee_polices.policy_end_date, emp_endorsement.new_value) + '$add_one_day')) / 365) * 0.18),
|
||||
((employee_polices.rata_premimum * (DATEDIFF(employee_polices.policy_end_date, emp_endorsement.new_value) + ?)) / 365) +
|
||||
(((employee_polices.rata_premimum * (DATEDIFF(employee_polices.policy_end_date, emp_endorsement.new_value) + ?)) / 365) * 0.18),
|
||||
2
|
||||
)) AS total_sum
|
||||
FROM
|
||||
@ -4639,14 +4644,14 @@ class EmpDataServiceController extends BaseController
|
||||
JOIN
|
||||
emp_endorsement ON emp_endorsement.pk = employee_polices.id
|
||||
WHERE
|
||||
employee_polices.id IN (" . implode(',', $arrayData['employeeIds']) . ")
|
||||
AND emp_endorsement.pk IN (" . implode(',', $arrayData['employeeIds']) . ")
|
||||
employee_polices.id IN ($placeholders)
|
||||
AND emp_endorsement.pk IN ($placeholders)
|
||||
AND employee_polices.claim_status = 0
|
||||
AND emp_endorsement.field_name = 'date_of_exit'
|
||||
AND emp_endorsement.is_active = 1
|
||||
AND emp_endorsement.actions = 'd'
|
||||
AND emp_endorsement.status != 'truncated';
|
||||
")->getRow();
|
||||
AND emp_endorsement.status != 'truncated'
|
||||
", array_merge([$safeAddOneDay, $safeAddOneDay], $sanitizedIds, $sanitizedIds))->getRow();
|
||||
|
||||
// dd(db_connect()->getLastQuery(), $amount);
|
||||
|
||||
|
||||
@ -844,6 +844,8 @@ class MediAssistApiController extends BaseController
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
log_message('error','MEDI_ASSIST - Claim Status started | for ticket count: ' . count($TicketData));
|
||||
|
||||
// dd($TicketData);
|
||||
|
||||
if (!$TicketData) {
|
||||
@ -890,7 +892,11 @@ class MediAssistApiController extends BaseController
|
||||
|
||||
if ($response['status'] != true || empty($response['data']['claimsData'][0])) {
|
||||
log_message('error','MEDI_ASSIST - Claim Status FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||
$error_data[$claimId][['status' => false,'message' => 'API call failed.','data' => $response]];
|
||||
$error_data[$claimId] = [
|
||||
'response' => $response,
|
||||
'body' => $body
|
||||
];
|
||||
continue; // skip to next ticket
|
||||
}
|
||||
|
||||
// Extract Claim Status
|
||||
|
||||
@ -7,6 +7,7 @@ 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 CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
@ -20,6 +21,8 @@ class SalesController extends BaseController
|
||||
protected $activityModel;
|
||||
protected $noteModel;
|
||||
protected $userModel;
|
||||
protected $targetModel;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -28,6 +31,7 @@ class SalesController extends BaseController
|
||||
$this->activityModel = new SalesActivityModel();
|
||||
$this->noteModel = new SalesLeadNoteModel();
|
||||
$this->userModel = new UserModel();
|
||||
$this->targetModel = new SalesTargetModel();
|
||||
}
|
||||
|
||||
|
||||
@ -53,55 +57,73 @@ class SalesController extends BaseController
|
||||
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
|
||||
*/
|
||||
private function getSalesStaffData(): array
|
||||
{
|
||||
$db = \Config\Database::connect();
|
||||
$db = \Config\Database::connect();
|
||||
$logged_user_id = get_session_userid();
|
||||
$role = get_role_id();
|
||||
$team_id = user_team();
|
||||
$role = get_role_id();
|
||||
$team_id = user_team();
|
||||
|
||||
$data = [
|
||||
'users' => [],
|
||||
'sales_manager_ids' => []
|
||||
'users' => [],
|
||||
'sales_manager_ids'=> [],
|
||||
'sales_role' => '',
|
||||
'nhance_branch_id' => null,
|
||||
'assigned_ids' => [],
|
||||
];
|
||||
|
||||
$row = $db->table('user_profiles')->select('*')
|
||||
->where('is_active', 1)->where('id', $logged_user_id)
|
||||
->get()->getRow();
|
||||
|
||||
$nhance_branch_id = $row ? $row->nhance_branch_id : null;
|
||||
|
||||
// Is the logged-in user a Sales Manager? (Role 4, Team 5)
|
||||
if ($role == 4 && in_array(5, $team_id)) {
|
||||
$row = $db->table('user_profiles')->select('*')
|
||||
->where('is_active', 1)->where('id', $logged_user_id)
|
||||
->get()->getRow();
|
||||
|
||||
$nhance_branch_id = $row ? $row->nhance_branch_id : null;
|
||||
$data['nhance_branch_id']= $nhance_branch_id;
|
||||
|
||||
// ── Sales Manager (Role 4, Team 5) ──────────────────────────
|
||||
if ($role == 4 && in_array(5, $team_id)) {
|
||||
|
||||
$data['sales_role'] = 'Sales Manager';
|
||||
$data['sales_manager_ids'] = [$logged_user_id];
|
||||
|
||||
$data['users'] = [
|
||||
$data['assigned_ids'] = [$logged_user_id];
|
||||
$data['users'] = [
|
||||
[
|
||||
'id' => $row->id,
|
||||
'first_name' => $row->first_name,
|
||||
'nhance_branch_id' => $nhance_branch_id
|
||||
'first_name' => $row->first_name,
|
||||
'last_name' => $row->last_name ?? '',
|
||||
'nhance_branch_id' => $nhance_branch_id,
|
||||
]
|
||||
];
|
||||
|
||||
}
|
||||
// Otherwise fetch ALL sales managers in this branch
|
||||
elseif (in_array($role,[1,5])) {
|
||||
|
||||
$data['users'] = $db->table('user_profiles up')
|
||||
->select('up.id, up.first_name, up.last_name, up.nhance_branch_id')
|
||||
->join('user_teams ut', 'ut.user_id = up.id')
|
||||
->where('up.is_active', 1)
|
||||
->where('ut.is_active', 1)
|
||||
->where('up.role', 4)
|
||||
->where('ut.team_id', 5)
|
||||
->where('up.nhance_branch_id', $nhance_branch_id)
|
||||
->get()
|
||||
->getResultArray();
|
||||
// ── Sales Head (Role 1 or 5) ─────────────────────────────────
|
||||
} elseif (in_array($role, [1, 5])) {
|
||||
|
||||
$data['sales_manager_ids'] = array_column($data['users'], 'id');
|
||||
$data['sales_role'] = 'Sales Head';
|
||||
$data['users'] = $db->table('user_profiles up')
|
||||
->select('up.id, up.first_name, up.last_name, up.nhance_branch_id')
|
||||
->join('user_teams ut', 'ut.user_id = up.id')
|
||||
->where('up.is_active', 1)
|
||||
->where('ut.is_active', 1)
|
||||
->where('up.role', 4)
|
||||
->where('ut.team_id', 5)
|
||||
->where('up.nhance_branch_id', $nhance_branch_id)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
$ids = array_column($data['users'], 'id');
|
||||
$data['sales_manager_ids'] = $ids;
|
||||
$data['assigned_ids'] = $ids; // same value, both available
|
||||
}
|
||||
|
||||
return $data;
|
||||
@ -751,6 +773,138 @@ class SalesController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 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 ====================
|
||||
|
||||
/**
|
||||
@ -773,52 +927,23 @@ class SalesController extends BaseController
|
||||
|
||||
|
||||
// ==================== Dashboard ====================
|
||||
public function dashboard(){
|
||||
|
||||
$logged_user_id = get_session_userid();
|
||||
$role = get_role_id();
|
||||
$team_id = user_team();
|
||||
public function dashboard()
|
||||
{
|
||||
$payload = $this->request->getGet();
|
||||
$base = $this->getSalesStaffData();
|
||||
$salesRole = $base['sales_role'];
|
||||
$salesManagerIds = $base['sales_manager_ids'];
|
||||
$userId = get_session_userid();
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
// Get branch id from users array
|
||||
$nhanceBranchId = $base['users'][0]['nhance_branch_id'] ?? null;
|
||||
|
||||
$row = $db->table('user_profiles')
|
||||
->select('*')
|
||||
->where('is_active', 1)
|
||||
->where('id', $logged_user_id)
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
$nhance_branch_id = $row ? $row['nhance_branch_id'] : null;
|
||||
|
||||
// dd($logged_user_id, $nhance_branch_id, $role, $team_id );
|
||||
|
||||
if (in_array($role,[1,5])) {
|
||||
|
||||
$sales_manager_ids = array_column(
|
||||
$db->table('user_profiles up')
|
||||
->select('up.id')
|
||||
->join('user_teams ut', 'ut.user_id = up.id')
|
||||
->where([
|
||||
'up.is_active' => 1,
|
||||
'ut.is_active' => 1,
|
||||
'up.role' => 4,
|
||||
'ut.team_id' => 5,
|
||||
'up.nhance_branch_id' => $nhance_branch_id
|
||||
])
|
||||
->get()
|
||||
->getResultArray(),
|
||||
'id'
|
||||
);
|
||||
$this->branchLevelDashboard($nhance_branch_id,$sales_manager_ids);
|
||||
|
||||
}
|
||||
elseif ($role == 4 && in_array(5, $team_id)) {
|
||||
$sales_manager_ids = [$logged_user_id];
|
||||
$this->salesManagerLevelDashboard($logged_user_id,$sales_manager_ids, $payload);
|
||||
if ($salesRole === 'Sales Head') {
|
||||
$this->branchLevelDashboard($nhanceBranchId, $salesManagerIds);
|
||||
} elseif ($salesRole === 'Sales Manager') {
|
||||
$this->salesManagerLevelDashboard($userId, $salesManagerIds,$payload);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function branchLevelDashboard($branchId,$sales_manager_ids)
|
||||
{
|
||||
@ -1093,9 +1218,23 @@ class SalesController extends BaseController
|
||||
|
||||
/* ---------------- SUMMARY ---------------- */
|
||||
|
||||
$summary = ucfirst($input['activity_type']) .
|
||||
' with ' .
|
||||
$lead_data['company_name'];
|
||||
$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 ---------------- */
|
||||
|
||||
|
||||
@ -567,6 +567,8 @@ class VidalApiController extends BaseController
|
||||
|
||||
// dd($TicketData);
|
||||
|
||||
log_message('error', "VIDAL - Claim Status | Total tickets fetched for status update: " . count($TicketData));
|
||||
|
||||
if (!$TicketData) {
|
||||
log_message('error', "VIDAL - Claim Status | Claims not found to update status");
|
||||
return $this->response->setJSON(['status' => false,'message' => 'Claims not found' ]);
|
||||
@ -599,7 +601,12 @@ class VidalApiController extends BaseController
|
||||
|
||||
if ($response['status'] != true || empty($response['data']['data']['claims'][0])) {
|
||||
log_message('error', 'VIDAL - Claim Status FAILED | for ticket ID: ' . $claimId.' | response: '.json_encode($response));
|
||||
$error_data[$claimId][['status' => false,'message' => 'API call failed.','data' => $response]];
|
||||
$error_data[$claimId] = [
|
||||
'status' => false,
|
||||
'message' => 'API call failed.',
|
||||
'data' => $response
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract claim status
|
||||
|
||||
@ -766,18 +766,18 @@ if(!function_exists('query_construct_for_emp_policy_id')){
|
||||
$gst_alise = '';
|
||||
}
|
||||
$subQuery .= "SELECT " . intval($index) . $row_id_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['emp_name']] . "'" . $emp_name_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['emp_code']] . "'" . $emp_code_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['emp_dob']] . "'" . $emp_dob_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['emp_gender']] . "'" . $emp_gender_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['pre_existing_alignments']] . "'" . $pre_existing_alignments_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['basic_cover_si']] . "'" . $basic_cover_si_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['emp_relationship']] . "'" . $emp_relationship_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['policy_end_date']] . "'" . $policy_end_date_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['days']] . "'" . $days_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['premium']] . "'" . $premium_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['rata_premimum']] . "'" . $rata_premimum_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['gst']] . "'" . $gst_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['emp_name']]) . $emp_name_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['emp_code']]) . $emp_code_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['emp_dob']]) . $emp_dob_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['emp_gender']]) . $emp_gender_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['pre_existing_alignments']]) . $pre_existing_alignments_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['basic_cover_si']]) . $basic_cover_si_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['emp_relationship']]) . $emp_relationship_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['policy_end_date']]) . $policy_end_date_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['days']]) . $days_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['premium']]) . $premium_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['rata_premimum']]) . $rata_premimum_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['gst']]) . $gst_alise;
|
||||
}
|
||||
|
||||
// Complete the query
|
||||
@ -893,18 +893,18 @@ if(!function_exists('query_construct_second_stage')){
|
||||
$gst_alise = '';
|
||||
}
|
||||
$subQuery .= "SELECT " . intval($index) . $row_id_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['emp_name']] . "'" . $emp_name_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['emp_code']] . "'" . $emp_code_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['emp_dob']] . "'" . $emp_dob_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['emp_gender']] . "'" . $emp_gender_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['pre_existing_alignments']] . "'" . $pre_existing_alignments_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['basic_cover_si']] . "'" . $basic_cover_si_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['emp_relationship']] . "'" . $emp_relationship_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['policy_end_date']] . "'" . $policy_end_date_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['days']] . "'" . $days_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['premium']] . "'" . $premium_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['rata_premimum']] . "'" . $rata_premimum_alise;
|
||||
$subQuery .= "'" . $employee[$col_index['gst']] . "'" . $gst_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['emp_name']]) . $emp_name_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['emp_code']]) . $emp_code_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['emp_dob']]) . $emp_dob_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['emp_gender']]) . $emp_gender_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['pre_existing_alignments']]) . $pre_existing_alignments_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['basic_cover_si']]) . $basic_cover_si_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['emp_relationship']]) . $emp_relationship_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['policy_end_date']]) . $policy_end_date_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['days']]) . $days_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['premium']]) . $premium_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['rata_premimum']]) . $rata_premimum_alise;
|
||||
$subQuery .= $db->escape($employee[$col_index['gst']]) . $gst_alise;
|
||||
}
|
||||
|
||||
// Complete the query
|
||||
|
||||
@ -65,12 +65,23 @@ class DataServiceSqlite
|
||||
$this->sqliteDb->exec($sql);
|
||||
}
|
||||
|
||||
private const ALLOWED_TABLES = ['http', 'log'];
|
||||
|
||||
public function insertData(array $data, string $tableName): bool
|
||||
{
|
||||
log_message('error', 'SQLite insert called: ');
|
||||
|
||||
if (!in_array($tableName, self::ALLOWED_TABLES, true)) {
|
||||
log_message('error', 'SQLite insert rejected: invalid table name: ' . $tableName);
|
||||
return false;
|
||||
}
|
||||
|
||||
unset($data['context']);
|
||||
$columns = implode(', ', array_keys($data));
|
||||
// print_r($columns);
|
||||
|
||||
$safeColumns = array_map(function ($col) {
|
||||
return preg_replace('/[^a-zA-Z0-9_]/', '', $col);
|
||||
}, array_keys($data));
|
||||
$columns = implode(', ', $safeColumns);
|
||||
$placeholders = implode(', ', array_fill(0, count($data), '?'));
|
||||
|
||||
$sql = "INSERT INTO $tableName ($columns) VALUES ($placeholders)";
|
||||
|
||||
@ -228,8 +228,14 @@ class ClientModel extends Model
|
||||
return $result;
|
||||
}
|
||||
|
||||
private const ALLOWED_CONTACT_FIELDS = ['email', 'mobile', 'phone', 'name', 'contact_name', 'contact_email', 'contact_mobile'];
|
||||
|
||||
public function isDuplicateByClientBranch($value, $field, $clientId, $branchId)
|
||||
{
|
||||
if (!in_array($field, self::ALLOWED_CONTACT_FIELDS, true)) {
|
||||
throw new \InvalidArgumentException("Invalid field name: {$field}");
|
||||
}
|
||||
|
||||
$builder = $this->db->table('level_contacts lc')
|
||||
->select('lc.id')
|
||||
->join('client_branch cb', 'lc.ref_id = cb.id', 'left')
|
||||
|
||||
@ -1146,6 +1146,8 @@ class EmployeePolicyModel extends Model
|
||||
|
||||
$endorsement_condition = "{$insurer_or_tpa}" === 'tpa' ? "AND (a.endorsement_id IS NULL OR a.endorsement_id = '')" : "AND (a.endorsement_id IS NULL OR a.endorsement_id = '')";
|
||||
|
||||
$add_one_day = (int)$add_one_day;
|
||||
|
||||
$query = $this->db->query("
|
||||
SELECT DISTINCT
|
||||
a.id as endorsement_primarykey,
|
||||
@ -1688,30 +1690,29 @@ class EmployeePolicyModel extends Model
|
||||
public function bulkUpdate($emp_details)
|
||||
{
|
||||
// Extract IDs, tpa_ids, and uhids
|
||||
$ids = array_column($emp_details, 'id');
|
||||
$ids = array_map('intval', array_column($emp_details, 'id'));
|
||||
$tpa_ids = array_column($emp_details, 'tpa_id');
|
||||
$uhids = array_column($emp_details, 'uhid');
|
||||
|
||||
// Escape values for SQL
|
||||
$escapedIds = array_map([$this->db, 'escape'], $ids);
|
||||
// Escape string values for SQL
|
||||
$escapedTpaIds = array_map([$this->db, 'escape'], $tpa_ids);
|
||||
$escapedUhids = array_map([$this->db, 'escape'], $uhids);
|
||||
|
||||
// Construct the CASE statements
|
||||
// Construct the CASE statements with int-cast IDs
|
||||
$caseTpaId = array_map(function($id, $tpa_id) {
|
||||
return "WHEN id = $id THEN $tpa_id";
|
||||
}, $escapedIds, $escapedTpaIds);
|
||||
return "WHEN id = {$id} THEN {$tpa_id}";
|
||||
}, $ids, $escapedTpaIds);
|
||||
|
||||
$caseUhid = array_map(function($id, $uhid) {
|
||||
return "WHEN id = $id THEN $uhid";
|
||||
}, $escapedIds, $escapedUhids);
|
||||
return "WHEN id = {$id} THEN {$uhid}";
|
||||
}, $ids, $escapedUhids);
|
||||
|
||||
// Convert cases to a string
|
||||
$caseTpaIdString = implode(' ', $caseTpaId);
|
||||
$caseUhidString = implode(' ', $caseUhid);
|
||||
|
||||
// Convert ids to a string
|
||||
$idsString = implode(', ', $escapedIds);
|
||||
$idsString = implode(', ', $ids);
|
||||
|
||||
// Construct the SQL query
|
||||
$sql = "
|
||||
@ -1837,18 +1838,19 @@ class EmployeePolicyModel extends Model
|
||||
$groupKeys = [];
|
||||
|
||||
foreach ($endorsement_details as $row) {
|
||||
$groupKey = $this->db->escape($row['group_key']);
|
||||
$groupKey = (int)$row['group_key'];
|
||||
$endorsementId = $this->db->escape($row['endorsement_id']);
|
||||
$caseParts[] = "WHEN group_key = {$groupKey} THEN {$endorsementId}";
|
||||
$groupKeys[] = $groupKey;
|
||||
}
|
||||
|
||||
$groupKeysStr = implode(', ', $groupKeys);
|
||||
$sql = "
|
||||
UPDATE emp_endorsement
|
||||
SET
|
||||
endorsement_id = CASE " . implode(' ', $caseParts) . " END,
|
||||
status = 'complete'
|
||||
WHERE group_key IN (" . implode(', ', $groupKeys) . ")
|
||||
WHERE group_key IN ({$groupKeysStr})
|
||||
";
|
||||
|
||||
$this->db->query($sql);
|
||||
@ -1866,26 +1868,37 @@ class EmployeePolicyModel extends Model
|
||||
|
||||
public function bulkUpdateForCorrection($emp_details){
|
||||
|
||||
$ids = [];
|
||||
$caseStatements = [];
|
||||
|
||||
$employeeTableFields = $this->db->getFieldNames('employees');
|
||||
|
||||
foreach ($emp_details as $employee) {
|
||||
$id = $this->db->escape($employee['id']);
|
||||
$id = (int)$employee['id'];
|
||||
$ids[] = $id;
|
||||
|
||||
foreach ($employee as $field => $value) {
|
||||
if ($field === 'id') continue;
|
||||
|
||||
$safeField = preg_replace('/[^a-zA-Z0-9_]/', '', $field);
|
||||
if (!in_array($safeField, $employeeTableFields, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$escapedValue = $this->db->escape($value);
|
||||
|
||||
if (!isset($caseStatements[$field])) {
|
||||
$caseStatements[$field] = [];
|
||||
if (!isset($caseStatements[$safeField])) {
|
||||
$caseStatements[$safeField] = [];
|
||||
}
|
||||
|
||||
$caseStatements[$field][] = "WHEN id = $id THEN $escapedValue";
|
||||
$caseStatements[$safeField][] = "WHEN id = {$id} THEN {$escapedValue}";
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the CASE strings
|
||||
$caseStrings = [];
|
||||
foreach ($caseStatements as $field => $cases) {
|
||||
$caseStrings[] = "$field = CASE " . implode(' ', $cases) . " END";
|
||||
$caseStrings[] = "{$field} = CASE " . implode(' ', $cases) . " END";
|
||||
}
|
||||
|
||||
// Convert ids to a string
|
||||
@ -1941,7 +1954,7 @@ class EmployeePolicyModel extends Model
|
||||
->getRowArray();
|
||||
|
||||
// Determine if one day should be added for deletion
|
||||
$add_one_day = (!empty($result) && $result['deletion_add_day'] == 1) ? 1 : 0;
|
||||
$add_one_day = (int)((!empty($result) && $result['deletion_add_day'] == 1) ? 1 : 0);
|
||||
|
||||
|
||||
$query = $this->db->query("
|
||||
@ -2130,14 +2143,17 @@ class EmployeePolicyModel extends Model
|
||||
$caseEndorsementId = "CASE ";
|
||||
$ids = [];
|
||||
foreach ($array as $item) {
|
||||
$caseStatus .= "WHEN id = {$item['id']} THEN '{$item['status']}' ";
|
||||
$caseEndorsementId .= "WHEN id = {$item['id']} THEN '{$item['endorsement_id']}' ";
|
||||
$ids[] = $item['id'];
|
||||
$safeId = (int)$item['id'];
|
||||
$safeStatus = $this->db->escape($item['status']);
|
||||
$safeEndorsementId = $this->db->escape($item['endorsement_id']);
|
||||
$caseStatus .= "WHEN id = {$safeId} THEN {$safeStatus} ";
|
||||
$caseEndorsementId .= "WHEN id = {$safeId} THEN {$safeEndorsementId} ";
|
||||
$ids[] = $safeId;
|
||||
}
|
||||
$caseStatus .= "END";
|
||||
$caseEndorsementId .= "END";
|
||||
$ids = implode(',', $ids);
|
||||
$query = "UPDATE emp_endorsement SET status = $caseStatus, endorsement_id = $caseEndorsementId WHERE id IN ($ids);"; // Execute the query
|
||||
$query = "UPDATE emp_endorsement SET status = $caseStatus, endorsement_id = $caseEndorsementId WHERE id IN ($ids);";
|
||||
$this->db->query($query);
|
||||
}
|
||||
|
||||
|
||||
@ -127,6 +127,22 @@
|
||||
return $data;
|
||||
}
|
||||
|
||||
private const ALLOWED_DATE_TYPES = [
|
||||
'policy_issue_date',
|
||||
'policy_start_date',
|
||||
'policy_end_date',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
private function validateDateType($date_type)
|
||||
{
|
||||
if (!in_array($date_type, self::ALLOWED_DATE_TYPES, true)) {
|
||||
throw new \InvalidArgumentException("Invalid date_type: {$date_type}");
|
||||
}
|
||||
return $date_type;
|
||||
}
|
||||
|
||||
// BDS Report OLD Functin for QUERY
|
||||
// public function getBDSReportList($client_id, $policy_id, $branch_id, $issuer)
|
||||
public function getBDSReportListOld($start_date = 0, $end_date = 0, $client_id = 0, $insurer_id = 0, $policy_type_id = 0, $date_type = 0, $issuer = 0)
|
||||
@ -282,11 +298,12 @@
|
||||
// Check if the start date and end date are provided
|
||||
if ($start_date != 0 && $end_date != 0 && $date_type != 0) {
|
||||
|
||||
$this->validateDateType($date_type);
|
||||
$startDate = date('Y-m-d 00:00:00', strtotime($start_date));
|
||||
$endDate = date('Y-m-d 23:59:59', strtotime($end_date));
|
||||
|
||||
$builder->where('policy_transaction.' . $date_type . '>=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . '<=', $endDate);
|
||||
$builder->where('policy_transaction.' . $date_type . ' >=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . ' <=', $endDate);
|
||||
} else {
|
||||
|
||||
// $fromDate = date('Y-m-d', strtotime('-30 days'));
|
||||
@ -835,18 +852,16 @@
|
||||
// Check if the start date and end date are provided
|
||||
if ($start_date != 0 && $end_date != 0 && $date_type != 0 && $date_type != 'statement_month') {
|
||||
|
||||
$this->validateDateType($date_type);
|
||||
$startDate = date('Y-m-d 00:00:00', strtotime($start_date));
|
||||
$endDate = date('Y-m-d 23:59:59', strtotime($end_date));
|
||||
|
||||
// $builder->where('policy_transaction.' . $date_type . '>=', $startDate)
|
||||
// ->where('policy_transaction.' . $date_type . '<=', $endDate);
|
||||
|
||||
if($date_type == "policy_issue_date"){
|
||||
$builder->where("pt_co_share_details.pt_policy_issue_date >=", $startDate)
|
||||
->where("pt_co_share_details.pt_policy_issue_date <=", $endDate);
|
||||
}else{
|
||||
$builder->where('policy_transaction.' . $date_type . '>=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . '<=', $endDate);
|
||||
$builder->where('policy_transaction.' . $date_type . ' >=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . ' <=', $endDate);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1064,6 +1079,7 @@
|
||||
// Optimize Date Filtering
|
||||
if (!empty($start_date) && !empty($end_date) && !empty($date_type)) {
|
||||
|
||||
$this->validateDateType($date_type);
|
||||
$startDate = change_date_format($start_date, 'd/m/Y', 'Y-m-d 00:00:00');
|
||||
$endDate = change_date_format($end_date, 'd/m/Y', 'Y-m-d 23:59:59');
|
||||
|
||||
@ -1165,6 +1181,7 @@
|
||||
|
||||
if ($start_date != 0 && $end_date != 0 && $date_type != 0) {
|
||||
|
||||
$this->validateDateType($date_type);
|
||||
$startDate = change_date_format($start_date, 'd/m/Y', 'Y-m-d 00:00:00');
|
||||
$endDate = change_date_format($end_date, 'd/m/Y', 'Y-m-d 23:59:59');
|
||||
|
||||
@ -1172,8 +1189,8 @@
|
||||
$builder->where("pt_co_share_details.pt_policy_issue_date >=", $startDate)
|
||||
->where("pt_co_share_details.pt_policy_issue_date <=", $endDate);
|
||||
}else{
|
||||
$builder->where('policy_transaction.' . $date_type . '>=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . '<=', $endDate);
|
||||
$builder->where('policy_transaction.' . $date_type . ' >=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . ' <=', $endDate);
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -1349,18 +1366,16 @@
|
||||
|
||||
if ($start_date != 0 && $end_date != 0 && $date_type != 0) {
|
||||
|
||||
$this->validateDateType($date_type);
|
||||
$startDate = date('Y-m-d 00:00:00', strtotime($start_date));
|
||||
$endDate = date('Y-m-d 23:59:59', strtotime($end_date));
|
||||
|
||||
// $builder->where('policy_transaction.' . $date_type . '>=', $startDate)
|
||||
// ->where('policy_transaction.' . $date_type . '<=', $endDate);
|
||||
|
||||
if($date_type == "policy_issue_date"){
|
||||
$builder->where("pt_co_share_details.pt_policy_issue_date >=", $startDate)
|
||||
->where("pt_co_share_details.pt_policy_issue_date <=", $endDate);
|
||||
}else{
|
||||
$builder->where('policy_transaction.' . $date_type . '>=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . '<=', $endDate);
|
||||
$builder->where('policy_transaction.' . $date_type . ' >=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . ' <=', $endDate);
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -1463,18 +1478,16 @@
|
||||
|
||||
if ($start_date != 0 && $end_date != 0 && $date_type != 0) {
|
||||
|
||||
$this->validateDateType($date_type);
|
||||
$startDate = date('Y-m-d 00:00:00', strtotime($start_date));
|
||||
$endDate = date('Y-m-d 23:59:59', strtotime($end_date));
|
||||
|
||||
// $builder->where('policy_transaction.' . $date_type . '>=', $startDate)
|
||||
// ->where('policy_transaction.' . $date_type . '<=', $endDate);
|
||||
|
||||
if($date_type == "policy_issue_date"){
|
||||
$builder->where("pt_co_share_details.pt_policy_issue_date >=", $startDate)
|
||||
->where("pt_co_share_details.pt_policy_issue_date <=", $endDate);
|
||||
}else{
|
||||
$builder->where('policy_transaction.' . $date_type . '>=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . '<=', $endDate);
|
||||
$builder->where('policy_transaction.' . $date_type . ' >=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . ' <=', $endDate);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -1574,18 +1587,16 @@
|
||||
|
||||
if ($start_date != 0 && $end_date != 0 && $date_type != 0) {
|
||||
|
||||
$this->validateDateType($date_type);
|
||||
$startDate = date('Y-m-d 00:00:00', strtotime($start_date));
|
||||
$endDate = date('Y-m-d 23:59:59', strtotime($end_date));
|
||||
|
||||
// $builder->where('policy_transaction.' . $date_type . '>=', $startDate)
|
||||
// ->where('policy_transaction.' . $date_type . '<=', $endDate);
|
||||
|
||||
if($date_type == "policy_issue_date"){
|
||||
$builder->where("pt_co_share_details.pt_policy_issue_date >=", $startDate)
|
||||
->where("pt_co_share_details.pt_policy_issue_date <=", $endDate);
|
||||
}else{
|
||||
$builder->where('policy_transaction.' . $date_type . '>=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . '<=', $endDate);
|
||||
$builder->where('policy_transaction.' . $date_type . ' >=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . ' <=', $endDate);
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -1689,18 +1700,16 @@
|
||||
|
||||
// Date range filtering
|
||||
if ($start_date != 0 && $end_date != 0 && $date_type != 0) {
|
||||
$this->validateDateType($date_type);
|
||||
$startDate = date('Y-m-d 00:00:00', strtotime($start_date));
|
||||
$endDate = date('Y-m-d 23:59:59', strtotime($end_date));
|
||||
|
||||
// $builder->where('policy_transaction.' . $date_type . '>=', $startDate)
|
||||
// ->where('policy_transaction.' . $date_type . '<=', $endDate);
|
||||
|
||||
if($date_type == "policy_issue_date"){
|
||||
$builder->where("pt_co_share_details.pt_policy_issue_date >=", $startDate)
|
||||
->where("pt_co_share_details.pt_policy_issue_date <=", $endDate);
|
||||
}else{
|
||||
$builder->where('policy_transaction.' . $date_type . '>=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . '<=', $endDate);
|
||||
$builder->where('policy_transaction.' . $date_type . ' >=', $startDate)
|
||||
->where('policy_transaction.' . $date_type . ' <=', $endDate);
|
||||
}
|
||||
} else {
|
||||
$fromDate = date('Y-m-d', strtotime('-90 days'));
|
||||
@ -2364,6 +2373,7 @@
|
||||
}
|
||||
|
||||
if ($start_date != 0 && $end_date != 0 && $date_type != 0 && $date_type != 'statement_month') {
|
||||
$this->validateDateType($date_type);
|
||||
$startDate = date('Y-m-d 00:00:00', strtotime($start_date));
|
||||
$endDate = date('Y-m-d 23:59:59', strtotime($end_date));
|
||||
|
||||
@ -2489,6 +2499,7 @@
|
||||
// 3. Date condition (except statement_month)
|
||||
if ($start_date != 0 && $end_date != 0 && $date_type != 0 && $date_type != 'statement_month') {
|
||||
|
||||
$this->validateDateType($date_type);
|
||||
$startDate = date('Y-m-d 00:00:00', strtotime($start_date));
|
||||
$endDate = date('Y-m-d 23:59:59', strtotime($end_date));
|
||||
|
||||
@ -3080,6 +3091,7 @@
|
||||
// 3. Date condition (except statement_month)
|
||||
if ($start_date != 0 && $end_date != 0 && $date_type != 0 && $date_type != 'statement_month') {
|
||||
|
||||
$this->validateDateType($date_type);
|
||||
$startDate = date('Y-m-d 00:00:00', strtotime($start_date));
|
||||
$endDate = date('Y-m-d 23:59:59', strtotime($end_date));
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ class SalesActivityModel extends Model
|
||||
// Validation
|
||||
protected $validationRules = [
|
||||
'lead_id' => 'required|integer',
|
||||
'activity_type' => 'required|in_list[Call,Email,Meeting,Demo,Share,Todo,Visit]',
|
||||
'activity_type' => 'required|in_list[Call,Email,Meeting,Visit,Demo,Share Docs,To Do]',
|
||||
'notes' => 'required',
|
||||
'scheduled_date' => 'required',
|
||||
'assigned_to' => 'required|integer',
|
||||
@ -102,7 +102,7 @@ class SalesActivityModel extends Model
|
||||
$assignedToIds = is_array($assigned_to) ? $assigned_to : explode(',', $assigned_to);
|
||||
|
||||
// Now it is guaranteed to be an array, making whereIn perfectly safe
|
||||
$this->whereIn('sales_actual_leads.assigned_to', $assignedToIds);
|
||||
$this->whereIn('sales_activities.assigned_to', $assignedToIds);
|
||||
}
|
||||
|
||||
|
||||
|
||||
38
app/Models/SalesTargetModel.php
Normal file
38
app/Models/SalesTargetModel.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class SalesTargetModel extends Model
|
||||
{
|
||||
protected $table = 'sales_target';
|
||||
protected $primaryKey = 'id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
|
||||
protected $allowedFields = [
|
||||
'user_id',
|
||||
'fy_year',
|
||||
'target_amount',
|
||||
'created_by',
|
||||
'updated_by'
|
||||
];
|
||||
|
||||
protected $useTimestamps = true;
|
||||
protected $createdField = 'created_at';
|
||||
protected $updatedField = 'updated_at';
|
||||
|
||||
protected $validationRules = [
|
||||
'user_id' => 'required|integer',
|
||||
'fy_year' => 'required|max_length[9]',
|
||||
'target_amount' => 'required|decimal',
|
||||
];
|
||||
|
||||
protected $validationMessages = [
|
||||
'user_id' => ['required' => 'User is required'],
|
||||
'fy_year' => ['required' => 'Financial year is required'],
|
||||
'target_amount' => ['required' => 'Target amount is required', 'decimal' => 'Target amount must be a valid number'],
|
||||
];
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
@ -12,7 +11,7 @@ class TicketMasterModel extends Model
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
protected $allowedFields = [
|
||||
protected $allowedFields = [
|
||||
'id',
|
||||
'ticket_type_id',
|
||||
'feedback_json',
|
||||
@ -79,9 +78,9 @@ class TicketMasterModel extends Model
|
||||
'approved_description',
|
||||
'file_id',
|
||||
'denial_letter',
|
||||
|
||||
|
||||
'manager_id',
|
||||
'agent_id' ,
|
||||
'agent_id',
|
||||
'vehicle_id',
|
||||
|
||||
'hospital_address',
|
||||
@ -98,17 +97,16 @@ class TicketMasterModel extends Model
|
||||
|
||||
];
|
||||
|
||||
|
||||
// Callbacks
|
||||
protected $allowCallbacks = true;
|
||||
protected $beforeInsert = ["checkAndADDCreatedByValue"];
|
||||
protected $afterInsert = [];
|
||||
protected $beforeUpdate = ["checkAndUpdateUpdatedByValue"];
|
||||
protected $afterUpdate = [];
|
||||
protected $beforeFind = [];
|
||||
protected $afterFind = [];
|
||||
protected $beforeDelete = [];
|
||||
protected $afterDelete = [];
|
||||
protected $allowCallbacks = true;
|
||||
protected $beforeInsert = ["checkAndADDCreatedByValue"];
|
||||
protected $afterInsert = [];
|
||||
protected $beforeUpdate = ["checkAndUpdateUpdatedByValue"];
|
||||
protected $afterUpdate = [];
|
||||
protected $beforeFind = [];
|
||||
protected $afterFind = [];
|
||||
protected $beforeDelete = [];
|
||||
protected $afterDelete = [];
|
||||
|
||||
protected function checkAndADDCreatedByValue(array $data)
|
||||
{
|
||||
@ -150,15 +148,15 @@ class TicketMasterModel extends Model
|
||||
public function getTemplateDataByTicketID($ticket_id)
|
||||
{
|
||||
$template_data = $this
|
||||
->select("ticket_mail_template.*, CASE
|
||||
WHEN employees.email_corporate IS NULL OR employees.email_corporate = ''
|
||||
THEN ticket_master.emp_mail
|
||||
ELSE employees.email_corporate
|
||||
->select("ticket_mail_template.*, CASE
|
||||
WHEN employees.email_corporate IS NULL OR employees.email_corporate = ''
|
||||
THEN ticket_master.emp_mail
|
||||
ELSE employees.email_corporate
|
||||
END as emp_mail, ,ticket_master.claim_status_id")
|
||||
->join('ticket_claim_status', 'ticket_master.claim_status_id = ticket_claim_status.id and ticket_claim_status.is_active = 1')
|
||||
->join('ticket_mail_template', '
|
||||
ticket_claim_status.trigger_type = ticket_mail_template.trigger_type
|
||||
and ticket_claim_status.ticket_type = ticket_mail_template.ticket_type
|
||||
ticket_claim_status.trigger_type = ticket_mail_template.trigger_type
|
||||
and ticket_claim_status.ticket_type = ticket_mail_template.ticket_type
|
||||
and ticket_mail_template.is_active = 1')
|
||||
->join('employees', 'employees.id = ticket_master.emp_id', 'left')
|
||||
->where('ticket_master.id', $ticket_id)
|
||||
@ -173,11 +171,11 @@ class TicketMasterModel extends Model
|
||||
{
|
||||
$ticket_data = $this->select("
|
||||
ticket_master.*,
|
||||
CASE
|
||||
WHEN employees.email_corporate IS NULL OR employees.email_corporate = ''
|
||||
THEN ticket_master.emp_mail
|
||||
ELSE employees.email_corporate
|
||||
END as emp_mail,
|
||||
CASE
|
||||
WHEN employees.email_corporate IS NULL OR employees.email_corporate = ''
|
||||
THEN ticket_master.emp_mail
|
||||
ELSE employees.email_corporate
|
||||
END as emp_mail,
|
||||
|
||||
user_profiles.first_name as acm,
|
||||
user_profiles.mobile as acm_mobile,
|
||||
@ -205,7 +203,7 @@ class TicketMasterModel extends Model
|
||||
|
||||
//get TAT report BAND wise Data
|
||||
// public function getTATReport($ticket_type = null, $start_date = null, $end_date = null)
|
||||
// {
|
||||
// {
|
||||
// //set default last 3 months data date
|
||||
// $fromDate = date('Y-m-d', strtotime('-90 days'));
|
||||
// $toDate = date('Y-m-d 23:59:59');
|
||||
@ -222,9 +220,9 @@ class TicketMasterModel extends Model
|
||||
// }
|
||||
|
||||
// // Fetch claim statuses from the `ticket_claim_status` table
|
||||
// $statusQuery = " SELECT
|
||||
// $statusQuery = " SELECT
|
||||
// id, claim_status, ticket_type
|
||||
// FROM ticket_claim_status
|
||||
// FROM ticket_claim_status
|
||||
// Where is_active = 1
|
||||
// $ticket_type_data_1
|
||||
// ";
|
||||
@ -273,7 +271,6 @@ class TicketMasterModel extends Model
|
||||
// $dynamicSelect = rtrim($dynamicSelect, ', ');
|
||||
// // dd($dynamicSelect);
|
||||
|
||||
|
||||
// // Construct the full SQL query
|
||||
// $sql = "
|
||||
// SELECT
|
||||
@ -326,7 +323,7 @@ class TicketMasterModel extends Model
|
||||
// WHERE
|
||||
// tm.ticket_type_id = tcs.id
|
||||
// $ticket_type_data_2
|
||||
// AND tm.created_at >= '$fromDate'
|
||||
// AND tm.created_at >= '$fromDate'
|
||||
// AND tm.created_at <= '$toDate'
|
||||
// AND tm.is_active = 1
|
||||
// AND th.is_active = 1
|
||||
@ -371,9 +368,9 @@ class TicketMasterModel extends Model
|
||||
// }
|
||||
|
||||
// // Fetch claim statuses from the `ticket_claim_status` table
|
||||
// $statusQuery = " SELECT
|
||||
// $statusQuery = " SELECT
|
||||
// id, claim_status, ticket_type
|
||||
// FROM ticket_claim_status
|
||||
// FROM ticket_claim_status
|
||||
// Where is_active = 1
|
||||
// $ticket_type_data_1
|
||||
// ";
|
||||
@ -422,7 +419,6 @@ class TicketMasterModel extends Model
|
||||
// $dynamicSelect = rtrim($dynamicSelect, ', ');
|
||||
// // dd($dynamicSelect);
|
||||
|
||||
|
||||
// // Construct the full SQL query
|
||||
// $sql = "
|
||||
// SELECT
|
||||
@ -451,20 +447,20 @@ class TicketMasterModel extends Model
|
||||
// FROM
|
||||
// ticket_master tm
|
||||
// LEFT JOIN (
|
||||
// SELECT
|
||||
// th.ticket_id,
|
||||
// SELECT
|
||||
// th.ticket_id,
|
||||
// th.new_value AS claim_status_id,
|
||||
// MAX(th.created_at) AS change_date
|
||||
// FROM
|
||||
// ticket_history th
|
||||
// WHERE
|
||||
// FROM
|
||||
// ticket_history th
|
||||
// WHERE
|
||||
// th.field_name = 'claim_status_id'
|
||||
// AND th.is_active = 1
|
||||
// GROUP BY
|
||||
// GROUP BY
|
||||
// th.ticket_id, th.new_value
|
||||
// ) AS latest_status ON latest_status.ticket_id = tm.id
|
||||
// WHERE tm.is_active = 1
|
||||
// AND tm.created_at >= '$fromDate'
|
||||
// AND tm.created_at >= '$fromDate'
|
||||
// AND tm.created_at <= '$toDate'
|
||||
// " . (!empty($ticket_type) ? " AND tm.ticket_type_id = $ticket_type" : "") . "
|
||||
// GROUP BY tm.id, latest_status.claim_status_id, latest_status.change_date, tm.created_at
|
||||
@ -493,33 +489,33 @@ class TicketMasterModel extends Model
|
||||
{
|
||||
// Set default date range (last 90 days)
|
||||
$fromDate = date('Y-m-d', strtotime('-90 days'));
|
||||
$toDate = date('Y-m-d 23:59:59');
|
||||
|
||||
if (!empty($start_date) && !empty($end_date)) {
|
||||
$toDate = date('Y-m-d 23:59:59');
|
||||
|
||||
if (! empty($start_date) && ! empty($end_date)) {
|
||||
$fromDate = change_date_format($start_date);
|
||||
$toDate = change_date_format($end_date);
|
||||
$toDate = change_date_format($end_date);
|
||||
}
|
||||
|
||||
|
||||
// Get all active claim statuses
|
||||
$statusQuery = $this->db->table('ticket_claim_status')
|
||||
->select('id, claim_status')
|
||||
->where('is_active', 1)
|
||||
->orderBy('id', 'ASC');
|
||||
|
||||
if (!empty($ticket_type)) {
|
||||
|
||||
if (! empty($ticket_type)) {
|
||||
$statusQuery->where('ticket_type', $ticket_type);
|
||||
}
|
||||
|
||||
|
||||
$statusResult = $statusQuery->get()->getResultArray();
|
||||
|
||||
|
||||
// Create a list of all TAT categories we want in the output
|
||||
$tatCategories = [
|
||||
'Above 20 Days',
|
||||
'13-20 Days',
|
||||
'13-20 Days',
|
||||
'7-12 Days',
|
||||
'0-6 Days'
|
||||
'0-6 Days',
|
||||
];
|
||||
|
||||
|
||||
// Initialize the result array with all TAT categories
|
||||
$result = [];
|
||||
foreach ($tatCategories as $category) {
|
||||
@ -529,39 +525,39 @@ class TicketMasterModel extends Model
|
||||
}
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
|
||||
// Get the base query for ticket counts by status and TAT category
|
||||
$query = $this->db->table('ticket_master tm')
|
||||
->select([
|
||||
'tcs.claim_status',
|
||||
'CASE
|
||||
WHEN DATEDIFF(CURDATE(), COALESCE(
|
||||
(SELECT MAX(th.created_at)
|
||||
FROM ticket_history th
|
||||
WHERE th.ticket_id = tm.id
|
||||
(SELECT MAX(th.created_at)
|
||||
FROM ticket_history th
|
||||
WHERE th.ticket_id = tm.id
|
||||
AND th.field_name = "claim_status_id"
|
||||
AND th.is_active = 1),
|
||||
tm.created_at
|
||||
)) BETWEEN 0 AND 6 THEN "0-6 Days"
|
||||
WHEN DATEDIFF(CURDATE(), COALESCE(
|
||||
(SELECT MAX(th.created_at)
|
||||
FROM ticket_history th
|
||||
WHERE th.ticket_id = tm.id
|
||||
(SELECT MAX(th.created_at)
|
||||
FROM ticket_history th
|
||||
WHERE th.ticket_id = tm.id
|
||||
AND th.field_name = "claim_status_id"
|
||||
AND th.is_active = 1),
|
||||
tm.created_at
|
||||
)) BETWEEN 7 AND 12 THEN "7-12 Days"
|
||||
WHEN DATEDIFF(CURDATE(), COALESCE(
|
||||
(SELECT MAX(th.created_at)
|
||||
FROM ticket_history th
|
||||
WHERE th.ticket_id = tm.id
|
||||
(SELECT MAX(th.created_at)
|
||||
FROM ticket_history th
|
||||
WHERE th.ticket_id = tm.id
|
||||
AND th.field_name = "claim_status_id"
|
||||
AND th.is_active = 1),
|
||||
tm.created_at
|
||||
)) BETWEEN 13 AND 20 THEN "13-20 Days"
|
||||
ELSE "Above 20 Days"
|
||||
END AS tat_category',
|
||||
'COUNT(*) AS count'
|
||||
'COUNT(*) AS count',
|
||||
])
|
||||
->join('ticket_claim_status tcs', 'tcs.id = tm.claim_status_id AND tcs.is_active = 1')
|
||||
->where('tm.is_active', 1)
|
||||
@ -569,23 +565,23 @@ class TicketMasterModel extends Model
|
||||
->where('tm.created_at <=', $toDate)
|
||||
->groupBy('tcs.claim_status, tat_category')
|
||||
->orderBy('FIELD(tat_category, "Above 20 Days", "13-20 Days", "7-12 Days", "0-6 Days")');
|
||||
|
||||
if (!empty($ticket_type)) {
|
||||
|
||||
if (! empty($ticket_type)) {
|
||||
$query->where('tm.ticket_type_id', $ticket_type);
|
||||
}
|
||||
|
||||
|
||||
$countResults = $query->get()->getResultArray();
|
||||
|
||||
|
||||
// Populate the result array with actual counts
|
||||
foreach ($countResults as $row) {
|
||||
foreach ($result as &$categoryRow) {
|
||||
if ($categoryRow['TAT_Category'] === $row['tat_category']) {
|
||||
$categoryRow[$row['claim_status']] = (int)$row['count'];
|
||||
$categoryRow[$row['claim_status']] = (int) $row['count'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@ -593,21 +589,21 @@ class TicketMasterModel extends Model
|
||||
{
|
||||
$ticket_type_data_1 = "";
|
||||
$ticket_type_data_2 = "";
|
||||
$statusBinds = [];
|
||||
$statusBinds = [];
|
||||
|
||||
if (!empty($policy_type)) {
|
||||
$ticket_type_data_1 = "AND ticket_type = :policy_type:";
|
||||
$ticket_type_data_2 = "AND master.ticket_type_id = :policy_type:";
|
||||
if (! empty($policy_type)) {
|
||||
$ticket_type_data_1 = "AND ticket_type = :policy_type:";
|
||||
$ticket_type_data_2 = "AND master.ticket_type_id = :policy_type:";
|
||||
$statusBinds['policy_type'] = $policy_type;
|
||||
}
|
||||
|
||||
// Fetch claim statuses dynamically
|
||||
$statusQuery = "SELECT id, claim_status FROM ticket_claim_status WHERE is_active = 1 $ticket_type_data_1";
|
||||
$statusQuery = "SELECT id, claim_status FROM ticket_claim_status WHERE is_active = 1 $ticket_type_data_1";
|
||||
$statusResult = $this->db->query($statusQuery, $statusBinds)->getResultArray();
|
||||
|
||||
// Initialize dynamic query parts
|
||||
$dynamicSelect = '';
|
||||
$dynamicTotal = '';
|
||||
$dynamicSelect = '';
|
||||
$dynamicTotal = '';
|
||||
$dynamicClosedTotal = '';
|
||||
|
||||
// Loop through each claim status and generate the CASE statements
|
||||
@ -624,7 +620,7 @@ class TicketMasterModel extends Model
|
||||
'UNDER PROCESS - INVESTIGATION STATUS',
|
||||
'UNDER PROCESS - QUERY DOCUMENT RECEIVED',
|
||||
'APPROVED',
|
||||
'PAYMENT INITIATED'
|
||||
'PAYMENT INITIATED',
|
||||
])) {
|
||||
$dynamicTotal .= "COUNT(CASE WHEN master.claim_status_id = {$status['id']} THEN master.id END) + ";
|
||||
}
|
||||
@ -638,34 +634,34 @@ class TicketMasterModel extends Model
|
||||
}
|
||||
|
||||
// Remove the trailing commas and `+` signs
|
||||
$dynamicSelect = rtrim($dynamicSelect, ', ');
|
||||
$dynamicTotal = rtrim($dynamicTotal, ' +');
|
||||
$dynamicSelect = rtrim($dynamicSelect, ', ');
|
||||
$dynamicTotal = rtrim($dynamicTotal, ' +');
|
||||
$dynamicClosedTotal = rtrim($dynamicClosedTotal, ' +');
|
||||
// print_rr($dynamicSelect);
|
||||
// Construct the final SQL query
|
||||
$sql = "
|
||||
SELECT
|
||||
SELECT
|
||||
tpa.id as TPA_ID,
|
||||
tpa.name AS TPA_NAME,
|
||||
$dynamicSelect,
|
||||
($dynamicTotal) AS TOTAL,
|
||||
($dynamicClosedTotal) AS CLEARED_TOTAL
|
||||
FROM
|
||||
FROM
|
||||
ticket_master master
|
||||
JOIN
|
||||
JOIN
|
||||
tpa ON master.tpa_id = tpa.id AND tpa.is_active = 1
|
||||
WHERE
|
||||
WHERE
|
||||
master.created_at BETWEEN :start_date: AND :end_date:
|
||||
AND master.is_active = 1
|
||||
$ticket_type_data_2
|
||||
GROUP BY
|
||||
GROUP BY
|
||||
tpa.id;
|
||||
";
|
||||
|
||||
// Execute the query
|
||||
$binds = ["start_date"=>$start_date,"end_date"=>$end_date];
|
||||
$binds = ["start_date" => $start_date, "end_date" => $end_date];
|
||||
// Merge policy_type binds if present
|
||||
if (!empty($statusBinds)) {
|
||||
if (! empty($statusBinds)) {
|
||||
$binds = array_merge($binds, $statusBinds);
|
||||
}
|
||||
$result = $this->db->query($sql, $binds)->getResultArray();
|
||||
@ -680,28 +676,27 @@ class TicketMasterModel extends Model
|
||||
if ($policy_type == 1) {
|
||||
$ticket_type_data_1 = "";
|
||||
$ticket_type_data_2 = "";
|
||||
$statusBinds = [];
|
||||
$statusBinds = [];
|
||||
|
||||
if (!empty($policy_type)) {
|
||||
$ticket_type_data_1 = "AND ticket_type = :policy_type:";
|
||||
$ticket_type_data_2 = "AND master.ticket_type_id = :policy_type:";
|
||||
if (! empty($policy_type)) {
|
||||
$ticket_type_data_1 = "AND ticket_type = :policy_type:";
|
||||
$ticket_type_data_2 = "AND master.ticket_type_id = :policy_type:";
|
||||
$statusBinds['policy_type'] = $policy_type;
|
||||
}
|
||||
|
||||
// Fetch claim statuses dynamically
|
||||
$statusQuery = "SELECT id, claim_status FROM ticket_claim_status WHERE is_active = 1 $ticket_type_data_1";
|
||||
$statusResult = $this->db->query($statusQuery,$statusBinds)->getResultArray();
|
||||
$statusQuery = "SELECT id, claim_status FROM ticket_claim_status WHERE is_active = 1 $ticket_type_data_1";
|
||||
$statusResult = $this->db->query($statusQuery, $statusBinds)->getResultArray();
|
||||
|
||||
// Initialize dynamic query parts
|
||||
$dynamicSelect = '';
|
||||
$dynamicTotal = '';
|
||||
$dynamicTotal = '';
|
||||
|
||||
// Loop through each claim status and generate the CASE statements
|
||||
foreach ($statusResult as $status) {
|
||||
$dynamicSelect .= "
|
||||
COUNT(CASE WHEN master.claim_status_id = {$status['id']} THEN master.id END) AS `{$status['claim_status']}`, ";
|
||||
|
||||
|
||||
// Include in total count
|
||||
$dynamicTotal .= "COUNT(CASE WHEN master.claim_status_id = {$status['id']} THEN master.id END) + ";
|
||||
|
||||
@ -711,54 +706,54 @@ class TicketMasterModel extends Model
|
||||
|
||||
// Remove the trailing commas and `+` signs
|
||||
$dynamicSelect = rtrim($dynamicSelect, ', ');
|
||||
$dynamicTotal = rtrim($dynamicTotal, ' +');
|
||||
$dynamicTotal = rtrim($dynamicTotal, ' +');
|
||||
// print_rr($dynamicSelect);
|
||||
// Construct the final SQL query
|
||||
$sql = "
|
||||
SELECT
|
||||
SELECT
|
||||
user_profiles.id as ACM_ID,
|
||||
user_profiles.first_name AS ACM_NAME,
|
||||
$dynamicSelect,
|
||||
($dynamicTotal) AS TOTAL
|
||||
FROM
|
||||
FROM
|
||||
ticket_master master
|
||||
JOIN
|
||||
JOIN
|
||||
user_profiles ON master.acm_id = user_profiles.id AND user_profiles.is_active = 1
|
||||
WHERE
|
||||
WHERE
|
||||
master.created_at BETWEEN :start_date: AND :end_date:
|
||||
AND master.is_active = 1
|
||||
$ticket_type_data_2
|
||||
GROUP BY
|
||||
GROUP BY
|
||||
user_profiles.id;
|
||||
";
|
||||
|
||||
$binds = ["start_date"=>$start_date,"end_date"=>$end_date];
|
||||
// Merge policy_type binds if present
|
||||
if (!empty($statusBinds)) {
|
||||
$binds = ["start_date" => $start_date, "end_date" => $end_date];
|
||||
// Merge policy_type binds if present
|
||||
if (! empty($statusBinds)) {
|
||||
$binds = array_merge($binds, $statusBinds);
|
||||
}
|
||||
$result = $this->db->query($sql,$binds)->getResultArray();
|
||||
$result = $this->db->query($sql, $binds)->getResultArray();
|
||||
// print_rr(count(($result)));
|
||||
// print_rr($this->db->lastQuery);die();
|
||||
return $result;
|
||||
} else {
|
||||
$ticket_type_data_1 = "";
|
||||
$ticket_type_data_2 = "";
|
||||
$statusBinds = [];
|
||||
$statusBinds = [];
|
||||
|
||||
if (!empty($policy_type)) {
|
||||
$ticket_type_data_1 = "AND ticket_type = :policy_type:";
|
||||
$ticket_type_data_2 = "AND master.ticket_type_id = :policy_type:";
|
||||
if (! empty($policy_type)) {
|
||||
$ticket_type_data_1 = "AND ticket_type = :policy_type:";
|
||||
$ticket_type_data_2 = "AND master.ticket_type_id = :policy_type:";
|
||||
$statusBinds['policy_type'] = $policy_type;
|
||||
}
|
||||
|
||||
// Fetch claim statuses dynamically
|
||||
$statusQuery = "SELECT id, claim_status FROM ticket_claim_status WHERE is_active = 1 $ticket_type_data_1";
|
||||
$statusResult = $this->db->query($statusQuery,$statusBinds)->getResultArray();
|
||||
$statusQuery = "SELECT id, claim_status FROM ticket_claim_status WHERE is_active = 1 $ticket_type_data_1";
|
||||
$statusResult = $this->db->query($statusQuery, $statusBinds)->getResultArray();
|
||||
|
||||
// Initialize dynamic query parts
|
||||
$dynamicSelect = '';
|
||||
$dynamicTotal = '';
|
||||
$dynamicSelect = '';
|
||||
$dynamicTotal = '';
|
||||
$dynamicClosedTotal = '';
|
||||
|
||||
// Loop through each claim status and generate the CASE statements
|
||||
@ -773,7 +768,7 @@ class TicketMasterModel extends Model
|
||||
'INSURER PENDING',
|
||||
'INVESTIGATION',
|
||||
'APPROVED',
|
||||
'ON HOLD'
|
||||
'ON HOLD',
|
||||
])) {
|
||||
$dynamicTotal .= "COUNT(CASE WHEN master.claim_status_id = {$status['id']} THEN master.id END) + ";
|
||||
}
|
||||
@ -787,52 +782,51 @@ class TicketMasterModel extends Model
|
||||
}
|
||||
|
||||
// Remove the trailing commas and `+` signs
|
||||
$dynamicSelect = rtrim($dynamicSelect, ', ');
|
||||
$dynamicTotal = rtrim($dynamicTotal, ' +');
|
||||
$dynamicSelect = rtrim($dynamicSelect, ', ');
|
||||
$dynamicTotal = rtrim($dynamicTotal, ' +');
|
||||
$dynamicClosedTotal = rtrim($dynamicClosedTotal, ' +');
|
||||
// print_rr($dynamicSelect);
|
||||
// Construct the final SQL query
|
||||
$sql = "
|
||||
SELECT
|
||||
SELECT
|
||||
user_profiles.id as ACM_ID,
|
||||
user_profiles.first_name AS ACM_NAME,
|
||||
$dynamicSelect,
|
||||
($dynamicTotal) AS TOTAL,
|
||||
($dynamicClosedTotal) AS CLEARED_TOTAL
|
||||
FROM
|
||||
FROM
|
||||
ticket_master master
|
||||
JOIN
|
||||
JOIN
|
||||
user_profiles ON master.acm_id = user_profiles.id AND user_profiles.is_active = 1
|
||||
WHERE
|
||||
WHERE
|
||||
master.created_at BETWEEN :start_date: AND :end_date:
|
||||
AND master.is_active = 1
|
||||
$ticket_type_data_2
|
||||
GROUP BY
|
||||
GROUP BY
|
||||
user_profiles.id;
|
||||
";
|
||||
|
||||
$binds = ["start_date"=>$start_date,"end_date"=>$end_date];
|
||||
$binds = ["start_date" => $start_date, "end_date" => $end_date];
|
||||
// Execute the query
|
||||
// Merge policy_type binds if present
|
||||
if (!empty($statusBinds)) {
|
||||
// Merge policy_type binds if present
|
||||
if (! empty($statusBinds)) {
|
||||
$binds = array_merge($binds, $statusBinds);
|
||||
}
|
||||
|
||||
$result = $this->db->query($sql,$binds)->getResultArray();
|
||||
$result = $this->db->query($sql, $binds)->getResultArray();
|
||||
// print_rr($this->db->lastQuery);die();
|
||||
return $result;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//api
|
||||
public function get_ticket_data($emp_id, $returnType,$ticket_type = null, $ticket_id = null)
|
||||
public function get_ticket_data($emp_id, $returnType, $ticket_type = null, $ticket_id = null)
|
||||
{
|
||||
$query = $this->select("
|
||||
|
||||
ticket_master.*,
|
||||
tms.mail_subject as subject,
|
||||
ticket_master.*,
|
||||
tms.mail_subject as subject,
|
||||
tms.id as ticket_message_id,
|
||||
(
|
||||
SELECT th1.old_value
|
||||
@ -854,11 +848,11 @@ class TicketMasterModel extends Model
|
||||
$query->whereIn('sender', ['staff', 'user']);
|
||||
$query->where('ticket_master.emp_id', $emp_id);
|
||||
|
||||
if (!empty($ticket_id)) {
|
||||
if (! empty($ticket_id)) {
|
||||
$query->where('ticket_master.id', $ticket_id);
|
||||
}
|
||||
|
||||
if (!empty($ticket_type)) {
|
||||
if (! empty($ticket_type)) {
|
||||
$query->where('ticket_master.ticket_type_id', $ticket_type);
|
||||
}
|
||||
|
||||
@ -879,9 +873,9 @@ class TicketMasterModel extends Model
|
||||
->select('tm.id, tm.ticket_type_id, tcs.claim_status, th.last_claim_status_change')
|
||||
->join('ticket_claim_status tcs', 'tm.claim_status_id = tcs.id', 'left')
|
||||
->join(
|
||||
'(SELECT ticket_id, MAX(created_at) AS last_claim_status_change
|
||||
FROM ticket_history
|
||||
WHERE field_name = \'claim_status_id\'
|
||||
'(SELECT ticket_id, MAX(created_at) AS last_claim_status_change
|
||||
FROM ticket_history
|
||||
WHERE field_name = \'claim_status_id\'
|
||||
GROUP BY ticket_id) th',
|
||||
'tm.id = th.ticket_id',
|
||||
'left'
|
||||
@ -898,8 +892,8 @@ class TicketMasterModel extends Model
|
||||
|
||||
$total = 0;
|
||||
foreach ($statuses as $status) {
|
||||
$alias = strtolower(str_replace(' ', '_', $status));
|
||||
$summary[$alias] = 0;
|
||||
$alias = strtolower(str_replace(' ', '_', $status));
|
||||
$summary[$alias] = 0;
|
||||
$summary[$alias . '_ids'] = '';
|
||||
|
||||
$dateLimit = $limit[$typeId][$status] ?? 3;
|
||||
@ -911,19 +905,18 @@ class TicketMasterModel extends Model
|
||||
if (
|
||||
$row['claim_status'] === $status &&
|
||||
(
|
||||
!$row['last_claim_status_change'] ||
|
||||
! $row['last_claim_status_change'] ||
|
||||
$row['last_claim_status_change'] <= $threshold
|
||||
)
|
||||
) {
|
||||
$summary[$alias]++;
|
||||
$ticketIds[] = $row['id'];
|
||||
if ($status == "APPROVED"|| $status == "SETTLED"|| $status == "CLOSED" ){
|
||||
if ($status == "APPROVED" || $status == "SETTLED" || $status == "CLOSED") {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
$total++;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -934,8 +927,8 @@ class TicketMasterModel extends Model
|
||||
$summary['total'] = $total;
|
||||
|
||||
// Add approved but not settled IDs and count
|
||||
$approvedNotSettled = $this->getNotSettledbutApprovedCount($typeId);
|
||||
$summary['approved_not_settled'] = $approvedNotSettled['count'];
|
||||
$approvedNotSettled = $this->getNotSettledbutApprovedCount($typeId);
|
||||
$summary['approved_not_settled'] = $approvedNotSettled['count'];
|
||||
$summary['approved_not_settled_ids'] = $approvedNotSettled['ticket_ids'];
|
||||
|
||||
$finalResults[$typeId] = $summary;
|
||||
@ -960,13 +953,13 @@ class TicketMasterModel extends Model
|
||||
$ids = $builder->get()->getRowArray();
|
||||
|
||||
// If no matching status IDs are found, return empty
|
||||
if (!$ids) {
|
||||
if (! $ids) {
|
||||
return ['count' => 0, 'ticket_ids' => ''];
|
||||
}
|
||||
|
||||
// Extract approved and settled status IDs
|
||||
$approved_id = $ids['approved_id'];
|
||||
$settled_id = $ids['settled_id'];
|
||||
$settled_id = $ids['settled_id'];
|
||||
|
||||
// Subquery to get the latest approval time for each ticket
|
||||
$subquery = $this->db->table('ticket_history')
|
||||
@ -983,12 +976,12 @@ class TicketMasterModel extends Model
|
||||
// Left join to find if there is a settled status for each ticket after approval
|
||||
$builder->join(
|
||||
'ticket_history th_settled',
|
||||
"th_settled.ticket_id = tm.id
|
||||
AND th_settled.field_name = 'claim_status_id'
|
||||
AND th_settled.new_value = {$settled_id}
|
||||
"th_settled.ticket_id = tm.id
|
||||
AND th_settled.field_name = 'claim_status_id'
|
||||
AND th_settled.new_value = {$settled_id}
|
||||
AND th_settled.created_at > latest_approval.approved_time",
|
||||
'left',
|
||||
false // Important for raw ON condition
|
||||
false// Important for raw ON condition
|
||||
);
|
||||
|
||||
// Filtering conditions: not settled and approved time older than 12 days
|
||||
@ -1001,7 +994,7 @@ class TicketMasterModel extends Model
|
||||
$builder->select('tm.id');
|
||||
|
||||
// Execute the query
|
||||
$query = $builder->get();
|
||||
$query = $builder->get();
|
||||
$ticketIds = array_column($query->getResultArray(), 'id'); // Extract the IDs
|
||||
|
||||
// Convert ticket IDs array to a comma-separated string
|
||||
@ -1009,8 +1002,8 @@ class TicketMasterModel extends Model
|
||||
|
||||
// Return the count and comma-separated ticket IDs
|
||||
return [
|
||||
'count' => count($ticketIds),
|
||||
'ticket_ids' => $ticketIdsString
|
||||
'count' => count($ticketIds),
|
||||
'ticket_ids' => $ticketIdsString,
|
||||
];
|
||||
}
|
||||
|
||||
@ -1024,8 +1017,8 @@ class TicketMasterModel extends Model
|
||||
AND cp.is_active = 1
|
||||
";
|
||||
|
||||
$binds = ["ticket_id"=>$ticket_id];
|
||||
$query = $this->db->query($sql,$binds);
|
||||
$binds = ["ticket_id" => $ticket_id];
|
||||
$query = $this->db->query($sql, $binds);
|
||||
|
||||
if ($query && $query->getNumRows() > 0) {
|
||||
$row = $query->getRowArray();
|
||||
@ -1039,11 +1032,10 @@ class TicketMasterModel extends Model
|
||||
|
||||
public function getPolicyData($params)
|
||||
{
|
||||
$client_name = $params['client_name'] ?? null;
|
||||
$client_name = $params['client_name'] ?? null;
|
||||
$insurer_short_name = $params['insurer_short_name'] ?? null;
|
||||
$tpa_short_name = $params['tpa_short_name'] ?? null;
|
||||
$policy_no = $params['policy_no'] ?? null;
|
||||
|
||||
$tpa_short_name = $params['tpa_short_name'] ?? null;
|
||||
$policy_no = $params['policy_no'] ?? null;
|
||||
|
||||
$builder = $this->db->table('client_policy');
|
||||
$builder->select('client_policy.*');
|
||||
@ -1055,19 +1047,19 @@ class TicketMasterModel extends Model
|
||||
$builder->where('i.is_active', 1);
|
||||
$builder->where('c.client_name', trim($client_name));
|
||||
$builder->where('i.short_name', trim($insurer_short_name));
|
||||
if(!empty($tpa_short_name)){
|
||||
$builder->where('tpa.short_name', trim($tpa_short_name));
|
||||
if (! empty($tpa_short_name)) {
|
||||
$builder->where('tpa.short_name', trim($tpa_short_name));
|
||||
}
|
||||
$builder->where('client_policy.policy_no', trim($policy_no));
|
||||
|
||||
$query = $builder->get();
|
||||
$query = $builder->get();
|
||||
$result = $query->getResultArray();
|
||||
// dd($this->db->getLastQuery());
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getTheClaimDetails($params)
|
||||
{
|
||||
{
|
||||
$client_name = $params['client_name'] ?? null;
|
||||
$policy_no = $params['policy_no'] ?? null;
|
||||
$insurer_short_name = $params['insurer_short_name'] ?? null;
|
||||
@ -1099,19 +1091,19 @@ class TicketMasterModel extends Model
|
||||
$builder->where('ticket_master.claim_number', trim($claim_no));
|
||||
$builder->where('ticket_master.relationship', trim($relationship));
|
||||
|
||||
if(!empty($tpa_short_name)){
|
||||
$builder->where('tpa.short_name', trim($tpa_short_name));
|
||||
if (! empty($tpa_short_name)) {
|
||||
$builder->where('tpa.short_name', trim($tpa_short_name));
|
||||
}
|
||||
|
||||
$query = $builder->get();
|
||||
$query = $builder->get();
|
||||
$result = $query->getResultArray();
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function getEmployeeAndEmployeePolicyDetails($params)
|
||||
{
|
||||
{
|
||||
$client_name = $params['client_name'] ?? null;
|
||||
$emp_code = $params['emp_code'] ?? null;
|
||||
$emp_name = $params['emp_name'] ?? null;
|
||||
@ -1124,7 +1116,6 @@ class TicketMasterModel extends Model
|
||||
// $emp_name = "Lokesh";
|
||||
// $policy_no = "GMC-1999/2000/2021/2022";
|
||||
|
||||
|
||||
$builder = $this->db->table('employees e');
|
||||
$builder->select("
|
||||
e.id as emp_id,
|
||||
@ -1143,7 +1134,7 @@ class TicketMasterModel extends Model
|
||||
cp.insurer_id,
|
||||
cp.tpa_id,
|
||||
cp.policy_no,
|
||||
CASE
|
||||
CASE
|
||||
WHEN cp.policy_type_id = 2 THEN 1
|
||||
WHEN cp.policy_type_id = 1 THEN 2
|
||||
WHEN cp.policy_type_id = 6 THEN 3
|
||||
@ -1165,7 +1156,7 @@ class TicketMasterModel extends Model
|
||||
$builder->where('cp.policy_no', trim($policy_no));
|
||||
$builder->where('LOWER(e.relationship)', strtolower('self'));
|
||||
|
||||
$query = $builder->get();
|
||||
$query = $builder->get();
|
||||
$result = $query->getRowArray();
|
||||
|
||||
return $result;
|
||||
@ -1181,8 +1172,8 @@ class TicketMasterModel extends Model
|
||||
$builder = $this->db->table('user_profiles');
|
||||
$builder->select("user_profiles.*");
|
||||
$builder->where('user_profiles.is_active', 1);
|
||||
$builder->where('user_profiles.mobile', trim($acm_mobile));
|
||||
$query = $builder->get();
|
||||
$builder->where('user_profiles.mobile', trim($acm_mobile));
|
||||
$query = $builder->get();
|
||||
$result = $query->getRowArray();
|
||||
|
||||
return $result;
|
||||
@ -1193,9 +1184,9 @@ class TicketMasterModel extends Model
|
||||
|
||||
public function getInsuredDetails($params)
|
||||
{
|
||||
$client_id = $params['client_id'] ?? null;
|
||||
$client_id = $params['client_id'] ?? null;
|
||||
$emp_code = $params['emp_code'] ?? null;
|
||||
$policy_id = $params['client_policy_id'] ?? null;
|
||||
$policy_id = $params['client_policy_id'] ?? null;
|
||||
$insured_name = $params['insured_name'] ?? null;
|
||||
|
||||
$builder = $this->db->table('employees e');
|
||||
@ -1220,7 +1211,7 @@ class TicketMasterModel extends Model
|
||||
$builder->where('e.client_id', trim($client_id));
|
||||
$builder->where('ep.client_policy_id', trim($policy_id));
|
||||
|
||||
$query = $builder->get();
|
||||
$query = $builder->get();
|
||||
$result = $query->getRowArray();
|
||||
// dd($this->db->getLastQuery());
|
||||
return $result;
|
||||
|
||||
@ -245,7 +245,7 @@
|
||||
<img src="<?= base_url() . "public"; ?>/assets/images/active_leads_and_bds_renewal.png" alt="Logo" height="14"
|
||||
class="active_leads " style="display:none;">
|
||||
|
||||
<span class="d-none d-sm-inline-block dash-tab dash-tab-font">Leads and BDS Renewals</span>
|
||||
<span class="d-none d-sm-inline-block dash-tab dash-tab-font">Opportunities and BDS Renewals</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
@ -2115,6 +2115,11 @@
|
||||
<!-- <li>
|
||||
<a href="<?= base_url('sales/page/Team') ?>"><i class="ri-bar-chart-line"></i> Sales Team </a>
|
||||
</li> -->
|
||||
<?php if (in_array(get_role_id(), [1, 5])){ ?>
|
||||
<li>
|
||||
<a href="<?= base_url('sales/loadtargets') ?>"><i class="ri-group-2-fill"></i>Sales Team Targets </a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@ -347,7 +347,7 @@ $isActive = get_role_id() == STAFF_ROLE_ID && in_array(ENROLLMENT_TEAM_ID, user_
|
||||
|
||||
if (type == 1 && leadType != null && leadType == 1) {
|
||||
$('.leadStatusTitle_1').show();
|
||||
$('#main_tile').text(" Leads");
|
||||
$('#main_tile').text("Opportunities");
|
||||
|
||||
}
|
||||
if (type == 1 && leadType != null && leadType == 2) {
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; }
|
||||
.main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
||||
|
||||
.btn-primary { background: #ff6b35; color: white; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s; }
|
||||
.btn-primary:hover { background: #ff5722; transform: translateY(-1px); }
|
||||
.btn-primary { background: #02a8b5; color: white; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s; }
|
||||
.btn-primary:hover { background: #02a8b5; transform: translateY(-1px); }
|
||||
.btn-complete { background: #4caf50; color: white; border: none;padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s;}
|
||||
.btn-complete:hover { background: #4caf50; transform: translateY(-1px); }
|
||||
.btn-view { background: #f0f0f0; color: #666; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s;}
|
||||
@ -14,7 +14,7 @@
|
||||
/* Filter Tabs */
|
||||
.filter-tabs { display: flex; gap: 10px; padding: 20px 30px; }
|
||||
.tab { padding: 10px 20px; border-radius: 20px; cursor: pointer; font-size: 14px; background: white; color: #666; border: 1px solid #e0e0e0; transition: all 0.2s; }
|
||||
.tab.active { background: #ff6b35; color: white; border-color: #ff6b35; }
|
||||
.tab.active { background: #02a8b5; color: white; border-color: #02a8b5; }
|
||||
|
||||
/* Leads Grid */
|
||||
.lead-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; /* responsive */ gap: 15px; }
|
||||
@ -256,8 +256,8 @@
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Meeting" onclick="selectType('Meeting', this)">📅 Meeting</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Visit" onclick="selectType('Visit', this)">🚗 Visit</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Demo" onclick="selectType('Demo', this)">🖥️ Demo</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Share" onclick="selectType('Share', this)">📄 Share Docs</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Todo" onclick="selectType('Todo', this)">✓ To Do</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Share Docs" onclick="selectType('Share Docs', this)">📄 Share Docs</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="To Do" onclick="selectType('To Do', this)">✓ To Do</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@ -326,8 +326,8 @@
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Meeting', this)">📅 Meeting</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Visit', this)">🚗 Visit</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Demo', this)">🖥️ Demo</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Share', this)">📄 Share Docs</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Todo', this)">✓ To Do</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Share Docs', this)">📄 Share Docs</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('To Do', this)">✓ To Do</button>
|
||||
|
||||
|
||||
</div>
|
||||
@ -429,7 +429,15 @@ function resetFlatpicker(){
|
||||
});
|
||||
}
|
||||
|
||||
const activityIcons = { Call: "📞", Email: "✉️", Meeting: "📅", Visit: "🚗",Demo: "🖥️", Share: "📄", Todo: "✓" };
|
||||
const activityIcons = {
|
||||
"Call": "📞",
|
||||
"Email": "✉️",
|
||||
"Meeting": "📅",
|
||||
"Visit": "🚗",
|
||||
"Demo": "🖥️",
|
||||
"Share Docs": "📄",
|
||||
"To Do": "✓"
|
||||
};
|
||||
const salesManagerIds = <?= json_encode($sales_manager_ids ?? []) ?>;
|
||||
|
||||
const API = '<?= base_url('sales') ?>';
|
||||
@ -441,6 +449,7 @@ let selectedFollowUpActivityType = '';
|
||||
let currentPage = 1;
|
||||
let limit = 10;
|
||||
let currentOffset = 0;
|
||||
const department = '<?= $sales_role ?>';
|
||||
|
||||
|
||||
function openModal(id) { document.getElementById(id).classList.add('active'); resetFlatpicker(); }
|
||||
@ -461,6 +470,7 @@ function closeModal(id) {
|
||||
selectedType = 'Call';
|
||||
$('#typeButtons .d_activity_type').removeClass('active');
|
||||
$('#typeButtons .d_activity_type[data-type="Call"]').addClass('active');
|
||||
fetchActivities(); // now i called because of Refresh the page .......
|
||||
}
|
||||
|
||||
// Specific cleanup for "Complete" modal (hidden follow-up sections)
|
||||
@ -652,7 +662,6 @@ async function fetchActivities(isLoadMore = false) {
|
||||
|
||||
// 2. Detail Logic
|
||||
async function viewDetail(id) {
|
||||
console.log("i am here");
|
||||
lead_id = id;
|
||||
let res = await fetch(`${API}/leads/${id}`);
|
||||
let json = await res.json();
|
||||
@ -740,6 +749,7 @@ function renderCard(opps) {
|
||||
|
||||
function renderTimeline(acts) {
|
||||
const cont = document.getElementById('timelineContainer');
|
||||
|
||||
|
||||
if (acts.length === 0) {
|
||||
cont.classList.add('no-line');
|
||||
@ -786,6 +796,7 @@ function renderTimeline(acts) {
|
||||
}
|
||||
|
||||
function openActivityModal() {
|
||||
|
||||
document.getElementById('act_owner').value = global_lead_assigned_to;
|
||||
document.getElementById('act_owner').dispatchEvent(new Event('change'));
|
||||
document.getElementById('act_lead_id').value = lead_id;
|
||||
@ -908,7 +919,7 @@ document.getElementById('activityForm').onsubmit = async (e) => {
|
||||
toastr.success('Activity Created Successfully');
|
||||
closeModal('activityModal');
|
||||
if (flag === "frompopup") {
|
||||
viewDetail(document.getElementById('act_lead_id').value);
|
||||
viewDetail(actLead);
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
@ -135,8 +135,8 @@
|
||||
'Meeting' => '📅',
|
||||
'Visit' => '🚗',
|
||||
'Demo' => '🖥️',
|
||||
'Share' => '📄',
|
||||
'Todo' => '✓'
|
||||
'Share Docs' => '📄',
|
||||
'To Do' => '✓'
|
||||
];
|
||||
$icon = $activityIcons[$a['activity_type']] ?? '📌';
|
||||
$statusClass = strtolower(str_replace(' ', '-', $a['status']));
|
||||
@ -217,8 +217,8 @@
|
||||
'Meeting' => ['color' => '#4299e1', 'icon' => '📅'],
|
||||
'Visit' => ['color' => '#ecc94b', 'icon' => '🚗'],
|
||||
'Demo' => ['color' => '#9f7aea', 'icon' => '🖥️'],
|
||||
'Share' => ['color' => '#ed8936', 'icon' => '📄'],
|
||||
'Todo' => ['color' => '#718096', 'icon' => '✓'],
|
||||
'Share Docs' => ['color' => '#ed8936', 'icon' => '📄'],
|
||||
'To Do' => ['color' => '#718096', 'icon' => '✓'],
|
||||
];
|
||||
?>
|
||||
|
||||
@ -248,14 +248,14 @@
|
||||
|
||||
<div class="card" style="grid-column: 1 / -1; width: 100%; box-sizing: border-box;">
|
||||
<div class="table-header">
|
||||
<h3 style="font-size: 16px; font-weight: 700;">All Leads Overview <?php echo !empty($leads_overview) ? "<i>(".count($leads_overview).")</i>" : ""; ?></h3>
|
||||
<!-- <span style="color: #718096; font-size: 12px; font-weight: 600;">Click a lead to see details</span> -->
|
||||
<h3 style="font-size: 16px; font-weight: 700;">All Leads Overview </h3>
|
||||
<!-- <span style="color: #718096; font-size: 12px; font-weight: 600;">Click a lead to see details</span>
|
||||
<a href="<?= base_url('sales') ?>"
|
||||
style="color: #718096; font-size: 12px; font-weight: 600; text-decoration: none; padding: 4px 8px; transition: color 0.2s ease; display: inline-block; cursor: pointer;"
|
||||
onmouseover="this.style.color='#ff6b35';"
|
||||
onmouseout="this.style.color='#718096';">
|
||||
Click a lead to see details
|
||||
</a>
|
||||
</a> -->
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
@ -134,7 +134,7 @@
|
||||
</div>
|
||||
<?php endforeach; ?> -->
|
||||
<?php foreach($upcoming as $u):
|
||||
$activityIcons = [ 'Call' => '📞', 'Email' => '✉️', 'Meeting' => '📅', 'Visit' => '🚗', 'Demo' => '🖥️', 'Share' => '📄', 'Todo' => '✓' ];
|
||||
$activityIcons = [ 'Call' => '📞', 'Email' => '✉️', 'Meeting' => '📅', 'Visit' => '🚗', 'Demo' => '🖥️', 'Share Docs' => '📄', 'To Do' => '✓' ];
|
||||
$icon = $activityIcons[$u['activity_type']] ?? '📌';
|
||||
$formattedscheduledDate = date('M d, Y, h:i A', strtotime($u['scheduled_date']));
|
||||
?>
|
||||
|
||||
602
app/Views/sales/target_view.php
Normal file
602
app/Views/sales/target_view.php
Normal file
@ -0,0 +1,602 @@
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; }
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
LAYOUT — Page Shell
|
||||
═══════════════════════════════════════════ */
|
||||
.main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
||||
|
||||
.top-bar {
|
||||
display: flex;
|
||||
justify-content: space-between; /* pushes subtitle left, search right */
|
||||
align-items: center; /* vertically centers both */
|
||||
padding: 14px 28px;
|
||||
background: white;
|
||||
/* border-bottom: 1px solid #e0e0e0; */
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
SEARCH INPUT
|
||||
═══════════════════════════════════════════ */
|
||||
.search-input { padding: 9px 14px; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 14px; width: 240px; outline: none; background: #f5f5f5; transition: border-color .2s;}
|
||||
.search-input:focus { border-color: #ff6b35; }
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
CONTENT AREA
|
||||
═══════════════════════════════════════════ */
|
||||
.content-area { width: 100%; }
|
||||
.content { padding: 10px; width: 100%; max-width: 100%;}
|
||||
.subtitle { font-size: 13px; color: #999; margin-bottom: 20px; }
|
||||
.view-container { display: none; }
|
||||
.view-container.active { display: block; }
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
MAIN TABLE
|
||||
═══════════════════════════════════════════ */
|
||||
.tt-table-wrapper { border: 1px solid #e0e0e0; border-radius: 10px; overflow: hidden; width: 100%;}
|
||||
.tt-main-table { width: 100%;border-collapse: collapse;table-layout: fixed;}
|
||||
.tt-main-table thead { display: table; width: 100%; table-layout: fixed; }
|
||||
.tt-main-table thead th { background: #f4f4f4; padding: 11px 14px; text-align: left;font-size: 12px;font-weight: 700;color: #777;text-transform: uppercase;letter-spacing: .4px;border-bottom: 1px solid #e0e0e0;}
|
||||
.tt-main-table tbody { display: block; max-height: 530px; overflow-y: auto;}
|
||||
.tt-main-table tbody tr { display: table; width: 100%; table-layout: fixed; }
|
||||
.tt-main-table tbody td { padding: 12px 14px; font-size: 14px; border-bottom: 1px solid #f0f0f0; vertical-align: middle;}
|
||||
.tt-main-table tbody tr:last-child td { border-bottom: none; }
|
||||
.tt-main-table tbody tr:hover td { background: #fafafa; }
|
||||
/* Empty row — must stay as table-row (not display:table override) */
|
||||
.tt-main-table tbody tr.empty-row { display: table-row; width: 100%; }
|
||||
.tt-main-table tbody tr.empty-row td { width: 100%; text-align: center; }
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
MEMBER AVATAR (used inside main table)
|
||||
═══════════════════════════════════════════ */
|
||||
.tt-person-cell { display: flex; align-items: center; gap: 1px;}
|
||||
/* .tt-avatar { width: 38px; height: 38px; border-radius: 50%; background: #ff6b35; color: white; font-size: 14px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0;} */
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
EMPTY STATE
|
||||
═══════════════════════════════════════════ */
|
||||
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 20px; color: #aaa; gap: 10px; }
|
||||
.empty-icon { font-size: 36px; opacity: 0.5; line-height: 1;}
|
||||
.empty-state p { margin: 0; font-size: 14px; font-weight: 500; color: #bbb;}
|
||||
.empty-row td { padding: 0 !important; border: none !important; background: transparent !important;}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
MODAL — Overlay & Box
|
||||
═══════════════════════════════════════════ */
|
||||
.modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 2000; align-items: center; justify-content: center;}
|
||||
.modal.active { display: flex; }
|
||||
.modal-box { background: white; border-radius: 14px; width: 92%; max-width: 640px; max-height: 90vh; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 8px 32px rgba(0, 0, 0, .18); animation: slideUp .2s ease;}
|
||||
.modal-header { padding: 6px 25px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; }
|
||||
.modal-body { padding: 25px; flex: 1; overflow-y: auto;}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
MODAL — Form Fields
|
||||
═══════════════════════════════════════════ */
|
||||
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 15px;}
|
||||
.form-group { margin-bottom: 20px; }
|
||||
.form-label { display: block; margin-bottom: 8px; font-size: 14px; font-weight: 500; color: #333;}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-select {
|
||||
width: 100%;
|
||||
padding: 10px 15px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
background: #f5f5f5; /* grey background */
|
||||
color: #333;
|
||||
transition: border-color .2s, background .2s;
|
||||
}
|
||||
|
||||
.form-textarea { resize: vertical; min-height: 100px;}
|
||||
|
||||
.form-input:focus,
|
||||
.form-textarea:focus,
|
||||
.form-select:focus { outline: none; border-color: #ff6b35; background: #fff;}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
MODAL — Detail Table (targets list)
|
||||
═══════════════════════════════════════════ */
|
||||
.table-wrap { border: 1px solid #e0e0e0; border-radius: 10px; overflow: hidden; margin-top: 16px;}
|
||||
.table-wrap table { width: 100%; border-collapse: collapse; table-layout: fixed; }
|
||||
.table-wrap thead { display: table; width: 100%; table-layout: fixed; }
|
||||
.table-wrap thead th { background: #f8f8f8; padding: 12px 16px; text-align: left; font-size: 13px; font-weight: 600; color: #666; border-bottom: 1px solid #e0e0e0;}
|
||||
.table-wrap tbody { display: block; max-height: 220px; overflow-y: auto; }
|
||||
.table-wrap tbody tr { display: table; width: 100%; table-layout: fixed; }
|
||||
.table-wrap tbody td { padding: 12px 16px; border-bottom: 1px solid #f0f0f0; font-size: 14px; color: #333; vertical-align: middle;}
|
||||
.table-wrap tbody tr:last-child td { border-bottom: none; }
|
||||
.table-wrap tbody tr:hover td { background: #fafafa; }
|
||||
/* Empty row fix for modal table */
|
||||
.table-wrap tbody tr.empty-row { display: table-row; }
|
||||
.table-wrap tbody tr.empty-row td { padding: 0 !important; border: none !important; text-align: center;}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
BUTTONS
|
||||
═══════════════════════════════════════════ */
|
||||
|
||||
/* Primary action — View Target (main table) */
|
||||
.btn-tt-view {
|
||||
background: #ff6b35;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 7px 16px;
|
||||
border-radius: 7px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background .2s;
|
||||
}
|
||||
.btn-tt-view:hover { background: #ff5722; }
|
||||
|
||||
/* Close button — modal header */
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 22px;
|
||||
cursor: pointer;
|
||||
color: #888;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background .2s;
|
||||
}
|
||||
.close-btn:hover { background: #f0f0f0; color: #333; }
|
||||
|
||||
/* Edit row — modal detail table */
|
||||
.btn-edit {
|
||||
background: #e8f4ff;
|
||||
color: #1976d2;
|
||||
border: none;
|
||||
padding: 5px 12px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-right: 6px;
|
||||
transition: background .2s;
|
||||
}
|
||||
.btn-edit:hover { background: #bbdefb; }
|
||||
|
||||
/* Remove row — modal detail table */
|
||||
.btn-remove {
|
||||
background: #ffeaea;
|
||||
color: #d32f2f;
|
||||
border: none;
|
||||
padding: 5px 12px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
transition: background .2s;
|
||||
}
|
||||
.btn-remove:hover { background: #ffcdd2; }
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
ANIMATIONS
|
||||
═══════════════════════════════════════════ */
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(20px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
RESPONSIVE
|
||||
═══════════════════════════════════════════ */
|
||||
@media (max-width: 540px) {
|
||||
.form-row { grid-template-columns: 1fr; }
|
||||
.search-input { width: 100%; }
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
GREY BOX — Input Section Container
|
||||
═══════════════════════════════════════════ */
|
||||
.grey-box {
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 10px;
|
||||
padding: 16px 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.grey-box .form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr auto; /* 2 fields + button */
|
||||
gap: 15px;
|
||||
align-items: flex-end; /* aligns button to bottom of fields */
|
||||
}
|
||||
|
||||
.grey-box .form-group {
|
||||
margin-bottom: 0; /* remove default gap inside grey box */
|
||||
}
|
||||
|
||||
.grey-box .btn {
|
||||
white-space: nowrap;
|
||||
height: 40px; /* match input height */
|
||||
width: 100%;
|
||||
}
|
||||
@media (max-width: 540px) {
|
||||
.grey-box .form-row {
|
||||
grid-template-columns: 1fr; /* stack vertically on mobile */
|
||||
}
|
||||
}
|
||||
@keyframes fadeHighlight {
|
||||
0% { background-color: #fffbcc; }
|
||||
100% { background-color: transparent; }
|
||||
}
|
||||
|
||||
.row-highlight {
|
||||
animation: fadeHighlight 2s ease forwards;
|
||||
}
|
||||
|
||||
</style>
|
||||
<div class="main-content">
|
||||
<hr class="my-0">
|
||||
<div class="top-bar">
|
||||
<p class="subtitle" id="memberCount"></p>
|
||||
<input type="text" class="search-input" id="searchInput" placeholder="Search member…" oninput="filterMembers(this.value)">
|
||||
</div>
|
||||
<!-- Content -->
|
||||
<div class="content">
|
||||
<div class="content-area view-container active" id="teamtargetView">
|
||||
<div class="tt-table-wrapper">
|
||||
<table class="tt-main-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Sales Manager</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ttMainTableBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal" id="targetModal">
|
||||
<div class="modal-box">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title" id="modalTitle">–</span>
|
||||
<button class="close-btn" onclick="closeModal()" title="Close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="recordId">
|
||||
<input type="hidden" id="currentUserId">
|
||||
|
||||
<div class="grey-box">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Financial Year</label>
|
||||
<select class="form-select" id="fyYear"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Amount (₹)</label>
|
||||
<input type="text" class="form-input" id="targetAmount" placeholder="e.g. 500000"
|
||||
oninput="this.value=this.value.replace(/[^0-9]/g,'')">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="button" class="btn btn-primary waves-effect btn-sm w-100" id="btn-save" onclick="saveRecord()">Save Target</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Financial Year</th>
|
||||
<th>Amount</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="detailTableBody">
|
||||
<tr class="empty-row"><td colspan="3">No targets added yet.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const users = <?= json_encode($users ?? []) ?>;
|
||||
|
||||
/* ──────────────────────────────────────────
|
||||
DATA
|
||||
────────────────────────────────────────── */
|
||||
const teamMembers = users.map(user => ({
|
||||
id: user.id,
|
||||
name: user.first_name + ' ' + user.last_name
|
||||
}));
|
||||
const API = '<?= base_url('sales') ?>';
|
||||
|
||||
let activeUserId = null;
|
||||
|
||||
/* ──────────────────────────────────────────
|
||||
RENDER MEMBER LIST
|
||||
────────────────────────────────────────── */
|
||||
function renderMemberList(filter = '') {
|
||||
const tbody = document.getElementById('ttMainTableBody');
|
||||
const countEl = document.getElementById('memberCount');
|
||||
const term = filter.trim().toLowerCase();
|
||||
|
||||
const filtered = teamMembers.filter(m =>
|
||||
m.name.toLowerCase().includes(term)
|
||||
);
|
||||
|
||||
|
||||
// countEl.textContent = `${filtered.length} member${filtered.length !== 1 ? 's' : ''}`;
|
||||
countEl.textContent = ``;
|
||||
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (filtered.length === 0) {
|
||||
tbody.innerHTML = `
|
||||
<tr class="empty-row">
|
||||
<td colspan="2">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">👤</div>
|
||||
<p>No Member found</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
filtered.forEach(m => {
|
||||
// const initials = m.name.split(' ').map(w => w[0]).join('').substring(0, 2).toUpperCase();
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td id="card_${m.id}">
|
||||
<div class="tt-person-cell">
|
||||
<div class="avatar-md">
|
||||
<div class="avatar-title bg-soft-primary rounded-circle text-primary font-20 font-weight-semibold"
|
||||
style="background-color: #f1f3fa; width: 48px; height: 48px; display: flex; align-items: center; justify-content: center;">
|
||||
${(m.name || '?').charAt(0).toUpperCase()}
|
||||
</div>
|
||||
</div>
|
||||
<span>${m.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-primary waves-effect waves-light" onclick="openModal(${m.id})">View Target</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function filterMembers(val) {
|
||||
renderMemberList(val);
|
||||
}
|
||||
|
||||
/* ──────────────────────────────────────────
|
||||
FY OPTIONS
|
||||
────────────────────────────────────────── */
|
||||
function populateFY(selectedFY = null) {
|
||||
const sel = document.getElementById('fyYear');
|
||||
const today = new Date();
|
||||
const year = today.getFullYear();
|
||||
const month = today.getMonth(); // 0-11
|
||||
|
||||
// Determine current FY start year
|
||||
let currentFYStart;
|
||||
|
||||
if (month >= 3) { // April (3) or later
|
||||
currentFYStart = year;
|
||||
} else { // Jan, Feb, Mar
|
||||
currentFYStart = year - 1;
|
||||
}
|
||||
|
||||
sel.innerHTML = '';
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const startYear = currentFYStart - i;
|
||||
const label = `${startYear}-${startYear + 1}`;
|
||||
|
||||
const opt = document.createElement('option');
|
||||
opt.value = label;
|
||||
opt.textContent = label;
|
||||
|
||||
if (label === selectedFY) opt.selected = true;
|
||||
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
}
|
||||
|
||||
/* ──────────────────────────────────────────
|
||||
MODAL
|
||||
────────────────────────────────────────── */
|
||||
function openModal(userId) {
|
||||
|
||||
activeUserId = userId;
|
||||
|
||||
const member = teamMembers.find(m => Number(m.id) === Number(userId));
|
||||
|
||||
document.getElementById('modalTitle').textContent = member ? member.name : '–';
|
||||
document.getElementById('currentUserId').value = userId;
|
||||
document.getElementById('recordId').value = '';
|
||||
document.getElementById('targetAmount').value = '';
|
||||
populateFY();
|
||||
renderDetailTable(userId);
|
||||
document.getElementById('targetModal').classList.add('active');
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('targetModal').classList.remove('active');
|
||||
activeUserId = null;
|
||||
}
|
||||
|
||||
document.getElementById('targetModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) closeModal();
|
||||
});
|
||||
|
||||
/* ──────────────────────────────────────────
|
||||
RENDER DETAIL TABLE
|
||||
────────────────────────────────────────── */
|
||||
async function renderDetailTable(userId) {
|
||||
const tbody = document.getElementById('detailTableBody');
|
||||
tbody.innerHTML = '<tr><td colspan="3">Loading...</td></tr>';
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API}/targets/user/${userId}`);
|
||||
const json = await res.json();
|
||||
const records = json.data || [];
|
||||
|
||||
if (records.length === 0) {
|
||||
tbody.innerHTML = '<tr class="empty-row"><td colspan="3">No targets added yet.</td></tr>';
|
||||
return;
|
||||
}
|
||||
// records.sort((a, b) => b.id - a.id);
|
||||
records.sort((a, b) => {
|
||||
let startYearA = parseInt(a.fy_year.split('-')[0], 10);
|
||||
let startYearB = parseInt(b.fy_year.split('-')[0], 10);
|
||||
return startYearB - startYearA;
|
||||
});
|
||||
|
||||
tbody.innerHTML = records.map(r => `
|
||||
<tr id="row_${r.id}">
|
||||
<td>${r.fy_year}</td>
|
||||
<td>₹ ${formatNum(r.target_amount)}</td>
|
||||
<td>
|
||||
<button class="btn-edit" onclick="editRecord(${r.id}, '${r.fy_year}', ${r.target_amount})">Edit</button>
|
||||
<button class="btn-remove" onclick="removeRecord(${r.id})">Remove</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
} catch (err) {
|
||||
tbody.innerHTML = '<tr><td colspan="3">Failed to load targets.</td></tr>';
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
/* ──────────────────────────────────────────
|
||||
SAVE (INSERT / UPDATE)
|
||||
────────────────────────────────────────── */
|
||||
async function saveRecord() {
|
||||
const userId = activeUserId;
|
||||
const recordId = document.getElementById('recordId').value.trim();
|
||||
const fyYear = document.getElementById('fyYear').value;
|
||||
const amtRaw = document.getElementById('targetAmount').value.trim();
|
||||
|
||||
// Validation
|
||||
if (!fyYear) { toastr.error('Please select a Financial Year.'); return; }
|
||||
if (!amtRaw) { toastr.error('Please enter an amount.'); return; }
|
||||
const amount = parseInt(amtRaw, 10);
|
||||
if (isNaN(amount) || amount <= 0) { toastr.error('Enter a valid positive amount.'); return; }
|
||||
|
||||
const existingRows = document.querySelectorAll('#detailTableBody tr[id^="row_"]');
|
||||
for (const row of existingRows) {
|
||||
const rowId = row.id.replace('row_', '');
|
||||
const rowFY = row.cells[0].textContent.trim();
|
||||
// Skip the row being edited
|
||||
if (recordId && rowId === recordId) continue;
|
||||
if (rowFY === fyYear) {
|
||||
toastr.error(`A target for ${fyYear} already exists.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const saveBtn = document.getElementById('btn-save');
|
||||
const originalBtnText = saveBtn.innerText;
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.innerText = recordId ? 'Updating...' : 'Saving...';
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
user_id: userId,
|
||||
fy_year: fyYear,
|
||||
target_amount: amount
|
||||
};
|
||||
|
||||
const isUpdate = !!recordId;
|
||||
const url = isUpdate ? `${API}/targets/${recordId}` : `${API}/targets`;
|
||||
const method = isUpdate ? 'PUT' : 'POST';
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const result = await res.json();
|
||||
|
||||
if (res.ok) {
|
||||
toastr.success(isUpdate ? 'Target updated!' : 'Target saved!');
|
||||
// Reset form
|
||||
document.getElementById('recordId').value = '';
|
||||
document.getElementById('targetAmount').value = '';
|
||||
populateFY();
|
||||
await renderDetailTable(userId);
|
||||
const savedId = result.data?.id || recordId;
|
||||
const targetRow = document.getElementById(`row_${savedId}`);
|
||||
if (targetRow) {
|
||||
targetRow.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
targetRow.classList.add('row-highlight');
|
||||
setTimeout(() => targetRow.classList.remove('row-highlight'), 3000);
|
||||
}
|
||||
} else {
|
||||
toastr.error(result.message || 'Failed to save target.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toastr.error('A network error occurred.');
|
||||
} finally {
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.innerText = originalBtnText;
|
||||
}
|
||||
}
|
||||
|
||||
/* ──────────────────────────────────────────
|
||||
EDIT — populate form from table row data
|
||||
────────────────────────────────────────── */
|
||||
function editRecord(id, fyYear, amount) {
|
||||
document.getElementById('recordId').value = id;
|
||||
document.getElementById('targetAmount').value = amount;
|
||||
populateFY(fyYear);
|
||||
}
|
||||
|
||||
/* ──────────────────────────────────────────
|
||||
REMOVE
|
||||
────────────────────────────────────────── */
|
||||
async function removeRecord(id) {
|
||||
if (!confirm('Are you sure you want to remove this target?')) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API}/targets/${id}`, { method: 'DELETE' });
|
||||
const result = await res.json();
|
||||
|
||||
if (res.ok) {
|
||||
toastr.success('Target removed.');
|
||||
renderDetailTable(activeUserId);
|
||||
} else {
|
||||
toastr.error(result.message || 'Failed to remove target.');
|
||||
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toastr.error('A network error occurred.');
|
||||
}
|
||||
}
|
||||
|
||||
/* ──────────────────────────────────────────
|
||||
HELPERS
|
||||
────────────────────────────────────────── */
|
||||
function formatNum(n) {
|
||||
return Number(n).toLocaleString('en-IN');
|
||||
}
|
||||
|
||||
/* ──────────────────────────────────────────
|
||||
INIT
|
||||
────────────────────────────────────────── */
|
||||
renderMemberList();
|
||||
</script>
|
||||
@ -5,13 +5,13 @@
|
||||
.main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
||||
/* .top-bar { background: white; padding: 15px 30px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; } */
|
||||
|
||||
.btn-primary { background: #ff6b35; color: white; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s; }
|
||||
.btn-primary:hover { background: #ff5722; transform: translateY(-1px); }
|
||||
.btn-primary { background: #02a8b5; color: white; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.2s; }
|
||||
.btn-primary:hover { background: #02a8b5; transform: translateY(-1px); }
|
||||
|
||||
/* Filter Tabs */
|
||||
.filter-tabs { display: flex; gap: 10px; padding: 20px 30px; }
|
||||
.tab { padding: 10px 20px; border-radius: 20px; cursor: pointer; font-size: 14px; background: white; color: #666; border: 1px solid #e0e0e0; transition: all 0.2s; }
|
||||
.tab.active { background: #ff6b35; color: white; border-color: #ff6b35; }
|
||||
.tab.active { background: #02a8b5; color: white; border-color: #02a8b5; }
|
||||
|
||||
/* Leads Grid */
|
||||
.lead-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; /* responsive */ gap: 15px; }
|
||||
@ -285,8 +285,8 @@
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Meeting" onclick="selectType('Meeting', this)">📅 Meeting</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Visit" onclick="selectType('Visit', this)">🚗 Visit</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Demo" onclick="selectType('Demo', this)">🖥️ Demo</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Share" onclick="selectType('Share', this)">📄 Share Docs</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Todo" onclick="selectType('Todo', this)">✓ To Do</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="Share Docs" onclick="selectType('Share Docs', this)">📄 Share Docs</button>
|
||||
<button type="button" class="activity-type-btn d_activity_type" data-type="To Do" onclick="selectType('To Do', this)">✓ To Do</button>
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@ -354,8 +354,8 @@
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Meeting', this)">📅 Meeting</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Visit', this)">🚗 Visit</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Demo', this)">🖥️ Demo</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Share', this)">📄 Share Docs</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Todo', this)">✓ To Do</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('Share Docs', this)">📄 Share Docs</button>
|
||||
<button type="button" class="activity-type-btn f_activity_type" onclick="selectFollowUpActivityType('To Do', this)">✓ To Do</button>
|
||||
|
||||
|
||||
</div>
|
||||
@ -936,7 +936,15 @@ function renderCard(opps) {
|
||||
}
|
||||
function renderTimeline(acts) {
|
||||
const cont = document.getElementById('timelineContainer');
|
||||
const activityIcons = { Call: "📞", Email: "✉️", Meeting: "📅", Visit: "🚗",Demo: "🖥️", Share: "📄", Todo: "✓" };
|
||||
const activityIcons = {
|
||||
"Call": "📞",
|
||||
"Email": "✉️",
|
||||
"Meeting": "📅",
|
||||
"Visit": "🚗",
|
||||
"Demo": "🖥️",
|
||||
"Share Docs": "📄",
|
||||
"To Do": "✓"
|
||||
};
|
||||
|
||||
if (acts.length === 0) {
|
||||
cont.classList.add('no-line');
|
||||
@ -1592,7 +1600,7 @@ if (btnSaveContact) {
|
||||
// Use .value instead of .val()
|
||||
let name = document.getElementById('contact_name').value.trim();
|
||||
let mobile = document.getElementById('contact_mobile').value.trim();
|
||||
let lead_id = document.getElementById('lead_id').value.trim();
|
||||
let lead_id = document.getElementById('hidden_lead_id').value.trim();
|
||||
|
||||
if (!name || !mobile) {
|
||||
return toastr.warning('Please enter both contact person name and mobile number.');
|
||||
@ -1607,7 +1615,7 @@ if (btnSaveContact) {
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const result = await res.json(); // Get the response body
|
||||
const contactResult = await res.json(); // Get the response body
|
||||
|
||||
if (res.ok) {
|
||||
// Clear inputs
|
||||
@ -1620,16 +1628,16 @@ if (btnSaveContact) {
|
||||
|
||||
// Create the HTML string
|
||||
let contactHtml = `
|
||||
<div class="contact-card d-flex justify-content-between align-items-center mb-2 p-2"
|
||||
<div class="contact-card d-flex justify-content-between align-items-center mb-2 p-3"
|
||||
style="background: #f8f8f8; border-radius: 8px;"
|
||||
data-id="${result.data.contact_id}">
|
||||
data-id="${contactResult.data.contact_id}">
|
||||
<div>
|
||||
<div class="fw-bold">${result.data.name}</div>
|
||||
<div class="text-muted">${result.data.mobile}</div>
|
||||
<div class="fw-bold">${contactResult.data.name}</div>
|
||||
<div class="text-muted">${contactResult.data.mobile}</div>
|
||||
</div>
|
||||
<button type="button"
|
||||
class="btn btn-danger btn-sm btnRemoveContact"
|
||||
data-id="${result.data.contact_id}">
|
||||
data-id="${contactResult.data.contact_id}">
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
@ -1637,19 +1645,19 @@ if (btnSaveContact) {
|
||||
|
||||
// <button type="button"
|
||||
// class="btn btn-secondary btn-sm btnEditContact"
|
||||
// data-id="${result.data.contact_id}">
|
||||
// data-id="${contactResult.data.contact_id}">
|
||||
// Update
|
||||
// </button>
|
||||
// Append to container using Vanilla JS
|
||||
document.getElementById('savedContactsContainer').insertAdjacentHTML('beforeend', contactHtml);
|
||||
toastr.success('Contact saved successfully');
|
||||
} else {
|
||||
let err = await res.json();
|
||||
|
||||
if (res.status === 400) {
|
||||
let errorMessages = "";
|
||||
let seenMessages = [];
|
||||
if (err.messages) {
|
||||
Object.entries(err.messages).forEach(([field, message]) => {
|
||||
if (contactResult.messages) {
|
||||
Object.entries(contactResult.messages).forEach(([field, message]) => {
|
||||
let inputElement = $('[name="' + field + '"]');
|
||||
|
||||
if (inputElement.length > 0) {
|
||||
@ -1673,11 +1681,11 @@ if (btnSaveContact) {
|
||||
});
|
||||
toastr.error(errorMessages, 'Validation Error', { allowHtml: true });
|
||||
} else {
|
||||
toastr.warning(err.message || 'Validation failed', 'Warning');
|
||||
toastr.warning(contactResult.message || 'Validation failed', 'Warning');
|
||||
}
|
||||
}
|
||||
else {
|
||||
toastr.error(err.message || 'Error adding lead');
|
||||
toastr.error(contactResult.message || 'Error adding lead');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user