diff --git a/ci4/app/Config/Routes.php b/ci4/app/Config/Routes.php index 96e0e1dc..06be3baa 100755 --- a/ci4/app/Config/Routes.php +++ b/ci4/app/Config/Routes.php @@ -791,7 +791,7 @@ $routes->group('logistica', ['namespace' => 'App\Controllers\Logistica'], functi $routes->get('print/label/test', 'LogisticaController::print_test_label'); $routes->get('panel', 'LogisticaController::panel', ['as' => 'LogisticaPanel']); - $routes->get('selectEnvios/(:any)', 'LogisticaController::selectorEnvios/$1', ['as' => 'selectEnvios']); + $routes->get('envios', 'LogisticaController::gestionEnvios', ['as' => 'gestionEnvios']); $routes->get('buscar/(:any)', 'LogisticaController::searchPedidoOrISBN/$1', ['as' => 'buscarPedidoOrISBN']); $routes->get('datatableEnvios', 'LogisticaController::datatable_envios'); $routes->get('datatableLineasEnvios/(:num)', 'LogisticaController::datatable_enviosEdit/$1'); @@ -803,6 +803,10 @@ $routes->group('logistica', ['namespace' => 'App\Controllers\Logistica'], functi $routes->post('updateLineaEnvio', 'LogisticaController::updateLineaEnvio'); $routes->post('updateComentariosEnvio', 'LogisticaController::saveComments'); $routes->post('updateCajasEnvio', 'LogisticaController::updateCajasEnvio'); + $routes->post('updateCodigoSeguimiento', 'LogisticaController::updateCodigoSeguimiento'); + $routes->post('updateProveedorEnvio', 'LogisticaController::updateProveedorEnvio'); + $routes->post('finalizarEnvio', 'LogisticaController::finalizarEnvio'); + $routes->post('generateEnvio', 'LogisticaController::generarEnvio'); }); /* diff --git a/ci4/app/Config/Routes/ComprasRoutes.php b/ci4/app/Config/Routes/ComprasRoutes.php index eea2da0c..7075f1b4 100755 --- a/ci4/app/Config/Routes/ComprasRoutes.php +++ b/ci4/app/Config/Routes/ComprasRoutes.php @@ -19,6 +19,7 @@ $routes->group('compras', ['namespace' => 'App\Controllers\Compras'], function ( $routes->get('delete/(:num)', 'Proveedores::delete/$1', ['as' => 'deleteProveedores']); $routes->post('allmenuitems', 'Proveedores::allItemsSelect', ['as' => 'select2ItemsOfProveedores']); $routes->post('menuitems', 'Proveedores::menuItems', ['as' => 'menuItemsOfProveedores']); + $routes->get('getProveedores', 'Proveedores::getForSelect'); }); }); diff --git a/ci4/app/Controllers/Compras/Proveedores.php b/ci4/app/Controllers/Compras/Proveedores.php index cca3fe73..61161a78 100755 --- a/ci4/app/Controllers/Compras/Proveedores.php +++ b/ci4/app/Controllers/Compras/Proveedores.php @@ -383,4 +383,26 @@ class Proveedores extends \App\Controllers\BaseResourceController { } } + public function getForSelect(){ + + if ($this->request->isAJAX()) { + $tipo_id = $this->request->getGet("tipo_id"); + $query = $this->model->builder()->select( + [ + "id", + "nombre as name" + ] + )->where('tipo_id', $tipo_id)->where('deleted_at', null)->orderBy("nombre", "asc"); + if ($this->request->getGet("q")) { + $query->groupStart() + ->orLike("lg_proveedores.nombre", $this->request->getGet("q")) + ->groupEnd(); + } + + return $this->response->setJSON($query->get()->getResultObject()); + } else { + return $this->failUnauthorized('Invalid request', 403); + } + } + } diff --git a/ci4/app/Controllers/Logistica/LogisticaController.php b/ci4/app/Controllers/Logistica/LogisticaController.php index 1e00f72f..19444426 100755 --- a/ci4/app/Controllers/Logistica/LogisticaController.php +++ b/ci4/app/Controllers/Logistica/LogisticaController.php @@ -29,7 +29,7 @@ class LogisticaController extends BaseController // Breadcrumbs $this->viewData['breadcrumb'] = [ - ['title' => lang("App.menu_logistica"), 'route' => "javascript:void(0);", 'active' => false], + ['title' => lang("App.menu_logistica"), 'route' => route_to("LogisticaPanel"), 'active' => false], ]; @@ -56,13 +56,12 @@ class LogisticaController extends BaseController return view(static::$viewPath . 'viewPanelLogistica', $viewData); } - public function selectorEnvios($tipoEnvio = null) + public function gestionEnvios() { $viewData = [ 'currentModule' => static::$controllerSlug, - 'boxTitle' => lang('Logistica.envioSimpleMultiple'), + 'boxTitle' => lang('Logistica.gestionEnvios'), 'usingServerSideDataTable' => true, - 'tipoEnvio' => $tipoEnvio, ]; $viewData = array_merge($this->viewData, $viewData); // merge any possible values from the parent controller class @@ -84,6 +83,20 @@ class LogisticaController extends BaseController return $this->response->setJSON($result); } + + public function generarEnvio() + { + if ($this->request->isAJAX()) { + + $pedido_id = $this->request->getPost('pedido_id'); + $direccion = $this->request->getPost('direccion'); + $result = LogisticaService::generateEnvio($pedido_id, $direccion); + return $this->response->setJSON($result); + } else { + return $this->failUnauthorized('Invalid request', 403); + } + } + public function selectAddEnvioLinea() { @@ -164,6 +177,16 @@ class LogisticaController extends BaseController return redirect()->to(base_url('logistica/selectEnvios/simple'))->with('error', lang('Logistica.errors.noEnvio')); } + $modelProveedor = model('App\Models\Compras\ProveedorModel'); + $proveedor = $modelProveedor->select('id, nombre') + ->where('deleted_at', null) + ->where('id', $envioEntity->proveedor_id) + ->orderBy('nombre', 'asc') + ->first(); + if(!empty($proveedor)){ + $envioEntity->proveedor_nombre = $proveedor->nombre; + } + $viewData = [ 'currentModule' => static::$controllerSlug, 'boxTitle' => '' . ' ' . lang('Logistica.envio') . ' [' . $envioEntity->id . ']: ' . $envioEntity->direccion, @@ -193,6 +216,21 @@ class LogisticaController extends BaseController } } + public function finalizarEnvio() + { + if ($this->request->isAJAX()) { + + $id = $this->request->getPost('id') ?? null; + $finalizar_ots = $this->request->getPost('finalizar_ots') ?? false; + + $result = LogisticaService::finalizarEnvio($id, $finalizar_ots); + return $this->response->setJSON($result); + } else { + return $this->failUnauthorized('Invalid request', 403); + } + } + + public function datatable_enviosEdit($idEnvio) { $model = model('App\Models\Logistica\EnvioLineaModel'); @@ -217,23 +255,16 @@ class LogisticaController extends BaseController function ($row, $meta) { return '' . $row->presupuesto . ''; } - ) - ->edit( - "cajas", - function ($row, $meta) { - return ''; - } )->edit( "unidadesEnvio", function ($row, $meta) { + if($row->finalizado == 1){ + return $row->unidadesEnvio; + } return ''; } - ) - ->edit('cajasRaw', function ($row) { - return is_null($row->cajas) ? '__SIN__ASIGNAR__' : $row->cajas; - }); + ); return $result->toJson(returnAsObject: true); } @@ -296,6 +327,52 @@ class LogisticaController extends BaseController ]); } + public function updateCodigoSeguimiento() + { + $id = $this->request->getPost('id'); + $fieldValue = $this->request->getPost('codigo_seguimiento'); + + if (!$id) { + return $this->response->setJSON([ + 'status' => false, + 'message' => 'Datos inválidos' + ]); + } + + $model = model('App\Models\Logistica\EnvioModel'); + $updated = $model->update($id, [ + "codigo_seguimiento" => $fieldValue==""? null: $fieldValue, + ]); + + return $this->response->setJSON([ + 'status' => $updated, + 'message' => $updated ? 'Actualizado' : 'Error al actualizar' + ]); + } + + public function updateProveedorEnvio() + { + $id = $this->request->getPost('id'); + $fieldValue = $this->request->getPost('proveedor_id'); + + if (!$id) { + return $this->response->setJSON([ + 'status' => false, + 'message' => 'Datos inválidos' + ]); + } + + $model = model('App\Models\Logistica\EnvioModel'); + $updated = $model->update($id, [ + "proveedor_id" => $fieldValue==""? null: $fieldValue, + ]); + + return $this->response->setJSON([ + 'status' => $updated, + 'message' => $updated ? 'Actualizado' : 'Error al actualizar' + ]); + } + public function saveComments() { $id = $this->request->getPost('id'); diff --git a/ci4/app/Language/es/Logistica.php b/ci4/app/Language/es/Logistica.php index c9eaf9c2..6f1d35a8 100755 --- a/ci4/app/Language/es/Logistica.php +++ b/ci4/app/Language/es/Logistica.php @@ -2,13 +2,11 @@ return [ 'logistica' => 'Logística', 'panel' => 'Panel', - 'envioSimple' => 'Envío simple', - 'envioMultiple' => 'Envío múltiple', - 'envioConjunto' => 'Envío conjunto', + 'gestionEnvios' => 'Gestión de Envíos de pedidos', 'etiquetasTitulos' => 'Etiquetas de títulos', 'etiquetasEnvio' => 'Etiquetas de envío', - 'envioFerros' => 'Envío de ferros', - 'cerrarOTauto' => 'Cerrar OT automáticamente', + 'envioFerros' => 'Gestión de Envíos de ferros/prototipos', + 'albaranes' => 'Albaranes', 'envioSimpleMultiple' => 'Envío simple/múltiple', 'nuevoEnvio' => 'Nuevo envío', 'buscadorPedidosTitle' => 'Código Pedido o ISBN', @@ -58,11 +56,21 @@ return [ 'peso' => 'Peso (kg): ', 'unidadesTotalesFooter' => 'Unidades:', + 'codigoSeguimiento' => 'Código de seguimiento', + 'empresaMensajería' => 'Empresa de mensajería', + 'finalizarEnvio' => 'Finalizar envío', + 'finalizarEnvioYOTs' => 'Finalizar envío y OTS', + '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 aún no se ha finalizado o no tiene envíos pendientes', + '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', ], + '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}', + ], ]; \ No newline at end of file diff --git a/ci4/app/Models/Logistica/EnvioLineaModel.php b/ci4/app/Models/Logistica/EnvioLineaModel.php index 420b4ae6..26938014 100644 --- a/ci4/app/Models/Logistica/EnvioLineaModel.php +++ b/ci4/app/Models/Logistica/EnvioLineaModel.php @@ -40,19 +40,24 @@ class EnvioLineaModel extends Model t1.unidades_total as unidadesTotal, IFNULL(( SELECT SUM(t_sub.unidades_envio) - FROM " . $this->table . " t_sub + FROM envios_lineas t_sub JOIN envios e ON e.id = t_sub.envio_id - JOIN presupuesto_direcciones d ON d.presupuesto_id = t_sub.presupuesto_id WHERE e.finalizado = 1 AND t_sub.pedido_id = t1.pedido_id - AND e.direccion = d.direccion COLLATE utf8mb3_general_ci - ), 0) as unidadesEnviadas, + AND e.direccion = t2.direccion COLLATE utf8mb3_general_ci + AND ( + t_sub.envio_id <> t1.envio_id + OR (t_sub.envio_id = t1.envio_id AND e.finalizado = 1) + ) + ), 0) AS unidadesEnviadas, IFNULL(( SELECT ROUND(SUM(peso) / 1000, 1) FROM presupuesto_linea WHERE presupuesto_id = t3.id - ), 0) AS pesoUnidad" + ), 0) AS pesoUnidad, + t2.finalizado as finalizado," ); + $builder->join("envios t2", "t1.envio_id = t2.id", "left"); $builder->join("presupuestos t3", "t1.presupuesto_id = t3.id", "left"); $builder->where("t1.envio_id", $envio_id); @@ -60,5 +65,5 @@ class EnvioLineaModel extends Model return $builder; } - + } diff --git a/ci4/app/Services/LogisticaService.php b/ci4/app/Services/LogisticaService.php index 3cbab564..de7440f7 100644 --- a/ci4/app/Services/LogisticaService.php +++ b/ci4/app/Services/LogisticaService.php @@ -1,6 +1,7 @@ groupStart() ->where('pedidos.id', $search) - ->whereIn('pedidos.estado', ['finalizado']) + ->whereIn('pedidos.estado', ['finalizado', 'produccion']) ->orWhere("REPLACE(presupuestos.isbn, '-', '')", $searchClean) ->groupEnd(); $builder->groupBy('pedidos_linea.id'); $builder->having('IFNULL(SUM(envios_lineas.unidades_envio), 0) < cantidad_linea', null, false); + $result = $builder->get()->getResult(); if (empty($result)) { @@ -59,6 +62,21 @@ class LogisticaService return $response; } else if ($numDirecciones > 1) { $multienvio = true; + $dirs = $PresupuestoDireccionesModel->select('direccion')->where('presupuesto_id', $result[0]->presupuesto_id) + ->findAll(); + foreach ($dirs as $key => $direccion) { + $modelEnvioLineasModel = model('App\Models\Logistica\EnvioLineaModel'); + $unidades_en_direccion = $modelEnvioLineasModel->select('SUM(envios_lineas.unidades_envio) as unidades_enviadas, + envios_lineas.unidades_total') + ->join('envios', 'envios.id = envios_lineas.envio_id') + ->where('pedido_id', $result[0]->pedido_id) + ->where('envios.direccion', $direccion->direccion) + ->where('envios.finalizado', 1) + ->groupBy('pedido_id')->get()->getResult(); + if (count($unidades_en_direccion) == 0 || $unidades_en_direccion[0]->unidades_enviadas < $unidades_en_direccion[0]->unidades_total) { + array_push($direcciones, $direccion->direccion); + } + } } $response = [ @@ -66,8 +84,18 @@ class LogisticaService 'data' => $result[0], ]; + if ($multienvio) { + $response_envio = [ + 'status' => true, + 'multienvio' => true, + 'direcciones' => $direcciones, + 'pedido_id' => $result[0]->pedido_id, + ]; + return $response_envio; + } - $response_envio = LogisticaService::generateEnvio($result[0]->pedido_id, $multienvio); + + $response_envio = LogisticaService::generateEnvio($result[0]->pedido_id); if ($response_envio['status'] == false) { $response = [ 'status' => false, @@ -83,84 +111,113 @@ class LogisticaService } + public static function findLineaEnvioPorEnvio(int $envio_id) { $db = \Config\Database::connect(); - $subCliente = $db->table('envios') - ->select('cliente_id') - ->where('id', $envio_id) - ->getCompiledSelect(); - $builder = $db->table('envios e_main'); + // 1. Obtener dirección del envío actual + $envio = $db->table('envios')->select('direccion')->where('id', $envio_id)->get()->getRow(); + if (!$envio) { + return []; + } - $builder->select(" - CONCAT('[', p.id, '] - ', pr.titulo) AS name, - pl.id AS id, - pl.cantidad, - ( - SELECT IFNULL(SUM(el.unidades_envio), 0) - FROM envios_lineas el - JOIN envios e ON e.id = el.envio_id - WHERE el.pedido_id = p.id - AND e.direccion = e_main.direccion - AND pr.cliente_id = ($subCliente) - ) AS unidades_enviadas, - ( - pl.cantidad - ( - SELECT IFNULL(SUM(el2.unidades_envio), 0) - FROM envios_lineas el2 - JOIN envios e2 ON e2.id = el2.envio_id - WHERE el2.pedido_id = p.id - AND e2.direccion = e_main.direccion - AND pr.cliente_id = ($subCliente) - ) - ) AS unidades_pendientes - "); + $direccionNormalizada = str_replace(' ', '', strtolower(trim($envio->direccion))); + $direccionSQL = $db->escape($direccionNormalizada); - $builder->join('pedidos_linea pl', '1=1'); // para incluir líneas sin envío aún - $builder->join('pedidos p', 'p.id = pl.pedido_id'); - $builder->join('presupuestos pr', 'pr.id = pl.presupuesto_id'); - $builder->where('e_main.id', $envio_id); - $builder->where('p.estado', 'finalizado'); - $builder->where("pr.cliente_id = ($subCliente)", null, false); + // 2. Obtener los presupuesto_id asociados a esa dirección + $presupuestosConEsaDireccion = $db->table('presupuesto_direcciones') + ->select('presupuesto_id') + ->where("REPLACE(LOWER(TRIM(direccion)), ' ', '') = $direccionSQL", null, false) + ->get() + ->getResultArray(); - $builder->having('unidades_pendientes >', 0); + $presupuestoIds = array_column($presupuestosConEsaDireccion, 'presupuesto_id'); + if (empty($presupuestoIds)) { + return []; + } + + // 3. Subconsulta principal con unidades pendientes + $subBuilder = $db->table('pedidos_linea pl') + ->select(" + pl.id AS id, + CONCAT('[', p.id, '] - ', pr.titulo) AS name, + ( + SELECT IFNULL(SUM(el.unidades_envio), 0) + FROM envios_lineas el + JOIN envios e ON e.id = el.envio_id + WHERE el.pedido_id = p.id + AND el.presupuesto_id = pr.id + AND REPLACE(LOWER(TRIM(e.direccion)), ' ', '') = $direccionSQL + AND (e.finalizado = 1 OR e.id = $envio_id) + ) AS unidades_enviadas, + pd.cantidad AS cantidad + ") + ->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') + ->whereIn('pr.id', $presupuestoIds) + ->whereIn('p.estado', ['finalizado', 'produccion']) + ->where("REPLACE(LOWER(TRIM(pd.direccion)), ' ', '') = $direccionSQL", null, false) + ->groupBy('pl.id'); + + // 4. Envolver y filtrar por unidades pendientes reales + $builder = $db->table("({$subBuilder->getCompiledSelect(false)}) AS sub"); + $builder->select('id, name'); + $builder->where('cantidad > unidades_enviadas'); $builder->orderBy('name', 'ASC'); return $builder; } + + + + public static function addLineaEnvio($envio_id = null, $pedido_id = null, $direccion = null) { - $modelPedido = model('App\Models\Pedidos\PedidoModel'); + $db = \Config\Database::connect(); - $builder = $modelPedido->builder(); + $direccionNormalizada = strtolower(trim($direccion)); - $builder->select(" - pedidos.id as pedido_id, - pedidos_linea.id as linea_id, - pedidos_linea.cantidad as total_unidades, - ( - SELECT IFNULL(SUM(el.unidades_envio), 0) - FROM envios_lineas el - JOIN envios e ON e.id = el.envio_id - WHERE el.pedido_id = pedidos.id - AND e.finalizado = 1 - AND TRIM(e.direccion) = '" . addslashes(trim($direccion)) . "' - ) as unidades_enviadas, - pedidos_linea.cantidad - IFNULL(SUM(envios_lineas.unidades_envio), 0) as unidades_envio, - presupuestos.id as presupuesto_id - "); - - $builder->join('pedidos_linea', 'pedidos_linea.pedido_id = pedidos.id', 'left'); - $builder->join('presupuestos', 'presupuestos.id = pedidos_linea.presupuesto_id', 'left'); - $builder->join('envios_lineas', 'envios_lineas.pedido_id = pedidos_linea.pedido_id', 'left'); - - $builder->groupBy('pedidos_linea.id'); - $builder->having('IFNULL(SUM(envios_lineas.unidades_envio), 0) < pedidos_linea.cantidad', null, false); - $builder->where('pedidos.estado', 'finalizado'); - $builder->where('pedidos.id', $pedido_id); + // 1. Obtener presupuesto_id y cantidad desde presupuesto_direcciones + $builder = $db->table('pedidos_linea pl') + ->select(" + p.id AS pedido_id, + pl.id AS linea_id, + pr.id AS presupuesto_id, + pd.cantidad AS total_unidades, + + ( + SELECT IFNULL(SUM(el.unidades_envio), 0) + FROM envios_lineas el + JOIN envios e ON e.id = el.envio_id + WHERE el.pedido_id = p.id + AND el.presupuesto_id = pr.id + AND TRIM(LOWER(e.direccion)) = '$direccionNormalizada' + AND (e.finalizado = 1 OR e.id = $envio_id) + ) AS unidades_enviadas, + + ( + pd.cantidad - ( + SELECT IFNULL(SUM(el2.unidades_envio), 0) + FROM envios_lineas el2 + JOIN envios e2 ON e2.id = el2.envio_id + WHERE el2.pedido_id = p.id + AND el2.presupuesto_id = pr.id + AND TRIM(LOWER(e2.direccion)) = '$direccionNormalizada' + AND (e2.finalizado = 1 OR e2.id = $envio_id) + ) + ) AS unidades_envio + ") + ->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') + ->where('p.id', $pedido_id) + ->whereIn('p.estado', ['finalizado', 'produccion']) + ->where("TRIM(LOWER(pd.direccion)) = '$direccionNormalizada'", null, false) + ->groupBy('pl.id'); $result = $builder->get()->getResultObject(); @@ -169,119 +226,216 @@ class LogisticaService 'status' => false, 'message' => lang('Logistica.errors.notFound'), ]; - } else { + } + + $r = $result[0]; + + // 2. Insertar solo si hay unidades a enviar + if ($r->unidades_envio > 0) { $EnvioLineasModel = model('App\Models\Logistica\EnvioLineaModel'); $EnvioLineasModel->save([ 'envio_id' => $envio_id, - 'pedido_id' => $result[0]->pedido_id, - 'unidades_envio' => $result[0]->unidades_envio, - 'unidades_total' => $result[0]->total_unidades, + 'pedido_id' => $r->pedido_id, + 'presupuesto_id' => $r->presupuesto_id, + 'unidades_envio' => $r->unidades_envio, + 'unidades_total' => $r->total_unidades, 'cajas' => null, 'unidades_cajas' => 1, 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), 'created_by' => auth()->user()->id, 'updated_by' => auth()->user()->id, - 'presupuesto_id' => $result[0]->presupuesto_id, ]); } return [ 'status' => true, 'data' => [ - 'unidades_envio' => $result[0]->unidades_envio, - 'unidades_enviadas' => $result[0]->unidades_enviadas, - 'total_unidades' => $result[0]->total_unidades, + 'unidades_envio' => $r->unidades_envio, + 'unidades_enviadas' => $r->unidades_enviadas, + 'total_unidades' => $r->total_unidades, ] ]; } - private static function generateEnvio($pedido_id, $multienvio = false) + public static function generateEnvio($pedido_id, $direccion = null) { - $presupuestoDireccionesModel = model('App\Models\Presupuestos\PresupuestoDireccionesModel'); + $direccionNormalizada = strtolower(trim($direccion)); - if (!$multienvio) { - // solo hay una dirección, se obtiene de los albaranes + // Consulta con cálculo exacto de unidades pendientes por dirección + $datosEnvio = $presupuestoDireccionesModel + ->select(" + presupuestos.id as presupuesto_id, + presupuesto_direcciones.att, + presupuesto_direcciones.direccion, + presupuesto_direcciones.provincia as ciudad, + presupuesto_direcciones.cp, + presupuesto_direcciones.telefono, + presupuesto_direcciones.email, + presupuesto_direcciones.pais_id, + presupuesto_direcciones.cantidad as cantidad_total, + presupuestos.cliente_id as cliente_id, - $datosEnvio = $presupuestoDireccionesModel - ->select(' - presupuestos.id as presupuesto_id, - presupuesto_direcciones.att, - presupuesto_direcciones.direccion, - presupuesto_direcciones.provincia as ciudad, - presupuesto_direcciones.cp, - presupuesto_direcciones.telefono, - presupuesto_direcciones.email, - presupuesto_direcciones.pais_id, - presupuesto_direcciones.cantidad - IFNULL(SUM(envios_lineas.unidades_envio), 0) as cantidad, - presupuesto_direcciones.cantidad as cantidad_total, - presupuestos.cliente_id as cliente_id - ') - ->join('pedidos_linea', 'pedidos_linea.presupuesto_id = presupuesto_direcciones.presupuesto_id') - ->join('pedidos', 'pedidos.id = pedidos_linea.pedido_id') - ->join('presupuestos', 'pedidos_linea.presupuesto_id = presupuestos.id') - ->join('envios_lineas', 'envios_lineas.pedido_id = pedidos.id', 'left') - ->join('envios', 'envios.id = envios_lineas.envio_id', 'left') - ->where('pedidos.id', $pedido_id) - ->groupBy('presupuesto_direcciones.id') // Necesario por el uso de SUM - ->first(); + ( + presupuesto_direcciones.cantidad - IFNULL(( + SELECT SUM(el.unidades_envio) + FROM envios_lineas el + JOIN envios e ON e.id = el.envio_id + WHERE el.pedido_id = pedidos.id + AND el.presupuesto_id = presupuestos.id + AND TRIM(LOWER(e.direccion)) = '$direccionNormalizada' + AND e.finalizado = 1 + ), 0) + ) as cantidad + ") + ->join('pedidos_linea', 'pedidos_linea.presupuesto_id = presupuesto_direcciones.presupuesto_id') + ->join('pedidos', 'pedidos.id = pedidos_linea.pedido_id') + ->join('presupuestos', 'pedidos_linea.presupuesto_id = presupuestos.id') + ->where('pedidos.id', $pedido_id) + ->where("TRIM(LOWER(presupuesto_direcciones.direccion)) = '$direccionNormalizada'", null, false) + ->groupBy('presupuesto_direcciones.id') + ->first(); - - // se genera un nuevo envio con estos datos - $EnvioModel = model('App\Models\Logistica\EnvioModel'); - $EnvioModel->set('cliente_id', $datosEnvio->cliente_id); - $EnvioModel->set('att', $datosEnvio->att); - $EnvioModel->set('direccion', $datosEnvio->direccion); - $EnvioModel->set('ciudad', $datosEnvio->ciudad); - $EnvioModel->set('cp', $datosEnvio->cp); - $EnvioModel->set('telefono', $datosEnvio->telefono); - $EnvioModel->set('email', $datosEnvio->email); - $EnvioModel->set('pais_id', $datosEnvio->pais_id); - $EnvioModel->set('cantidad', $datosEnvio->cantidad); - $EnvioModel->set('cajas', 1); - $EnvioModel->set('multienvio', $multienvio ? 1 : 0); - $EnvioModel->set('created_at', date('Y-m-d H:i:s')); - $EnvioModel->set('updated_at', date('Y-m-d H:i:s')); - $EnvioModel->insert(); - $idEnvio = $EnvioModel->insertID(); - - // se genera la linea de envio - $EnvioLineasModel = model('App\Models\Logistica\EnvioLineaModel'); - $EnvioLineasModel->save([ - 'envio_id' => $idEnvio, - 'pedido_id' => $pedido_id, - 'unidades_envio' => $datosEnvio->cantidad, - 'unidades_total' => $datosEnvio->cantidad_total, - 'cajas' => 1, - 'unidades_cajas' => 1, - 'created_at' => date('Y-m-d H:i:s'), - 'updated_at' => date('Y-m-d H:i:s'), - 'created_by' => auth()->user()->id, - 'updated_by' => auth()->user()->id, - 'presupuesto_id' => (int) $datosEnvio->presupuesto_id - ]); - - - - return [ - 'status' => true, - 'data' => [ - 'id_envio' => $idEnvio, - 'multienvio' => false, - ], - ]; - - } - - if (empty($datosEnvio)) { + // Validación si no hay datos o no quedan unidades + if (empty($datosEnvio) || $datosEnvio->cantidad <= 0) { return [ 'status' => false, - 'message' => lang('Logistica.errors.noAddresses'), + 'message' => lang('Logistica.errors.noAddresses') . ' o no hay unidades pendientes.', ]; } + // Crear envío + $EnvioModel = model('App\Models\Logistica\EnvioModel'); + $EnvioModel->set([ + 'cliente_id' => $datosEnvio->cliente_id, + 'att' => $datosEnvio->att, + 'direccion' => $datosEnvio->direccion, + 'ciudad' => $datosEnvio->ciudad, + 'cp' => $datosEnvio->cp, + 'telefono' => $datosEnvio->telefono, + 'email' => $datosEnvio->email, + 'pais_id' => $datosEnvio->pais_id, + 'cantidad' => $datosEnvio->cantidad, + 'cajas' => 1, + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + ]); + $EnvioModel->insert(); + $idEnvio = $EnvioModel->insertID(); + + // Crear línea de envío + $EnvioLineasModel = model('App\Models\Logistica\EnvioLineaModel'); + $EnvioLineasModel->save([ + 'envio_id' => $idEnvio, + 'pedido_id' => $pedido_id, + 'unidades_envio' => $datosEnvio->cantidad, + 'unidades_total' => $datosEnvio->cantidad_total, + 'cajas' => 1, + 'unidades_cajas' => 1, + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + 'created_by' => auth()->user()->id, + 'updated_by' => auth()->user()->id, + 'presupuesto_id' => (int) $datosEnvio->presupuesto_id + ]); + + return [ + 'status' => true, + 'data' => [ + 'id_envio' => $idEnvio, + ], + ]; + } + + + + public static function finalizarEnvio($envio_id, $finalizar_ot = false) + { + // hay que comprobar que para todas las lineas de envio de este envio + // se ha enviado toda la cantidad teniendo en cuenta otros envios + $EnvioModel = model('App\Models\Logistica\EnvioModel'); + $EnvioLineasModel = model('App\Models\Logistica\EnvioLineaModel'); + + $ots = []; + + $envio = $EnvioModel->find($envio_id); + if (empty($envio)) { + return [ + 'status' => false, + 'message' => lang('Logistica.errors.noEnvio'), + ]; + } + $lineasEnvio = $EnvioLineasModel->where('envio_id', $envio_id) + ->findAll(); + if (empty($lineasEnvio)) { + return [ + 'status' => false, + 'message' => lang('Logistica.errors.noEnvioLineas'), + ]; + } + + foreach ($lineasEnvio as $linea) { + + $pedido = model('App\Models\Pedidos\PedidoModel')->find($linea->pedido_id); + if (empty($pedido)) { + return [ + 'status' => false, + 'message' => lang('Logistica.errors.notFound'), + ]; + } + $cantidad_enviada = $EnvioLineasModel->select('SUM(envios_lineas.unidades_envio) as unidades_enviadas') + ->where('envios_lineas.pedido_id', $linea->pedido_id) + ->where('envios_lineas.envio_id !=', $envio_id) + ->where('envios.finalizado', 1) + ->join('envios', 'envios.id = envios_lineas.envio_id') + ->get()->getResult(); + + if ( + count($cantidad_enviada) == 0 || + empty($cantidad_enviada[0]->unidades_enviadas) || + $cantidad_enviada[0]->unidades_enviadas == null + ) { + $cantidad_enviada = 0; + } else { + $cantidad_enviada = $cantidad_enviada[0]->unidades_enviadas; + } + + if ($cantidad_enviada + $linea->unidades_envio == $pedido->total_tirada) { + $otModel = model('App\Models\OrdenTrabajo\OrdenTrabajoModel'); + $ot = $otModel->where('pedido_id', $linea->pedido_id) + ->first(); + $ps = (new ProductionService())->init($ot->id); + $ps->updateOrdenTrabajoDate([ + "name" => "envio_at", + "envio_at" => date('Y-m-d H:i:s') + ]); + if ($finalizar_ot) { + $ps->updateOrdenTrabajo( + [ + "estado" => 'F' + ] + ); + array_push($ots, $ot->id); + } + } + } + + $EnvioModel->update($envio_id, ['finalizado' => 1]); + + $data_return = [ + 'status' => true, + 'message' => lang('Logistica.success.finalizado'), + ]; + + if ($finalizar_ot) { + $data_return['message'] = lang('Logistica.success.finalizadoOTs', [ + 'ots' => implode(', ', $ots) + ]); + $data_return['ots'] = $ots; + } + return $data_return; } } diff --git a/ci4/app/Services/PresupuestoService.php b/ci4/app/Services/PresupuestoService.php index 0709eede..0c46362a 100755 --- a/ci4/app/Services/PresupuestoService.php +++ b/ci4/app/Services/PresupuestoService.php @@ -144,7 +144,7 @@ class PresupuestoService extends BaseService $maquina->velocidad ); $tiempo = round($tiempo, 2); - [$precio_hora, $margen_precio_hora] = $clientePreciosModel->get_precio_hora($cliente_id, $config, $tiempo); + [$precio_hora, $margen_precio_hora] = $clientePreciosModel->get_precio_hora($cliente_id, $config, round($tiempo, 2)); if (is_null($precio_hora)) { diff --git a/ci4/app/Views/themes/vuexy/form/logistica/viewEnvioEditForm.php b/ci4/app/Views/themes/vuexy/form/logistica/viewEnvioEditForm.php index d9016dc6..e012b79d 100644 --- a/ci4/app/Views/themes/vuexy/form/logistica/viewEnvioEditForm.php +++ b/ci4/app/Views/themes/vuexy/form/logistica/viewEnvioEditForm.php @@ -9,7 +9,7 @@
-

+

finalizado == 0)?'':'FINALIZADO' ?>

@@ -112,41 +112,43 @@
-
-
-

- -

+ finalizado == 0): ?> +
+
+

+ +

-
+
-
-
-

+
+
+

+
+ +
+
+ + +
+
+ +
+
+
- -
-
- - -
-
- -
-
-
-
+
@@ -168,13 +170,15 @@
-
- -
+ finalizado == 0): ?> +
+ +
+
-
-
+
-
-

- -

+
+

+ +

-
-
+
+
+
+
-
+ +
+
+

+ +

+ +
+
+ + finalizado == 0) ? "" : "readonly" ?> + value="codigo_seguimiento) ?>"> +
+
+ + finalizado == 0): ?> + + + + +
+ finalizado == 0): ?> +
+ +
+
+ +
+ +
+
+
+
- -
diff --git a/ci4/app/Views/themes/vuexy/form/logistica/viewPanelLogistica.php b/ci4/app/Views/themes/vuexy/form/logistica/viewPanelLogistica.php index 4ba6b66c..76698bb6 100755 --- a/ci4/app/Views/themes/vuexy/form/logistica/viewPanelLogistica.php +++ b/ci4/app/Views/themes/vuexy/form/logistica/viewPanelLogistica.php @@ -12,48 +12,24 @@
-
-
- +
+
+ " alt="Envíos"> +
-
- +
+ " alt="Envío de Ferros/Prototipos"> +
-
- +
+ " alt="Etiquetas de títulos"> +
-
-
-
- -
-
- -
-
- -
-
-
-
- +
+ " alt="Albaranes"> +
+
@@ -63,6 +39,7 @@ endSection(); ?> section('css') ?> + endSection() ?> diff --git a/httpdocs/assets/img/logistica/albaranes.png b/httpdocs/assets/img/logistica/albaranes.png new file mode 100644 index 00000000..5d735a1f Binary files /dev/null and b/httpdocs/assets/img/logistica/albaranes.png differ diff --git a/httpdocs/assets/img/logistica/envios.jpg b/httpdocs/assets/img/logistica/envios.jpg new file mode 100644 index 00000000..1f3ff423 Binary files /dev/null and b/httpdocs/assets/img/logistica/envios.jpg differ diff --git a/httpdocs/assets/img/logistica/envios_ferros.png b/httpdocs/assets/img/logistica/envios_ferros.png new file mode 100644 index 00000000..d91ba2d4 Binary files /dev/null and b/httpdocs/assets/img/logistica/envios_ferros.png differ diff --git a/httpdocs/assets/img/logistica/impresionEtiquetas.jpg b/httpdocs/assets/img/logistica/impresionEtiquetas.jpg new file mode 100644 index 00000000..00f1e251 Binary files /dev/null and b/httpdocs/assets/img/logistica/impresionEtiquetas.jpg differ diff --git a/httpdocs/assets/js/safekat/pages/logistica/envio.js b/httpdocs/assets/js/safekat/pages/logistica/envio.js index 0a9d1c27..4caed7e6 100644 --- a/httpdocs/assets/js/safekat/pages/logistica/envio.js +++ b/httpdocs/assets/js/safekat/pages/logistica/envio.js @@ -1,33 +1,36 @@ import Ajax from '../../components/ajax.js'; -$(()=>{ +$(() => { - $('#buscadorPedidos').on('keydown', function(e) { + $('#buscadorPedidos').on('keydown', function (e) { if (e.key === 'Enter' || e.keyCode === 13) { e.preventDefault(); // Evita el submit si está dentro de un form let search = $(this).val().trim(); new Ajax( - '/logistica/buscar/'+search, + '/logistica/buscar/' + search, {}, {}, - function(response) { - if(!response.status){ + function (response) { + if (!response.status) { popErrorAlert(response.message); } - if(response.data){ + if (response.multienvio) { + mostrarSwalDirecciones(response); + } + else if (response.data) { window.open(`${window.location.origin}/logistica/envio/${response.data.id_envio}`); } - + }, - function(xhr, status, error) { - if(status == 'error' && typeof(error)== 'string') + function (xhr, status, error) { + if (status == 'error' && typeof (error) == 'string') popErrorAlert(error); else popErrorAlert(error.responseJSON.message); } ).get(); - + } }); @@ -55,7 +58,7 @@ $(()=>{ { "data": "cp" }, { "data": "email" }, { "data": "telefono" }, - { + { "data": "finalizado", "className": "text-center", }, @@ -78,5 +81,52 @@ $(()=>{ $(document).on('click', '.btn-edit', function (e) { window.location.href = '/logistica/envio/' + $(this).attr('data-id'); }); - + + function mostrarSwalDirecciones(response) { + // Crear el select como HTML + let options = response.direcciones.map((dir, index) => + `` + ).join(""); + + Swal.fire({ + title: 'Múltiples direcciones', + html: ` +

El pedido tiene múltiples direcciones. Debe seleccionar una dirección:

+ + `, + confirmButtonText: 'Aceptar', + cancelButtonText: 'Cancelar', + customClass: { + confirmButton: 'swal2-confirm btn btn-primary', + cancelButton: 'swal2-cancel btn btn-secondary' // Estilo gris + }, + preConfirm: () => { + const select = document.getElementById('swal-select-direccion'); + if (!select.value) { + Swal.showValidationMessage('Debe seleccionar una dirección'); + return false; + } + return response.direcciones[select.value]; // Devuelve la dirección seleccionada + } + }).then((result) => { + if (result.isConfirmed) { + const direccionSeleccionada = result.value; + $.post('/logistica/generateEnvio', { + pedido_id: response.pedido_id, + direccion: direccionSeleccionada + }, function (response) { + if (response.status) { + window.open(`${window.location.origin}/logistica/envio/${response.data.id_envio}`); + } else { + popErrorAlert(response.message); + } + }).fail(function (xhr, status, error) { + popErrorAlert(error); + }); + } + }); + } + }); \ No newline at end of file diff --git a/httpdocs/assets/js/safekat/pages/logistica/envioEdit.js b/httpdocs/assets/js/safekat/pages/logistica/envioEdit.js index 2fbff388..5e85f9bd 100644 --- a/httpdocs/assets/js/safekat/pages/logistica/envioEdit.js +++ b/httpdocs/assets/js/safekat/pages/logistica/envioEdit.js @@ -28,9 +28,18 @@ class EnvioEdit { this.btnGenerarAlbaran = $("#btnGenerarAlbaran"); this.btnbtnSelectAll = $("#btnSelectAll"); this.cajas = $("#cajas"); + this.codigoSeguimiento = $("#codigoSeguimiento"); + if (!$("#empresaMensajeriaInput").length) { + this.proveedor = new ClassSelect($("#empresaMensajeria"), '/compras/proveedores/getProveedores', "", true, { 'tipo_id': 2 }); + } } init() { + + if (!$("#empresaMensajeriaInput").length) { + this.proveedor.init(); + } + this.table = $('#tableLineasEnvio').DataTable({ processing: true, serverSide: true, @@ -53,14 +62,14 @@ class EnvioEdit { footerCallback: function (row, data, start, end, display) { let totalUnidades = 0; let totalPeso = 0; - + data.forEach(row => { const unidades = parseFloat(row.unidadesEnvioRaw) || 0; const pesoUnidad = parseFloat(row.pesoUnidad) || 0; totalUnidades += unidades; totalPeso += unidades * pesoUnidad; }); - + // Mostrar en spans personalizados del $('#footer-unidades-envio').text(totalUnidades); $('#footer-peso').text(totalPeso.toFixed(2)); @@ -117,7 +126,7 @@ class EnvioEdit { buttonsStyling: false }); $(e.currentTarget).val(0); - } + } }).fail(() => { Swal.fire({ title: 'Error', @@ -205,12 +214,240 @@ class EnvioEdit { const checkboxes = this.table.$('input[type="checkbox"]'); const allChecked = checkboxes.length === checkboxes.filter(':checked').length; checkboxes.prop('checked', !allChecked); + }); + + this.codigoSeguimiento.on('change', (e) => { + const value = $(e.currentTarget).val(); + + $.post('/logistica/updateCodigoSeguimiento', { + id: $('#id').val(), + codigo_seguimiento: value + }, function (response) { + if (!response.status) { + Swal.fire({ + title: 'Error', + text: response.message, + icon: 'error', + confirmButtonColor: '#3085d6', + confirmButtonText: 'Ok', + customClass: { + confirmButton: 'btn btn-primary me-1', + }, + buttonsStyling: false + }); + $(e.currentTarget).val(value.substring(0, 100)); + } + }).fail(() => { + Swal.fire({ + title: 'Error', + text: 'No se pudo actualizar el código de seguimiento.', + icon: 'error', + confirmButtonColor: '#3085d6', + confirmButtonText: 'Ok', + customClass: { + confirmButton: 'btn btn-primary me-1', + }, + buttonsStyling: false + }); + $(e.currentTarget).val(value.substring(0, 100)); + } + ); + }); + + if (!$("#empresaMensajeriaInput").length) { + this.proveedor.onChange((e) => { + + const value = this.proveedor.getVal(); + $.post('/logistica/updateProveedorEnvio', { + id: $('#id').val(), + proveedor_id: value + }, function (response) { + if (!response.status) { + Swal.fire({ + title: 'Error', + text: response.message, + icon: 'error', + confirmButtonColor: '#3085d6', + confirmButtonText: 'Ok', + customClass: { + confirmButton: 'btn btn-primary me-1', + }, + buttonsStyling: false + }); + this.proveedor.setVal(0); + } + }).fail(() => { + Swal.fire({ + title: 'Error', + text: 'No se pudo actualizar el proveedor.', + icon: 'error', + confirmButtonColor: '#3085d6', + confirmButtonText: 'Ok', + customClass: { + confirmButton: 'btn btn-primary me-1', + }, + buttonsStyling: false + }); + this.proveedor.setVal(0); + } + ); + }); } - ); + + $('#finalizarEnvio').on('click', (e) => { + + if (!this._checkDatosFinalizar()) + return; + + Swal.fire({ + title: 'Finalizar envío', + text: '¿Está seguro de que desea finalizar el envío?', + 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('/logistica/finalizarEnvio', { + id: $('#id').val() + }, function (response) { + if (response.status) { + Swal.fire({ + text: response.message, + icon: 'success', + confirmButtonColor: '#3085d6', + confirmButtonText: 'Ok', + customClass: { + confirmButton: 'btn btn-primary me-1', + }, + buttonsStyling: false + }).then(() => { + window.location.reload(); + }); + } else { + Swal.fire({ + title: 'Error', + text: response.message, + icon: 'error', + confirmButtonColor: '#3085d6', + confirmButtonText: 'Ok', + customClass: { + confirmButton: 'btn btn-primary me-1', + }, + buttonsStyling: false + }); + } + }).fail(() => { + Swal.fire({ + title: 'Error', + text: 'No se pudo finalizar el envío.', + icon: 'error', + confirmButtonColor: '#3085d6', + confirmButtonText: 'Ok', + customClass: { + confirmButton: 'btn btn-primary me-1', + }, + buttonsStyling: false + }); + }); + } + }); + }); + + $('#finalizarEnvioYOTs').on('click', (e) => { + + if (!this._checkDatosFinalizar()) + return; + + Swal.fire({ + title: 'Finalizar envío y las OTs', + text: '¿Está seguro de que desea finalizar el envío y las OTs de las líneas de envío?', + 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('/logistica/finalizarEnvio', { + id: $('#id').val(), + finalizar_ots: true + }, function (response) { + if (response.status) { + + Swal.fire({ + text: response.message, + icon: 'success', + confirmButtonColor: '#3085d6', + confirmButtonText: 'Ok', + customClass: { + confirmButton: 'btn btn-primary me-1', + }, + buttonsStyling: false + }).then(() => { + window.location.reload(); + }); + } else { + Swal.fire({ + title: 'Error', + text: response.message, + icon: 'error', + confirmButtonColor: '#3085d6', + confirmButtonText: 'Ok', + customClass: { + confirmButton: 'btn btn-primary me-1', + }, + buttonsStyling: false + }); + } + }).fail(() => { + Swal.fire({ + title: 'Error', + text: 'No se pudo finalizar el envío.', + icon: 'error', + confirmButtonColor: '#3085d6', + confirmButtonText: 'Ok', + customClass: { + confirmButton: 'btn btn-primary me-1', + }, + buttonsStyling: false + }); + }); + } + }); + }); this._getAlbaranes(); } + _checkDatosFinalizar() { + + if (this.codigoSeguimiento.val().length <= 0 || this.proveedor.getVal() <= 0) { + Swal.fire({ + title: 'Atención!', + text: 'Debe seleccionar un proveedor y un código de seguimiento antes de finalizar el envío.', + icon: 'info', + confirmButtonColor: '#3085d6', + confirmButtonText: 'Ok', + customClass: { + confirmButton: 'btn btn-primary me-1', + }, + buttonsStyling: false + }); + return false; + } + return true; + } + _getAlbaranes() { $.get('/albaranes/albaranesEnvio', { envio_id: $('#id').val(), diff --git a/httpdocs/themes/vuexy/css/logisticaPanel.css b/httpdocs/themes/vuexy/css/logisticaPanel.css new file mode 100644 index 00000000..00304b28 --- /dev/null +++ b/httpdocs/themes/vuexy/css/logisticaPanel.css @@ -0,0 +1,26 @@ +.grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + } + + .item { + text-align: center; + } + + .item img { + width: 100%; + max-width: 300px; + height: auto; + } + + .item p { + margin-top: 10px; + font-size: 1rem; + } + + @media (max-width: 700px) { + .grid { + grid-template-columns: 1fr; + } + } \ No newline at end of file