diff --git a/ci4/app/Config/Routes.php b/ci4/app/Config/Routes.php index 5da5a2d7..0278a980 100755 --- a/ci4/app/Config/Routes.php +++ b/ci4/app/Config/Routes.php @@ -767,6 +767,7 @@ $routes->group('produccion', ['namespace' => 'App\Controllers\Produccion'], func $routes->get('tareas/datatable/(:num)', 'Ordentrabajo::tareas_datatable/$1', ['as' => 'datatableTareasOrdenTrabajo']); $routes->get('maquinas/ots/datatable/(:num)','Ordentrabajo::datatable_maquina_ordenes_trabajo/$1'); $routes->get('maquinas/ots/(:num)','Ordentrabajo::get_maquina_ots/$1'); + /**====================== * UPDATES *========================**/ @@ -873,6 +874,8 @@ $routes->group('logistica', ['namespace' => 'App\Controllers\Logistica'], functi $routes->get('selectForNewEnvio', 'LogisticaController::findForNewEnvio'); $routes->get('selectDireccionForEnvio', 'LogisticaController::selectDireccionForEnvio'); $routes->post('imprimirEtiquetas', 'LogisticaController::imprimirEtiquetas'); + $routes->post('ficharEmbalaje', 'LogisticaController::ficharEmbalaje'); + $routes->get('datatableProximosEnvios/(:num)', 'LogisticaController::datatable_proximosEnvios/$1'); $routes->get('listAlbaranes', 'LogisticaController::listAlbaranes', ['as' => 'albaranesList']); }); diff --git a/ci4/app/Controllers/Importadores/ImportadorBubok.php b/ci4/app/Controllers/Importadores/ImportadorBubok.php index 27641c64..09527b3a 100644 --- a/ci4/app/Controllers/Importadores/ImportadorBubok.php +++ b/ci4/app/Controllers/Importadores/ImportadorBubok.php @@ -450,6 +450,76 @@ class ImportadorBubok extends BaseResourceController ], 400); } + + // Descarga y subida de archivos al SFTP + $presupuestoFicheroModel = model('App\Models\Presupuestos\PresupuestoFicheroModel'); + $ftp = new \App\Libraries\SafekatFtpClient(); + + $archivoUrls = [ + 'cover' => $producto->cover->file ?? null, + 'body' => $producto->body->file ?? null, + ]; + + foreach ($archivoUrls as $tipo => $url) { + if (!$url) + continue; + + try { + $contenido = @file_get_contents($url); // silenciar errores de PHP + + if ($contenido === false || strlen($contenido) === 0) { + // No se pudo descargar el archivo: generar archivo de error para FTP + $errorMessage = "ERROR: No se pudo descargar el archivo remoto para $tipo desde la URL: $url"; + + $remoteDir = $ftp->getPresupuestoRemotePath($response['sk_id']); // crea esta función si no existe + $remoteErrorFile = $remoteDir . '/ERROR_' . strtoupper($tipo) . '.txt'; + + // Crear archivo temporal con el mensaje de error + $tempErrorFile = WRITEPATH . 'uploads/presupuestos/ERROR_' . $tipo . '.txt'; + file_put_contents($tempErrorFile, $errorMessage); + + if (!$ftp->is_dir($remoteDir)) { + $ftp->mkdir($remoteDir, recursive: true); + } + + $ftp->put($remoteErrorFile, $tempErrorFile, $ftp::SOURCE_LOCAL_FILE); + + continue; // no procesar este archivo + } + + // ✅ Procesar normalmente si la descarga tuvo éxito + $nombreOriginal = basename(parse_url($url, PHP_URL_PATH)); + $extension = pathinfo($nombreOriginal, PATHINFO_EXTENSION); + + $nombreLimpio = $presupuestoFicheroModel->saveFileInBBDD( + $response['sk_id'], + $nombreOriginal, + $extension, + auth()->id() + ); + + if (is_null($nombreLimpio)) + continue; + + $rutaLocal = WRITEPATH . 'uploads/presupuestos/'; + if (!is_dir($rutaLocal)) { + mkdir($rutaLocal, 0777, true); + } + + file_put_contents($rutaLocal . $nombreLimpio, $contenido); + } catch (\Throwable $e) { + //log_message('error', 'Error inesperado en descarga de archivo remoto: ' . $e->getMessage()); + } + } + + + // Subir al FTP después de guardar localmente + try { + $ftp->uploadFilePresupuesto($response['sk_id']); + } catch (\Throwable $e) { + log_message('error', 'Error subiendo archivos al FTP: ' . $e->getMessage()); + } + return $this->respond([ 'status' => 200, 'data' => [ @@ -458,6 +528,7 @@ class ImportadorBubok extends BaseResourceController ] ]); + } catch (\Throwable $e) { return $this->respond([ 'status' => 500, diff --git a/ci4/app/Controllers/Logistica/EtiquetasTitulosController.php b/ci4/app/Controllers/Logistica/EtiquetasTitulosController.php index d82805a0..dc0fa78a 100644 --- a/ci4/app/Controllers/Logistica/EtiquetasTitulosController.php +++ b/ci4/app/Controllers/Logistica/EtiquetasTitulosController.php @@ -163,10 +163,10 @@ class EtiquetasTitulosController extends BaseController $modelImpresora = model('App\Models\Configuracion\ImpresoraEtiquetaModel'); - $impresoras = $modelImpresora->select('id, name') + $impresoras = $modelImpresora->select('id, name, description') ->where('deleted_at', null) ->where('tipo', 1) - ->orderBy('name', 'desc') + ->orderBy('name', 'asc') ->findAll(); $etiquetaEntity->impresoras = $impresoras; @@ -440,7 +440,7 @@ class EtiquetasTitulosController extends BaseController $impresora_id = $this->request->getPost('impresora_id') ?? null; $modelImpresora = model('App\Models\Configuracion\ImpresoraEtiquetaModel'); - $impresora = $modelImpresora->select('id, name, ip, port, user, pass') + $impresora = $modelImpresora->select('id, name, description, ip, port, user, pass') ->where('deleted_at', null) ->where('id', $impresora_id) ->orderBy('name', 'asc') diff --git a/ci4/app/Controllers/Logistica/LogisticaController.php b/ci4/app/Controllers/Logistica/LogisticaController.php index b69b0f4c..82a1a6a5 100755 --- a/ci4/app/Controllers/Logistica/LogisticaController.php +++ b/ci4/app/Controllers/Logistica/LogisticaController.php @@ -98,7 +98,8 @@ class LogisticaController extends BaseController return view(static::$viewPath . 'viewImpresionEtiquetas', $viewData); } - public function listAlbaranes(){ + public function listAlbaranes() + { $viewData = [ 'currentModule' => static::$controllerSlug, 'boxTitle' => lang('Albaran.albaranes'), @@ -117,7 +118,7 @@ class LogisticaController extends BaseController $tipo_envio = $this->request->getGet('tipo_envio') ?? 'estandar'; - if($tipo_envio == 'ferro_prototipo'){ + if ($tipo_envio == 'ferro_prototipo') { $query = LogisticaService::findForNewEnvioFerro(); } else { $query = LogisticaService::findForNewEnvio(); @@ -132,22 +133,25 @@ class LogisticaController extends BaseController $result = $query->orderBy("name", "asc")->get()->getResultObject(); + $query = model('App\Models\Logistica\EnvioModel')->db->getLastQuery(); + return $this->response->setJSON($result); } else { return $this->failUnauthorized('Invalid request', 403); } } - public function selectDireccionForEnvio(){ + public function selectDireccionForEnvio() + { if ($this->request->isAJAX()) { $ot = $this->request->getGet('ot_id'); - if($ot == null || $ot == 0){ + if ($ot == null || $ot == 0) { return []; } $searchVal = $this->request->getGet("q") ?? ""; $result = LogisticaService::findDireccionesNewEnvio($ot, $searchVal); - + return $this->response->setJSON($result); } else { return $this->failUnauthorized('Invalid request', 403); @@ -185,12 +189,12 @@ class LogisticaController extends BaseController public function imprimirEtiquetas() { if ($this->request->isAJAX()) { - $envio_id = $this->request->getPost('envio_id'); + $envio_id = $this->request->getPost('envio_id'); $ids = $this->request->getPost('envio_lineas'); $cajas = $this->request->getPost('cajas'); $printer_id = $this->request->getPost('printer_id'); - if($cajas == null || $cajas == 0){ + if ($cajas == null || $cajas == 0) { return $this->response->setJSON([ 'status' => false, 'message' => 'Cajas no válidas' @@ -202,7 +206,7 @@ class LogisticaController extends BaseController ->join('clientes', 'clientes.id = envios.cliente_id', 'left') ->where('envios.id', $envio_id) ->first(); - if($envio == null){ + if ($envio == null) { return $this->response->setJSON([ 'status' => false, 'message' => 'Envio no válido' @@ -213,7 +217,7 @@ class LogisticaController extends BaseController $lineas = $model->select('envios_lineas.*, presupuestos.titulo as titulo, presupuestos.referencia_cliente as referencia_cliente') ->join('presupuestos', 'presupuestos.id = envios_lineas.presupuesto_id', 'left') ->whereIn('envios_lineas.id', $ids)->findAll(); - if($lineas == null){ + if ($lineas == null) { return $this->response->setJSON([ 'status' => false, 'message' => 'Lineas no válidas' @@ -221,12 +225,12 @@ class LogisticaController extends BaseController } $modelImpresora = model('App\Models\Configuracion\ImpresoraEtiquetaModel'); - $impresora = $modelImpresora->select('id, name, ip, port, user, pass') + $impresora = $modelImpresora->select('id, name, description, ip, port, user, pass') ->where('deleted_at', null) ->where('id', $printer_id) ->orderBy('name', 'asc') ->first(); - if($impresora == null){ + if ($impresora == null) { return $this->response->setJSON([ 'status' => false, 'message' => 'Impresora no válida' @@ -330,22 +334,22 @@ class LogisticaController extends BaseController if (empty($envioEntity)) { 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)){ + if (!empty($proveedor)) { $envioEntity->proveedor_nombre = $proveedor->nombre; } $modelImpresora = model('App\Models\Configuracion\ImpresoraEtiquetaModel'); - $impresoras = $modelImpresora->select('id, name') + $impresoras = $modelImpresora->select('id, name, description') ->where('deleted_at', null) ->where('tipo', 1) - ->orderBy('name', 'desc') + ->orderBy('name', 'asc') ->findAll(); $envioEntity->impresoras = $impresoras; @@ -384,7 +388,7 @@ class LogisticaController extends BaseController $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 { @@ -392,6 +396,17 @@ class LogisticaController extends BaseController } } + public function ficharEmbalaje() + { + if ($this->request->isAJAX()) { + + $ids = $this->request->getPost('ids') ?? []; + $result = LogisticaService::ficharEmbalaje($ids); + return $this->response->setJSON($result); + } else { + return $this->failUnauthorized('Invalid request', 403); + } + } public function datatable_enviosEdit($idEnvio) { @@ -406,6 +421,12 @@ class LogisticaController extends BaseController return ''; } ) + ->edit( + "ordenTrabajo", + function ($row, $meta) { + return '' . $row->ordenTrabajo . ''; + } + ) ->edit( "pedido", function ($row, $meta) { @@ -420,17 +441,35 @@ class LogisticaController extends BaseController )->edit( "unidadesEnvio", function ($row, $meta) { - if($row->finalizado == 1 || $row->tipo_envio == 'ferro_prototipo'){ + if ($row->finalizado == 1 || $row->tipo_envio == 'ferro_prototipo') { return $row->unidadesEnvio; } return ''; + data-id="' . $row->id . '" data-name="unidades_envio" value="' . $row->unidadesEnvio . '">'; } ); return $result->toJson(returnAsObject: true); } + public function datatable_proximosEnvios($envio_id = null) + { + $q = LogisticaService::findNextEnvios($envio_id); + + $result = DataTable::of($q) + ->edit( + "ot", + function ($row, $meta) { + return '' . $row->ot . ''; + } + ); + + $result = $result->toJson(returnAsObject: true); + $query = model('App\Models\Logistica\EnvioModel')->db->getLastQuery(); + return $result; + + } + public function setCajaLinea() { @@ -471,7 +510,7 @@ class LogisticaController extends BaseController $fieldName = $this->request->getPost('name'); $fieldValue = $this->request->getPost('value'); - if (!$id || !$fieldName || ($fieldName=='unidades_envio' && !$fieldValue)) { + if (!$id || !$fieldName || ($fieldName == 'unidades_envio' && !$fieldValue)) { return $this->response->setJSON([ 'status' => false, 'message' => 'Datos inválidos' @@ -480,7 +519,7 @@ class LogisticaController extends BaseController $model = model('App\Models\Logistica\EnvioLineaModel'); $updated = $model->update($id, [ - "" . $fieldName => $fieldValue==""? null: $fieldValue, + "" . $fieldName => $fieldValue == "" ? null : $fieldValue, ]); return $this->response->setJSON([ @@ -503,7 +542,7 @@ class LogisticaController extends BaseController $model = model('App\Models\Logistica\EnvioModel'); $updated = $model->update($id, [ - "codigo_seguimiento" => $fieldValue==""? null: $fieldValue, + "codigo_seguimiento" => $fieldValue == "" ? null : $fieldValue, ]); return $this->response->setJSON([ @@ -526,7 +565,7 @@ class LogisticaController extends BaseController $model = model('App\Models\Logistica\EnvioModel'); $updated = $model->update($id, [ - "proveedor_id" => $fieldValue==""? null: $fieldValue, + "proveedor_id" => $fieldValue == "" ? null : $fieldValue, ]); return $this->response->setJSON([ diff --git a/ci4/app/Controllers/Produccion/Ordentrabajo.php b/ci4/app/Controllers/Produccion/Ordentrabajo.php index 4f631279..ce6dacdd 100755 --- a/ci4/app/Controllers/Produccion/Ordentrabajo.php +++ b/ci4/app/Controllers/Produccion/Ordentrabajo.php @@ -951,7 +951,7 @@ class Ordentrabajo extends BaseController } $modelImpresora = model('App\Models\Configuracion\ImpresoraEtiquetaModel'); - $impresora = $modelImpresora->select('id, name, ip, port, user, pass') + $impresora = $modelImpresora->select('id, name, description, ip, port, user, pass') ->where('deleted_at', null) ->where('id', $impresora_id) ->orderBy('name', 'asc') diff --git a/ci4/app/Language/es/Logistica.php b/ci4/app/Language/es/Logistica.php index 0ef3e713..4213a476 100755 --- a/ci4/app/Language/es/Logistica.php +++ b/ci4/app/Language/es/Logistica.php @@ -44,6 +44,7 @@ return [ 'totales' => 'Totales', 'cajas' => 'Cajas', + 'ordenTrabajo' => 'OT', 'pedido' => 'Pedido', 'presupuesto' => 'Presupuesto', 'unidadesEnvio' => 'Unidades envío', @@ -59,9 +60,11 @@ return [ 'selectAll' => 'Seleccionar todo', 'peso' => 'Peso (kg): ', 'unidadesTotalesFooter' => 'Unidades:', + 'fechaEncuadernado' => 'Fecha encuadernado', 'codigoSeguimiento' => 'Código de seguimiento', 'empresaMensajería' => 'Empresa de mensajería', + 'ficharEmbalaje' => 'Fichar embalaje', 'finalizarEnvio' => 'Finalizar envío', 'finalizarEnvioYOTs' => 'Finalizar envío y OTS', @@ -90,6 +93,7 @@ return [ 'errorInsertarEtiqueta' => 'Error al insertar la etiqueta', 'noEtiqueta' => 'No se ha encontrado la etiqueta', 'noEtiquetaLineas' => 'No se han encontrado líneas de etiqueta', + 'noLineas' => 'No se ha seleccionado ninguna línea', ], 'success' => [ 'finalizado' => 'El envío se ha finalizado correctamente', @@ -101,6 +105,7 @@ return [ 'comentariosUpdated' => 'Comentarios actualizados correctamente', 'successReordenarCajas' => 'Cajas reordenadas correctamente', 'imprimirEtiquetas' => 'Etiquetas impresas correctamente', + 'successFicharEmbalaje' => 'Embalaje fichado correctamente', ], ]; \ No newline at end of file diff --git a/ci4/app/Language/es/Produccion.php b/ci4/app/Language/es/Produccion.php index 6f085694..a7907544 100755 --- a/ci4/app/Language/es/Produccion.php +++ b/ci4/app/Language/es/Produccion.php @@ -182,6 +182,7 @@ return [ 'duplicate_estado_tarea_progress' => "Último estado de la tarea repetido", 'task_already_finished' => "La tarea se ha marcado como finalizada.", 'print_label' => "Imprimir etiqueta", + 'fichar_embalaje' => "Fichar embalaje", 'click_init' => "Clicks al inicio", 'click_end' => "Clicks al final", "comentarios" => "Comentarios", diff --git a/ci4/app/Libraries/SafekatFtpClient.php b/ci4/app/Libraries/SafekatFtpClient.php index 663e8c7a..c69b2d85 100755 --- a/ci4/app/Libraries/SafekatFtpClient.php +++ b/ci4/app/Libraries/SafekatFtpClient.php @@ -40,13 +40,14 @@ class SafekatFtpClient public function uploadXML(string $content, string $filename): bool { try { - if ($this->xml_enabled == false) return false; - $remotePath = implode("/", [$this->base_dir,'pedidos','xml_nuevos']); + if ($this->xml_enabled == false) + return false; + $remotePath = implode("/", [$this->base_dir, 'pedidos', 'xml_nuevos']); $this->ftp->login(username: $this->username, password: $this->password); - if(!$this->ftp->is_dir($remotePath)){ - $this->ftp->mkdir($remotePath,recursive:true); + if (!$this->ftp->is_dir($remotePath)) { + $this->ftp->mkdir($remotePath, recursive: true); } - $this->ftp->put($remotePath.'/'.$filename, $content); + $this->ftp->put($remotePath . '/' . $filename, $content); return true; } catch (\Throwable $th) { @@ -58,22 +59,23 @@ class SafekatFtpClient public function uploadFilePresupuesto(int $presupuesto_id) { try { - if ($this->xml_enabled == false) return false; + if ($this->xml_enabled == false) + return false; $model = model(PresupuestoFicheroModel::class); $modelPedidoLinea = model(PedidoLineaModel::class); $pedidoLinea = $modelPedidoLinea->findByPresupuesto($presupuesto_id); $rootIdExtern = $this->pedido_xml_config->id_offset + $pedidoLinea->pedido_id; $presupuestoFiles = $model->getFiles($presupuesto_id); $this->ftp->login(username: $this->username, password: $this->password); - + foreach ($presupuestoFiles as $key => $value) { $filename = array_reverse(explode("/", $value->file_path))[0]; - $remoteDir = implode("/", [$this->base_dir,"pedidos_files",$rootIdExtern]); - $remoteFile = implode("/", [$this->base_dir,"pedidos_files",$rootIdExtern,$filename]); - if(!$this->ftp->is_dir($remoteDir)){ - $this->ftp->mkdir($remoteDir,recursive:true); + $remoteDir = implode("/", [$this->base_dir, "pedidos_files", $rootIdExtern]); + $remoteFile = implode("/", [$this->base_dir, "pedidos_files", $rootIdExtern, $filename]); + if (!$this->ftp->is_dir($remoteDir)) { + $this->ftp->mkdir($remoteDir, recursive: true); } - $this->ftp->put($remoteFile,$value->file_path,mode:$this->ftp::SOURCE_LOCAL_FILE); + $this->ftp->put($remoteFile, $value->file_path, mode: $this->ftp::SOURCE_LOCAL_FILE); } $this->ftp->disconnect(); } catch (Exception $e) { @@ -91,10 +93,10 @@ class SafekatFtpClient $rootIdExtern = $this->pedido_xml_config->id_offset + $pedidoLinea->pedido_id; $presupuestoFiles = $model->getFiles($presupuesto_id); $this->ftp->login(username: $this->username, password: $this->password); - + foreach ($presupuestoFiles as $key => $value) { $filename = array_reverse(explode("/", $value->file_path))[0]; - $remoteFile = implode("/", [$this->base_dir,"pedidos_files",$rootIdExtern,$filename]); + $remoteFile = implode("/", [$this->base_dir, "pedidos_files", $rootIdExtern, $filename]); $this->ftp->delete($remoteFile); } $this->ftp->disconnect(); @@ -103,4 +105,13 @@ class SafekatFtpClient throw $e; } } + + public function getPresupuestoRemotePath(int $presupuesto_id): string + { + $modelPedidoLinea = model(PedidoLineaModel::class); + $pedidoLinea = $modelPedidoLinea->findByPresupuesto($presupuesto_id); + $rootIdExtern = $this->pedido_xml_config->id_offset + $pedidoLinea->pedido_id; + + return implode('/', [$this->base_dir, 'pedidos_files', $rootIdExtern]); + } } diff --git a/ci4/app/Models/Logistica/EnvioLineaModel.php b/ci4/app/Models/Logistica/EnvioLineaModel.php index a50709d3..76ae7b9b 100644 --- a/ci4/app/Models/Logistica/EnvioLineaModel.php +++ b/ci4/app/Models/Logistica/EnvioLineaModel.php @@ -35,7 +35,7 @@ class EnvioLineaModel extends Model $builder = $this->db ->table($this->table . " t1") ->select( - "t1.id, t1.pedido_id as pedido, t3.id as presupuesto, + "t1.id, t1.pedido_id as pedido, t3.id as presupuesto, t4.id as ordenTrabajo, t3.titulo as titulo, t1.unidades_envio as unidadesEnvio, t1.unidades_envio as unidadesEnvioRaw, t1.unidades_total as unidadesTotal, t2.tipo_envio as tipo_envio, IFNULL(( @@ -59,6 +59,7 @@ class EnvioLineaModel extends Model ); $builder->join("envios t2", "t1.envio_id = t2.id", "left"); $builder->join("presupuestos t3", "t1.presupuesto_id = t3.id", "left"); + $builder->join("ordenes_trabajo t4", "t1.pedido_id = t4.pedido_id", "left"); $builder->where("t1.envio_id", $envio_id); diff --git a/ci4/app/Models/OrdenTrabajo/OrdenTrabajoModel.php b/ci4/app/Models/OrdenTrabajo/OrdenTrabajoModel.php index e28a7e1b..5c81a5ab 100755 --- a/ci4/app/Models/OrdenTrabajo/OrdenTrabajoModel.php +++ b/ci4/app/Models/OrdenTrabajo/OrdenTrabajoModel.php @@ -143,4 +143,25 @@ class OrdenTrabajoModel extends Model ->groupBy('orden_trabajo_tareas.id'); return $query; } + + public function queryProximosEnvios() + { + $query = $this->builder() + ->select([ + 'ordenes_trabajo.id as ot', + 'orden_trabajo_dates.encuadernacion_at as fechaEncuadernado', + ]) + ->join('pedidos', 'pedidos.id = ordenes_trabajo.pedido_id', 'left') + ->join('pedidos_linea', 'pedidos.id = pedidos_linea.pedido_id', 'left') + ->join('presupuestos', 'presupuestos.id = pedidos_linea.presupuesto_id', 'left') + ->join('presupuesto_direcciones', 'presupuestos.id = presupuesto_direcciones.presupuesto_id', 'left') + ->join('orden_trabajo_dates', 'orden_trabajo_dates.orden_trabajo_id = ordenes_trabajo.id', 'left') + ->where('ordenes_trabajo.deleted_at', null) + ->where('orden_trabajo_dates.encuadernacion_at !=', null) + + //->where('orden_trabajo_dates.fecha_encuadernado_at >=', 0) + //->where('ordenes_trabajo.fecha_entrega_warning >=', date("Y-m-d H:i:s")) + ->groupBy('ordenes_trabajo.id'); + return $query; + } } diff --git a/ci4/app/Models/Presupuestos/ImportadorModel.php b/ci4/app/Models/Presupuestos/ImportadorModel.php index 0a0a6ab5..10f79cf9 100755 --- a/ci4/app/Models/Presupuestos/ImportadorModel.php +++ b/ci4/app/Models/Presupuestos/ImportadorModel.php @@ -25,7 +25,7 @@ class ImportadorModel extends \App\Models\BaseModel $builder = $db->table('pedido_libro'); $builder->select('id as id, CONCAT(id, " - ", titulo) as name'); $builder->where('customer_id', $clienteId); - $builder->whereIn('estado', ['finalizado', 'validado']); + $builder->whereIn('estado', ['finalizado', 'validado', 'presupuesto']); $builder->where('deleted_at', NULL); $builder->orderBy('updated_at', 'DESC'); diff --git a/ci4/app/Services/EmailService.php b/ci4/app/Services/EmailService.php index 5b6f7f99..be7dc57a 100755 --- a/ci4/app/Services/EmailService.php +++ b/ci4/app/Services/EmailService.php @@ -13,7 +13,7 @@ class EmailService // Si no estamos en producción, forzar el destinatario a uno fijo if ($skEnv !== 'production') { - $recipient = env('MAIL_DEV_RECIPIENT', 'imnavajas@coit.es'); // fallback opcional + $recipient = env('MAIL_DEV_RECIPIENT', 'imnavajas@coit.es,info@jjimenez.eu'); // fallback opcional } $settings_model = model('App\Models\Configuracion\ConfigVariableModel'); @@ -43,7 +43,13 @@ class EmailService $email->setSubject($subject); $email->setMessage($body); - return $email->send(); + if (!$email->send()) { + log_message('error', 'Error enviando email: ' . $email->printDebugger(['headers', 'subject', 'body'])); + return false; + } + + + return true; } catch (\Throwable $e) { log_message('error', 'EmailService failed: ' . $e->getMessage()); return false; diff --git a/ci4/app/Services/LogisticaService.php b/ci4/app/Services/LogisticaService.php index cb4fb1b5..b5f28237 100644 --- a/ci4/app/Services/LogisticaService.php +++ b/ci4/app/Services/LogisticaService.php @@ -57,7 +57,8 @@ class LogisticaService ->join('orden_trabajo_dates ot_dates', 'ot_dates.orden_trabajo_id = ot.id') ->whereIn('pr.id', $presupuestoIds) ->whereIn('p.estado', ['finalizado', 'produccion']) - ->where('ot_dates.embalaje_at IS NOT NULL') + ->where('p.fecha_encuadernado IS NOT NULL') + ->where('DATE(p.fecha_encuadernado) <=', date('Y-m-d')) ->where("NOT EXISTS ( SELECT 1 FROM envios_lineas el @@ -78,6 +79,79 @@ class LogisticaService return $builder; } + public static function findNextEnvios(int $envio_id) + { + $db = \Config\Database::connect(); + + // 1. Dirección del envío actual + $envio = $db->table('envios')->select('direccion')->where('id', $envio_id)->get()->getRow(); + if (!$envio) { + return $db->table('(SELECT NULL AS id, NULL AS name) AS empty')->where('1 = 0'); + } + + $direccionNormalizada = str_replace(' ', '', strtolower(trim($envio->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) AS empty')->where('1 = 0'); + } + + $hoy = date('Y-m-d'); + $sieteDiasDespues = date('Y-m-d', strtotime('+7 days')); + + // 3. Subconsulta principal + $subBuilder = $db->table('pedidos_linea pl') + ->select(" + ot.id AS ot, + DATE(p.fecha_encuadernado) as fechaEncuadernado, + ( + 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') + ->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']) + ->where('p.fecha_encuadernado IS NOT NULL') + ->where('DATE(p.fecha_encuadernado) >=', $hoy) + ->where('DATE(p.fecha_encuadernado) <=', $sieteDiasDespues) + ->where("NOT EXISTS ( + SELECT 1 + FROM envios_lineas el + WHERE el.envio_id = $envio_id + AND el.pedido_id = p.id + AND el.presupuesto_id = pr.id + GROUP BY el.pedido_id, el.presupuesto_id + HAVING SUM(el.unidades_envio) >= pd.cantidad + )", null, false) + ->groupBy('pl.id'); + + // 4. Envolver y filtrar por unidades pendientes + $builder = $db->table("({$subBuilder->getCompiledSelect(false)}) AS sub"); + $builder->select('ot, fechaEncuadernado'); + $builder->where('cantidad > unidades_enviadas'); + + return $builder; + } + public static function findForNewEnvio() { @@ -103,16 +177,15 @@ class LogisticaService ->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('p.estado', ['finalizado', 'produccion']) - ->where('ot_dates.embalaje_at IS NOT NULL') + ->where('p.fecha_encuadernado IS NOT NULL') + ->where('DATE(p.fecha_encuadernado) <=', date('Y-m-d')) ->groupBy('pl.id'); // 4. Envolver y filtrar por unidades pendientes $builder = $db->table("({$subBuilder->getCompiledSelect(false)}) AS sub"); $builder->select('id, name'); $builder->where('cantidad > unidades_enviadas'); - $builder->orderBy('name', 'ASC'); return $builder; } @@ -289,6 +362,81 @@ class LogisticaService } + public static function sendConfirmacionEnvio($envio, $lineaEnvio, $isFerro = false) + { + + $view = \Config\Services::renderer(); + + if ($isFerro) + $subject = '[Safekat]' . " El envio del ferro de su pedido se ha realizado"; + else + $subject = '[Safekat]' . " El envio de su pedido se ha realizado"; + + $presupuestoModel = model('App\Models\Presupuestos\PresupuestoModel'); + $presupuesto = $presupuestoModel->find($lineaEnvio->presupuesto_id); + $proveedorModel = model('App\Models\Compras\ProveedorModel'); + $proveedor = $proveedorModel->find($envio->proveedor_id); + $userModel = model('App\Models\Usuarios\UserModel'); + $datos_correo = $userModel->select("CONCAT(users.first_name, ' ', users.last_name) as comercial_nombre, auth_identities.secret as comercial_correo, clientes.email as cliente_email") + ->join('auth_identities', 'auth_identities.user_id = users.id') + ->join('clientes', 'clientes.comercial_id = users.id') + ->where('clientes.id', $presupuesto->cliente_id) + ->first(); + + + + $pedido = (object) [ + 'pedido_id' => $lineaEnvio->pedido_id, + 'titulo' => $presupuesto->titulo, + 'cp' => $envio->cp, + 'proveedor_nombre' => $proveedor->nombre, + 'codigo_seguimiento' => $envio->codigo_seguimiento, + 'comercial_nombre' => $datos_correo->comercial_nombre, + 'comercial_correo' => $datos_correo->comercial_correo, + ]; + + if ($proveedor->nombre == "GLS") { + $pedido->url = 'https://m.asmred.com/e/' . $envio->codigo_seguimiento . '/' . $envio->cp; + } + + $content = $view->setVar('datos_pedido', $pedido) + ->render('themes/vuexy/mail/envio_pedido'); + // Renderiza la plantilla completa + if ($isFerro) + $finalBody = $view->setVar('emailTitle2', "El ferro de su pedido " . $lineaEnvio->pedido_id . " ha sido enviado el " . date('d/m/Y')) + ->setVar('content', $content) + ->render('themes/vuexy/mail/mail_layout_2'); + else + $finalBody = $view->setVar('emailTitle2', "Su pedido " . $lineaEnvio->pedido_id . " ha sido enviado el " . date('d/m/Y')) + ->setVar('content', $content) + ->render('themes/vuexy/mail/mail_layout_2'); + + + $email = service('emailService'); + $result = $email->send($subject, $finalBody, $datos_correo->cliente_email); + + $chat = Service('chat'); + $data = [ + 'chat_department_id' => 5, + 'client' => $presupuesto->cliente_id, + 'message' => "El pedido " . $lineaEnvio->pedido_id . " ha sido enviado el " . date('d/m/Y') . ".

" . + "CP:" . $envio->cp . ".
" . + "Proveedor envío: " . $proveedor->nombre . ".
" . + "Código de seguimiento: " . $envio->codigo_seguimiento . ".
" + ]; + if ($proveedor->nombre == "GLS") { + $data['message'] = $data['message'] . 'URL segumiento: ' . + 'https://m.asmred.com/e/' . $envio->codigo_seguimiento . '/' . $envio->cp . ''; + } + $chat->storeChatMessage(5, "pedido", $lineaEnvio->pedido_id, $data); + + return [ + 'status' => $result, + 'message' => $result ? lang('Logistica.success.emailSent') : lang('Logistica.errors.emailNotSent'), + ]; + } + + public static function generateEnvio($ot_id, $direccion = null) { @@ -533,16 +681,29 @@ class LogisticaService "name" => "ferro_en_cliente_at", "ferro_en_cliente_at" => date('Y-m-d H:i:s') ]); + + LogisticaService::sendConfirmacionEnvio($envio, $linea, true); + } else { - if ($cantidad_enviada + $linea->unidades_envio == $pedido->total_tirada) { + 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); + $date = $ps->getOrdenTrabajo()->dates()->embalaje_at; + if (is_null($date) || empty($date)) { + $ps->updateOrdenTrabajoDate([ + "name" => "embalaje_at", + "embalaje_at" => date('Y-m-d H:i:s') + ]); + } $ps->updateOrdenTrabajoDate([ "name" => "envio_at", "envio_at" => date('Y-m-d H:i:s') ]); + + LogisticaService::sendConfirmacionEnvio($envio, $linea); + if ($finalizar_ot) { $ps->updateOrdenTrabajo( [ @@ -572,6 +733,29 @@ class LogisticaService return $data_return; } + public static function ficharEmbalaje($ids = null) + { + + if (is_null($ids) || empty($ids) || count($ids) == 0) { + return [ + 'status' => false, + 'message' => lang('Logistica.errors.noLineas'), + ]; + } + + for ($index = 0; $index < count($ids); $index++) { + $ps = (new ProductionService())->init($ids[$index]); + $ps->updateOrdenTrabajoDate([ + "name" => "embalaje_at", + "embalaje_at" => date('Y-m-d') + ]); + } + return [ + 'status' => true, + 'message' => lang('Logistica.success.successFicharEmbalaje'), + ]; + } + public static function generateEtiquetasTitulos($envio, $lineas, $printer, $cajas) { $data = [ diff --git a/ci4/app/Views/themes/vuexy/form/logistica/viewEnvioEditForm.php b/ci4/app/Views/themes/vuexy/form/logistica/viewEnvioEditForm.php index 7c63bd17..489222a3 100644 --- a/ci4/app/Views/themes/vuexy/form/logistica/viewEnvioEditForm.php +++ b/ci4/app/Views/themes/vuexy/form/logistica/viewEnvioEditForm.php @@ -10,7 +10,7 @@

- tipo_envio == 'ferro_prototipo') ? 'FERRO':'' ?> + tipo_envio == 'ferro_prototipo') ? 'FERRO' : '' ?> finalizado == 0) ? '' : 'FINALIZADO' ?>

@@ -114,7 +114,7 @@
- finalizado == 0 && $envioEntity->tipo_envio=='estandar'): ?> + finalizado == 0 && $envioEntity->tipo_envio == 'estandar'): ?>

@@ -130,6 +130,7 @@

+
-
-
- - - +
-
-
-

- -

+
-
-
-
-

-
-
-
- -
- finalizado == 0 && $envioEntity->tipo_envio=='estandar'): ?> -
- -
- -
- -
-
- -
+
+

+ +

+
+
-
-
- - -
-
+ + + + + + + + + +
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - -
-
- -
-
-
- - -
-
+
-
- - +
+ +
-
-
- -
- -
-

- -

- -
-
+ -
+
+
+

+ +

-
-
-
- -
-
-

- -

- -
-
- - finalizado == 0) ? '' : 'readonly' ?> - value="codigo_seguimiento)) ?>"> -
-
- - finalizado == 0): ?> - - - - +
+ +
+
+ +
+
+ +
+ +
+
+ + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+ + +
+
+
+ + +
+
+ +
+ + + +
+
+
- finalizado == 0): ?> -
- -
- tipo_envio=='estandar'): ?> -
- -
- -
+ +
+ +
+

+ +

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

+ +

+ +
+
+ + finalizado == 0) ? '' : 'readonly' ?> + value="codigo_seguimiento)) ?>"> +
+
+ + finalizado == 0): ?> + + + + +
+ finalizado == 0): ?> +
+ +
+ tipo_envio == 'estandar'): ?> +
+ +
+ + +
+
+
+
-
- - -endSection() ?> + endSection() ?> -section('css') ?> - - - -"> + section('css') ?> + + + + "> + " /> -endSection() ?> + endSection() ?> -section('additionalExternalJs') ?> - - - - -endSection() ?> \ No newline at end of file + section('additionalExternalJs') ?> + + + + + endSection() ?> \ No newline at end of file diff --git a/ci4/app/Views/themes/vuexy/form/logistica/viewEtiquetasTitulosEdit.php b/ci4/app/Views/themes/vuexy/form/logistica/viewEtiquetasTitulosEdit.php index b48cef9b..911e8578 100644 --- a/ci4/app/Views/themes/vuexy/form/logistica/viewEtiquetasTitulosEdit.php +++ b/ci4/app/Views/themes/vuexy/form/logistica/viewEtiquetasTitulosEdit.php @@ -158,7 +158,7 @@ -
+