diff --git a/ci4/app/Config/Routes.php b/ci4/app/Config/Routes.php index cc21364d..2d7aefc5 100755 --- a/ci4/app/Config/Routes.php +++ b/ci4/app/Config/Routes.php @@ -846,6 +846,7 @@ $routes->group('logistica', ['namespace' => 'App\Controllers\Logistica'], functi $routes->get('panel', 'LogisticaController::panel', ['as' => 'LogisticaPanel']); $routes->get('envios', 'LogisticaController::gestionEnvios', ['as' => 'gestionEnvios']); $routes->get('enviosFerros', 'LogisticaController::gestionEnviosFerros', ['as' => 'gestionEnviosFerros']); + $routes->get('etiquetasLogistica', 'LogisticaController::etiquetasLogistica', ['as' => 'etiquetasLogistica']); $routes->get('datatableEnvios', 'LogisticaController::datatable_envios'); $routes->get('datatableLineasEnvios/(:num)', 'LogisticaController::datatable_enviosEdit/$1'); $routes->get('envio/(:num)', 'LogisticaController::editEnvio/$1'); @@ -868,6 +869,25 @@ $routes->group('logistica', ['namespace' => 'App\Controllers\Logistica'], functi $routes->get('listAlbaranes', 'LogisticaController::listAlbaranes', ['as' => 'albaranesList']); }); +$routes->group('etiquetasTitulos', ['namespace' => 'App\Controllers\Logistica'], function ($routes) { + + $routes->get('otList', 'EtiquetasTitulosController::findOTs'); + $routes->get('addList', 'EtiquetasTitulosController::findAddresses'); + $routes->post('newEtiquetaTitulos', 'EtiquetasTitulosController::addEtiqueta'); + $routes->get('datatable', 'EtiquetasTitulosController::datatable'); + $routes->post('delete', 'EtiquetasTitulosController::deleteEtiqueta'); + $routes->get('edit/(:num)', 'EtiquetasTitulosController::edit/$1'); + $routes->get('datatableLineas/(:num)', 'EtiquetasTitulosController::datatableLineasEtiquetas/$1'); + $routes->get('findOts', 'EtiquetasTitulosController::findOtsWithAddress'); + $routes->post('addLineas', 'EtiquetasTitulosController::addLineasEtiqueta'); + $routes->post('deleteLineas', 'EtiquetasTitulosController::deleteLineasEtiqueta'); + $routes->post('updateLineas', 'EtiquetasTitulosController::updateLineasEtiqueta'); + $routes->post('updateComentarios', 'EtiquetasTitulosController::updateComentarios'); + $routes->post('updateOrdenCajas', 'EtiquetasTitulosController::updateOrdenCajas'); + $routes->post('renumber', 'EtiquetasTitulosController::renumberCajas'); + $routes->post('imprimirEtiquetas', 'EtiquetasTitulosController::imprimirEtiquetas'); +}); + /* * -------------------------------------------------------------------- * Translation diff --git a/ci4/app/Controllers/Logistica/EtiquetasTitulosController.php b/ci4/app/Controllers/Logistica/EtiquetasTitulosController.php new file mode 100644 index 00000000..2026d8b8 --- /dev/null +++ b/ci4/app/Controllers/Logistica/EtiquetasTitulosController.php @@ -0,0 +1,467 @@ +impresoraEtiquetaService = service('impresora_etiqueta'); + $this->locale = session()->get('lang'); + $this->model = model('App\Models\Etiquetas\EtiquetasTitulosModel'); + + $this->viewData['pageTitle'] = lang('Logistica.logistica'); + + // Breadcrumbs + $this->viewData['breadcrumb'] = [ + ['title' => lang("App.menu_logistica"), 'route' => route_to("LogisticaPanel"), 'active' => false], + ]; + + + parent::initController($request, $response, $logger); + } + + public function findOTs() + { + if ($this->request->isAJAX()) { + + $query = EtiquetasTitulosService::getOtsWithTitulos(); + + if ($this->request->getGet("q")) { + $query->groupStart() + ->orLike("ot.id", $this->request->getGet("q")) + ->orLike("pr.titulo)", $this->request->getGet("q")) + ->groupEnd(); + } + + + $result = $query->orderBy("id", "DESC")->get()->getResultObject(); + + return $this->response->setJSON($result); + } else { + return $this->failUnauthorized('Invalid request', 403); + } + } + + public function findAddresses() + { + if ($this->request->isAJAX()) { + + $ot_id = $this->request->getGet("ot_id"); + + $query = EtiquetasTitulosService::getDireccionesOT($ot_id); + + if ($this->request->getGet("q")) { + $query->groupStart() + ->orLike("pd.direccion", $this->request->getGet("q")) + ->groupEnd(); + } + + + $result = $query->orderBy("pd.direccion", "ASC")->get()->getResultObject(); + + return $this->response->setJSON($result); + } else { + return $this->failUnauthorized('Invalid request', 403); + } + } + + public function addEtiqueta() + { + if ($this->request->isAJAX()) { + + $data = []; + $data['user_id'] = auth()->user()->id; + $data['ot_id'] = $this->request->getPost('ot_id') ?? null; + $data['direccion'] = $this->request->getPost('direccion') ?? null; + $data['unidades_caja'] = $this->request->getPost('unidades_caja') ?? null; + + if ( + $this->request->getPost('ot_id') == null || + $this->request->getPost('direccion') == null || + $this->request->getPost('unidades_caja') == null + ) { + return [ + 'status' => false, + 'message' => lang('Logistica.errorMissingData') + ]; + } + + $result = EtiquetasTitulosService::addEtiqueta($data); + + return $this->response->setJSON($result); + } else { + return $this->failUnauthorized('Invalid request', 403); + } + } + + public function deleteEtiqueta($id = null) + { + if ($this->request->isAJAX()) { + $id = $this->request->getPost('id') ?? null; + + if ($id == null) { + return [ + 'status' => false, + 'message' => lang('Logistica.errorMissingData') + ]; + } + + $modelLineas = model('App\Models\Etiquetas\EtiquetasTitulosLineasModel'); + $ids = $modelLineas->where('etiqueta_titulos_id', $id)->findColumn('id'); + if ($ids) { + $modelLineas->delete($ids); + } + $model = model('App\Models\Etiquetas\EtiquetasTitulosModel'); + $id = $model->where('id', $id)->findColumn('id'); + if ($id) { + $model->delete($id); + } + $result = [ + 'status' => true, + 'message' => lang('Logistica.success.jhnresponse->setJSON($result); + } else { + return $this->failUnauthorized('Invalid request', 403); + } + + } + + + public function edit($id = null) + { + + if (empty($id)) { + return redirect()->to(base_url('logistica/selectEnvios/simple'))->with('error', lang('Logistica.errors.noEnvio')); + } + $model = model('App\Models\Etiquetas\EtiquetasTitulosModel'); + $etiquetaEntity = $model->select('etiquetas_titulos.*, clientes.nombre as cliente') + ->join('clientes', 'clientes.id = etiquetas_titulos.cliente_id', 'left') + ->where('etiquetas_titulos.id', $id) + ->first(); + if (empty($etiquetaEntity)) { + return redirect()->to(base_url('logistica/etiquetasLogistica'))->with('error', lang('Logistica.errors.noEnvio')); + } + + + $modelImpresora = model('App\Models\Configuracion\ImpresoraEtiquetaModel'); + $impresoras = $modelImpresora->select('id, name') + ->where('deleted_at', null) + ->where('tipo', 1) + ->orderBy('name', 'asc') + ->findAll(); + $etiquetaEntity->impresoras = $impresoras; + + $viewData = [ + 'currentModule' => static::$controllerSlug, + 'boxTitle' => '' . ' ' . lang('Logistica.EtiquetasTitulos') . ' [' . $etiquetaEntity->id . ']: ' . $etiquetaEntity->direccion, + 'usingServerSideDataTable' => true, + 'etiquetaEntity' => $etiquetaEntity, + ]; + + $viewData = array_merge($this->viewData, $viewData); // merge any possible values from the parent controller class + + return view(static::$viewPath . 'viewEtiquetasTitulosEdit', $viewData); + } + + + public function datatable() + { + $q = $this->model->getEtiquetasTitulos(); + + if (!empty($otsFilter)) { + $q->groupStart(); + $q->like('etl.ot_id', $otsFilter); + $q->groupEnd(); + } + + $result = DataTable::of($q) + ->add("action", callback: function ($q) { + return ' +
+ + +
+ '; + }); + + return $result->toJson(returnAsObject: true); + } + + public function findOtsWithAddress() + { + if ($this->request->isAJAX()) { + $id = $this->request->getGet('id') ?? null; + + $query = EtiquetasTitulosService::findOTsWithAddress($id); + + if ($this->request->getGet("q")) { + $query->groupStart() + ->orLike("name", $this->request->getGet("q")) + ->groupEnd(); + } + + + $result = $query->orderBy("id", "DESC")->get()->getResultObject(); + + return $this->response->setJSON($result); + + } else { + return $this->failUnauthorized('Invalid request', 403); + } + } + + public function addLineasEtiqueta() + { + + if ($this->request->isAJAX()) { + + $etiqueta_id = $this->request->getPost('etiqueta_id') ?? null; + $ot_id = $this->request->getPost('ot_id') ?? null; + $unidades = $this->request->getPost('unidades') ?? null; + $cajas = $this->request->getPost('cajas') ?? null; + + $result = EtiquetasTitulosService::addLineasEtiqueta($etiqueta_id, $ot_id, $unidades, $cajas); + return $this->response->setJSON($result); + } else { + return $this->failUnauthorized('Invalid request', 403); + } + + } + + public function datatableLineasEtiquetas($id = null) + { + + $model = model('App\Models\Etiquetas\EtiquetasTitulosLineasModel'); + + $id = $this->request->getGet('id') ?? null; + $direccion = $this->request->getGet('direccion') ?? null; + + $q = $model->getDatatableQuery($id, $direccion); + + + $result = DataTable::of($q) + ->add( + "rowSelected", + callback: function ($q) { + return ''; + } + ) + ->edit( + "ot", + function ($row, $meta) { + return '' . $row->ot . ''; + } + ) + ->edit( + "unidades", + function ($row, $meta) { + return ''; + } + ) + ->edit( + "numero_caja", + function ($row, $meta) { + return ''; + } + ) + ->add("unidades_raw", fn($row) => $row->unidades) + ->add("pesoUnidad_raw", fn($row) => $row->pesoUnidad) + ->add( + "action", + callback: function ($q) { + return ' +
+ +
+ '; + } + ); + + return $result->toJson(returnAsObject: true); + } + + public function deleteLineasEtiqueta() + { + if ($this->request->isAJAX()) { + $ids = $this->request->getPost('ids') ?? []; + + if ($ids == [] || $ids == null) { + return [ + 'status' => false, + 'message' => lang('Logistica.errors.errorMissingData') + ]; + } + + $model = model('App\Models\Etiquetas\EtiquetasTitulosLineasModel'); + for ($i = 0; $i < count($ids); $i++) { + $model->delete($ids[$i]); + } + + + $result = [ + 'status' => true, + 'message' => lang('Logistica.success.successDeleteLines'), + ]; + + return $this->response->setJSON($result); + } else { + return $this->failUnauthorized('Invalid request', 403); + } + } + + public function updateLineasEtiqueta() + { + + if ($this->request->isAJAX()) { + $id = $this->request->getPost('id') ?? null; + $name = $this->request->getPost('name') ?? null; + $value = $this->request->getPost('value') ?? null; + + if ($id == null || $name == null || $value == null) { + return [ + 'status' => false, + 'message' => lang('Logistica.errors.errorMissingData') + ]; + } + + $model = model('App\Models\Etiquetas\EtiquetasTitulosLineasModel'); + $model->update($id, [$name => $value]); + + $result = [ + 'status' => true, + 'message' => lang('Logistica.success.successUpdateLine'), + ]; + + return $this->response->setJSON($result); + } else { + return $this->failUnauthorized('Invalid request', 403); + } + } + + public function updateComentarios() + { + + if ($this->request->isAJAX()) { + $id = $this->request->getPost('id') ?? null; + $comentarios = $this->request->getPost('comentarios') ?? null; + + if ($id == null || $comentarios == null) { + return [ + 'status' => false, + 'message' => lang('Logistica.errors.errorMissingData') + ]; + } + + $model = model('App\Models\Etiquetas\EtiquetasTitulosModel'); + $model->update($id, ['comentarios' => $comentarios]); + + $result = [ + 'status' => true, + 'message' => lang('Logistica.success.comentariosUpdated'), + ]; + + return $this->response->setJSON($result); + } else { + return $this->failUnauthorized('Invalid request', 403); + } + } + + public function updateOrdenCajas() + { + $rawInput = $this->request->getBody(); + $data = json_decode($rawInput, true); + $orden = $data['orden'] ?? []; + + if (!is_array($orden)) { + return $this->response->setJSON([ + 'status' => false, + 'message' => 'Datos inválidos' + ]); + } + + $model = model('App\Models\Etiquetas\EtiquetasTitulosLineasModel'); + + foreach ($orden as $item) { + if (isset($item['id'], $item['numero_caja'])) { + $model->update($item['id'], [ + 'numero_caja' => $item['numero_caja'], + 'updated_at' => date('Y-m-d H:i:s') // opcional + ]); + } + } + + return $this->response->setJSON([ + 'status' => true, + 'message' => 'Orden de cajas actualizado correctamente' + ]); + } + + public function renumberCajas() + { + $id = $this->request->getPost('id') ?? null; + + if ($id == null) { + return [ + 'status' => false, + 'message' => lang('Logistica.errors.errorMissingData') + ]; + } + + $result = EtiquetasTitulosService::reordenarCajas($id); + + return $this->response->setJSON($result); + } + + public function imprimirEtiquetas() + { + $etiqueta_id = $this->request->getPost('etiqueta_id') ?? null; + $ids = $this->request->getPost('ids') ?? []; + $impresora_id = $this->request->getPost('impresora_id') ?? null; + + $modelImpresora = model('App\Models\Configuracion\ImpresoraEtiquetaModel'); + $impresora = $modelImpresora->select('id, name, ip, port, user, pass') + ->where('deleted_at', null) + ->where('id', $impresora_id) + ->orderBy('name', 'asc') + ->first(); + if ($impresora == null) { + return $this->response->setJSON([ + 'status' => false, + 'message' => 'Impresora no válida' + ]); + } + + if ($etiqueta_id == null || $ids == []) { + return [ + 'status' => false, + 'message' => lang('Logistica.errors.errorMissingData') + ]; + } + + $result = EtiquetasTitulosService::imprimirEtiquetas($etiqueta_id, $ids, $impresora); + + return $this->response->setJSON($result); + } + +} \ No newline at end of file diff --git a/ci4/app/Controllers/Logistica/LogisticaController.php b/ci4/app/Controllers/Logistica/LogisticaController.php index d8dade10..586bb83d 100755 --- a/ci4/app/Controllers/Logistica/LogisticaController.php +++ b/ci4/app/Controllers/Logistica/LogisticaController.php @@ -85,6 +85,19 @@ class LogisticaController extends BaseController return view(static::$viewPath . 'viewLogisticaSelectEnvios', $viewData); } + public function etiquetasLogistica() + { + $viewData = [ + 'currentModule' => static::$controllerSlug, + 'boxTitle' => lang('Logistica.etiquetasTitulos'), + 'usingServerSideDataTable' => true, + ]; + + $viewData = array_merge($this->viewData, $viewData); // merge any possible values from the parent controller class + + return view(static::$viewPath . 'viewImpresionEtiquetas', $viewData); + } + public function listAlbaranes(){ $viewData = [ 'currentModule' => static::$controllerSlug, diff --git a/ci4/app/Database/Migrations/2025-05-03-094500_CreateEtiquetasTitulos.php b/ci4/app/Database/Migrations/2025-05-03-094500_CreateEtiquetasTitulos.php new file mode 100644 index 00000000..18baa8a8 --- /dev/null +++ b/ci4/app/Database/Migrations/2025-05-03-094500_CreateEtiquetasTitulos.php @@ -0,0 +1,60 @@ +forge->addField([ + 'id' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true], + 'comentarios' => ['type' => 'TEXT', 'null' => true], + 'direccion' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => false], + 'att' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => false], + 'cliente_id' => ['type' => 'INT', 'unsigned' => true, 'null' => true], + 'created_at' => ['type' => 'DATETIME', 'null' => true], + 'updated_at' => ['type' => 'DATETIME', 'null' => true], + 'deleted_at' => ['type' => 'DATETIME', 'null' => true], + 'user_created_at' => ['type' => 'INT', 'unsigned' => true, 'null' => true], + 'user_updated_at' => ['type' => 'INT', 'unsigned' => true, 'null' => true], + 'user_deleted_at' => ['type' => 'INT', 'unsigned' => true, 'null' => true], + ]); + $this->forge->addKey('id', true); + $this->forge->addForeignKey('user_created_at', 'users', 'id', 'SET NULL', 'CASCADE'); + $this->forge->addForeignKey('user_updated_at', 'users', 'id', 'SET NULL', 'CASCADE'); + $this->forge->addForeignKey('user_deleted_at', 'users', 'id', 'SET NULL', 'CASCADE'); + $this->forge->addForeignKey('cliente_id', 'clientes', 'id', 'SET NULL', 'CASCADE'); + $this->forge->createTable('etiquetas_titulos'); + + // Tabla: etiquetas_titulos_lineas + $this->forge->addField([ + 'id' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true], + 'etiqueta_titulos_id' => ['type' => 'INT', 'unsigned' => true], + 'ot_id' => ['type' => 'INT', 'unsigned' => true], + 'unidades' => ['type' => 'INT', 'unsigned' => true], + 'numero_caja' => ['type' => 'INT', 'unsigned' => true], + 'created_at' => ['type' => 'DATETIME', 'null' => true], + 'updated_at' => ['type' => 'DATETIME', 'null' => true], + 'deleted_at' => ['type' => 'DATETIME', 'null' => true], + 'user_created_at' => ['type' => 'INT', 'unsigned' => true, 'null' => true], + 'user_updated_at' => ['type' => 'INT', 'unsigned' => true, 'null' => true], + 'user_deleted_at' => ['type' => 'INT', 'unsigned' => true, 'null' => true], + ]); + $this->forge->addKey('id', true); + $this->forge->addForeignKey('etiqueta_titulos_id', 'etiquetas_titulos', 'id', 'CASCADE', 'CASCADE'); + $this->forge->addForeignKey('ot_id', 'ordenes_trabajo', 'id', 'CASCADE', 'CASCADE'); + $this->forge->addForeignKey('user_created_at', 'users', 'id', 'SET NULL', 'CASCADE'); + $this->forge->addForeignKey('user_updated_at', 'users', 'id', 'SET NULL', 'CASCADE'); + $this->forge->addForeignKey('user_deleted_at', 'users', 'id', 'SET NULL', 'CASCADE'); + $this->forge->createTable('etiquetas_titulos_lineas'); + } + + public function down() + { + $this->forge->dropTable('etiquetas_titulos_lineas', true); + $this->forge->dropTable('etiquetas_titulos', true); + } +} diff --git a/ci4/app/Entities/Etiquetas/EtiquetaTitulo.php b/ci4/app/Entities/Etiquetas/EtiquetaTitulo.php new file mode 100644 index 00000000..35168ebf --- /dev/null +++ b/ci4/app/Entities/Etiquetas/EtiquetaTitulo.php @@ -0,0 +1,17 @@ +where('etiqueta_titulos_id', $this->id)->findAll(); + } +} diff --git a/ci4/app/Entities/Etiquetas/EtiquetaTituloLinea.php b/ci4/app/Entities/Etiquetas/EtiquetaTituloLinea.php new file mode 100644 index 00000000..74ee95a6 --- /dev/null +++ b/ci4/app/Entities/Etiquetas/EtiquetaTituloLinea.php @@ -0,0 +1,10 @@ + "Informes", "menu_pedidos" => "Pedidos", - "menu_pedidos_validacion" => "Validación", + "menu_pedidos_validacion" => "Aprobación", "menu_pedidos_activos" => "Producción", "menu_pedidos_finalizados" => "Finalizados", "menu_pedidos_cancelados" => "Cancelados", diff --git a/ci4/app/Language/es/Logistica.php b/ci4/app/Language/es/Logistica.php index 5b6331a4..0ef3e713 100755 --- a/ci4/app/Language/es/Logistica.php +++ b/ci4/app/Language/es/Logistica.php @@ -65,16 +65,42 @@ return [ 'finalizarEnvio' => 'Finalizar envío', 'finalizarEnvioYOTs' => 'Finalizar envío y OTS', + + 'EtiquetasTitulos' => 'Etiquetas de títulos', + 'id' => 'ID', + 'otId' => 'OT ID', + 'num_caja' => 'Nº Caja', + 'unidadesTotalesOt' => 'Unidades totales OT', + 'numeroCajas' => 'Nº Cajas', + 'unidadesEnCaja' => 'Unidades en caja', + 'listadoEtiquetas' => 'Listado de etiquetas', + 'nuevaEtiqueta' => 'Nueva etiqueta', + 'cliente' => 'Cliente', + 'comentariosEtiqueta' => 'Comentarios etiqueta', + 'addLineaEtiqueta' => 'Añadir líneas a la etiqueta', + 'renumber' => 'Renumerar etiquetas', + 'errors' => [ 'noEnvio' => 'No se ha encontrado el envio', 'noEnvioLineas' => 'No se han encontrado líneas de envío', 'noDataToFind' => 'No se ha introducido ningún dato para buscar', 'notFound' => 'No se encuentra el pedido o ISBN, el pedido no está en producción o finalizado o no tiene envíos pendientes', 'noAddresses' => 'El pedido no tiene direcciones de envío', + 'errorMissingData' => 'Faltan datos para crear la etiqueta', + 'errorInsertarEtiqueta' => 'Error al insertar la etiqueta', + 'noEtiqueta' => 'No se ha encontrado la etiqueta', + 'noEtiquetaLineas' => 'No se han encontrado líneas de etiqueta', ], 'success' => [ 'finalizado' => 'El envío se ha finalizado correctamente', 'finalizadoOTs' => 'El envío se ha finalizado correctamente.\nSe han creado las OTs siguientes OTs: {ots}', + 'successDeleteEtiqueta' => 'Etiqueta eliminada correctamente', + 'successInsertLines' => 'Lineas de etiqueta insertadas correctamente', + 'successDeleteLines' => 'Lineas de etiqueta eliminadas correctamente', + 'successUpdateLine' => 'Linea de etiqueta actualizadas correctamente', + 'comentariosUpdated' => 'Comentarios actualizados correctamente', + 'successReordenarCajas' => 'Cajas reordenadas correctamente', + 'imprimirEtiquetas' => 'Etiquetas impresas correctamente', ], ]; \ No newline at end of file diff --git a/ci4/app/Models/Etiquetas/EtiquetasTitulosLineasModel.php b/ci4/app/Models/Etiquetas/EtiquetasTitulosLineasModel.php new file mode 100644 index 00000000..a98b0ef1 --- /dev/null +++ b/ci4/app/Models/Etiquetas/EtiquetasTitulosLineasModel.php @@ -0,0 +1,108 @@ +user()->id; + + if (!isset($data['data'])) { + $data['data'] = []; + } + + if (!empty($data['id'])) { + $builder = $this->builder(); + $builder->whereIn($this->primaryKey, (array) $data['id']) + ->update(['user_deleted_at' => $userId]); + } + return $data; + } + + protected function addUserUpdated(array $data) + { + $userId = auth()->user()->id; + + if (!isset($data['data'])) { + $data['data'] = []; + } + + if (!empty($data['id'])) { + $builder = $this->builder(); + $builder->whereIn($this->primaryKey, (array) $data['id']) + ->update(['user_updated_at' => $userId]); + } + return $data; + } + + public function getDatatableQuery($etiqueta_id, $direccion = null) + { + $direccionNormalizada = str_replace(' ', '', strtolower(trim($direccion))); + + // Subconsulta: suma de pesos por presupuesto + $subPeso = $this->db->table('presupuesto_linea') + ->select('presupuesto_id, ROUND(SUM(peso)/1000, 2) as pesoUnidad') + ->groupBy('presupuesto_id'); + + $builder = $this->db->table('etiquetas_titulos_lineas etl') + ->select(' + etl.id as id, + etl.ot_id as ot, + pr.titulo as titulo, + etl.unidades as unidades, + etl.numero_caja as numero_caja, + etl.numero_caja as numero_caja_raw, + pd.cantidad as unidadesTotal, + etl.id as id, + etl.unidades as unidadesRaw, + peso_sub.pesoUnidad + ') + ->join('etiquetas_titulos et', 'et.id = etl.etiqueta_titulos_id') + ->join('ordenes_trabajo ot', 'ot.id = etl.ot_id') + ->join('pedidos p', 'p.id = ot.pedido_id') + ->join('pedidos_linea pl', 'pl.pedido_id = p.id') + ->join('presupuestos pr', 'pr.id = pl.presupuesto_id') + ->join("({$subPeso->getCompiledSelect()}) as peso_sub", 'peso_sub.presupuesto_id = pr.id', 'left') + ->join('presupuesto_direcciones pd', 'pd.presupuesto_id = pr.id') + ->where('etl.deleted_at IS NULL') + ->where('et.id', $etiqueta_id) + ->where("REPLACE(LOWER(TRIM(pd.direccion)), ' ', '') = '{$direccionNormalizada}'", null, false) + ->orderBy('etl.numero_caja'); + + return $builder; + } + +} diff --git a/ci4/app/Models/Etiquetas/EtiquetasTitulosModel.php b/ci4/app/Models/Etiquetas/EtiquetasTitulosModel.php new file mode 100644 index 00000000..3ad4cec8 --- /dev/null +++ b/ci4/app/Models/Etiquetas/EtiquetasTitulosModel.php @@ -0,0 +1,82 @@ +user()->id; + + if (!isset($data['data'])) { + $data['data'] = []; + } + + if (!empty($data['id'])) { + $builder = $this->builder(); + $builder->whereIn($this->primaryKey, (array) $data['id']) + ->update(['user_deleted_at' => $userId]); + } + return $data; + } + + protected function addUserUpdated(array $data) + { + $userId = auth()->user()->id; + + if (!isset($data['data'])) { + $data['data'] = []; + } + + if (!empty($data['id'])) { + $builder = $this->builder(); + $builder->whereIn($this->primaryKey, (array) $data['id']) + ->update(['user_updated_at' => $userId]); + } + return $data; + } + + public function getEtiquetasTitulos() + { + return $this->db->table('etiquetas_titulos et') + ->select('et.id, GROUP_CONCAT(DISTINCT etl.ot_id ORDER BY etl.ot_id ASC SEPARATOR ", ") as lista_ots') + ->select('COUNT(DISTINCT etl.numero_caja) as cajas, et.att, et.direccion') + ->join('etiquetas_titulos_lineas etl', 'etl.etiqueta_titulos_id = et.id') + ->where('et.deleted_at IS NULL') + ->where('etl.deleted_at IS NULL') + ->groupBy('et.id, et.att, et.direccion'); + } +} diff --git a/ci4/app/Services/EtiquetasTitulosService.php b/ci4/app/Services/EtiquetasTitulosService.php new file mode 100644 index 00000000..55c6b53e --- /dev/null +++ b/ci4/app/Services/EtiquetasTitulosService.php @@ -0,0 +1,364 @@ +table('ordenes_trabajo ot') + ->select(" + ot.id AS id, + CONCAT('[', ot.id, '] - ', pr.titulo) AS name") + ->join('pedidos p', 'p.id = ot.pedido_id') + ->join('pedidos_linea pl', 'p.id = pl.pedido_id') + ->join('presupuestos pr', 'pr.id = pl.presupuesto_id') + ->join('orden_trabajo_dates ot_dates', 'ot_dates.orden_trabajo_id = ot.id') + ->whereIn('p.estado', ['finalizado', 'produccion']); + + return $builder; + } + + public static function getDireccionesOT($ot_id) + { + + $db = \Config\Database::connect(); + + // 3. Subconsulta principal + $builder = $db->table('presupuesto_direcciones pd') + ->select(" + pd.id AS id, + pd.direccion AS name") + ->join('presupuestos pr', 'pr.id = pd.presupuesto_id') + ->join('pedidos_linea pl', 'pr.id = pl.presupuesto_id') + ->join('pedidos p', 'p.id = pl.pedido_id') + ->join('ordenes_trabajo ot', 'ot.pedido_id = p.id') + ->join('orden_trabajo_dates ot_dates', 'ot_dates.orden_trabajo_id = ot.id') + ->whereIn('p.estado', ['finalizado', 'produccion']) + ->where('ot.id', $ot_id); + + return $builder; + } + + public static function addEtiqueta($data) + { + $db = \Config\Database::connect(); + + $builder = $db->table('presupuesto_direcciones pd'); + $builder->select('pd.att, pd.direccion, pd.cantidad, pr.cliente_id'); + $builder->join('presupuestos pr', 'pr.id = pd.presupuesto_id'); + $builder->join('pedidos_linea pl', 'pr.id = pl.presupuesto_id'); + $builder->join('pedidos p', 'p.id = pl.pedido_id'); + $builder->join('ordenes_trabajo ot', 'ot.pedido_id = p.id'); + $builder->where('ot.id', $data['ot_id']); + $builder->where('pd.direccion', $data['direccion']); + + $result = $builder->get()->getRow(); + $data['att'] = $result->att; + $data['direccion'] = $result->direccion; + $data['cantidad'] = $result->cantidad; + $data['cliente_id'] = $result->cliente_id; + + $modelEtiquetasTitulos = model('App\Models\Etiquetas\EtiquetasTitulosModel'); + $modelEtiquetasTitulos->insert([ + 'direccion' => $data['direccion'], + 'att' => $data['att'], + 'cliente_id' => $data['cliente_id'], + 'user_created_at' => $data['user_id'], + ]); + $etiquetaId = $modelEtiquetasTitulos->getInsertID(); + + if ($etiquetaId == null) { + return [ + 'status' => false, + 'message' => lang('Logistica.errorInsertarEtiqueta'), + ]; + } + + $cantidad_restante = intval($data['cantidad']); + $numero_caja = 1; + while ($cantidad_restante > 0) { + $modelEtiquetasTitulosLineas = model('App\Models\Etiquetas\EtiquetasTitulosLineasModel'); + $modelEtiquetasTitulosLineas->insert([ + 'etiqueta_titulos_id' => $etiquetaId, + 'ot_id' => $data['ot_id'], + 'unidades' => $cantidad_restante - intval($data['unidades_caja']) < 0 ? $cantidad_restante : intval($data['unidades_caja']), + 'numero_caja' => $numero_caja, + 'user_created_at' => $data['user_id'], + ]); + $cantidad_restante -= $data['unidades_caja']; + $numero_caja++; + } + + + return [ + 'status' => true, + 'etiqueta' => $etiquetaId, + ]; + } + + public static function findOTsWithAddress(int $etiqueta_id) + { + $db = \Config\Database::connect(); + + // 1. Dirección del envío actual + $etiqueta = $db->table('etiquetas_titulos')->select('direccion')->where('id', $etiqueta_id)->get()->getRow(); + if (!$etiqueta) { + return $db->table('(SELECT NULL AS id, NULL AS name, NULL as unidades) AS empty')->where('1 = 0'); + } + + $direccionNormalizada = str_replace(' ', '', strtolower(trim($etiqueta->direccion))); + $direccionSQL = $db->escape($direccionNormalizada); + + // 2. Obtener presupuestos con esa dirección + $presupuestosConEsaDireccion = $db->table('presupuesto_direcciones') + ->select('presupuesto_id') + ->where("REPLACE(LOWER(TRIM(direccion)), ' ', '') = $direccionSQL", null, false) + ->get() + ->getResultArray(); + + $presupuestoIds = array_column($presupuestosConEsaDireccion, 'presupuesto_id'); + if (empty($presupuestoIds)) { + return $db->table('(SELECT NULL AS id, NULL AS name, NULL as unidades) AS empty')->where('1 = 0'); + } + + // 3. Subconsulta principal + $subBuilder = $db->table('pedidos_linea pl') + ->select(" + ot.id AS id, + CONCAT('[', ot.id, '] - ', pr.titulo) AS name, + pd.cantidad AS description + ") + ->join('pedidos p', 'p.id = pl.pedido_id') + ->join('presupuestos pr', 'pr.id = pl.presupuesto_id') + ->join('presupuesto_direcciones pd', 'pd.presupuesto_id = pr.id') + ->join('ordenes_trabajo ot', 'ot.pedido_id = p.id') + ->join('orden_trabajo_dates ot_dates', 'ot_dates.orden_trabajo_id = ot.id') + ->whereIn('pr.id', $presupuestoIds) + ->whereIn('p.estado', ['finalizado', 'produccion']) + ->groupBy('pl.id'); + + // 4. Envolver y filtrar por unidades pendientes + $builder = $db->table("({$subBuilder->getCompiledSelect(false)}) AS sub"); + $builder->select('id, name, description'); + + return $builder; + } + + public static function addLineasEtiqueta($etiqueta_id, $ot_id, $unidades, $cajas) + { + + $unidades_caja = intdiv($unidades, $cajas); + $unidades_caja = $unidades_caja == 0 ? $unidades : $unidades_caja; + + $unidades_restantes = $unidades; + + $modelEtitquetaLinea = model('\App\Models\Etiquetas\EtiquetasTitulosLineasModel'); + $next_caja = $modelEtitquetaLinea + ->selectMax('numero_caja') + ->where('etiqueta_titulos_id', $etiqueta_id) + ->first(); + $next_caja = $next_caja->numero_caja ?? 0; + $next_caja++; + + $user_id = auth()->user()->id; + + while ($unidades_restantes > 0) { + $modelEtitquetaLinea->insert([ + 'etiqueta_titulos_id' => $etiqueta_id, + 'ot_id' => $ot_id, + 'unidades' => $unidades_restantes - $unidades_caja < 0 ? $unidades_restantes : intval($unidades_caja), + 'numero_caja' => $next_caja, + 'user_created_at' => $user_id, + ]); + $unidades_restantes -= $unidades_caja; + $next_caja++; + } + + return [ + 'status' => true, + 'message' => lang('Logistica.success.successInsertLines'), + ]; + + } + + public static function reordenarCajas($etiqueta_id) + { + $model = model('App\Models\Etiquetas\EtiquetasTitulosLineasModel'); + + // 1. Obtener todas las líneas ordenadas por numero_caja e id + $lineas = $model + ->where('etiqueta_titulos_id', $etiqueta_id) + ->orderBy('numero_caja ASC') + ->orderBy('id ASC') + ->findAll(); + + // 2. Agrupar por caja + $grupos = []; + foreach ($lineas as $linea) { + $grupos[$linea->numero_caja][] = $linea; + } + + // 3. Reasignar números de caja de forma consecutiva + $nuevoNumeroCaja = 1; + foreach ($grupos as $grupo) { + foreach ($grupo as $linea) { + $model->update($linea->id, ['numero_caja' => $nuevoNumeroCaja]); + + } + $nuevoNumeroCaja++; + } + + return [ + 'status' => true, + 'message' => lang('Logistica.success.successReordenarCajas'), + ]; + } + + public static function imprimirEtiquetas($etiqueta_id = null, $ids = [], $printer = null){ + + $model = model('App\Models\Etiquetas\EtiquetasTitulosModel'); + $modelLineas = model('App\Models\Etiquetas\EtiquetasTitulosLineasModel'); + + if ($etiqueta_id) { + $etiquetas = $model->where('id', $etiqueta_id)->first(); + $etiquetas_lineas = $modelLineas->whereIn('id', $ids)->orderBy('numero_caja', 'ASC')->findAll(); + } + + // buscar el maximo numero_cajas en el array de objetos etiquetas_lineas + $max_num_cajas = 0; + foreach ($etiquetas_lineas as $linea) { + if ($linea->numero_caja > $max_num_cajas) { + $max_num_cajas = $linea->numero_caja; + } + } + + // se obtienen los numero_caja diferentes en un array + $numero_cajas = []; + foreach ($etiquetas_lineas as $linea) { + if (!in_array($linea->numero_caja, $numero_cajas)) { + $numero_cajas[] = $linea->numero_caja; + } + } + + if (empty($etiquetas)) { + return [ + 'status' => false, + 'message' => lang('Logistica.errors.noEtiqueta'), + ]; + } + if (empty($etiquetas_lineas)) { + return [ + 'status' => false, + 'message' => lang('Logistica.errors.noEtiquetaLineas'), + ]; + } + + + $data = [ + "printer" => $printer->name, + "header" => [ + "_FORMAT" => "E:MULTI.ZPL", + "_QUANTITY" => 1, + "_PRINBTERNAME" => $printer->name, + "_JOBNAME" => "LBL101" + ], + 'direccion' => $etiquetas->direccion, + 'grupos' => [], + 'notas' => $etiquetas->comentarios, + 'nombre' => $etiquetas->att, + ]; + + + $index_etiqueta = 0; + foreach ($numero_cajas as $caja) { + + array_push($data['grupos'], [ + ]); + + $prefix = 1; + $lineas = array_filter($etiquetas_lineas, function ($linea) use ($caja) { + return $linea->numero_caja == $caja; + }); + + $lineaCounter = 0; + foreach($lineas as $linea){ + + + if($lineaCounter >= 5){ + $lineaCounter = 0; + $index_etiqueta++; + array_push($data['grupos'], []); + } + + $modelPresupuestos = model('App\Models\OrdenTrabajo\OrdenTrabajoModel'); + $datos_etiqueta = $modelPresupuestos->select(' + pr.titulo as titulo, + pr.isbn as isbn, + pr.referencia_cliente as referencia_cliente, + p.id as id_pedido, + ordenes_trabajo.total_tirada as total_tirada,') + ->join('pedidos p', 'p.id = ordenes_trabajo.pedido_id') + ->join('pedidos_linea pl', 'pl.pedido_id = p.id') + ->join('presupuestos pr', 'pr.id = pl.presupuesto_id') + ->where('ordenes_trabajo.id',$linea->ot_id)->first(); + + + + $data['grupos'][$index_etiqueta][] = [ + 'prefix' => $caja, + 'titulo' => mb_substr($datos_etiqueta->titulo, 0, 40), + 'cantidad' => $linea->unidades, + 'tirada' => $datos_etiqueta->total_tirada, + 'ean' => str_replace('-', '', $datos_etiqueta->isbn), + 'npedido' => $datos_etiqueta->id, + 'refcliente' => $datos_etiqueta->referencia_cliente, + ]; + + $lineaCounter++; + } + $index_etiqueta++; + } + + $servicioImpresora = new ImpresoraEtiquetaService(); + $xml = $servicioImpresora->createEtiquetaTitulos($data); + if ($xml == null) { + return [ + 'status' => false, + 'message' => lang('Logistica.errors.noEtiquetas'), + ]; + } + $sk_environment = getenv('SK_ENVIRONMENT'); + if ($sk_environment == 'production') { + + $status = $servicioImpresora->sendToImpresoraEtiqueta("ETIQUETA", $xml, $printer); + if ($status) { + return [ + 'status' => true, + 'message' => lang('Logistica.success.imprimirEtiquetas'), + ]; + } else { + return [ + 'status' => false, + 'message' => lang('Logistica.errors.noEtiquetas'), + ]; + } + + } else { + return [ + 'status' => true, + 'message' => lang('Logistica.success.imprimirEtiquetas'), + 'data' => $xml + ]; + } + + + } +} \ No newline at end of file diff --git a/ci4/app/Services/ImpresoraEtiquetaService.php b/ci4/app/Services/ImpresoraEtiquetaService.php index 41be8605..9f884c09 100755 --- a/ci4/app/Services/ImpresoraEtiquetaService.php +++ b/ci4/app/Services/ImpresoraEtiquetaService.php @@ -24,9 +24,9 @@ class ImpresoraEtiquetaService extends BaseService "labels" => [ [ "cliente" => "Cliente Potencial", - "titulo" => "[1234] TEST OLIVEROS", + "titulo" => "[1234] TEST OLIVEROS", "cantidad" => 100, - "tirada" => 50, + "tirada" => 50, "cajas" => 1, "ean" => null, "nombre" => "___Nombre___", @@ -76,6 +76,66 @@ class ImpresoraEtiquetaService extends BaseService $xml->appendChild($labels); return $xml->saveXML(); } + + + public function createEtiquetaTitulos(array $data_label = []): ?string + { + $xml = new DOMDocument('1.0', 'utf-8'); + + // Crear nodo raíz "labels" + $labels = $xml->createElement("labels"); + + // Establecer atributos de "labels" desde "header" + foreach ($data_label["header"] as $key => $value) { + $labels->setAttribute($key, $value); + } + + // Recorrer grupos de etiquetas + foreach ($data_label["grupos"] as $grupo) { + $labelChild = $xml->createElement('label'); + + // Crear variables específicas del grupo + foreach ($grupo as $libro) { + $prefix = $libro['prefix']; + + $variables = [ + "titulo$prefix" => $libro['titulo'], + "tirada$prefix" => $libro['tirada'], + "ean$prefix" => $libro['ean'], + "npedido$prefix" => $libro['npedido'], + "refcliente$prefix" => $libro['refcliente'], + "textpedido$prefix" => 'Pedido', + "textcantidad$prefix" => 'Cantidad:', + "textref$prefix" => 'Ref:', + "textean$prefix" => 'ISBN', + ]; + + foreach ($variables as $varName => $varValue) { + $variableChild = $xml->createElement('variable'); + $variableChild->setAttribute("name", $varName); + $variableChild->appendChild($xml->createTextNode((string) $varValue)); + $labelChild->appendChild($variableChild); + } + } + + // Variables comunes del grupo + $nombreVariable = $xml->createElement('variable', htmlspecialchars($data_label['nombre'])); + $nombreVariable->setAttribute('name', 'nombre'); + $labelChild->appendChild($nombreVariable); + + $direccionVariable = $xml->createElement('variable', htmlspecialchars($data_label['direccion'])); + $direccionVariable->setAttribute('name', 'direccion'); + $labelChild->appendChild($direccionVariable); + + $labels->appendChild($labelChild); + } + + $xml->appendChild($labels); + return $xml->saveXML(); + } + + + public function sendToImpresoraEtiqueta(string $name, string $content, ImpresoraEtiquetaEntity $impresoraEtiqueta): bool { $tmpFile = tmpfile(); @@ -84,8 +144,8 @@ class ImpresoraEtiquetaService extends BaseService rewind($tmpFile); $tmpMetaData = stream_get_meta_data($tmpFile); - $conn = @ftp_connect($impresoraEtiqueta->ip,$impresoraEtiqueta->port); - if(!$conn){ + $conn = @ftp_connect($impresoraEtiqueta->ip, $impresoraEtiqueta->port); + if (!$conn) { throw new Exception('Error al establecer conexión FTP'); } $isLoginSuccess = @ftp_login($conn, $impresoraEtiqueta->user, $impresoraEtiqueta->pass); @@ -99,10 +159,10 @@ class ImpresoraEtiquetaService extends BaseService if (ftp_put($conn, $name, $tmpMetaData['uri'], FTP_ASCII) === FALSE) { $status = false; ftp_close($conn); - }else{ + } else { $status = true; } - + return $status; } } diff --git a/ci4/app/Services/LogisticaService.php b/ci4/app/Services/LogisticaService.php index d2251313..cb4fb1b5 100644 --- a/ci4/app/Services/LogisticaService.php +++ b/ci4/app/Services/LogisticaService.php @@ -78,6 +78,7 @@ class LogisticaService return $builder; } + public static function findForNewEnvio() { $db = \Config\Database::connect(); @@ -576,11 +577,11 @@ class LogisticaService $data = [ "printer" => $printer->name, "header" => [ - "_FORMAT" => "E:PEDIDO.ZPL", - "_QUANTITY" => 1, - "_PRINBTERNAME" => $printer->name, - "_JOBNAME" => "LBL101" - ], + "_FORMAT" => "E:PEDIDO.ZPL", + "_QUANTITY" => 1, + "_PRINBTERNAME" => $printer->name, + "_JOBNAME" => "LBL101" + ], ]; foreach ($lineas as $linea) { diff --git a/ci4/app/Views/themes/vuexy/form/logistica/viewEtiquetasTitulosEdit.php b/ci4/app/Views/themes/vuexy/form/logistica/viewEtiquetasTitulosEdit.php new file mode 100644 index 00000000..c02a6c2c --- /dev/null +++ b/ci4/app/Views/themes/vuexy/form/logistica/viewEtiquetasTitulosEdit.php @@ -0,0 +1,249 @@ +include("themes/_commonPartialsBs/sweetalert") ?> +include('themes/_commonPartialsBs/datatables') ?> +include("themes/_commonPartialsBs/select2bs5") ?> +extend('themes/vuexy/main/defaultlayout') ?> + + +section('content'); ?> +
+
+
+
+

+

+
+
+ + + + + +
+
+

+ +

+ +
+
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ +
+
+
+ +
+
+
+
+ + +
+
+

+ +

+ +
+ +
+
+

+
+
+ +
+
+ + +
+
+ +
+
+ +
+
+
+ + +
+
+

+ +

+ +
+
+
+

+
+
+
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+
+ + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+ + +
+
+
+ + +
+
+ +
+
+
+
+ +
+
+
+
+
+endSection() ?> + +section('css') ?> + + + +"> + +endSection() ?> + +section('additionalExternalJs') ?> + + + + + +endSection() ?> \ No newline at end of file diff --git a/ci4/app/Views/themes/vuexy/form/logistica/viewImpresionEtiquetas.php b/ci4/app/Views/themes/vuexy/form/logistica/viewImpresionEtiquetas.php new file mode 100644 index 00000000..911f3dfc --- /dev/null +++ b/ci4/app/Views/themes/vuexy/form/logistica/viewImpresionEtiquetas.php @@ -0,0 +1,119 @@ +include("themes/_commonPartialsBs/sweetalert") ?> +include('themes/_commonPartialsBs/datatables') ?> +include("themes/_commonPartialsBs/select2bs5") ?> +extend('themes/vuexy/main/defaultlayout') ?> + +section('content'); ?> +
+
+
+
+

+
+
+ + + +
+

+ +

+ +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+
+
+
+ + +
+

+ +

+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+
+ +
+ +
+
+ +
+
+ endSection() ?> + + + section('css') ?> + + endSection() ?> + + section('additionalExternalJs') ?> + + + endSection() ?> \ No newline at end of file diff --git a/ci4/app/Views/themes/vuexy/form/logistica/viewPanelLogistica.php b/ci4/app/Views/themes/vuexy/form/logistica/viewPanelLogistica.php index 150044b0..0788a3fc 100755 --- a/ci4/app/Views/themes/vuexy/form/logistica/viewPanelLogistica.php +++ b/ci4/app/Views/themes/vuexy/form/logistica/viewPanelLogistica.php @@ -21,7 +21,7 @@ " alt="Envío de Ferros/Prototipos">
-
+
" alt="Etiquetas de títulos">
diff --git a/httpdocs/assets/js/safekat/components/select2.js b/httpdocs/assets/js/safekat/components/select2.js index c6199c56..f3ee718e 100644 --- a/httpdocs/assets/js/safekat/components/select2.js +++ b/httpdocs/assets/js/safekat/components/select2.js @@ -88,6 +88,9 @@ let ClassSelect = function (domItem, url, placeholder, allowClear = false, param this.getText = () => { return this.item.find(":selected").text(); }; + this.getDesc = () => { + return this.item.find(":selected").data("desc"); + }; this.onChange = function (callback) { this.item.on('change', callback); }; diff --git a/httpdocs/assets/js/safekat/pages/logistica/etiquetaEdit.js b/httpdocs/assets/js/safekat/pages/logistica/etiquetaEdit.js new file mode 100644 index 00000000..d81e6db0 --- /dev/null +++ b/httpdocs/assets/js/safekat/pages/logistica/etiquetaEdit.js @@ -0,0 +1,467 @@ +import ClassSelect from '../../components/select2.js'; +import Ajax from '../../components/ajax.js'; +import AlbaranComponent from '../../components/albaranComponent.js'; + +class EtiquetaEdit { + + constructor() { + + this.tableCols = [ + { data: "rowSelected" }, + { data: "ot" }, + { data: "titulo" }, + { data: "numero_caja" }, + { data: "unidades" }, + { data: "unidadesTotal" }, + { data: "id" }, + { data: "pesoUnidad" }, + { data: "unidadesRaw" }, + { data: "action" }, + { data: "numero_caja_raw" } + + ]; + + this.table = null; + + this.direccion = $('#direccion'); + this.addLineas = $('#btnAddLinea'); + this.btnEliminarLineas = $('#btnEliminarLineas'); + this.btbnGuardarComentarios = $('#guardarComentarios'); + this.btnSelectAll = $('#btnSelectAll'); + this.btnRenumber = $('#btnRenumber'); + this.btnImprimirEtiquetas = $('#btnImprimirEtiquetas'); + + this.buscador = new ClassSelect($('#buscadorPedidos'), '/etiquetasTitulos/findOts', '', true, { 'id': $('#id').val() }); + } + + init() { + + const self = this; + + this._initDatatable(); + this.buscador.init(); + + this.addLineas.on('click', this._addLineas.bind(this)); + $(document).on('click', '.btn-delete', this._deleteLinea.bind(this)); + $(document).on('change', '.input-lineas', this._updateLinea.bind(this)); + this.btnEliminarLineas.on('click', this._deleteLinea.bind(this)); + this.btbnGuardarComentarios.on('click', this._guardarComentarios.bind(this)); + this.btnSelectAll.on('click', this._selectAll.bind(this)); + this.btnRenumber.on('click', this._renumber.bind(this)); + this.btnImprimirEtiquetas.on('click', this._imprimirEtiquetas.bind(this)); + } + + _imprimirEtiquetas() { + + const self = this; + const ids = []; + this.table.rows().every(function (rowIdx, tableLoop, rowLoop) { + const node = this.node(); // DOM del tr + const checkbox = $(node).find('.checkbox-linea-envio'); + if (checkbox.is(':checked')) { + const data = this.data(); + ids.push(data.id); + } + }); + + $.post( + '/etiquetasTitulos/imprimirEtiquetas', + { + etiqueta_id: $('#id').val(), + ids: ids, + impresora_id: $('#impresoraEtiquetas').val() + }, + function (response) { + if (response.status) { + popSuccessAlert(response.message); + if(response.data) { + // show xml in new tab + const blob = new Blob([response.data], { type: 'application/xml' }); + const url = URL.createObjectURL(blob); + const newTab = window.open(url, '_blank'); + if (newTab) { + newTab.onload = function () { + // Revoke the object URL after the new tab has loaded + URL.revokeObjectURL(url); + }; + } else { + popErrorAlert('Error abriendo la pestaña'); + } + } + } else { + popErrorAlert('Error imprimiendo las etiquetas'); + } + } + ).fail(function (xhr, status, error) { + popErrorAlert(error); + }); + } + + + _renumber() { + const self = this; + $.post( + '/etiquetasTitulos/renumber', + { + id: $('#id').val() + }, + function (response) { + if (response.status) { + self.table.ajax.reload(); + popSuccessAlert(response.message); + } else { + popErrorAlert('Error renumerando las lineas'); + } + } + ).fail(function (xhr, status, error) { + popErrorAlert(error); + }); + } + + + _selectAll() { + const checkboxes = this.table.$('input[type="checkbox"]'); + const allChecked = checkboxes.length === checkboxes.filter(':checked').length; + checkboxes.prop('checked', !allChecked); + } + _initDatatable() { + + const self = this; + + this.table = $('#tableLineasEtiqueta').DataTable({ + processing: true, + serverSide: true, + autoWidth: true, + responsive: true, + scrollX: true, + orderCellsTop: true, + rowId: 'id', + order: [[10, 'asc']], + rowGroup: { + dataSrc: 'numero_caja_raw', + startRender: function (rows, group) { + let totalUnidades = 0; + let totalPeso = 0; + + rows.data().each(function (row) { + const unidades = parseFloat(row.unidadesRaw) || 0; + const pesoUnidad = parseFloat(row.pesoUnidad) || 0; + totalUnidades += unidades; + totalPeso += unidades * pesoUnidad; + }); + + const groupId = 'grupo-caja-' + group; + + return $('') + .attr('data-group', groupId) + .addClass('group-header bg-light fw-bold') + .css('cursor', 'pointer') + .append( + ` + 📦 CAJA ${group} – UNIDADES: ${totalUnidades} – PESO: ${totalPeso.toFixed(2)} kg + ` + ); + } + }, + rowReorder: { + dataSrc: 'numero_caja_raw', + update: false, + selector: 'td:not(.dt-no-reorder)' // evita inputs + }, + lengthMenu: [5, 10, 25, 50, 100], + pageLength: 50, + ajax: { + url: "/etiquetasTitulos/datatableLineas/" + $('#id').val(), + data: function (d) { + d.direccion = $('#direccion').val(); + d.id = $('#id').val(); + }, + }, + columns: this.tableCols, + language: { + url: "/themes/vuexy/vendor/libs/datatables-sk/plugins/i18n/es-ES.json" + }, + columnDefs: [ + { + targets: [0, 9], + className: "text-center dt-no-reorder", + orderable: false, + searchable: false + }, + { + targets: [3, 4], + className: "text-center dt-no-reorder" + }, + { + targets: [1, 2, 4, 5, 6], + className: "text-center" + }, + { + targets: [6, 7, 8, 10], + visible: false + } + ] + }); + + $('#tableLineasEtiqueta tbody').on('click', 'tr.group-header', function () { + const group = $(this).data('group'); + const table = $('#tableLineasEtiqueta').DataTable(); + const icon = $(this).find('.group-toggle-icon'); + + let visible = true; + + table.rows().every(function () { + const row = this.node(); + const data = this.data(); + + if ('numero_caja' in data && ('grupo-caja-' + data.numero_caja) === group) { + if (!$(row).hasClass('group-header')) { + $(row).toggle(); + visible = !$(row).is(':visible'); + } + } + }); + + // Cambiar el icono + icon.text(visible ? '▼' : '►'); + }); + + + this.table.on('row-reorder', function (e, diff, edit) { + if (!diff.length || !edit.triggerRow) return; + + const table = self.table; + const movedRowData = table.row(edit.triggerRow).data(); + if (!movedRowData?.id) return; + + const movedItem = diff.find(d => { + const rowData = table.row(d.node).data(); + return rowData?.id === movedRowData.id; + }); + if (!movedItem) return; + + const payload = { + id: movedRowData.id, + numero_caja: movedItem.newData + }; + + $.ajax({ + url: '/etiquetasTitulos/updateOrdenCajas', + method: 'POST', + contentType: 'application/json', + data: JSON.stringify({ orden: [payload] }), + success: function (response) { + if (response.status) { + setTimeout(() => { + self.table.ajax.reload(null, false); + }, 100); + } else { + popErrorAlert('Error actualizando el orden'); + } + }, + error: function () { + popErrorAlert('Error en la solicitud AJAX'); + } + }); + }); + + + } + + _addLineas() { + if (this.buscador.item.select2('data').length > 0) { + + let maxUnidades = 0; + if (this.buscador.item.select2('data')[0].desc) { + maxUnidades = parseInt(this.buscador.item.select2('data')[0].desc); + Swal.fire({ + title: 'Unidades', + html: ` +
+ + +
+
+ + +
+ `, + icon: 'info', + showCancelButton: true, + confirmButtonColor: '#3085d6', + confirmButtonText: 'Aceptar', + cancelButtonText: 'Cancelar', + customClass: { + confirmButton: 'btn btn-primary me-1', + cancelButton: 'btn btn-secondary' + }, + buttonsStyling: false, + preConfirm: () => { + const unidades = parseInt(document.getElementById('inputUnidades').value); + const cajas = parseInt(document.getElementById('inputCajas').value); + if (isNaN(unidades) || isNaN(cajas)) { + Swal.showValidationMessage('Debe completar ambos campos'); + } + return { unidades, cajas }; + } + }).then((result) => { + if (result.isConfirmed) { + const { unidades, cajas } = result.value; + const url = '/etiquetasTitulos/addLineas'; + const data = { + etiqueta_id: $('#id').val(), + ot_id: this.buscador.getVal(), + unidades: unidades, + cajas: cajas + }; + $.post( + url, + data, + function (response) { + if (response.status) { + self.table.ajax.reload(); + popSuccessAlert(response.message); + + } else { + popErrorAlert('Error en la respuesta'); + } + } + ).fail(function (xhr, status, error) { + popErrorAlert(error); + }); + } + }); + + } + } + } + + _deleteLinea(e) { + e.preventDefault(); + const self = this; + let ids = []; + if (e.currentTarget.id == "btnEliminarLineas") { + + this.table.rows().every(function (rowIdx, tableLoop, rowLoop) { + const node = this.node(); // DOM del tr + const checkbox = $(node).find('.checkbox-linea-envio'); + if (checkbox.is(':checked')) { + const data = this.data(); + ids.push(data.id); + } + }); + } + else { + ids.push($(e.currentTarget).attr('data-id')); + } + Swal.fire({ + title: '¿Está seguro de eliminar la linea?', + text: "No podrá revertir esta acción", + icon: 'warning', + showCancelButton: true, + confirmButtonText: 'Sí', + cancelButtonText: 'Cancelar', + customClass: { + confirmButton: 'btn btn-danger me-1', + cancelButton: 'btn btn-secondary' + }, + buttonsStyling: false + }).then((result) => { + if (result.isConfirmed) { + $.post( + '/etiquetasTitulos/deleteLineas', + { + ids: ids + }, + function (response) { + if (response.status) { + self.table.ajax.reload(); + popSuccessAlert(response.message); + } else { + popErrorAlert('Error borrando la etiqueta'); + } + } + ).fail(function (xhr, status, error) { + popErrorAlert(error); + }); + } + }) + } + + _updateLinea(e) { + e.preventDefault(); + const self = this; + const id = $(e.currentTarget).attr('data-id'); + const name = $(e.currentTarget).attr('data-name'); + const value = $(e.currentTarget).val(); + const url = '/etiquetasTitulos/updateLineas'; + const data = { + id: id, + name: name, + value: value + }; + $.post( + url, + data, + function (response) { + if (!response.status) { + popErrorAlert('Error actualizando la linea'); + } + else { + self.table.ajax.reload(); + } + } + ).fail(function (xhr, status, error) { + popErrorAlert(error); + }); + } + + _guardarComentarios() { + const self = this; + const id = $('#id').val(); + const comentarios = $('#comentarios').val(); + const url = '/etiquetasTitulos/updateComentarios'; + const data = { + id: id, + comentarios: comentarios + }; + $.post( + url, + data, + function (response) { + if (!response.status) { + popErrorAlert('Error actualizando comentarios'); + } + else { + popSuccessAlert(response.message); + } + } + ).fail(function (xhr, status, error) { + popErrorAlert(error); + }); + } + +} + +document.addEventListener('DOMContentLoaded', function () { + /*const dropdown = document.querySelector(".dropdown-language"); + const activeItem = dropdown.querySelector(".dropdown-menu .dropdown-item"); + let locale = 'es'; + if (activeItem) { + locale = activeItem.getAttribute("data-language"); + } + + new Ajax('/translate/getTranslation', { locale: locale, translationFile: [] }, {}, + function (translations) { + window.language = JSON.parse(translations); + new EtiquetaEdit().init(); + }, + function (error) { + console.log("Error getting translations:", error); + } + ).post(); + */ + new EtiquetaEdit().init(); +}); + +export default EtiquetaEdit; diff --git a/httpdocs/assets/js/safekat/pages/logistica/impresionEtiquetas.js b/httpdocs/assets/js/safekat/pages/logistica/impresionEtiquetas.js new file mode 100644 index 00000000..0a8ae3c8 --- /dev/null +++ b/httpdocs/assets/js/safekat/pages/logistica/impresionEtiquetas.js @@ -0,0 +1,177 @@ +import ClassSelect from '../../components/select2.js'; + +$(() => { + + let otsFilter = ''; + + const selectOts = new ClassSelect($('#buscadorPedidos'), '/etiquetasTitulos/otList', "", true); + selectOts.init(); + + const selectDirecciones = new ClassSelect($('#selectDirecciones'), '/etiquetasTitulos/addList', "", true, { + ot_id: () => selectOts.getVal() + }); + selectDirecciones.init(); + + selectOts.item.on('select2:open', () => { + $('.select-direcciones').addClass('d-none'); + $('.add-etiqueta').addClass('d-none'); + }) + selectOts.item.on('change', () => { + selectDirecciones.empty(); + $('.add-etiqueta').addClass('d-none'); + $('.select-direcciones').removeClass('d-none'); + }) + selectDirecciones.item.on('select2:open', () => { + $('.add-etiqueta').addClass('d-none'); + }) + + selectDirecciones.item.on('change', () => { + $('.add-etiqueta').removeClass('d-none'); + }) + + $('#btnAddEtiqueta').on('click', () => { + + Swal.fire({ + title: 'Unidades por caja', + text: 'Seleccione la cantidad de unidades por caja', + icon: 'info', + input: 'text', + inputLabel: 'Unidades/caja', + inputValue: 0, + showCancelButton: true, + confirmButtonColor: '#3085d6', + confirmButtonText: 'Aceptar', + cancelButtonText: 'Cancelar', + customClass: { + confirmButton: 'btn btn-primary me-1', + cancelButton: 'btn btn-secondary' + }, + buttonsStyling: false + }).then((result) => { + if (result.isConfirmed) { + const unidades_caja = result.value; + const url = '/etiquetasTitulos/newEtiquetaTitulos'; + const data = { + ot_id: selectOts.getVal(), + direccion: selectDirecciones.getText(), + unidades_caja: unidades_caja + }; + $.post( + url, + data, + function (response) { + if (response.status) { + tableEtiquetas.ajax.reload(); + // open the new etiqueta in a new tab + window.open(`${window.location.origin}/etiquetasTitulos/edit/${response.etiqueta}`, '_blank'); + } else { + popErrorAlert('Error en la respuesta'); + } + } + ).fail(function (xhr, status, error) { + popErrorAlert(error); + }); + } + }); + }) + + const tableEtiquetas = $('#tableOfEquiquetas').DataTable({ + processing: true, + serverSide: true, + autoWidth: true, + responsive: true, + scrollX: true, + orderCellsTop: true, + lengthMenu: [5, 10, 25, 50, 75, 100, 250, 500, 1000, 2500], + pageLength: 50, + "dom": 'lBrtip', + "ajax": { + "url": "/etiquetasTitulos/datatable", + "data": function (d) { + d.otsFilter = otsFilter; + } + }, + "columns": [ + { "data": "id" }, + { "data": "lista_ots" }, + { "data": "cajas" }, + { "data": "att" }, + { "data": "direccion" }, + { "data": "action" } + + ], + "language": { + url: "/themes/vuexy/vendor/libs/datatables-sk/plugins/i18n/es-ES.json" + }, + "columnDefs": [ + { + orderable: false, + searchable: false, + targets: [5] + }, + ], + "order": [[0, "desc"]], + }); + + $(document).on('click', '.btn-edit', function (e) { + window.location.href = '/etiquetasTitulos/edit/' + $(this).attr('data-id'); + }); + + $(document).on('click', '.btn-delete', function (e) { + Swal.fire({ + title: '¿Está seguro de eliminar la etiqueta?', + text: "No podrá revertir esta acción", + icon: 'warning', + showCancelButton: true, + confirmButtonText: 'Sí', + cancelButtonText: 'Cancelar', + customClass: { + confirmButton: 'btn btn-danger me-1', + cancelButton: 'btn btn-secondary' + }, + buttonsStyling: false + }).then((result) => { + if (result.isConfirmed) { + $.post( + '/etiquetasTitulos/delete', + { + id: $(this).attr('data-id') + }, + function (response) { + if (response.status) { + tableEtiquetas.ajax.reload(); + popSuccessAlert(response.message); + } else { + popErrorAlert('Error borrando la etiqueta'); + } + } + ).fail(function (xhr, status, error) { + popErrorAlert(error); + }); + } + }) + + }); + + $(document).on("keyup", ".envio-filter", (event) => { + let columnName = $(event.currentTarget).attr("name"); + let columnIndex = $('#tableOfEquiquetas').DataTable().columns().eq(0).filter(function (index) { + return $('#tableOfEquiquetas').DataTable().column(index).dataSrc() === columnName; + })[0]; + $('#tableOfEquiquetas').DataTable().column(columnIndex).search($(event.currentTarget).val()).draw() + }) + + + $(document).on("keyup", ".envio-filter-ots", (event) => { + otsFilter = $(event.currentTarget).val(); + $('#tableOfEquiquetas').DataTable().ajax.reload(); + }) + + $(document).on("change", ".envio-filter-select", (event) => { + let columnName = $(event.currentTarget).attr("name"); + let columnIndex = $('#tableOfEquiquetas').DataTable().columns().eq(0).filter(function (index) { + return $('#tableOfEquiquetas').DataTable().column(index).dataSrc() === columnName; + })[0]; + $('#tableOfEquiquetas').DataTable().column(columnIndex).search($(event.currentTarget).val()).draw(); + }); +}) diff --git a/httpdocs/themes/vuexy/css/safekat.css b/httpdocs/themes/vuexy/css/safekat.css index 21983314..9c9fbcd8 100644 --- a/httpdocs/themes/vuexy/css/safekat.css +++ b/httpdocs/themes/vuexy/css/safekat.css @@ -139,4 +139,8 @@ .dtrg-group.ui-droppable-hover { background-color: #d1ecf1 !important; cursor: move; +} + +.dt-no-reorder { + cursor: auto !important; } \ No newline at end of file