MERGE_TEST_MINOR_CHANGES
This commit is contained in:
commit
ac0e159f5e
@ -32,6 +32,8 @@ class Acl
|
||||
'#^/metaTpaDashboardDemo#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]],
|
||||
'#^/sales/dashboard#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]],
|
||||
'#^/sales#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]],
|
||||
'#^/expense#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID, STAFF_ROLE_ID]],
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -444,7 +444,9 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) {
|
||||
$routes->get('proceedExcelFileDataValidation', 'EmployeeController::proceedExcelFileDataValidation');
|
||||
$routes->get('checkTpaApiEnable', 'EmployeeRestController::checkTpaApiEnable');
|
||||
$routes->get('generateDemographyDataTable', 'LeadsController::generateDemographyDataTable');
|
||||
$routes->post('insufficientCdBalanceHrMailSend', 'EmployeeController::insufficientCdBalanceHrMailSend');
|
||||
$routes->post('insufficientCdBalanceHrMailSend', 'EmployeeController::insufficientCdBalanceHrMailSend');
|
||||
$routes->get('getTpaClaimDumpErrorData/(:any)', 'TicketServiceController::getTpaClaimDumpErrorData/$1');
|
||||
|
||||
});
|
||||
|
||||
$routes->post("policy_tranction/sendInstallmentRemainderMail","PolicyTransactionController::sendInstallmentRemainderMail");
|
||||
@ -915,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
|
||||
@ -995,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');
|
||||
@ -1002,4 +1013,13 @@ $routes->group('sales', function($routes) {
|
||||
$routes->get('salesManagerLevelDashboard', 'SalesController::salesManagerLevelDashboard');
|
||||
});
|
||||
|
||||
// Expence Module Route Group
|
||||
$routes->group('expense', ["filter" => "authMVC", 'namespace' => 'App\Controllers'], static function($routes) {
|
||||
$routes->get('/', 'ExpenseController::index');
|
||||
$routes->post('save', 'ExpenseController::save');
|
||||
$routes->get('get/(:num)', 'ExpenseController::getExpense/$1');
|
||||
$routes->post('delete/(:num)', 'ExpenseController::delete/$1');
|
||||
$routes->get('client-policies', 'ExpenseController::clientPolicies');
|
||||
});
|
||||
|
||||
|
||||
|
||||
@ -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]);
|
||||
}
|
||||
|
||||
@ -6955,16 +6969,21 @@ class ClientController extends AdminController
|
||||
// $response = $ticketServiceController->getClaimExcelErrorData(["file_id" => 41]);
|
||||
// $response = $ticketServiceController->claimDumpOnBoardProcess(["file_id" => 17]);
|
||||
// $response = $ticketServiceController->extractExcelData("claims_dump_form_client.xlsx");
|
||||
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 51]); //abhi
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 50]); //fhpl
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 53]); //icici
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 54]); //mediassist
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 52]); //reliance
|
||||
// $response = $ticketServiceController->tpaClaimDumpImporter(["file_id" => 48]); //vidal
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 48]); //vidal
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 54]); //mediassist
|
||||
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 4]); //abhi
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 51]); //abhi
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 50]); //fhpl
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 53]); //icici
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 54]); //mediassist
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 52]); //reliance
|
||||
// $response = $ticketServiceController->tpaClaimDumpToTicketMasterImporters(["file_id" => 48]); //vidal
|
||||
// $response = $ticketServiceController->getTpaClaimDumpErrorData(["file_id" => 48]);
|
||||
// dd($response);
|
||||
|
||||
// ---------- TICKET CONTROLLER --------------------------------------------------------------------------------
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
412
app/Controllers/ExpenseController.php
Normal file
412
app/Controllers/ExpenseController.php
Normal file
@ -0,0 +1,412 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\ExpenseModel;
|
||||
use App\Models\ClientModel;
|
||||
use App\Models\ClientPolicyModel;
|
||||
use CodeIgniter\API\ResponseTrait;
|
||||
|
||||
class ExpenseController extends AdminController
|
||||
{
|
||||
use ResponseTrait;
|
||||
|
||||
protected $myLogger;
|
||||
protected $expenseModel;
|
||||
protected $clientModel;
|
||||
protected $clientPolicyModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
set_session_context('Expense');
|
||||
|
||||
$this->myLogger = \Config\Services::mylogger();
|
||||
$this->expenseModel = new ExpenseModel();
|
||||
$this->clientModel = new ClientModel();
|
||||
$this->clientPolicyModel = new ClientPolicyModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Web list + form view
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$data['tab_name'] = 'Expense';
|
||||
$data['page_name'] = 'Expense';
|
||||
$descriptionPattern = '/^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+]+$/u';
|
||||
|
||||
// Raw GET filters
|
||||
$rawFilters = $this->request->getGet() ?? [];
|
||||
$rawFilters = is_array($rawFilters) ? $rawFilters : [];
|
||||
|
||||
// Sanitize input array using existing helper
|
||||
$sanitized = sanitizeInputArrayAdvanced($rawFilters);
|
||||
|
||||
$filters = [
|
||||
'client_id' => trim($sanitized['client_id'] ?? ''),
|
||||
'client_policy_id' => trim($sanitized['client_policy_id'] ?? ''),
|
||||
'approved_by' => trim($sanitized['approved_by'] ?? ''),
|
||||
'description' => trim($sanitized['description'] ?? ''),
|
||||
'amount' => trim($sanitized['amount'] ?? ''),
|
||||
'expense_date' => trim($sanitized['expense_date'] ?? ''),
|
||||
];
|
||||
|
||||
$validationErrors = [];
|
||||
|
||||
// Basic type / format validation for filters
|
||||
if ($filters['client_id'] !== '' && ! ctype_digit($filters['client_id'])) {
|
||||
$validationErrors[] = 'Invalid client selected for search.';
|
||||
$filters['client_id'] = '';
|
||||
}
|
||||
|
||||
if ($filters['client_policy_id'] !== '' && ! ctype_digit($filters['client_policy_id'])) {
|
||||
$validationErrors[] = 'Invalid policy selected for search.';
|
||||
$filters['client_policy_id'] = '';
|
||||
}
|
||||
|
||||
if ($filters['approved_by'] !== '' && ! ctype_digit($filters['approved_by'])) {
|
||||
$validationErrors[] = 'Invalid approver selected for search.';
|
||||
$filters['approved_by'] = '';
|
||||
}
|
||||
|
||||
if ($filters['description'] !== '' && ! preg_match($descriptionPattern, $filters['description'])) {
|
||||
$validationErrors[] = 'Description filter contains invalid characters.';
|
||||
$filters['description'] = '';
|
||||
}
|
||||
|
||||
if ($filters['amount'] !== '') {
|
||||
if (! is_numeric($filters['amount']) || (float) $filters['amount'] < 0) {
|
||||
$validationErrors[] = 'Amount filter must be a non-negative number.';
|
||||
$filters['amount'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ($filters['expense_date'] !== '') {
|
||||
$dt = \DateTime::createFromFormat('d-m-Y', $filters['expense_date']);
|
||||
$errors = $dt ? \DateTime::getLastErrors() : ['warning_count' => 1, 'error_count' => 1];
|
||||
if (! $dt || ! empty($errors['warning_count']) || ! empty($errors['error_count'])) {
|
||||
$validationErrors[] = 'Expense Date filter must be in DD-MM-YYYY format.';
|
||||
$filters['expense_date'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
$data['filters'] = $filters;
|
||||
$data['validation_errors'] = $validationErrors;
|
||||
|
||||
// Clients for dropdown
|
||||
$data['clients'] = $this->clientModel
|
||||
->select('id, client_name, short_name')
|
||||
->where('is_active', 1)
|
||||
->orderBy('client_name', 'ASC')
|
||||
->findAll();
|
||||
|
||||
// Approved by (users) dropdown
|
||||
$db = db_connect();
|
||||
$data['approved_users'] = $db->table('user_profiles')
|
||||
->select('id, first_name')
|
||||
->where('is_active', 1)
|
||||
->whereIn('id', [7, 8])
|
||||
->orderBy('first_name', 'ASC')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
// Policies for filter dropdown (when client filter is selected)
|
||||
$data['policies_for_filter'] = [];
|
||||
if ($filters['client_id'] !== '') {
|
||||
$data['policies_for_filter'] = $this->clientPolicyModel
|
||||
->select('id, policy_no')
|
||||
->where('client_id', (int) $filters['client_id'])
|
||||
->where('is_active', 1)
|
||||
->orderBy('id', 'ASC')
|
||||
->findAll();
|
||||
}
|
||||
|
||||
// Existing expenses with optional filters
|
||||
$builder = $this->expenseModel
|
||||
->select('
|
||||
expenses.*,
|
||||
clients.client_name,
|
||||
clients.short_name,
|
||||
client_policy.policy_no,
|
||||
user_profiles.first_name AS approved_by_name
|
||||
')
|
||||
->join('clients', 'clients.id = expenses.client_id')
|
||||
->join('client_policy', 'client_policy.id = expenses.client_policy_id', 'left')
|
||||
->join('user_profiles', 'user_profiles.id = expenses.approved_by', 'left')
|
||||
->where('expenses.is_active', 1);
|
||||
|
||||
if ($filters['client_id'] !== '') {
|
||||
$builder->where('expenses.client_id', (int) $filters['client_id']);
|
||||
}
|
||||
|
||||
if ($filters['client_policy_id'] !== '') {
|
||||
$builder->where('expenses.client_policy_id', (int) $filters['client_policy_id']);
|
||||
}
|
||||
|
||||
if ($filters['approved_by'] !== '') {
|
||||
$builder->where('expenses.approved_by', (int) $filters['approved_by']);
|
||||
}
|
||||
|
||||
if ($filters['description'] !== '') {
|
||||
$builder->like('expenses.description', (string) $filters['description']);
|
||||
}
|
||||
|
||||
if ($filters['amount'] !== '') {
|
||||
$builder->where('expenses.amount', (float) $filters['amount']);
|
||||
}
|
||||
|
||||
if ($filters['expense_date'] !== '') {
|
||||
$dt = \DateTime::createFromFormat('d-m-Y', $filters['expense_date']);
|
||||
if ($dt) {
|
||||
$builder->where('expenses.expense_date', $dt->format('Y-m-d'));
|
||||
}
|
||||
}
|
||||
|
||||
$data['expenses'] = $builder
|
||||
->orderBy('expenses.id', 'DESC')
|
||||
->findAll();
|
||||
|
||||
return $this->loadLayout('expense_list', $data);
|
||||
} catch (\Throwable $e) {
|
||||
return handle_exception($e, $this->myLogger, $this->response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create / update expense (AJAX)
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
try {
|
||||
if ($this->request->getMethod() !== 'post') {
|
||||
return $this->response
|
||||
->setStatusCode(405)
|
||||
->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Invalid request method',
|
||||
]);
|
||||
}
|
||||
|
||||
$rawData = $this->request->getPost();
|
||||
$data = sanitizeInputArrayAdvanced($rawData);
|
||||
|
||||
$id = isset($data['id']) && $data['id'] !== '' ? (int) $data['id'] : null;
|
||||
|
||||
$rules = [
|
||||
'client_id' => [
|
||||
'rules' => 'required|is_natural_no_zero',
|
||||
'errors' => [
|
||||
'required' => 'Client is required',
|
||||
],
|
||||
],
|
||||
'client_policy_id' => [
|
||||
'rules' => 'required|is_natural_no_zero',
|
||||
'errors' => [
|
||||
'required' => 'Policy is required',
|
||||
],
|
||||
],
|
||||
'description' => [
|
||||
'rules' => 'required|string|min_length[1]|max_length[2000]|regex_match[/^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+]+$/]',
|
||||
'errors' => [
|
||||
'required' => 'Description is required',
|
||||
'min_length' => 'Description cannot be empty',
|
||||
'regex_match' => 'Description contains invalid characters.',
|
||||
],
|
||||
],
|
||||
'expense_date' => [
|
||||
'rules' => 'required|valid_date[d-m-Y]',
|
||||
'errors' => [
|
||||
'required' => 'Expense Date is required',
|
||||
'valid_date' => 'Expense Date must be in DD-MM-YYYY format',
|
||||
],
|
||||
],
|
||||
'approved_by' => [
|
||||
'rules' => 'required|is_natural_no_zero',
|
||||
'errors' => [
|
||||
'required' => 'Approved By is required',
|
||||
],
|
||||
],
|
||||
'amount' => [
|
||||
'rules' => 'required|numeric|greater_than_equal_to[0]',
|
||||
'errors' => [
|
||||
'required' => 'Amount is required',
|
||||
'numeric' => 'Amount must be numeric',
|
||||
'greater_than_equal_to' => 'Amount cannot be negative',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if (! $this->validate($rules)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Input validation failed',
|
||||
'errors' => $this->validator->getErrors(),
|
||||
]);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'client_id' => (int) ($data['client_id'] ?? 0),
|
||||
'client_policy_id' => (int) ($data['client_policy_id'] ?? 0),
|
||||
'description' => $data['description'] ?? null,
|
||||
'approved_by' => (int) ($data['approved_by'] ?? 0),
|
||||
'amount' => $data['amount'] ?? null,
|
||||
'is_active' => 1,
|
||||
];
|
||||
|
||||
$expenseDate = $data['expense_date'] ?? null;
|
||||
if (! empty($expenseDate)) {
|
||||
$dt = \DateTime::createFromFormat('d-m-Y', $expenseDate);
|
||||
$payload['expense_date'] = $dt ? $dt->format('Y-m-d') : null;
|
||||
} else {
|
||||
$payload['expense_date'] = null;
|
||||
}
|
||||
|
||||
if ($id === null) {
|
||||
$insertId = $this->expenseModel->insert($payload, true);
|
||||
$success = ! empty($insertId);
|
||||
$id = $insertId;
|
||||
$message = $success
|
||||
? 'Expense created successfully'
|
||||
: 'Unable to create expense. Please try again.';
|
||||
} else {
|
||||
$success = $this->expenseModel->update($id, $payload);
|
||||
$message = $success
|
||||
? 'Expense updated successfully'
|
||||
: 'Unable to update expense. Please try again.';
|
||||
}
|
||||
|
||||
return $this->response
|
||||
->setStatusCode($success ? 200 : 400)
|
||||
->setJSON([
|
||||
'status' => (bool) $success,
|
||||
'message' => $message,
|
||||
'id' => $id,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handle_exception($e, $this->myLogger, $this->response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single expense (AJAX)
|
||||
*/
|
||||
public function getExpense($id = null)
|
||||
{
|
||||
try {
|
||||
$id = (int) $id;
|
||||
|
||||
if (empty($id)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Invalid expense id',
|
||||
]);
|
||||
}
|
||||
|
||||
$expense = $this->expenseModel
|
||||
->select('
|
||||
expenses.*,
|
||||
clients.client_name,
|
||||
clients.short_name,
|
||||
client_policy.policy_no,
|
||||
user_profiles.first_name AS approved_by_name
|
||||
')
|
||||
->join('clients', 'clients.id = expenses.client_id', 'left')
|
||||
->join('client_policy', 'client_policy.id = expenses.client_policy_id', 'left')
|
||||
->join('user_profiles', 'user_profiles.id = expenses.approved_by', 'left')
|
||||
->where('expenses.id', $id)
|
||||
->where('expenses.is_active', 1)
|
||||
->first();
|
||||
|
||||
if (empty($expense)) {
|
||||
return $this->response->setStatusCode(404)->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Expense not found',
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->response->setStatusCode(200)->setJSON([
|
||||
'status' => true,
|
||||
'data' => $expense,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handle_exception($e, $this->myLogger, $this->response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft delete expense (AJAX)
|
||||
*/
|
||||
public function delete($id = null)
|
||||
{
|
||||
try {
|
||||
if ($this->request->getMethod() !== 'post') {
|
||||
return $this->response
|
||||
->setStatusCode(405)
|
||||
->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Invalid request method',
|
||||
]);
|
||||
}
|
||||
|
||||
$id = (int) $id;
|
||||
|
||||
if (empty($id)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Invalid expense id',
|
||||
]);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'is_active' => 0,
|
||||
];
|
||||
|
||||
$success = $this->expenseModel->update($id, $payload);
|
||||
|
||||
return $this->response
|
||||
->setStatusCode($success ? 200 : 400)
|
||||
->setJSON([
|
||||
'status' => (bool) $success,
|
||||
'message' => $success
|
||||
? 'Expense deleted successfully'
|
||||
: 'Unable to delete expense. Please try again.',
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handle_exception($e, $this->myLogger, $this->response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get policies by client for dropdown (AJAX)
|
||||
*/
|
||||
public function clientPolicies()
|
||||
{
|
||||
try {
|
||||
$clientId = (int) ($this->request->getGet('client_id') ?? 0);
|
||||
|
||||
if (empty($clientId)) {
|
||||
return $this->response->setStatusCode(400)->setJSON([
|
||||
'status' => false,
|
||||
'message' => 'Client is required',
|
||||
]);
|
||||
}
|
||||
|
||||
$policies = $this->clientPolicyModel
|
||||
->select('client_policy.id, client_policy.policy_no, policy_type.policy_type')
|
||||
->join('policy_type', 'policy_type.id = client_policy.policy_type_id AND policy_type.is_active = 1')
|
||||
->where('client_policy.client_id', $clientId)
|
||||
->where('client_policy.is_active', 1)
|
||||
->orderBy('client_policy.id', 'ASC')
|
||||
->findAll();
|
||||
|
||||
return $this->response->setStatusCode(200)->setJSON([
|
||||
'status' => true,
|
||||
'data' => $policies,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handle_exception($e, $this->myLogger, $this->response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
{
|
||||
@ -1001,7 +1126,7 @@ class SalesController extends BaseController
|
||||
->findAll();
|
||||
|
||||
$data = [
|
||||
'target' => $targetAmount,
|
||||
'target_amt' => $targetAmount,
|
||||
'achieved' => $achievedAmount,
|
||||
'remaining' => $remainingAmount,
|
||||
'percent' => $achievementPercent,
|
||||
@ -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 ---------------- */
|
||||
|
||||
|
||||
@ -30,8 +30,9 @@ use App\Models\TicketMasterModel;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||
|
||||
class TicketServiceController extends BaseController
|
||||
class TicketServiceController extends AdminController
|
||||
{
|
||||
use ResponseTrait;
|
||||
|
||||
@ -1840,39 +1841,86 @@ class TicketServiceController extends BaseController
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
public function tpaClaimDumpImporter($params)
|
||||
/**
|
||||
* Resolve and validate Claim Dump file metadata and physical file for TPA imports.
|
||||
*
|
||||
* @param int|null $fileId
|
||||
* @return array{status:bool,message?:string,fileData?:array,filePath?:string}
|
||||
*/
|
||||
private function resolveTpaClaimDumpFile(?int $fileId): array
|
||||
{
|
||||
$file_id = $params['file_id'] ?? null; // Move outside try to ensure catch can see it
|
||||
if (empty($fileId)) {
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'File ID is missing',
|
||||
];
|
||||
}
|
||||
|
||||
$fileData = $this->claimDumpFileModel->find((int) $fileId);
|
||||
if (!$fileData) {
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'Invalid file ID. No file data found',
|
||||
];
|
||||
}
|
||||
|
||||
$filePath = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . 'claim_dump_excel' . DIRECTORY_SEPARATOR . $fileData['file_name'];
|
||||
|
||||
if (!is_file($filePath)) {
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'Claim dump file not found',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => true,
|
||||
'fileData' => $fileData,
|
||||
'filePath' => $filePath,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* First TPA-wise job – parse Excel and push data into TPA staging table.
|
||||
*
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
public function tpaClaimDumpImporter(array $params)
|
||||
{
|
||||
$file_id = isset($params['file_id']) ? (int) $params['file_id'] : null;
|
||||
|
||||
try {
|
||||
$file_path = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . 'claim_dump_excel' . DIRECTORY_SEPARATOR;
|
||||
$resolved = $this->resolveTpaClaimDumpFile($file_id);
|
||||
|
||||
if (!$file_id) {
|
||||
return ['status' => false, 'message' => 'File ID is missing'];
|
||||
if ($resolved['status'] === false) {
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => $resolved['message'] ?? 'Unable to resolve claim dump file',
|
||||
];
|
||||
}
|
||||
|
||||
$fileData = $this->claimDumpFileModel->find((int)$file_id);
|
||||
if (!$fileData) {
|
||||
return ['status' => false, 'message' => 'Invalid file ID. No file data found'];
|
||||
}
|
||||
|
||||
$file_full_path = $file_path . $fileData['file_name'];
|
||||
if (!is_file($file_full_path)) {
|
||||
return ['status' => false, 'message' => 'Claim dump file not found'];
|
||||
}
|
||||
$fileData = $resolved['fileData'];
|
||||
$filePath = $resolved['filePath'];
|
||||
|
||||
$handler = TpaClaimsImportFactory::make($fileData['tpa_id'] ?? 0);
|
||||
$result = $handler->runTpaClaimDumpInsert($file_full_path, $file_id);
|
||||
$result = $handler->runTpaClaimDumpInsert($filePath, $file_id);
|
||||
|
||||
if (!empty($result['status']) && $result['status'] === true) {
|
||||
Jobs::addJob(['job_name' => 'tpaClaimDumpToTicketMasterImporters', 'payload' => ['file_id' => $file_id]]);
|
||||
Jobs::addJob([
|
||||
'job_name' => 'tpaClaimDumpToTicketMasterImporters',
|
||||
'payload' => ['file_id' => $file_id],
|
||||
]);
|
||||
} else {
|
||||
// FORCE FAIL LOGIC
|
||||
$this->markAsFailed($file_id, $result['message'] ?? 'System error contact admin', $fileData['created_by'] ?? null);
|
||||
$this->markAsFailed(
|
||||
$file_id,
|
||||
$result['message'] ?? 'System error contact admin',
|
||||
$fileData['created_by'] ?? null
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
@ -1884,9 +1932,9 @@ class TicketServiceController extends BaseController
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => 'TPA Claim dump import failed',
|
||||
'error_data' => $th->getMessage()
|
||||
'status' => false,
|
||||
'message' => 'TPA Claim dump import failed',
|
||||
'error_data' => $th->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -1908,30 +1956,37 @@ class TicketServiceController extends BaseController
|
||||
return $this->claimDumpFileModel->update($file_id, $data);
|
||||
}
|
||||
|
||||
public function tpaClaimDumpToTicketMasterImporters($params)
|
||||
/**
|
||||
* Second TPA-wise job – move data from TPA staging into ticket_master.
|
||||
*
|
||||
* @param array $params
|
||||
* @return array
|
||||
*/
|
||||
public function tpaClaimDumpToTicketMasterImporters(array $params)
|
||||
{
|
||||
$file_id = $params['file_id'] ?? null;
|
||||
$file_id = isset($params['file_id']) ? (int) $params['file_id'] : null;
|
||||
$fileData = null;
|
||||
|
||||
try {
|
||||
if (!$file_id) {
|
||||
return ['status' => false, 'message' => 'File ID is missing'];
|
||||
$resolved = $this->resolveTpaClaimDumpFile($file_id);
|
||||
|
||||
if ($resolved['status'] === false) {
|
||||
return [
|
||||
'status' => false,
|
||||
'message' => $resolved['message'] ?? 'Unable to resolve claim dump file',
|
||||
];
|
||||
}
|
||||
|
||||
$fileData = $this->claimDumpFileModel->where('id', $file_id)->first();
|
||||
|
||||
if (!$fileData) {
|
||||
return ['status' => false, 'message' => 'Invalid file ID. No file data found to import'];
|
||||
}
|
||||
$fileData = $resolved['fileData'];
|
||||
|
||||
$handler = TpaClaimsImportFactory::make($fileData['tpa_id'] ?? 0);
|
||||
$result = $handler->runTicketMasterInsert($params);
|
||||
$result = $handler->runTicketMasterInsert($params);
|
||||
|
||||
if (!empty($result['status']) && $result['status'] === true) {
|
||||
// Success: Update the status to success
|
||||
$this->claimDumpFileModel->update($file_id, [
|
||||
'status' => 'success',
|
||||
'reason' => null
|
||||
'reason' => null,
|
||||
]);
|
||||
} else {
|
||||
// Logic failure: The runTicketMasterInsert returned status false
|
||||
@ -1957,8 +2012,8 @@ class TicketServiceController extends BaseController
|
||||
'message' => $th->getMessage(),
|
||||
'error_data' => [
|
||||
'line' => $th->getLine(),
|
||||
'file' => $th->getFile()
|
||||
]
|
||||
'file' => $th->getFile(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -2011,5 +2066,176 @@ class TicketServiceController extends BaseController
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
public function getTpaClaimDumpErrorData($file_id)
|
||||
{
|
||||
$file_id = (int) $file_id;
|
||||
|
||||
// Ensure we always have a Response object, even if controller was instantiated manually
|
||||
$response = $this->response ?? service('response');
|
||||
|
||||
if ($file_id <= 0) {
|
||||
return $response
|
||||
->setStatusCode(ResponseInterface::HTTP_BAD_REQUEST)
|
||||
->setJSON(['status' => false, 'message' => 'Invalid file id']);
|
||||
}
|
||||
|
||||
$fileData = $this->claimDumpFileModel->find($file_id);
|
||||
|
||||
if (!$fileData) {
|
||||
return $response
|
||||
->setStatusCode(ResponseInterface::HTTP_NOT_FOUND)
|
||||
->setJSON(['status' => false, 'message' => 'File record not found']);
|
||||
}
|
||||
|
||||
$tpaId = (int) ($fileData['tpa_id'] ?? 0);
|
||||
|
||||
if ($tpaId <= 0) {
|
||||
return $response
|
||||
->setStatusCode(ResponseInterface::HTTP_BAD_REQUEST)
|
||||
->setJSON(['status' => false, 'message' => 'TPA not linked with this file']);
|
||||
}
|
||||
|
||||
// Resolve TPA staging table based on configured TPA IDs
|
||||
$tableName = match ($tpaId) {
|
||||
(int) env('VIDAL_PRIMARY_KEY_CONSTANT') => 'claims_dump_vidal',
|
||||
(int) env('ABHI_PRIMARY_KEY_CONSTANT') => 'claims_dump_abhi',
|
||||
(int) env('MEDI_ASSIST_PRIMARY_KEY_CONSTANT') => 'claims_dump_medi_assist',
|
||||
(int) env('FHPL_PRIMARY_KEY_CONSTANT') => 'claims_dump_fhpl',
|
||||
(int) env('R_CARE_PRIMARY_KEY_CONSTANT') => 'claims_dump_reliance',
|
||||
(int) env('ICICI_PRIMARY_KEY_CONSTANT') => 'claims_dump_icici',
|
||||
default => null,
|
||||
};
|
||||
|
||||
if ($tableName === null) {
|
||||
return $response
|
||||
->setStatusCode(ResponseInterface::HTTP_BAD_REQUEST)
|
||||
->setJSON(['status' => false, 'message' => 'Unsupported TPA for error dump export']);
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$builder = $db->table($tableName);
|
||||
|
||||
// Fetch only records belonging to this file and having a rejection reason
|
||||
$rows = $builder
|
||||
->where('file_id', $file_id)
|
||||
->where('is_active', 1)
|
||||
->where('master_reject_reason IS NOT NULL', null, false)
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
// No error records – return a small Excel file with just a message
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$sheet->setTitle('Errors');
|
||||
$sheet->setCellValue('A1', 'Message');
|
||||
$sheet->setCellValue('A2', 'No rejected records found for this file.');
|
||||
|
||||
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
|
||||
ob_start();
|
||||
$writer->save('php://output');
|
||||
$excelOutput = ob_get_clean();
|
||||
|
||||
$filename = 'tpa_claim_dump_errors_' . $file_id . '.xlsx';
|
||||
|
||||
return $response
|
||||
->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"')
|
||||
->setHeader('Cache-Control', 'max-age=0')
|
||||
->setBody($excelOutput);
|
||||
}
|
||||
|
||||
// Columns that must NOT be included in the Excel export
|
||||
$excludedColumns = [
|
||||
'id',
|
||||
'client_id',
|
||||
'client_policy_id',
|
||||
'ticket_id',
|
||||
'file_id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
$firstRow = $rows[0];
|
||||
|
||||
// Build header mapping and track the "Rejected Reason" column index
|
||||
$dbColumnOrder = [];
|
||||
$displayHeaderLabels = [];
|
||||
$rejectedReasonColIdx = null; // 1-based index for Excel column
|
||||
|
||||
foreach ($firstRow as $columnName => $_) {
|
||||
if (in_array($columnName, $excludedColumns, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dbColumnOrder[] = $columnName;
|
||||
|
||||
if ($columnName === 'master_reject_reason' || $columnName === 'master_rejection_reason_key') {
|
||||
$displayHeaderLabels[] = 'Rejected Reason';
|
||||
$rejectedReasonColIdx = count($displayHeaderLabels); // current column index (1-based)
|
||||
} else {
|
||||
$displayHeaderLabels[] = $columnName;
|
||||
}
|
||||
}
|
||||
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$sheet = $spreadsheet->getActiveSheet();
|
||||
$sheet->setTitle('Errors');
|
||||
|
||||
// Header row
|
||||
$rowIndex = 1;
|
||||
foreach ($displayHeaderLabels as $colIndex => $headerLabel) {
|
||||
$columnLetter = Coordinate::stringFromColumnIndex($colIndex + 1);
|
||||
$cellAddress = $columnLetter . $rowIndex;
|
||||
$sheet->setCellValue($cellAddress, $headerLabel);
|
||||
}
|
||||
|
||||
// Data rows
|
||||
$rowIndex = 2;
|
||||
foreach ($rows as $row) {
|
||||
foreach ($dbColumnOrder as $i => $columnName) {
|
||||
$colIndex = $i + 1;
|
||||
$columnLetter = Coordinate::stringFromColumnIndex($colIndex);
|
||||
$cellAddress = $columnLetter . $rowIndex;
|
||||
$value = $row[$columnName] ?? null;
|
||||
|
||||
$sheet->setCellValue($cellAddress, $value);
|
||||
|
||||
// Highlight the "Rejected Reason" values
|
||||
if ($rejectedReasonColIdx !== null && $colIndex === $rejectedReasonColIdx) {
|
||||
$sheet->getStyle($cellAddress)
|
||||
->getFill()
|
||||
->setFillType(Fill::FILL_SOLID)
|
||||
->getStartColor()
|
||||
->setARGB('FFFFF4B2'); // light yellow
|
||||
}
|
||||
}
|
||||
|
||||
$rowIndex++;
|
||||
}
|
||||
|
||||
// Autosize columns for better readability
|
||||
$highestColumnIndex = count($displayHeaderLabels);
|
||||
for ($col = 1; $col <= $highestColumnIndex; $col++) {
|
||||
$columnLetter = Coordinate::stringFromColumnIndex($col);
|
||||
$sheet->getColumnDimension($columnLetter)->setAutoSize(true);
|
||||
}
|
||||
|
||||
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
|
||||
ob_start();
|
||||
$writer->save('php://output');
|
||||
$excelOutput = ob_get_clean();
|
||||
|
||||
$filename = 'tpa_claim_dump_errors_' . $file_id . '.xlsx';
|
||||
|
||||
return $response
|
||||
->setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"')
|
||||
->setHeader('Cache-Control', 'max-age=0')
|
||||
->setBody($excelOutput);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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)";
|
||||
|
||||
@ -119,6 +119,19 @@ class AbhiClaimImportService extends BaseTpaClaimImportService
|
||||
'Settled' => 11,
|
||||
'Rejected' => 8,
|
||||
'Cancelled' => 13,
|
||||
'Approved' => 9,
|
||||
'Closed' => 12,
|
||||
'Query' => 4,
|
||||
'Required Information' => 4,
|
||||
'In-Progress' => 5,
|
||||
'Paid' => 11,
|
||||
'Denied' => 8,
|
||||
'Cancelled' => 13,
|
||||
'Processed' => 61,
|
||||
'Information Awaited' => 4,
|
||||
'Denied Letter Sent' => 66,
|
||||
'Cashless Document Awaited' => 3,
|
||||
'RI Cancelled' => 13,
|
||||
];
|
||||
|
||||
protected $dateColumns = [
|
||||
@ -164,14 +177,30 @@ class AbhiClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
|
||||
public function updateTicketIdInTPATable(): bool
|
||||
public function updateTicketIdInTPATable(int $fileId): bool
|
||||
{
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
$rows = $this->db->table('claims_dump_abhi cd')
|
||||
->select('cd.id, tm.id AS ticket_id')
|
||||
->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner')
|
||||
->where('cd.is_active', 1)
|
||||
->where('cd.file_id', $fileId)
|
||||
->where('cd.ticket_id IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('claims_dump_abhi');
|
||||
return $builder->updateBatch($data, 'id');
|
||||
$updateData = [];
|
||||
foreach ($rows as $row) {
|
||||
$updateData[] = [
|
||||
'id' => $row['id'],
|
||||
'ticket_id' => $row['ticket_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->db->table('claims_dump_abhi')->updateBatch($updateData, 'id') !== false;
|
||||
}
|
||||
|
||||
|
||||
@ -329,7 +358,7 @@ class AbhiClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
// Meta fields
|
||||
$item['claim_status_id'] = $statusMapping[$row['claim_status']] ?? 61;
|
||||
$item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['claim_status']);
|
||||
$item['file_id'] = $file_id;
|
||||
$item['claim_dump_ref_id'] = $row['id'];
|
||||
$item['created_by'] = $file_data['created_by'] ?? null;
|
||||
|
||||
@ -81,7 +81,7 @@ abstract class BaseTpaClaimImportService
|
||||
$this->db->transBegin();
|
||||
|
||||
try {
|
||||
$file_id = $params['file_id'];
|
||||
$file_id = $params['file_id'];
|
||||
$ticketMasterData = $this->mapClaimMasterData($file_id);
|
||||
|
||||
// Check if mapping failed
|
||||
@ -101,6 +101,12 @@ abstract class BaseTpaClaimImportService
|
||||
$this->db->transRollback();
|
||||
return ['status' => false, 'message' => 'Ticket Master Claim bulk insert failed'];
|
||||
}
|
||||
// Map newly created ticket IDs back to the TPA staging table
|
||||
if (!$this->updateTicketIdInTPATable($file_id)) {
|
||||
$this->db->transRollback();
|
||||
return ['status' => false, 'message' => 'Updating ticket_id in TPA table failed'];
|
||||
}
|
||||
|
||||
$message .= 'Ticket Master Claim bulk insert success. ';
|
||||
$hasExecutedTask = true;
|
||||
}else{
|
||||
@ -328,6 +334,16 @@ abstract class BaseTpaClaimImportService
|
||||
return $employeeData ?? [];
|
||||
}
|
||||
|
||||
public function checkStatusMapping($statusArray, $statusString)
|
||||
{
|
||||
foreach ($statusArray as $key => $value) {
|
||||
if (strtolower($statusString) == strtolower($key) || strtolower($statusString) == strtolower(trim($key))) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Excel rows to TPA table structure
|
||||
*/
|
||||
@ -349,9 +365,9 @@ abstract class BaseTpaClaimImportService
|
||||
abstract protected function importClaimMaster(array $data): bool;
|
||||
|
||||
/**
|
||||
* Update TPA table with ticket_master primary key
|
||||
* Update TPA table with ticket_master primary key for a given file.
|
||||
*/
|
||||
abstract protected function updateTicketIdInTPATable(): bool;
|
||||
abstract protected function updateTicketIdInTPATable(int $fileId): bool;
|
||||
|
||||
/**
|
||||
* Update TPA table with ticket_master insert rejected reason
|
||||
|
||||
@ -181,6 +181,13 @@ class FhplClaimImportService extends BaseTpaClaimImportService
|
||||
protected $statusMapping = [
|
||||
'Settled' => 11,
|
||||
'Rejected' => 8,
|
||||
'Paid' => 11,
|
||||
'Approved' => 9,
|
||||
'Closed' => 12,
|
||||
'Under Process' => 5,
|
||||
'Query' => 4,
|
||||
'Required Information' => 4,
|
||||
'In-Progress' => 5,
|
||||
];
|
||||
|
||||
protected $dateColumns = [
|
||||
@ -235,14 +242,31 @@ class FhplClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
|
||||
public function updateTicketIdInTPATable(): bool
|
||||
public function updateTicketIdInTPATable(int $fileId): bool
|
||||
{
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
// Join ticket_master with FHPL staging on file_id + claim_dump_ref_id -> id
|
||||
$rows = $this->db->table('claims_dump_fhpl cd')
|
||||
->select('cd.id, tm.id AS ticket_id')
|
||||
->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner')
|
||||
->where('cd.is_active', 1)
|
||||
->where('cd.file_id', $fileId)
|
||||
->where('cd.ticket_id IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('claims_dump_fhpl');
|
||||
return $builder->updateBatch($data, 'id');
|
||||
|
||||
$updateData = [];
|
||||
foreach ($rows as $row) {
|
||||
$updateData[] = [
|
||||
'id' => $row['id'],
|
||||
'ticket_id' => $row['ticket_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->db->table('claims_dump_fhpl')->updateBatch($updateData, 'id') !== false;
|
||||
}
|
||||
|
||||
|
||||
@ -400,7 +424,7 @@ class FhplClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
// Meta fields
|
||||
$item['claim_status_id'] = $statusMapping[$row['current_claim_status']] ?? 61;
|
||||
$item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['current_claim_status']) ?? 61;
|
||||
$item['file_id'] = $file_id;
|
||||
$item['claim_dump_ref_id'] = $row['id'];
|
||||
$item['created_by'] = $file_data['created_by'] ?? null;
|
||||
|
||||
@ -106,7 +106,13 @@ class IciciClaimImportService extends BaseTpaClaimImportService
|
||||
|
||||
protected $statusMapping = [
|
||||
'PAID' => 11,
|
||||
'SETTLED' => 11,
|
||||
'REJECTED' => 8,
|
||||
'APPROVED' => 9,
|
||||
'CLOSED' => 12,
|
||||
'QUERY' => 4,
|
||||
'REQUIRED INFORMATION' => 4,
|
||||
'IN-PROGRESS' => 5,
|
||||
];
|
||||
|
||||
protected $dateColumns = [
|
||||
@ -152,14 +158,30 @@ class IciciClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
|
||||
public function updateTicketIdInTPATable(): bool
|
||||
public function updateTicketIdInTPATable(int $fileId): bool
|
||||
{
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
$rows = $this->db->table('claims_dump_icici cd')
|
||||
->select('cd.id, tm.id AS ticket_id')
|
||||
->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner')
|
||||
->where('cd.is_active', 1)
|
||||
->where('cd.file_id', $fileId)
|
||||
->where('cd.ticket_id IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('claims_dump_icici');
|
||||
return $builder->upsertBatch($data, 'id');
|
||||
|
||||
$updateData = [];
|
||||
foreach ($rows as $row) {
|
||||
$updateData[] = [
|
||||
'id' => $row['id'],
|
||||
'ticket_id' => $row['ticket_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->db->table('claims_dump_icici')->updateBatch($updateData, 'id') !== false;
|
||||
}
|
||||
|
||||
|
||||
@ -287,7 +309,7 @@ class IciciClaimImportService extends BaseTpaClaimImportService
|
||||
$item['tpa_id'] = $client_policy_data['tpa_id'] ?? null;
|
||||
$item['acm_id'] = $client_policy_data['acm_id'] ?? null;
|
||||
$item['policy_no'] = $client_policy_data['policy_no'] ?? null;
|
||||
$item['relationship'] = $this->convertRelation($row['relation_group'] ?? '');
|
||||
$item['relationship'] = $this->convertRelation($row['relation'] ?? '');
|
||||
|
||||
$employee_data = $this->getEmployeeDetails($file_data['client_id'], $file_data['client_policy_id'], $row['employee_member_id'], $item['relationship']);
|
||||
|
||||
@ -317,7 +339,7 @@ class IciciClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
// Meta fields
|
||||
$item['claim_status_id'] = $statusMapping[$row['updated_status']] ?? 61;
|
||||
$item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['updated_status']) ?? 61;
|
||||
$item['claim_dump_ref_id'] = $row['id'];
|
||||
$item['file_id'] = $file_id;
|
||||
$item['created_by'] = $file_data['created_by'] ?? null;
|
||||
|
||||
@ -158,12 +158,15 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService
|
||||
protected $statusMapping = [
|
||||
'Settled' => 11,
|
||||
'Rejected' => 8,
|
||||
'Paid' => 11,
|
||||
'Denied' => 8,
|
||||
'Cancelled' => 13,
|
||||
'Processed' => 61,
|
||||
'Information Awaited' => 4,
|
||||
'Denied Letter Sent' => 66,
|
||||
'Cashless Document Awaited' => 3,
|
||||
'Approved' => 9,
|
||||
'Closed' => 12,
|
||||
];
|
||||
|
||||
/**
|
||||
@ -192,16 +195,30 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
|
||||
public function updateTicketIdInTPATable(): bool
|
||||
public function updateTicketIdInTPATable(int $fileId): bool
|
||||
{
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('claims_dump_medi_assist');
|
||||
$builder->insertBatch($data);
|
||||
$rows = $this->db->table('claims_dump_medi_assist cd')
|
||||
->select('cd.id, tm.id AS ticket_id')
|
||||
->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner')
|
||||
->where('cd.is_active', 1)
|
||||
->where('cd.file_id', $fileId)
|
||||
->where('cd.ticket_id IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
return true;
|
||||
if (empty($rows)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$updateData = [];
|
||||
foreach ($rows as $row) {
|
||||
$updateData[] = [
|
||||
'id' => $row['id'],
|
||||
'ticket_id' => $row['ticket_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->db->table('claims_dump_medi_assist')->updateBatch($updateData, 'id') !== false;
|
||||
}
|
||||
|
||||
|
||||
@ -356,8 +373,8 @@ class MediAssistClaimImportService extends BaseTpaClaimImportService
|
||||
|
||||
// Meta fields
|
||||
$item['claim_dump_ref_id'] = $row['id'];
|
||||
$item['file_id'] = $file_id;
|
||||
$item['claim_status_id'] = $statusMapping[$row['claim_status']] ?? 61;
|
||||
$item['file_id'] = $file_id;
|
||||
$item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['claim_status']) ?? 61;
|
||||
$item['created_by'] = $file_data['created_by'] ?? null;
|
||||
$item['claim_type'] = 1;
|
||||
$item['priority'] = 1;
|
||||
|
||||
@ -96,14 +96,15 @@ class RcareClaimImportService extends BaseTpaClaimImportService
|
||||
];
|
||||
|
||||
protected $statusMapping = [
|
||||
'CL Paid with Settlement Letter' => 11,
|
||||
'Settled' => 11,
|
||||
'Paid' => 11,
|
||||
'Rejected' => 8,
|
||||
'Denied' => 8,
|
||||
'Cancelled' => 13,
|
||||
'Processed' => 61,
|
||||
'Information Awaited' => 4,
|
||||
'Denied Letter Sent' => 66,
|
||||
'Cashless Document Awaited' => 3,
|
||||
'Approved' => 9,
|
||||
'Closed' => 12,
|
||||
'CL Rejected' => 8,
|
||||
'CL Approved' => 9,
|
||||
'AL Closed' => 12,
|
||||
];
|
||||
|
||||
protected $dateColumns = [
|
||||
@ -143,14 +144,30 @@ class RcareClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
|
||||
public function updateTicketIdInTPATable(): bool
|
||||
public function updateTicketIdInTPATable(int $fileId): bool
|
||||
{
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
$rows = $this->db->table('claims_dump_reliance cd')
|
||||
->select('cd.id, tm.id AS ticket_id')
|
||||
->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner')
|
||||
->where('cd.is_active', 1)
|
||||
->where('cd.file_id', $fileId)
|
||||
->where('cd.ticket_id IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('claims_dump_reliance');
|
||||
return $builder->updateBatch($data, 'id');
|
||||
|
||||
$updateData = [];
|
||||
foreach ($rows as $row) {
|
||||
$updateData[] = [
|
||||
'id' => $row['id'],
|
||||
'ticket_id' => $row['ticket_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->db->table('claims_dump_reliance')->updateBatch($updateData, 'id') !== false;
|
||||
}
|
||||
|
||||
|
||||
@ -307,7 +324,7 @@ class RcareClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
// Meta fields
|
||||
$item['claim_status_id'] = $statusMapping[$row['final_status']] ?? 61;
|
||||
$item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['final_status']) ?? 61;
|
||||
$item['file_id'] = $file_id;
|
||||
$item['created_by'] = $file_data['created_by'] ?? null;
|
||||
$item['claim_dump_ref_id'] = $row['id'];
|
||||
|
||||
@ -349,10 +349,17 @@ class VidalClaimImportService extends BaseTpaClaimImportService
|
||||
];
|
||||
|
||||
protected $statusMapping = [
|
||||
'CL Paid with Settlement Letter' => 11,
|
||||
'CL Rejected' => 8,
|
||||
'CL Approved' => 9,
|
||||
'AL Closed' => 12,
|
||||
'Settled' => 11,
|
||||
'Rejected' => 8,
|
||||
'Paid' => 11,
|
||||
'Denied' => 8,
|
||||
'Cancelled' => 13,
|
||||
'Processed' => 61,
|
||||
'Information Awaited' => 4,
|
||||
'Denied Letter Sent' => 66,
|
||||
'Cashless Document Awaited' => 3,
|
||||
'Approved' => 9,
|
||||
'Closed' => 12,
|
||||
];
|
||||
|
||||
|
||||
@ -382,14 +389,30 @@ class VidalClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
|
||||
public function updateTicketIdInTPATable(): bool
|
||||
public function updateTicketIdInTPATable(int $fileId): bool
|
||||
{
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
$rows = $this->db->table('claims_dump_vidal cd')
|
||||
->select('cd.id, tm.id AS ticket_id')
|
||||
->join('ticket_master tm', 'tm.claim_dump_ref_id = cd.id AND tm.file_id = cd.file_id', 'inner')
|
||||
->where('cd.is_active', 1)
|
||||
->where('cd.file_id', $fileId)
|
||||
->where('cd.ticket_id IS NULL')
|
||||
->get()
|
||||
->getResultArray();
|
||||
|
||||
if (empty($rows)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$builder = $this->db->table('claims_dump_vidal');
|
||||
return $builder->updateBatch($data, 'id');
|
||||
|
||||
$updateData = [];
|
||||
foreach ($rows as $row) {
|
||||
$updateData[] = [
|
||||
'id' => $row['id'],
|
||||
'ticket_id' => $row['ticket_id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->db->table('claims_dump_vidal')->updateBatch($updateData, 'id') !== false;
|
||||
}
|
||||
|
||||
|
||||
@ -542,7 +565,7 @@ class VidalClaimImportService extends BaseTpaClaimImportService
|
||||
}
|
||||
|
||||
// Meta fields
|
||||
$item['claim_status_id'] = $statusMapping[$row['claim_status']] ?? 61;
|
||||
$item['claim_status_id'] = $this->checkStatusMapping($this->statusMapping, $row['claim_status']) ?? 61;
|
||||
$item['file_id'] = $file_id;
|
||||
$item['claim_dump_ref_id'] = $row['id'];
|
||||
$item['created_by'] = $file_data['created_by'] ?? null;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
73
app/Models/ExpenseModel.php
Normal file
73
app/Models/ExpenseModel.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Model;
|
||||
|
||||
class ExpenseModel extends Model
|
||||
{
|
||||
protected $table = 'expenses';
|
||||
protected $primaryKey = 'id';
|
||||
protected $DBGroup = 'default';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $returnType = 'array';
|
||||
protected $useSoftDeletes = false;
|
||||
protected $protectFields = true;
|
||||
|
||||
protected $allowedFields = [
|
||||
'id',
|
||||
'client_id',
|
||||
'client_policy_id',
|
||||
'description',
|
||||
'approved_by',
|
||||
'amount',
|
||||
'expense_date',
|
||||
'created_at',
|
||||
'created_by',
|
||||
'updated_at',
|
||||
'updated_by',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
// Callbacks
|
||||
protected $allowCallbacks = true;
|
||||
protected $beforeInsert = ['setCreatedBy'];
|
||||
protected $afterInsert = [];
|
||||
protected $beforeUpdate = ['setUpdatedBy'];
|
||||
protected $afterUpdate = [];
|
||||
protected $beforeFind = [];
|
||||
protected $afterFind = [];
|
||||
protected $beforeDelete = [];
|
||||
protected $afterDelete = [];
|
||||
|
||||
protected function setCreatedBy(array $data): array
|
||||
{
|
||||
if (empty($data['data']['created_by'])) {
|
||||
$data['data']['created_by'] = get_session_userid();
|
||||
}
|
||||
|
||||
if (empty($data['data']['created_at'])) {
|
||||
$data['data']['created_at'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
if (! isset($data['data']['is_active'])) {
|
||||
$data['data']['is_active'] = 1;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function setUpdatedBy(array $data): array
|
||||
{
|
||||
if (empty($data['data']['updated_by'])) {
|
||||
$data['data']['updated_by'] = get_session_userid();
|
||||
}
|
||||
|
||||
if (empty($data['data']['updated_at'])) {
|
||||
$data['data']['updated_at'] = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -24,6 +24,66 @@
|
||||
.dataTables_length label {height: 21px !important;}
|
||||
|
||||
.readonly-select { background-color: #f3f3f3 !important; cursor: not-allowed; pointer-events: none; }
|
||||
|
||||
.custom-tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
font-family: Georgia, serif;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.tooltiptext {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
position: fixed; /* fixed instead of absolute to escape modal overflow */
|
||||
background: #333;
|
||||
color: #fff;
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
white-space: normal; /* allow text wrapping */
|
||||
width: 320px; /* fixed width for multiline */
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
line-height: 1.6;
|
||||
z-index: 99999;
|
||||
transition: opacity 0.2s ease;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
top: 20px !important;
|
||||
left: 155px !important;
|
||||
}
|
||||
|
||||
.tooltiptext::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 20px;
|
||||
border: 6px solid transparent;
|
||||
border-top-color: #333;
|
||||
}
|
||||
|
||||
.custom-tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<!-- <div class="row">
|
||||
@ -117,6 +177,16 @@
|
||||
class="mdi mdi-dots-horizontal"></i></a>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a target="_blank" class="dropdown-item" href="<?= base_url("util/download_claim_dump_file/") . $file['file_id']; ?>"><i class="mdi mdi-download mr-2 text-muted font-18 vertical-middle"></i>Download</a>
|
||||
|
||||
<?php if ($file['status'] === 'failed') : ?>
|
||||
<a href="javascript:void(0);"
|
||||
class="dropdown-item"
|
||||
onclick="downloadTpaErrorFile(<?= (int) $file['file_id']; ?>)">
|
||||
<i class="mdi mdi-file-excel mr-2 text-danger font-18 vertical-middle"></i>
|
||||
Download Error File
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- <a data-id="<?php echo $file['file_id'] ?>" data-toggle="modal" data-target="#full-width-modal-emp-list" class="dropdown-item view_emp_list" href="#"><i class="mdi mdi-eye mr-2 text-muted font-18 vertical-middle"></i>View</a> -->
|
||||
</div>
|
||||
</div>
|
||||
@ -151,7 +221,16 @@
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="myCenterModalLabel">Claim Dump Upload</h4>
|
||||
<h4 class="modal-title" id="myCenterModalLabel">
|
||||
Claim Dump Upload
|
||||
<i class="info-icon"
|
||||
data-toggle="tooltip"
|
||||
data-placement="right"
|
||||
data-html="true"
|
||||
title="1. If you want to upload a TPA-wise claim dump, select the client and policy, then upload the TPA's Excel file. <br><br> 2. The second option without selecting the client and policy allows you to upload multiple claims. You can download the sample file provided below, fill it in, and upload it. This is for bulk dump upload.">
|
||||
i
|
||||
</i>
|
||||
</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@ -219,6 +298,20 @@
|
||||
let policyListByClient = null;
|
||||
let client_list = null;
|
||||
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
|
||||
document.querySelectorAll('.custom-tooltip').forEach(function(tooltip) {
|
||||
tooltip.addEventListener('mouseenter', function() {
|
||||
var icon = this.querySelector('.info-icon');
|
||||
var tip = this.querySelector('.tooltiptext');
|
||||
var rect = icon.getBoundingClientRect();
|
||||
tip.style.left = rect.left + 'px';
|
||||
tip.style.top = (rect.top - tip.offsetHeight - 10) + 'px';
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
getClientAndBranchAndPolicy();
|
||||
@ -232,10 +325,17 @@
|
||||
|
||||
let client_id = $('#client_id').val() ?? null;
|
||||
let client_policy_id = $('#client_policy_id').val() ?? null;
|
||||
let tpa_id = $('#tpa_id').val() ?? null;
|
||||
|
||||
if(client_id){
|
||||
|
||||
if(!client_policy_id){
|
||||
toastr.warning('Please select the client policy', 'Warning');
|
||||
toastr.warning('Please select the client policy', 'WARNING');
|
||||
return;
|
||||
}
|
||||
|
||||
if(!tpa_id){
|
||||
toastr.warning('TPA is required', 'WARNING');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -251,9 +351,15 @@
|
||||
'application/vnd.oasis.opendocument.spreadsheet'
|
||||
];
|
||||
|
||||
if (!fileInput.files.length) {
|
||||
toastr.warning('Please select an Excel file', 'WARNING');
|
||||
return false;
|
||||
}
|
||||
|
||||
var selectedFile = fileInput.files[0];
|
||||
|
||||
if (!allowedTypes.includes(selectedFile.type)) {
|
||||
alert('Please upload a valid Excel file (.xlsx, .xls, .ods)');
|
||||
toastr.warning('Please upload a valid Excel file (.xlsx, .xls, .ods)', 'WARNING');
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -500,6 +606,70 @@
|
||||
|
||||
}
|
||||
|
||||
function downloadTpaErrorFile(file_id) {
|
||||
if (!file_id) {
|
||||
toastr.error('Invalid file reference for error download', 'Error');
|
||||
return;
|
||||
}
|
||||
|
||||
var url = '<?= base_url("util/getTpaClaimDumpErrorData"); ?>/' + file_id;
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
xhrFields: { responseType: 'blob' },
|
||||
success: function (data, status, xhr) {
|
||||
var disposition = xhr.getResponseHeader('Content-Disposition') || '';
|
||||
var contentType = xhr.getResponseHeader('Content-Type') || '';
|
||||
|
||||
// If server sent JSON (error), parse it and show via Toastr
|
||||
if (contentType.indexOf('application/json') !== -1) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
try {
|
||||
var json = JSON.parse(reader.result);
|
||||
var msg = json.message || 'Unable to download error file.';
|
||||
toastr.error(msg, 'Error');
|
||||
} catch (e) {
|
||||
toastr.error('Unable to download error file.', 'Error');
|
||||
}
|
||||
};
|
||||
reader.readAsText(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise assume it is Excel and trigger a download
|
||||
var filename = 'tpa_claim_dump_errors_' + file_id + '.xlsx';
|
||||
var match = /filename="?([^"]+)"?/.exec(disposition);
|
||||
if (match && match[1]) {
|
||||
filename = match[1];
|
||||
}
|
||||
|
||||
var blob = new Blob([data], {
|
||||
type: contentType || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
});
|
||||
var link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
toastr.success('Error file download started', 'Success');
|
||||
},
|
||||
error: function (xhr) {
|
||||
var msg = 'Unable to download error file.';
|
||||
try {
|
||||
var json = JSON.parse(xhr.responseText);
|
||||
if (json.message) {
|
||||
msg = json.message;
|
||||
}
|
||||
} catch (e) {}
|
||||
toastr.error(msg, 'Error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('.close').click(function(){
|
||||
$('#modal_body').empty()
|
||||
let html = `<div class="spinner-border text-primary" role="status" style="position: relative; left: 200px;"></div>`
|
||||
|
||||
586
app/Views/expense_list.php
Normal file
586
app/Views/expense_list.php
Normal file
@ -0,0 +1,586 @@
|
||||
<div class="row" id="expense_module">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-6 d-flex align-items-center">
|
||||
<!-- <h4 class="mb-0">Expense</h4> -->
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<!-- <button type="button" class="btn app-btn-secondary" id="btnResetExpense">
|
||||
<i class="mdi mdi-refresh mr-1"></i>Reset
|
||||
</button> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="custom-form mb-4">
|
||||
<form role="form" class="parsley-examples" method="get" id="expense_form">
|
||||
<input type="hidden" value="<?= csrf_hash() ?>" name="<?= csrf_token() ?>" />
|
||||
<input type="hidden" name="id" id="expense_id" />
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-4">
|
||||
<label for="client_id">Client Name <span class="text-danger">*</span></label>
|
||||
<select class="form-control" id="client_id" name="client_id" required>
|
||||
<option value="">Select Client</option>
|
||||
<?php if (! empty($clients)) : ?>
|
||||
<?php foreach ($clients as $client) : ?>
|
||||
<option value="<?= $client['id']; ?>" <?= ! empty($filters['client_id']) && (int) $filters['client_id'] === (int) $client['id'] ? 'selected' : ''; ?>>
|
||||
<?= esc($client['client_name']); ?>
|
||||
<?= ! empty($client['short_name']) ? ' (' . esc($client['short_name']) . ')' : ''; ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<label for="client_policy_id">Policy No <span class="text-danger">*</span></label>
|
||||
<select class="form-control" id="client_policy_id" name="client_policy_id" required>
|
||||
<option value="">Select Policy</option>
|
||||
<?php if (! empty($policies_for_filter)) : ?>
|
||||
<?php foreach ($policies_for_filter as $policy) : ?>
|
||||
<option value="<?= $policy['id']; ?>" <?= ! empty($filters['client_policy_id']) && (int) $filters['client_policy_id'] === (int) $policy['id'] ? 'selected' : ''; ?>>
|
||||
<?= ! empty($policy['policy_type']) ? esc($policy['policy_type']) . ' - ' : ''; ?> <?= esc($policy['policy_no']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<label for="approved_by">Approved By <span class="text-danger">*</span></label>
|
||||
<select class="form-control" id="approved_by" name="approved_by" required>
|
||||
<option value="">Select User</option>
|
||||
<?php if (! empty($approved_users)) : ?>
|
||||
<?php foreach ($approved_users as $user) : ?>
|
||||
<option value="<?= $user['id']; ?>" <?= ! empty($filters['approved_by']) && (int) $filters['approved_by'] === (int) $user['id'] ? 'selected' : ''; ?>>
|
||||
<?= esc($user['first_name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<label for="expense_date">Expense Date <span class="text-danger">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="expense_date"
|
||||
name="expense_date"
|
||||
placeholder="DD-MM-YYYY"
|
||||
value="<?= isset($filters['expense_date']) ? esc($filters['expense_date']) : ''; ?>"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<label for="amount">Amount <span class="text-danger">*</span></label>
|
||||
<input type="number" step="0.01" min="0" class="form-control" id="amount" name="amount" placeholder="Enter amount" value="<?= isset($filters['amount']) ? esc($filters['amount']) : ''; ?>" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-4">
|
||||
<label for="description">Description <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" id="description" name="description" rows="3" placeholder="Enter description" required><?= isset($filters['description']) ? esc($filters['description']) : ''; ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-right m-b-0">
|
||||
<!-- <button type="button" class="btn app-btn-secondary waves-effect waves-light mr-1" id="btnSearchExpense">
|
||||
Search
|
||||
</button> -->
|
||||
<button type="submit" class="btn app-btn-secondary waves-effect waves-light mr-1" id="btnSubmitExpense">
|
||||
Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary waves-effect" id="btnCancelExpense">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<table data-custom-table-css="table" class="table mb-0 nowrap w-100 table-centered" cellspacing="0" id="expense-table">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="font-weight-medium">S.No</th>
|
||||
<th class="font-weight-medium">Client</th>
|
||||
<th class="font-weight-medium">Policy No</th>
|
||||
<th class="font-weight-medium">Description</th>
|
||||
<th class="font-weight-medium">Approved By</th>
|
||||
<th class="font-weight-medium">Amount</th>
|
||||
<th class="font-weight-medium">Expense Date</th>
|
||||
<th class="font-weight-medium">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (! empty($expenses)) : ?>
|
||||
<?php foreach ($expenses as $index => $row) : ?>
|
||||
<tr>
|
||||
<td class="text-center"><?= $index + 1; ?></td>
|
||||
<td>
|
||||
<?= esc($row['client_name'] ?? ''); ?>
|
||||
<?= ! empty($row['short_name']) ? ' (' . esc($row['short_name']) . ')' : ''; ?>
|
||||
</td>
|
||||
<td><?= esc($row['policy_no'] ?? ''); ?></td>
|
||||
<td><?= esc($row['description'] ?? ''); ?></td>
|
||||
<td><?= esc($row['approved_by_name'] ?? ''); ?></td>
|
||||
<td><?= number_format((float) ($row['amount'] ?? 0), 2); ?></td>
|
||||
<td>
|
||||
<?php if (! empty($row['expense_date'])): ?>
|
||||
<?= date('d-m-Y', strtotime($row['expense_date'])); ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group dropdown">
|
||||
<a href="javascript:void(0);" class="dropdown-toggle arrow-none btn btn-light btn-sm" data-toggle="dropdown" aria-expanded="false">
|
||||
<i class="mdi mdi-dots-horizontal"></i>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item" href="javascript:void(0);" onclick="editExpense(<?= (int) $row['id']; ?>)">
|
||||
<i class="mdi mdi-pencil mr-2 text-muted font-18 vertical-middle"></i>Edit
|
||||
</a>
|
||||
<a class="dropdown-item" href="javascript:void(0);" onclick="deleteExpense(<?= (int) $row['id']; ?>)">
|
||||
<i class="mdi mdi-delete mr-2 text-muted font-18 vertical-middle"></i>Delete
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const expenseValidationErrors = <?= ! empty($validation_errors) ? json_encode(array_values($validation_errors)) : '[]'; ?>;
|
||||
let expensePoliciesCache = {};
|
||||
|
||||
function resetExpenseForm() {
|
||||
$('#client_id').val('').trigger('change');
|
||||
$('#client_policy_id').empty().append('<option value="">Select Policy</option>').trigger('change');
|
||||
$('#approved_by').val('').trigger('change');
|
||||
$('#description').val('');
|
||||
$('#amount').val('');
|
||||
$('#expense_date').val('');
|
||||
$('#btnSubmitExpense').text('Save');
|
||||
window.location.href = '<?= base_url("expense"); ?>';
|
||||
}
|
||||
|
||||
function validateExpenseSave() {
|
||||
const errors = [];
|
||||
const clientId = $('#client_id').val();
|
||||
const policyId = $('#client_policy_id').val();
|
||||
const approvedBy = $('#approved_by').val();
|
||||
const desc = ($('#description').val() || '').trim();
|
||||
const amount = ($('#amount').val() || '').trim();
|
||||
const expenseDate= ($('#expense_date').val() || '').trim();
|
||||
|
||||
const idPattern = /^[0-9]+$/;
|
||||
const descPattern = /^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+]+$/;
|
||||
const datePattern = /^[0-9]{2}-[0-9]{2}-[0-9]{4}$/;
|
||||
|
||||
if (!clientId) {
|
||||
errors.push('Client is required.');
|
||||
} else if (!idPattern.test(clientId)) {
|
||||
errors.push('Invalid client selected.');
|
||||
}
|
||||
|
||||
if (!policyId) {
|
||||
errors.push('Policy is required.');
|
||||
} else if (!idPattern.test(policyId)) {
|
||||
errors.push('Invalid policy selected.');
|
||||
}
|
||||
|
||||
if (!approvedBy) {
|
||||
errors.push('Approved By is required.');
|
||||
} else if (!idPattern.test(approvedBy)) {
|
||||
errors.push('Invalid approver selected.');
|
||||
}
|
||||
|
||||
if (!desc) {
|
||||
errors.push('Description is required.');
|
||||
} else if (!descPattern.test(desc)) {
|
||||
errors.push('Description contains invalid characters.');
|
||||
}
|
||||
|
||||
if (!amount) {
|
||||
errors.push('Amount is required.');
|
||||
} else if (isNaN(amount) || Number(amount) < 0) {
|
||||
errors.push('Amount must be a non-negative number.');
|
||||
}
|
||||
|
||||
if (!expenseDate) {
|
||||
errors.push('Expense Date is required.');
|
||||
} else if (!datePattern.test(expenseDate)) {
|
||||
errors.push('Expense Date must be in DD-MM-YYYY format.');
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
toastr.warning(errors.join('<br>'), 'Validation');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateExpenseSearchFilters() {
|
||||
const errors = [];
|
||||
const clientId = $('#client_id').val();
|
||||
const policyId = $('#client_policy_id').val();
|
||||
const approvedBy = $('#approved_by').val();
|
||||
const desc = ($('#description').val() || '').trim();
|
||||
const amount = ($('#amount').val() || '').trim();
|
||||
const expenseDate= ($('#expense_date').val() || '').trim();
|
||||
|
||||
const idPattern = /^[0-9]+$/;
|
||||
const descPattern = /^[a-zA-Z0-9\s\.\,\-\(\)\/\&\+]+$/;
|
||||
const datePattern = /^[0-9]{2}-[0-9]{2}-[0-9]{4}$/;
|
||||
|
||||
if (clientId && !idPattern.test(clientId)) {
|
||||
errors.push('Invalid client selected for search.');
|
||||
}
|
||||
|
||||
if (policyId && !idPattern.test(policyId)) {
|
||||
errors.push('Invalid policy selected for search.');
|
||||
}
|
||||
|
||||
if (approvedBy && !idPattern.test(approvedBy)) {
|
||||
errors.push('Invalid approver selected for search.');
|
||||
}
|
||||
|
||||
if (desc && !descPattern.test(desc)) {
|
||||
errors.push('Description filter contains invalid characters.');
|
||||
}
|
||||
|
||||
if (amount) {
|
||||
if (isNaN(amount) || Number(amount) < 0) {
|
||||
errors.push('Amount filter must be a non-negative number.');
|
||||
}
|
||||
}
|
||||
|
||||
if (expenseDate && !datePattern.test(expenseDate)) {
|
||||
errors.push('Expense Date filter must be in DD-MM-YYYY format.');
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
toastr.warning(errors.join('<br>'), 'Validation');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function populatePolicies(clientId, selectedPolicyId = null) {
|
||||
if (!clientId) {
|
||||
$('#client_policy_id').empty().append('<option value="">Select Policy</option>').trigger('change');
|
||||
return;
|
||||
}
|
||||
|
||||
if (expensePoliciesCache[clientId]) {
|
||||
const policies = expensePoliciesCache[clientId];
|
||||
let optionsHtml = '<option value=\"\">Select Policy</option>';
|
||||
|
||||
policies.forEach(function (p) {
|
||||
optionsHtml += `<option value=\"${p.id}\">${p.policy_no}</option>`;
|
||||
});
|
||||
|
||||
$('#client_policy_id').html(optionsHtml);
|
||||
|
||||
if (selectedPolicyId) {
|
||||
$('#client_policy_id').val(selectedPolicyId).trigger('change');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url("expense/client-policies"); ?>',
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
data: {client_id: clientId},
|
||||
success: function (res) {
|
||||
if (res.status) {
|
||||
expensePoliciesCache[clientId] = res.data || [];
|
||||
|
||||
let optionsHtml = '<option value=\"\">Select Policy</option>';
|
||||
(res.data || []).forEach(function (p) {
|
||||
optionsHtml += `<option value=\"${p.id}\">${p.policy_no}</option>`;
|
||||
});
|
||||
|
||||
$('#client_policy_id').html(optionsHtml);
|
||||
|
||||
if (selectedPolicyId) {
|
||||
$('#client_policy_id').val(selectedPolicyId).trigger('change');
|
||||
}
|
||||
} else {
|
||||
toastr.warning(res.message || 'Unable to load policies', 'WARNING');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('Unexpected error while loading policies', 'ERROR');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function editExpense(id) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url("expense/get"); ?>/' + id,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: function (res) {
|
||||
if (res.status && res.data) {
|
||||
const data = res.data;
|
||||
|
||||
$('#expense_id').val(data.id);
|
||||
$('#description').val(data.description || '');
|
||||
$('#amount').val(data.amount || '');
|
||||
if (data.expense_date) {
|
||||
const parts = (data.expense_date || '').split('-'); // Y-m-d
|
||||
if (parts.length === 3) {
|
||||
const formatted = parts[2] + '-' + parts[1] + '-' + parts[0];
|
||||
$('#expense_date').val(formatted);
|
||||
if (window.expenseDatePicker) {
|
||||
window.expenseDatePicker.setDate(formatted, true, 'd-m-Y');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.approved_by) {
|
||||
$('#approved_by').val(data.approved_by).trigger('change');
|
||||
}
|
||||
|
||||
if (data.client_id) {
|
||||
$('#client_id').val(data.client_id).trigger('change');
|
||||
populatePolicies(data.client_id, data.client_policy_id || null);
|
||||
}
|
||||
|
||||
$('#btnSubmitExpense').text('Update');
|
||||
$('html, body').animate({
|
||||
scrollTop: $('#expense_form').offset().top - 100
|
||||
}, 400);
|
||||
} else {
|
||||
toastr.warning(res.message || 'Expense not found', 'WARNING');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('Unable to fetch expense details', 'ERROR');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteExpense(id) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
title: "Are you sure?",
|
||||
text: "You want to delete this expense",
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#3085d6",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Yes",
|
||||
}).then((result) => {
|
||||
if (!result.isConfirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const postData = {};
|
||||
postData['<?= csrf_token() ?>'] = '<?= csrf_hash() ?>';
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url("expense/delete"); ?>/' + id,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: postData,
|
||||
success: function (res) {
|
||||
if (res.status) {
|
||||
toastr.success(res.message || 'Expense deleted successfully', 'SUCCESS');
|
||||
setTimeout(function () {
|
||||
location.reload();
|
||||
}, 800);
|
||||
} else {
|
||||
toastr.warning(res.message || 'Unable to delete expense', 'WARNING');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('Unable to delete expense', 'ERROR');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#client_id').select2();
|
||||
$('#client_policy_id').select2();
|
||||
$('#approved_by').select2();
|
||||
|
||||
window.expenseDatePicker = flatpickr('#expense_date', {
|
||||
dateFormat: 'd-m-Y',
|
||||
allowInput: true
|
||||
});
|
||||
|
||||
$('#expense-table').DataTable({
|
||||
dom: "<'row'<'col-12 d-flex justify-content-between align-items-center'<'datatable-search dataTables_filter'f><'datatable-buttons dt-buttons'B>>>" +
|
||||
"<'row'<'col-sm-12'tr>>" +
|
||||
"<'row align-items-center'<'col-5 text-start'i><'col-7 d-flex justify-content-end align-items-center'<'me-2'l>p>>",
|
||||
lengthMenu: [[10, 20, 50, 100], [10, 20, 50, 100]],
|
||||
buttons: [
|
||||
{
|
||||
extend: 'collection',
|
||||
text: '<span class=" btn-custom"> Export </span><i class="mdi mdi-menu-down"></i>',
|
||||
className: 'btn app-btn-secondary ',
|
||||
buttons: [
|
||||
{
|
||||
extend: 'csv',
|
||||
text: '<i class="mdi mdi-file-delimited"></i><span class="btn-custom"> CSV </span>',
|
||||
title: 'Expense List',
|
||||
className: 'app-btn-primary ',
|
||||
exportOptions: {
|
||||
columns: ':not(:last-child)'
|
||||
}
|
||||
},
|
||||
{
|
||||
extend: 'excel',
|
||||
title: 'Expense-List',
|
||||
sheetName: 'Expense-List',
|
||||
text: '<i class="mdi mdi-file-excel"></i><span class="btn-custom"> EXCEL </span>',
|
||||
className: 'app-btn-primary ',
|
||||
exportOptions: {
|
||||
orthogonal: 'sort',
|
||||
columns: ':not(:last-child)'
|
||||
}
|
||||
},
|
||||
{
|
||||
extend: 'pdf',
|
||||
text: '<i class="mdi mdi-file-pdf"></i><span class="btn-custom"> PDF </span>',
|
||||
title: 'Expense List',
|
||||
className: 'app-btn-primary ',
|
||||
exportOptions: {
|
||||
columns: ':not(:last-child)'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
language: {
|
||||
search: `
|
||||
<div class="datatable-search-wrapper" style="position:relative; display:inline-block;">
|
||||
_INPUT_
|
||||
<i class="mdi mdi-magnify datatable-search-icon"
|
||||
style="position:absolute; right:11px; top:50%; transform:translateY(-53%); color:#666;"></i>
|
||||
<i class="mdi mdi-close-circle datatable-clear-icon"
|
||||
style="position:absolute; right:11px; top:50%; transform:translateY(-53%); color:#666; display:none;"></i>
|
||||
</div>`,
|
||||
searchPlaceholder: "Search",
|
||||
emptyTable: '<div class="text-center text-muted">No Data found</div>'
|
||||
}
|
||||
});
|
||||
|
||||
$('.dataTables_length label').css('height', '21px');
|
||||
|
||||
// Show any backend validation errors from search
|
||||
if (Array.isArray(expenseValidationErrors) && expenseValidationErrors.length) {
|
||||
expenseValidationErrors.forEach(function (msg) {
|
||||
toastr.warning(msg, 'Validation');
|
||||
});
|
||||
}
|
||||
|
||||
$('#client_id').on('change', function () {
|
||||
const clientId = $(this).val();
|
||||
populatePolicies(clientId);
|
||||
});
|
||||
|
||||
$('#btnSearchExpense').on('click', function () {
|
||||
if (!validateExpenseSearchFilters()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params = {};
|
||||
const clientId = $('#client_id').val();
|
||||
const policyId = $('#client_policy_id').val();
|
||||
const approvedBy = $('#approved_by').val();
|
||||
const desc = $('#description').val();
|
||||
const amount = $('#amount').val();
|
||||
const expenseDate = $('#expense_date').val();
|
||||
|
||||
if (clientId) {
|
||||
params.client_id = clientId;
|
||||
}
|
||||
if (policyId) {
|
||||
params.client_policy_id = policyId;
|
||||
}
|
||||
if (approvedBy) {
|
||||
params.approved_by = approvedBy;
|
||||
}
|
||||
if (desc) {
|
||||
params.description = desc;
|
||||
}
|
||||
if (amount) {
|
||||
params.amount = amount;
|
||||
}
|
||||
if (expenseDate) {
|
||||
params.expense_date = expenseDate;
|
||||
}
|
||||
|
||||
const query = $.param(params);
|
||||
const baseUrl = '<?= base_url("expense"); ?>';
|
||||
window.location.href = query ? baseUrl + '?' + query : baseUrl;
|
||||
});
|
||||
|
||||
$('#btnSubmitExpense').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateExpenseSave()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = $('#expense_form').serialize();
|
||||
|
||||
$.ajax({
|
||||
url: '<?= base_url("expense/save"); ?>',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: formData,
|
||||
success: function (res) {
|
||||
if (res.status) {
|
||||
toastr.success(res.message || 'Expense saved successfully', 'SUCCESS');
|
||||
setTimeout(function () {
|
||||
location.reload();
|
||||
}, 800);
|
||||
} else {
|
||||
const errors = res.errors || {};
|
||||
let errorMsg = res.message || 'Unable to save expense';
|
||||
|
||||
if (Object.keys(errors).length) {
|
||||
errorMsg = Object.values(errors).join('<br>');
|
||||
}
|
||||
|
||||
toastr.warning(errorMsg, 'WARNING');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
toastr.error('Unexpected error while saving expense', 'ERROR');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('#btnResetExpense, #btnCancelExpense').on('click', function () {
|
||||
resetExpenseForm();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1876,6 +1876,17 @@
|
||||
</li>
|
||||
<?php } ?>
|
||||
|
||||
<!-- Expence -->
|
||||
<?php if (in_array(get_role_id(), [1, 5])) { ?>
|
||||
<li class="li-seperate" id="clients-li">
|
||||
<a href="<?= base_url('expense') ?>" class="img-inactive">
|
||||
<img style="border-radius: 5px;"
|
||||
src="<?= base_url() . "public"; ?>/assets/images/expense_icon.png" alt="Logo" height="24">
|
||||
<span> Expense </span>
|
||||
</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
|
||||
<!-- BDS -->
|
||||
<?php if ((get_role_id() == 1 || get_role_id() == 5 || get_role_id() == 4) || (in_array(MANAGEMENT_TEAM_ID, user_team()) || in_array(FINANCE_TEAM_ID, user_team()) || in_array(BUSINESS_TEAM_ID, user_team()) || in_array(POS_TEAM_ID, user_team()))) { ?>
|
||||
<li class="li-seperate" id="policy-transactions-li">
|
||||
@ -2104,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) {
|
||||
|
||||
@ -452,6 +452,33 @@
|
||||
policyEnd.attr("id", end_date_id);
|
||||
$("label[for='policy_end_date'], label[for^='policy_end_date_']").attr("for", end_date_id);
|
||||
}, 3000);
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#salse_person_id').parsley({
|
||||
errorsContainer: function(ParsleyField) {
|
||||
if (ParsleyField.$element.hasClass('select2-hidden-accessible')) {
|
||||
return ParsleyField.$element.siblings('.select2-container');
|
||||
}
|
||||
return ParsleyField.$element;
|
||||
}
|
||||
});
|
||||
$('#client_id').parsley({
|
||||
errorsContainer: function(ParsleyField) {
|
||||
if (ParsleyField.$element.hasClass('select2-hidden-accessible')) {
|
||||
return ParsleyField.$element.siblings('.select2-container');
|
||||
}
|
||||
return ParsleyField.$element;
|
||||
}
|
||||
});
|
||||
$('#client_branch_id').parsley({
|
||||
errorsContainer: function(ParsleyField) {
|
||||
if (ParsleyField.$element.hasClass('select2-hidden-accessible')) {
|
||||
return ParsleyField.$element.siblings('.select2-container');
|
||||
}
|
||||
return ParsleyField.$element;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@ -1241,6 +1268,15 @@
|
||||
var lead_type = $('#lead_type').val();
|
||||
leadTypeBsedHideAndShow(lead_type)
|
||||
|
||||
$('#policy_type_id_' + increment).parsley({
|
||||
errorsContainer: function(ParsleyField) {
|
||||
if (ParsleyField.$element.hasClass('select2-hidden-accessible')) {
|
||||
return ParsleyField.$element.siblings('.select2-container');
|
||||
}
|
||||
return ParsleyField.$element;
|
||||
}
|
||||
});
|
||||
|
||||
$('#insurer_' + increment).select2();
|
||||
$('#tpa_' + increment).select2();
|
||||
$('#proposed_insurer_' + increment).select2();
|
||||
|
||||
@ -432,6 +432,42 @@
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#salse_person_id').parsley({
|
||||
errorsContainer: function(ParsleyField) {
|
||||
if (ParsleyField.$element.hasClass('select2-hidden-accessible')) {
|
||||
return ParsleyField.$element.siblings('.select2-container');
|
||||
}
|
||||
return ParsleyField.$element;
|
||||
}
|
||||
});
|
||||
$('#client_id').parsley({
|
||||
errorsContainer: function(ParsleyField) {
|
||||
if (ParsleyField.$element.hasClass('select2-hidden-accessible')) {
|
||||
return ParsleyField.$element.siblings('.select2-container');
|
||||
}
|
||||
return ParsleyField.$element;
|
||||
}
|
||||
});
|
||||
$('#client_branch_id').parsley({
|
||||
errorsContainer: function(ParsleyField) {
|
||||
if (ParsleyField.$element.hasClass('select2-hidden-accessible')) {
|
||||
return ParsleyField.$element.siblings('.select2-container');
|
||||
}
|
||||
return ParsleyField.$element;
|
||||
}
|
||||
});
|
||||
$('#policy_type_id').parsley({
|
||||
errorsContainer: function(ParsleyField) {
|
||||
if (ParsleyField.$element.hasClass('select2-hidden-accessible')) {
|
||||
return ParsleyField.$element.siblings('.select2-container');
|
||||
}
|
||||
return ParsleyField.$element;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
|
||||
|
||||
@ -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>
|
||||
@ -406,7 +406,7 @@ $(document).ready(function() {
|
||||
$('.searchable').each(function() {
|
||||
let parentModal = $(this).closest('.modal');
|
||||
$(this).select2({
|
||||
placeholder: "Select User",
|
||||
placeholder: "Select..",
|
||||
dropdownParent: parentModal.length ? parentModal : $(document.body)
|
||||
});
|
||||
});
|
||||
@ -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>
|
||||
|
||||
@ -86,7 +86,7 @@
|
||||
|
||||
<div class="target-card">
|
||||
<div style="font-size:11px; color:#999; letter-spacing: 1px; font-weight: 600;">YEARLY TARGET</div>
|
||||
<div class="target-amount">₹<?= number_format($target / 100000, 1) ?>L</div>
|
||||
<div class="target-amount">₹<?= number_format($target_amt / 100000, 1) ?>L</div>
|
||||
<div style="font-size:12px; color:#777;"><?= $display_fin_years ?></div>
|
||||
|
||||
<div class="chart-circle" style="position: absolute; right: 40px; top: 35px; width: 75px; height: 75px;">
|
||||
@ -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>
|
||||
@ -604,7 +604,7 @@ $(document).ready(function() {
|
||||
$('.searchable').each(function() {
|
||||
let parentModal = $(this).closest('.modal');
|
||||
$(this).select2({
|
||||
placeholder: "Select User",
|
||||
placeholder: "Select..",
|
||||
dropdownParent: parentModal.length ? parentModal : $(document.body)
|
||||
});
|
||||
});
|
||||
@ -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) {
|
||||
|
||||
BIN
public/assets/images/expense_icon.png
Normal file
BIN
public/assets/images/expense_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
399
tests/unit/ExpenseControllerTest.php
Normal file
399
tests/unit/ExpenseControllerTest.php
Normal file
@ -0,0 +1,399 @@
|
||||
<?php
|
||||
|
||||
namespace PhpOption {
|
||||
/**
|
||||
* Lightweight stub of PhpOption\Option used only for testing env() handling.
|
||||
* This is intentionally minimal and should not be relied on in application code.
|
||||
*/
|
||||
class Option
|
||||
{
|
||||
private $value;
|
||||
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public static function fromValue($value): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
public function map($callback): self
|
||||
{
|
||||
if ($this->value !== null) {
|
||||
$this->value = $callback($this->value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOrCall($callback)
|
||||
{
|
||||
return $this->value !== null ? $this->value : $callback();
|
||||
}
|
||||
|
||||
public function getOrThrow($exception)
|
||||
{
|
||||
if ($this->value === null) {
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace App\Filters {
|
||||
use CodeIgniter\Filters\FilterInterface;
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
|
||||
// Test stubs for application filters to bypass logging, ACL, and security checks during tests.
|
||||
|
||||
class AclFilter implements FilterInterface
|
||||
{
|
||||
public function before(RequestInterface $request, $arguments = null) { return null; }
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { return; }
|
||||
}
|
||||
|
||||
class HttpRequestLog implements FilterInterface
|
||||
{
|
||||
public function before(RequestInterface $request, $arguments = null) { return null; }
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { return; }
|
||||
}
|
||||
|
||||
class SecurityInputFilter implements FilterInterface
|
||||
{
|
||||
public function before(RequestInterface $request, $arguments = null) { return null; }
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { return; }
|
||||
}
|
||||
|
||||
class GlobalPostFileUploadGuard implements FilterInterface
|
||||
{
|
||||
public function before(RequestInterface $request, $arguments = null) { return null; }
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { return; }
|
||||
}
|
||||
|
||||
class Cors implements FilterInterface
|
||||
{
|
||||
public function before(RequestInterface $request, $arguments = null) { return null; }
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace Dotenv\Repository {
|
||||
interface RepositoryInterface
|
||||
{
|
||||
public function get($name);
|
||||
|
||||
public function set($name, $value);
|
||||
}
|
||||
|
||||
class RepositoryBuilder
|
||||
{
|
||||
public static function createWithDefaultAdapters(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function addAdapter($adapter): self
|
||||
{
|
||||
// No-op for testing.
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function immutable(): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function make(): RepositoryInterface
|
||||
{
|
||||
return new class() implements RepositoryInterface {
|
||||
public function get($name)
|
||||
{
|
||||
$value = getenv($name);
|
||||
|
||||
return $value === false ? null : $value;
|
||||
}
|
||||
|
||||
public function set($name, $value)
|
||||
{
|
||||
putenv("$name=$value");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Dotenv\Repository\Adapter {
|
||||
class PutenvAdapter
|
||||
{
|
||||
// Stub class used only to satisfy Illuminate\Support\Env references.
|
||||
}
|
||||
}
|
||||
|
||||
namespace Tests\unit {
|
||||
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use CodeIgniter\Test\FeatureTestTrait;
|
||||
|
||||
class ExpenseControllerTest extends CIUnitTestCase
|
||||
{
|
||||
use FeatureTestTrait;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Override routes for testing to avoid filters/middleware.
|
||||
$this->withRoutes([
|
||||
['get', 'expense', 'ExpenseController::index'],
|
||||
['post', 'expense/save', 'ExpenseController::save'],
|
||||
['get', 'expense/get/(:num)', 'ExpenseController::getExpense/$1'],
|
||||
['post', 'expense/delete/(:num)','ExpenseController::delete/$1'],
|
||||
['get', 'expense/client-policies', 'ExpenseController::clientPolicies'],
|
||||
]);
|
||||
|
||||
// Clean up any test data from previous runs
|
||||
$db = db_connect('default');
|
||||
$db->table('expenses')->like('description', 'CI4_TEST_', 'after')->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to insert an expense row directly into the database.
|
||||
*/
|
||||
private function createExpenseRecord(array $overrides = []): int
|
||||
{
|
||||
$db = db_connect('default');
|
||||
|
||||
$data = array_merge([
|
||||
'client_id' => 1,
|
||||
'client_policy_id' => 1,
|
||||
'description' => 'CI4_TEST_' . uniqid('', true),
|
||||
'approved_by' => 1,
|
||||
'amount' => 100.00,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'is_active' => 1,
|
||||
], $overrides);
|
||||
|
||||
$db->table('expenses')->insert($data);
|
||||
|
||||
return (int) $db->insertID();
|
||||
}
|
||||
|
||||
public function testCreateExpenseSuccess(): void
|
||||
{
|
||||
$payload = [
|
||||
'client_id' => '1',
|
||||
'client_policy_id' => '1',
|
||||
'approved_by' => '1',
|
||||
'description' => 'CI4_TEST_Valid Description 123-ABC',
|
||||
'expense_date' => '12-01-2026',
|
||||
'amount' => '250.75',
|
||||
];
|
||||
|
||||
$result = $this->post('expense/save', $payload);
|
||||
|
||||
$result->assertStatus(200);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertIsArray($json);
|
||||
$this->assertTrue($json['status'] ?? false);
|
||||
$this->assertNotEmpty($json['id'] ?? null);
|
||||
|
||||
$insertedId = (int) $json['id'];
|
||||
|
||||
$db = db_connect('default');
|
||||
$row = $db->table('expenses')
|
||||
->where('description', $payload['description'])
|
||||
->orderBy('id', 'DESC')
|
||||
->get()
|
||||
->getRowArray();
|
||||
|
||||
$this->assertNotEmpty($row);
|
||||
$this->assertSame(1, (int) $row['is_active']);
|
||||
$this->assertSame($payload['description'], $row['description']);
|
||||
$this->assertEquals(250.75, (float) $row['amount']);
|
||||
}
|
||||
|
||||
public function testCreateExpenseValidationFailureMissingFields(): void
|
||||
{
|
||||
$payload = [
|
||||
// all required fields missing / empty
|
||||
];
|
||||
|
||||
$result = $this->post('expense/save', $payload);
|
||||
|
||||
$result->assertStatus(400);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertFalse($json['status'] ?? true);
|
||||
$this->assertSame('Input validation failed', $json['message'] ?? '');
|
||||
$this->assertArrayHasKey('client_id', $json['errors'] ?? []);
|
||||
$this->assertArrayHasKey('client_policy_id', $json['errors'] ?? []);
|
||||
$this->assertArrayHasKey('approved_by', $json['errors'] ?? []);
|
||||
$this->assertArrayHasKey('description', $json['errors'] ?? []);
|
||||
$this->assertArrayHasKey('amount', $json['errors'] ?? []);
|
||||
$this->assertArrayHasKey('expense_date', $json['errors'] ?? []);
|
||||
}
|
||||
|
||||
public function testCreateExpenseValidationFailureInvalidDescription(): void
|
||||
{
|
||||
$payload = [
|
||||
'client_id' => '1',
|
||||
'client_policy_id' => '1',
|
||||
'approved_by' => '1',
|
||||
'description' => 'Invalid @ Description <>', // invalid characters
|
||||
'expense_date' => '12-01-2026',
|
||||
'amount' => '100.00',
|
||||
];
|
||||
|
||||
$result = $this->post('expense/save', $payload);
|
||||
|
||||
$result->assertStatus(400);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertFalse($json['status'] ?? true);
|
||||
$this->assertArrayHasKey('description', $json['errors'] ?? []);
|
||||
$this->assertStringContainsString(
|
||||
'invalid characters',
|
||||
strtolower($json['errors']['description'] ?? '')
|
||||
);
|
||||
}
|
||||
|
||||
public function testUpdateExpenseSuccess(): void
|
||||
{
|
||||
$id = $this->createExpenseRecord([
|
||||
'description' => 'CI4_TEST_Original Description',
|
||||
'amount' => 50.00,
|
||||
]);
|
||||
|
||||
$payload = [
|
||||
'id' => (string) $id,
|
||||
'client_id' => '1',
|
||||
'client_policy_id'=> '1',
|
||||
'approved_by' => '1',
|
||||
'description' => 'Updated Description 456',
|
||||
'expense_date' => '13-01-2026',
|
||||
'amount' => '75.50',
|
||||
];
|
||||
|
||||
$result = $this->post('expense/save', $payload);
|
||||
|
||||
$result->assertStatus(200);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertTrue($json['status'] ?? false);
|
||||
$this->assertSame($id, (int) ($json['id'] ?? 0));
|
||||
|
||||
$db = db_connect('default');
|
||||
$row = $db->table('expenses')->where('id', $id)->get()->getRowArray();
|
||||
|
||||
$this->assertNotEmpty($row);
|
||||
$this->assertSame('Updated Description 456', $row['description']);
|
||||
$this->assertEquals(75.50, (float) $row['amount']);
|
||||
}
|
||||
|
||||
public function testUpdateExpenseValidationFailureInvalidAmount(): void
|
||||
{
|
||||
$id = $this->createExpenseRecord();
|
||||
|
||||
$payload = [
|
||||
'id' => (string) $id,
|
||||
'client_id' => '1',
|
||||
'client_policy_id'=> '1',
|
||||
'approved_by' => '1',
|
||||
'description' => 'Another Valid Description',
|
||||
'expense_date' => '14-01-2026',
|
||||
'amount' => '-10', // invalid negative
|
||||
];
|
||||
|
||||
$result = $this->post('expense/save', $payload);
|
||||
|
||||
$result->assertStatus(400);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertFalse($json['status'] ?? true);
|
||||
$this->assertArrayHasKey('amount', $json['errors'] ?? []);
|
||||
$this->assertStringContainsString(
|
||||
'cannot be negative',
|
||||
strtolower($json['errors']['amount'] ?? '')
|
||||
);
|
||||
}
|
||||
|
||||
public function testDeleteExpenseSoftDeleteSuccess(): void
|
||||
{
|
||||
$id = $this->createExpenseRecord();
|
||||
|
||||
$result = $this->post('expense/delete/' . $id);
|
||||
|
||||
$result->assertStatus(200);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertTrue($json['status'] ?? false);
|
||||
|
||||
$db = db_connect();
|
||||
$row = $db->table('expenses')->where('id', $id)->get()->getRowArray();
|
||||
|
||||
$this->assertNotEmpty($row);
|
||||
$this->assertSame(0, (int) $row['is_active']);
|
||||
}
|
||||
|
||||
public function testDeleteExpenseInvalidId(): void
|
||||
{
|
||||
$result = $this->post('expense/delete/0');
|
||||
|
||||
$result->assertStatus(400);
|
||||
|
||||
$json = json_decode($result->getJSON(), true);
|
||||
|
||||
$this->assertFalse($json['status'] ?? true);
|
||||
$this->assertSame('Invalid expense id', $json['message'] ?? '');
|
||||
}
|
||||
|
||||
public function testSearchFilterByClientAndDescription(): void
|
||||
{
|
||||
// Create two distinct expenses
|
||||
$this->createExpenseRecord([
|
||||
'client_id' => 10,
|
||||
'description' => 'FilterMatch Description',
|
||||
]);
|
||||
|
||||
$this->createExpenseRecord([
|
||||
'client_id' => 20,
|
||||
'description' => 'Other Description',
|
||||
]);
|
||||
|
||||
$result = $this->get('expense?client_id=10&description=FilterMatch');
|
||||
|
||||
$result->assertStatus(200);
|
||||
|
||||
$body = $result->getBody();
|
||||
|
||||
$this->assertStringContainsString('FilterMatch Description', $body);
|
||||
$this->assertStringNotContainsString('Other Description', $body);
|
||||
}
|
||||
|
||||
public function testSearchFilterValidationInvalidCharacters(): void
|
||||
{
|
||||
$result = $this->get('expense?description=<script>alert(1)</script>');
|
||||
|
||||
$result->assertStatus(200);
|
||||
|
||||
$body = $result->getBody();
|
||||
|
||||
$this->assertStringContainsString('Description filter contains invalid characters.', $body);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user