diff --git a/ci4/app/Config/RBAC/permissionMatrix.php b/ci4/app/Config/RBAC/permissionMatrix.php index c13aa388..7506cfec 100644 --- a/ci4/app/Config/RBAC/permissionMatrix.php +++ b/ci4/app/Config/RBAC/permissionMatrix.php @@ -45,10 +45,6 @@ const SK_PERMISSION_MATRIX = [ "tarifa-encuadernacion.edit", "tarifa-encuadernacion.delete", "tarifa-encuadernacion.menu", - "tarifa-extra.create", - "tarifa-extra.edit", - "tarifa-extra.delete", - "tarifa-extra.menu", "tarifa-envio.create", "tarifa-envio.edit", "tarifa-envio.delete", @@ -93,6 +89,9 @@ const SK_PERMISSION_MATRIX = [ "roles-permisos.edit", "roles-permisos.delete", "roles-permisos.menu", + "tickets.create", + "tickets.edit", + "tickets.menu", ], "cliente-admin" => [ "presupuesto-cliente.create", diff --git a/ci4/app/Config/RBAC/permissions.php b/ci4/app/Config/RBAC/permissions.php index 4c640b77..35e8235f 100644 --- a/ci4/app/Config/RBAC/permissions.php +++ b/ci4/app/Config/RBAC/permissions.php @@ -93,4 +93,7 @@ const SK_PERMISSIONS = [ 'roles-permisos.edit' => 'Can edit', 'roles-permisos.delete' => 'Can delete', 'roles-permisos.menu' => 'Menu shall be visualize', + 'tickets.create' => 'Can create', + 'tickets.edit' => 'Can edit', + 'tickets.menu' => 'Menu shall be visualize', ]; diff --git a/ci4/app/Config/Routes.php b/ci4/app/Config/Routes.php index 264e3ad3..aad9dcaf 100644 --- a/ci4/app/Config/Routes.php +++ b/ci4/app/Config/Routes.php @@ -923,6 +923,8 @@ $routes->group('messages', ['namespace' => 'App\Controllers\Chat'], function ($r $routes->group('soporte', ['namespace' => 'App\Controllers\Soporte'], function ($routes) { $routes->get('', 'Ticketcontroller::index', ['as' => 'TicketIndex']); $routes->get('add', 'Ticketcontroller::add', ['as' => 'NewTicket']); + $routes->post('add', 'Ticketcontroller::add', ['as' => 'createTicket']); + $routes->post('ticketlist', 'Ticketcontroller::datatable'); }); diff --git a/ci4/app/Controllers/Sistema/Intranet.php b/ci4/app/Controllers/Sistema/Intranet.php index 99d3b206..73aa48f2 100644 --- a/ci4/app/Controllers/Sistema/Intranet.php +++ b/ci4/app/Controllers/Sistema/Intranet.php @@ -32,4 +32,29 @@ class Intranet extends Controller } + function tickets($resource_name) + { + helper('file'); + + $resource_path = WRITEPATH . 'uploads/tickets/' . $resource_name; + + if (file_exists($resource_path)) { + // Get the mime type of the file + $mime_type = mime_content_type($resource_path); + + // Get an instance of the Response class + $response = service('response'); + + // Set the content type + $response->setContentType($mime_type); + + // Set the output + $response->setBody(file_get_contents($resource_path)); + + // Send the response to the browser + $response->send(); + } + + } + } \ No newline at end of file diff --git a/ci4/app/Controllers/Soporte/Ticketcontroller.php b/ci4/app/Controllers/Soporte/Ticketcontroller.php index 32cadbbb..6b12717e 100644 --- a/ci4/app/Controllers/Soporte/Ticketcontroller.php +++ b/ci4/app/Controllers/Soporte/Ticketcontroller.php @@ -7,11 +7,10 @@ use App\Models\CategoriaModel; use App\Models\EstadoModel; use CodeIgniter\Controller; use App\Entities\Soporte\TicketEntity; +use App\Models\Soporte\ticketFileModel; class Ticketcontroller extends \App\Controllers\GoBaseController { - - protected static $primaryModelName = 'App\Models\Soporte\ticketModel'; protected static $singularObjectNameCc = 'ticket'; @@ -21,7 +20,7 @@ class Ticketcontroller extends \App\Controllers\GoBaseController protected static $viewPath = 'themes/vuexy/form/soporte/'; - protected $indexRoute = 'TicketIndex'; + protected $indexRoute = 'viewTicketList'; public function initController(\CodeIgniter\HTTP\RequestInterface $request, \CodeIgniter\HTTP\ResponseInterface $response, \Psr\Log\LoggerInterface $logger) @@ -44,7 +43,7 @@ class Ticketcontroller extends \App\Controllers\GoBaseController $this->viewData['usingClientSideDataTable'] = true; - $this->viewData['pageSubTitle'] = lang('Basic.global.ManageAllRecords', [lang('Tarifaextra.tarifaextra')]); + $this->viewData['pageSubTitle'] = lang('Basic.global.ManageAllRecords', [lang('Tickets.tickets')]); parent::index(); } @@ -53,14 +52,18 @@ class Ticketcontroller extends \App\Controllers\GoBaseController { //checkPermission('tickets.create', $this->indexRoute); - + if ($this->request->getPost()) : $nullIfEmpty = false; // !(phpversion() >= '8.1'); $postData = $this->request->getPost(); - $sanitizedData = $this->sanitized($postData, $nullIfEmpty); + // get user id + $postData['usuario_id'] = auth()->user()->id; + $postData['user_soporte_id'] = model('App\Models\Configuracion\ConfigVariableModel')->getVariable('default_soporte_user_id')->value; + + $sanitizedData = $this->sanitized($postData, $nullIfEmpty); $noException = true; if ($successfulResult = $this->canValidate()) : // if ($successfulResult = $this->validate($this->formValidationRules) ) : @@ -84,6 +87,34 @@ class Ticketcontroller extends \App\Controllers\GoBaseController $id = $this->model->db->insertID(); + $uploadPath = WRITEPATH . 'uploads/tickets/'; + + $fileModel = new ticketFileModel(); + $files = $this->request->getFiles(); + if ($files && isset($files['files'])) { + foreach ($files['files'] as $file) { + if ($file->isValid() && !$file->hasMoved()) { + $originalName = $file->getClientName(); + $fileExt = $file->getExtension(); + + // Generar hash SHA-256 basado en el contenido del archivo + $fileHash = hash_file("sha256", $file->getTempName()); + $newFileName = $fileHash . '.' . $fileExt; + + // Mover el archivo con el nombre basado en el hash + $file->move($uploadPath, $newFileName); + + // Guardar en la base de datos + $fileModel->insert([ + 'nombre' => $originalName, + 'ticket_id' => $id, + 'hash' => $fileHash, + 'path' => 'uploads/' . $newFileName + ]); + } + } + } + $message = lang('Basic.global.saveSuccess', [lang('Basic.global.record')]) . '.'; if ($thenRedirect) : @@ -190,4 +221,41 @@ class Ticketcontroller extends \App\Controllers\GoBaseController */ } // end function edit(...) + public function datatable() + { + if ($this->request->isAJAX()) { + $reqData = $this->request->getPost(); + if (!isset($reqData['draw']) || !isset($reqData['columns'])) { + $errstr = 'No data available in response to this specific request.'; + $response = $this->respond(Collection::datatable([], 0, 0, $errstr), 400, $errstr); + return $response; + } + $start = $reqData['start'] ?? 0; + $length = $reqData['length'] ?? 5; + + $requestedOrder = $reqData['order'] ?? []; + + $searchValues = get_filter_datatables_columns($reqData); + + $resourceData = $this->model->getResource($searchValues, $cliente_id); + foreach ($requestedOrder as $order) { + $column = $order['column'] ?? 0; + $dir = $order['dir'] ?? 'asc'; + $orderColumn = $this->model::SORTABLE[$column] ?? null; + if ($orderColumn) { + $resourceData->orderBy($orderColumn, $dir); + } + } + $resourceData = $resourceData->limit($length, $start)->get()->getResultObject(); + + return $this->respond(Collection::datatable( + $resourceData, + $this->model->getResource($searchValues)->countAllResults(), + $this->model->getResource($searchValues)->countAllResults() + )); + } else { + return $this->failUnauthorized('Invalid request', 403); + } + } + } diff --git a/ci4/app/Database/Migrations/2025-02-14-114500_create_tickets_system.php b/ci4/app/Database/Migrations/2025-02-14-114500_create_tickets_system.php index f93d609c..716bf956 100644 --- a/ci4/app/Database/Migrations/2025-02-14-114500_create_tickets_system.php +++ b/ci4/app/Database/Migrations/2025-02-14-114500_create_tickets_system.php @@ -36,8 +36,10 @@ class CreateTicketsSystem extends Migration $this->forge->addField([ 'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], 'usuario_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true], + 'user_soporte_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true], 'categoria_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true], - 'estado_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true], + 'seccion_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true], + 'estado_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'default' => 1], 'prioridad' => ['type' => 'ENUM', 'constraint' => ['alta', 'media', 'baja'], 'default' => 'media'], 'titulo' => ['type' => 'VARCHAR', 'constraint' => 255], 'descripcion' => ['type' => 'TEXT'], @@ -45,9 +47,11 @@ class CreateTicketsSystem extends Migration 'updated_at' => ['type' => 'DATETIME', 'null' => true], ]); $this->forge->addPrimaryKey('id'); - $this->forge->addForeignKey('usuario_id', 'users', 'id', 'CASCADE', 'CASCADE'); - $this->forge->addForeignKey('categoria_id', 'tickets_categorias', 'id', 'CASCADE', 'CASCADE'); - $this->forge->addForeignKey('estado_id', 'tickets_estados', 'id', 'CASCADE', 'CASCADE'); + $this->forge->addForeignKey('usuario_id', 'users', 'id', 'NO ACTION', 'NO ACTION'); + $this->forge->addForeignKey('user_soporte_id', 'users', 'id', 'NO ACTION', 'NO ACTION'); + $this->forge->addForeignKey('categoria_id', 'tickets_categorias', 'id', 'NO ACTION', 'NO ACTION'); + $this->forge->addForeignKey('seccion_id', 'tickets_secciones', 'id', 'NO ACTION', 'NO ACTION'); + $this->forge->addForeignKey('estado_id', 'tickets_estados', 'id', 'NO ACTION', 'NO ACTION'); $this->forge->createTable('tickets'); // Tabla de Respuestas @@ -59,8 +63,8 @@ class CreateTicketsSystem extends Migration 'created_at' => ['type' => 'DATETIME', 'null' => true], ]); $this->forge->addPrimaryKey('id'); - $this->forge->addForeignKey('ticket_id', 'tickets', 'id', 'CASCADE', 'CASCADE'); - $this->forge->addForeignKey('usuario_id', 'users', 'id', 'CASCADE', 'CASCADE'); + $this->forge->addForeignKey('ticket_id', 'tickets', 'id', '', 'NO ACTION'); + $this->forge->addForeignKey('usuario_id', 'users', 'id', 'NO ACTION', 'NO ACTION'); $this->forge->createTable('tickets_respuestas'); // Tabla de Adjuntos @@ -68,12 +72,15 @@ class CreateTicketsSystem extends Migration 'id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], 'ticket_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true], 'respuesta_id' => ['type' => 'INT', 'constraint' => 11, 'unsigned' => true, 'null' => true], - 'archivo' => ['type' => 'VARCHAR', 'constraint' => 255], + 'nombre' => ['type' => 'VARCHAR', 'constraint' => 255], + 'hash' => ['type' => 'VARCHAR', 'constraint' => 255], + 'path' => ['type' => 'VARCHAR', 'constraint' => 255], 'created_at' => ['type' => 'DATETIME', 'null' => true], + 'updated_at' => ['type' => 'DATETIME', 'null' => true], ]); $this->forge->addPrimaryKey('id'); - $this->forge->addForeignKey('ticket_id', 'tickets', 'id', 'CASCADE', 'CASCADE'); - $this->forge->addForeignKey('respuesta_id', 'tickets_respuestas', 'id', 'CASCADE', 'CASCADE'); + $this->forge->addForeignKey('ticket_id', 'tickets', 'id', 'NO ACTION', 'NO ACTION'); + $this->forge->addForeignKey('respuesta_id', 'tickets_respuestas', 'id', 'NO ACTION', 'NO ACTION'); $this->forge->createTable('tickets_adjuntos'); } @@ -84,5 +91,6 @@ class CreateTicketsSystem extends Migration $this->forge->dropTable('tickets'); $this->forge->dropTable('tickets_categorias'); $this->forge->dropTable('tickets_estados'); + $this->forge->dropTable('tickets_secciones'); } } diff --git a/ci4/app/Database/Seeds/TicketsSeeder.php b/ci4/app/Database/Seeds/TicketsSeeder.php index 3fd49111..f7d0ea93 100644 --- a/ci4/app/Database/Seeds/TicketsSeeder.php +++ b/ci4/app/Database/Seeds/TicketsSeeder.php @@ -37,6 +37,12 @@ class TicketsSeeder extends Seeder [ 'keyword' => 'logistica', ], + [ + 'keyword' => 'configuracion', + ], + [ + 'keyword' => 'general', + ], ]; $this->db->table('tickets_secciones')->insertBatch($data); @@ -56,5 +62,15 @@ class TicketsSeeder extends Seeder ], ]; $this->db->table('tickets_estados')->insertBatch($data); + + // config variables + $data = [ + [ + 'name' => 'default_soporte_user_id', + 'value' => '10', + 'description' => 'ID del usuario por defecto para asignar tickets', + ], + ]; + $this->db->table('config_variables_app')->insertBatch($data); } } \ No newline at end of file diff --git a/ci4/app/Language/es/RolesPermisos.php b/ci4/app/Language/es/RolesPermisos.php index 84e56b7f..ccc4176a 100644 --- a/ci4/app/Language/es/RolesPermisos.php +++ b/ci4/app/Language/es/RolesPermisos.php @@ -55,7 +55,7 @@ return [ 'ajustesSection' => 'Ajustes', 'actividadSection' => 'Accesos', - + "ticketsSection" => "Tickets", 'validation' => [ 'id' => [ diff --git a/ci4/app/Language/es/Tickets.php b/ci4/app/Language/es/Tickets.php index b3208387..26a17628 100644 --- a/ci4/app/Language/es/Tickets.php +++ b/ci4/app/Language/es/Tickets.php @@ -14,7 +14,9 @@ return [ "prioridad" => "Prioridad", "descripcion" => "Descripción", "asignarTo" => "Asignado a", + "usuario" => "Creado por", "createTicket" => "Crear Ticket", + "fechaCreacion" => "Fecha de creación", // categorías 'errores' => 'Errores', @@ -37,4 +39,9 @@ return [ 'alta' => 'Alta', 'media' => 'Media', 'baja' => 'Baja', + + // FIcheros + 'adjuntos' => 'Adjuntar imágenes', + 'adjuntos_main_text' => 'Arrastra y suelta las imágenes aquí o haz clic para subirlas', + ]; diff --git a/ci4/app/Models/Soporte/ticketFileModel.php b/ci4/app/Models/Soporte/ticketFileModel.php new file mode 100644 index 00000000..dbbd24e5 --- /dev/null +++ b/ci4/app/Models/Soporte/ticketFileModel.php @@ -0,0 +1,11 @@ + "t1.categoria_id", + 1 => "t1.seccion_id", + 2 => "t1.estado_id", + 3 => "t1.prioridad", + 4 => "t1.titulo", + 5 => "t1.usuario_id", + 6 => "t1.usuario_soporte_id", + 7 => "t1.created_at", + ]; + public function getEstados() { $values = $this->db->table('tickets_estados')->get()->getResultArray(); - $data = []; - - foreach ($values as $value) { - $data[$value['keyword']] = lang("Tickets." . $value['keyword']); + + for($i = 0; $i < count($values); $i++){ + $values[$i]['text'] = lang("Tickets." . $values[$i]['keyword']); } - return $data; + return $values; } public function getCategorias() { $values = $this->db->table('tickets_categorias')->get()->getResultArray(); - $data = []; - - foreach ($values as $value) { - $data[$value['keyword']] = lang("Tickets." . $value['keyword']); + + for($i = 0; $i < count($values); $i++){ + $values[$i]['text'] = lang("Tickets." . $values[$i]['keyword']); } - return $data; + return $values; } public function getSecciones() { $values = $this->db->table('tickets_secciones')->get()->getResultArray(); - $data = []; - foreach ($values as $value) { - $data[$value['keyword']] = lang("Tickets." . $value['keyword']); + for($i = 0; $i < count($values); $i++){ + $values[$i]['text'] = lang("Tickets." . $values[$i]['keyword']); } - return $data; + return $values; } public function getTickets($id = null) @@ -60,4 +68,37 @@ class TicketModel extends \App\Models\BaseModel return $this->find($id); } + + public function getResource($search = [], ) + { + $builder = $this->db + ->table($this->table . " t1") + ->select( + "t1.id as id, t1.usuario_id AS usuario_id, CONCAT(t2.first_name, ' ', t2.last_name) AS usuario, + t1.user_soporte_id AS user_soporte_id, CONCAT(t2.first_name, ' ', t2.last_name) AS user_soporte, + t1.categoria_id AS categoria_id, t3.keyword AS categoria, t1.seccion_id AS seccion_id, t1.estado_id AS estado_id, + t1.prioridad AS prioridad, t1.titulo AS titulo, t1.created_at AS created_at + " + ); + + $builder->join("users t2", "t1.usuario_id = t2.id", "left"); + $builder->join("users t2", "t1.user_soporte_id = t2.id", "left"); + $builder->join("tickets_categorias t3", "t1.categoria_id = t3.id", "left"); + $builder->join("tickets_estados t4", "t1.estado_id = t4.id", "left"); + $builder->join("tickets_secciones t5", "t1.seccion_id = t5.id", "left"); + + if (empty($search)) + return $builder; + else { + $builder->groupStart(); + foreach ($search as $col_search) { + if ($col_search[0] > 0 && $col_search[0] < 4) + $builder->where(self::SORTABLE[$col_search[0]], $col_search[2]); + else + $builder->like(self::SORTABLE[$col_search[0]], $col_search[2]); + } + $builder->groupEnd(); + return $builder; + } + } } diff --git a/ci4/app/Views/themes/vuexy/form/soporte/viewTicketForm.php b/ci4/app/Views/themes/vuexy/form/soporte/viewTicketForm.php index 8b814aae..ab8fa533 100644 --- a/ci4/app/Views/themes/vuexy/form/soporte/viewTicketForm.php +++ b/ci4/app/Views/themes/vuexy/form/soporte/viewTicketForm.php @@ -6,10 +6,10 @@
-

Crear Ticket

+

-
+
@@ -23,9 +23,9 @@
- + +
@@ -33,48 +33,104 @@
- + +
-
- + +
-
-
- - + user()->can('tickets.edit')): ?> + +
+
+ + +
+ +
+ + +
+
-
- - -
- -
+
+ + +
+
+ + +
+
+ + + + + + + + +
@@ -82,4 +138,15 @@
-endSection(); ?> \ No newline at end of file +endSection(); ?> + +section('css') ?> + +endSection() ?> + + +section('additionalExternalJs') ?> + + + +endSection() ?> \ No newline at end of file diff --git a/ci4/app/Views/themes/vuexy/form/soporte/viewTicketList.php b/ci4/app/Views/themes/vuexy/form/soporte/viewTicketList.php index e69de29b..7fe03d7a 100644 --- a/ci4/app/Views/themes/vuexy/form/soporte/viewTicketList.php +++ b/ci4/app/Views/themes/vuexy/form/soporte/viewTicketList.php @@ -0,0 +1,64 @@ +include('themes/_commonPartialsBs/datatables') ?> +extend('themes/vuexy/main/defaultlayout') ?> +section('content')?> + +
+
+ +
+
+

+ 'btn btn-primary float-end']); ?> +
+
+ + + + + + + + + + + + + + + + + + + +
ID
+
+ +
+
+
+ +endSection() ?> + +section('css') ?> +"> +endSection() ?> + + +section('additionalExternalJs') ?> + + + + + + + + + +endSection() ?> diff --git a/ci4/app/Views/themes/vuexy/main/menus/soporte_menu.php b/ci4/app/Views/themes/vuexy/main/menus/soporte_menu.php index aed71d1e..be2b5866 100644 --- a/ci4/app/Views/themes/vuexy/main/menus/soporte_menu.php +++ b/ci4/app/Views/themes/vuexy/main/menus/soporte_menu.php @@ -13,7 +13,7 @@ diff --git a/httpdocs/assets/js/safekat/pages/soporte/tickets.js b/httpdocs/assets/js/safekat/pages/soporte/tickets.js new file mode 100644 index 00000000..b6567306 --- /dev/null +++ b/httpdocs/assets/js/safekat/pages/soporte/tickets.js @@ -0,0 +1,98 @@ +import Table from '../../components/table.js'; +import Ajax from '../../components/ajax.js'; + +/*$(function () { + + + +/* + document.querySelectorAll('[data-bs-toggle="lightbox"]').forEach(anchor => { + anchor.addEventListener('click', function (event) { + event.preventDefault(); + const imgSrc = this.getAttribute('href'); + document.getElementById('lightboxImage').src = imgSrc; + new bootstrap.Modal(document.getElementById('lightboxModal')).show(); + }); + }); +});*/ + + +class Ticket { + + constructor() { + + // check if url includes "add" + this.action = 'list'; + if(window.location.href.includes("add")) + this.action = "add"; + else if(window.location.href.includes("edit")) + this.action = "edit"; + + this.table = null; + + this.init(); + } + + + init() { + + if(this.action == "edit") { + + } + else if (this.action == "list") { + this.#initDatatable(); + } + } + + #initDatatable() { + + const self = this; + + const actions = ['view']; + const columns = [ + { 'data': 'id' }, + { 'data': 'categoria_id' }, + { 'data': 'seccion_id' }, + { 'data': 'estado_id' }, + { 'data': 'prioridad' }, + { 'data': 'titulo' }, + { 'data': 'usuario_id' }, + { 'data': 'user_soporte_id' }, + { 'data': 'created_at' }, + ]; + + this.table = new Table( + $('#tableOfTickets'), + 'tickets', + '/soporte/ticketlist', + columns, + ); + + this.table.init({ + actions: actions, + buttonsExport: true, + }); + + + this.tableTarifas.table.on('init.dt', function () { + self.tableTarifas.table.page.len(50).draw(); + }); + } +} + +document.addEventListener('DOMContentLoaded', function () { + + const locale = document.querySelector('meta[name="locale"]').getAttribute('content'); + + new Ajax('/translate/getTranslation', { locale: locale, translationFile: ['Tickets'] }, {}, + function(translations) { + window.language = JSON.parse(translations); + new Ticket(); + }, + function (error) { + console.log("Error getting translations:", error); + } + ).post(); +}); + +export default Ticket; \ No newline at end of file