diff --git a/ci4/app/Config/Routes/PresupuestosRoutes.php b/ci4/app/Config/Routes/PresupuestosRoutes.php index a4e9dd37..2f5ed704 100755 --- a/ci4/app/Config/Routes/PresupuestosRoutes.php +++ b/ci4/app/Config/Routes/PresupuestosRoutes.php @@ -30,6 +30,8 @@ $routes->group('presupuestoadmin', ['namespace' => 'App\Controllers\Presupuestos $routes->get('presupuestosCliente', 'Presupuestoadmin::tablaClienteForm'); $routes->get('getSumCliente/(:num)', 'Presupuestoadmin::obtenerTotalPresupuestosCliente/$1'); + + $routes->post('download_zip', 'Presupuestocliente::download_zip', ['as' => 'descargarAdminArchivos']); }); //$routes->resource('presupuestoadmin', ['namespace' => 'App\Controllers\Presupuestos', 'controller' => 'Presupuestoadmin', 'except' => 'show,new,create,update']); @@ -51,6 +53,7 @@ $routes->group('presupuestocliente', ['namespace' => 'App\Controllers\Presupuest $routes->post('calcular', 'Presupuestocliente::calcular', ['as' => 'calcularPresupuesto']); $routes->post('calcularsolapas', 'Presupuestocliente::calcularMaxSolapas', ['as' => 'calcularSolapas']); $routes->post('checklomo', 'Presupuestocliente::check_lomo_interior'); + $routes->post('download_zip', 'Presupuestocliente::download_zip', ['as' => 'descargarClienteArchivos']); }); //$routes->resource('presupuestocliente', ['namespace' => 'App\Controllers\Presupuestos', 'controller' => 'Presupuestocliente', 'except' => 'show,new,create,update']); diff --git a/ci4/app/Controllers/Presupuestos/Presupuestocliente.php b/ci4/app/Controllers/Presupuestos/Presupuestocliente.php index 41395737..99f809a6 100755 --- a/ci4/app/Controllers/Presupuestos/Presupuestocliente.php +++ b/ci4/app/Controllers/Presupuestos/Presupuestocliente.php @@ -332,15 +332,14 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController $cliente_model = model(('App\Models\Clientes\ClienteModel')); $cliente = $cliente_model->find($cliente_id); - + $forzarRotativa = false; if ($tirada[0] <= $POD && $cliente->forzar_rotativa_pod) { $forzarRotativa = true; - } - else if ($tirada[0] <= $POD && !$cliente->forzar_rotativa_pod) { + } else if ($tirada[0] <= $POD && !$cliente->forzar_rotativa_pod) { $excluirRotativa = true; - } - + } + $input_data = array( 'uso' => 'interior', @@ -760,14 +759,13 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController $cliente_model = model(('App\Models\Clientes\ClienteModel')); $cliente = $cliente_model->find($cliente_id); - + $forzarRotativa = false; if ($tirada[0] <= $POD && $cliente->forzar_rotativa_pod) { $forzarRotativa = true; - } - else if ($tirada[0] <= $POD && !$cliente->forzar_rotativa_pod) { + } else if ($tirada[0] <= $POD && !$cliente->forzar_rotativa_pod) { $excluirRotativa = true; - } + } $input_data = array( 'uso' => 'interior', @@ -1340,8 +1338,8 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController $datos_presupuesto['entrega_taller'] = $reqData['entrega_taller'] ?? 0; - $resultado_presupuesto['info']['merma'] = isset($resultado_presupuesto['info']['num_formas']) ? - PresupuestoService::calcular_merma($selected_tirada, $POD, $resultado_presupuesto['info']['num_formas']): PresupuestoService::calcular_merma($selected_tirada, $POD); + $resultado_presupuesto['info']['merma'] = isset($resultado_presupuesto['info']['num_formas']) ? + PresupuestoService::calcular_merma($selected_tirada, $POD, $resultado_presupuesto['info']['num_formas']) : PresupuestoService::calcular_merma($selected_tirada, $POD); $datos_presupuesto['faja'] = $faja; @@ -2080,14 +2078,13 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController $cliente_model = model(('App\Models\Clientes\ClienteModel')); $cliente = $cliente_model->find($cliente_id); - + $forzarRotativa = false; if ($tirada[$t] <= $POD && $cliente->forzar_rotativa_pod) { $forzarRotativa = true; - } - else if ($tirada[0] <= $POD && !$cliente->forzar_rotativa_pod) { + } else if ($tirada[0] <= $POD && !$cliente->forzar_rotativa_pod) { $excluirRotativa = true; - } + } $input_data = array( 'uso' => 'interior', @@ -3586,4 +3583,29 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController return $servicios; } + public function download_zip() + { + $presupuesto_id = $this->request->getPost('presupuesto_id'); + if (!$presupuesto_id) { + return $this->response->setStatusCode(400)->setBody('Presupuesto ID requerido'); + } + + $ftpClient = new \App\Libraries\SafekatFtpClient(); + try { + $zipPath = $ftpClient->downloadZipPresupuesto((int) $presupuesto_id); + + if ($zipPath === null || !file_exists($zipPath)) { + return $this->response->setStatusCode(404)->setBody('No se encontraron archivos'); + } + + return $this->response + ->download($zipPath, null) // null = usar nombre original del archivo + ->setFileName('archivos_presupuesto_' . $presupuesto_id . '.zip'); + } catch (\Throwable $e) { + log_message('error', $e->getMessage()); + return $this->response->setStatusCode(500)->setBody('Error interno'); + } + } + + } diff --git a/ci4/app/Language/es/App.php b/ci4/app/Language/es/App.php index 66873e89..78ac6a61 100755 --- a/ci4/app/Language/es/App.php +++ b/ci4/app/Language/es/App.php @@ -35,6 +35,7 @@ return [ "global_next" => "Siguiente", "global_save_file" => "Guardar ficheros", "global_upload_files" => "Subir ficheros", + "global_download_files" => "Descargar ficheros", "global_all" => "Todos", // LOGIN - Index "login_title" => "Iniciar sesión en su cuenta", diff --git a/ci4/app/Libraries/SafekatFtpClient.php b/ci4/app/Libraries/SafekatFtpClient.php index c69b2d85..9993eb49 100755 --- a/ci4/app/Libraries/SafekatFtpClient.php +++ b/ci4/app/Libraries/SafekatFtpClient.php @@ -114,4 +114,57 @@ class SafekatFtpClient return implode('/', [$this->base_dir, 'pedidos_files', $rootIdExtern]); } + + public function downloadZipPresupuesto(int $presupuesto_id): ?string + { + $modelPedidoLinea = model(PedidoLineaModel::class); + $model = model(PresupuestoFicheroModel::class); + + $pedidoLinea = $modelPedidoLinea->findByPresupuesto($presupuesto_id); + $rootIdExtern = $this->pedido_xml_config->id_offset + $pedidoLinea->pedido_id; + + $remotePath = implode('/', [$this->base_dir, 'pedidos_files', $rootIdExtern]); + + $this->ftp->login(username: $this->username, password: $this->password); + + if (!$this->ftp->is_dir($remotePath)) { + return null; + } + + $files = $model->getFiles($presupuesto_id); + if (empty($files)) { + return null; + } + + $localTempDir = WRITEPATH . 'zip_presupuestos/' . uniqid("presupuesto_"); + if (!is_dir($localTempDir)) { + mkdir($localTempDir, 0777, true); + } + + foreach ($files as $file) { + $originalName = $file->nombre ?? basename($file->file_path); + $localFile = $localTempDir . '/' . $originalName; + $remoteFile = $remotePath . '/' . basename($file->file_path); + $this->ftp->get($remoteFile, $localFile); + } + + $zipPath = $localTempDir . '.zip'; + $zip = new \ZipArchive(); + if ($zip->open($zipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE)) { + foreach (glob($localTempDir . '/*') as $localFile) { + $zip->addFile($localFile, basename($localFile)); + } + $zip->close(); + } + + // Limpieza temporal + foreach (glob($localTempDir . '/*') as $localFile) { + unlink($localFile); + } + rmdir($localTempDir); + + return $zipPath; + } + + } diff --git a/ci4/app/Views/themes/vuexy/components/dropzone.php b/ci4/app/Views/themes/vuexy/components/dropzone.php index ad2893df..4065ca91 100755 --- a/ci4/app/Views/themes/vuexy/components/dropzone.php +++ b/ci4/app/Views/themes/vuexy/components/dropzone.php @@ -31,6 +31,10 @@ + diff --git a/ci4/app/Views/themes/vuexy/form/presupuestos/cliente/items/_resumen.php b/ci4/app/Views/themes/vuexy/form/presupuestos/cliente/items/_resumen.php index 042ab029..fa41104b 100755 --- a/ci4/app/Views/themes/vuexy/form/presupuestos/cliente/items/_resumen.php +++ b/ci4/app/Views/themes/vuexy/form/presupuestos/cliente/items/_resumen.php @@ -213,6 +213,10 @@ + diff --git a/httpdocs/assets/js/safekat/components/forms/fileUploadDropzone.js b/httpdocs/assets/js/safekat/components/forms/fileUploadDropzone.js index a6e6fee8..4741f218 100644 --- a/httpdocs/assets/js/safekat/components/forms/fileUploadDropzone.js +++ b/httpdocs/assets/js/safekat/components/forms/fileUploadDropzone.js @@ -30,6 +30,7 @@ class FileUploadDropzone { this.modelId = this.jqElement.data('id') this.btnSelectFiles = $(`#${domElement.replace('#', '')}_btnUploadFiles`) this.btnSubmitFile = $(`#${domElement.replace('#', '')}_btnSubmitFiles`) + this.btnDownloadFiles = $(`#${domElement.replace('#', '')}_btnDownloadFiles`); this.dataPost = {} this.nameId = nameId; this.getUri = getUri @@ -44,6 +45,7 @@ class FileUploadDropzone { this.btnSelectFiles.on('click', () => { this.jqElement.trigger('click') }) + this.btnDownloadFiles.on('click', this._handleDownloadFiles.bind(this)) this.dropzone = new Dropzone(this.domElement, { url: this.postUri, @@ -67,12 +69,12 @@ class FileUploadDropzone { var viewButton = Dropzone.createElement("Ver"); file.previewElement.appendChild(viewButton); // Listen to the view button click event - viewButton.addEventListener("click", this.onViewButton.bind(this,file)); + viewButton.addEventListener("click", this.onViewButton.bind(this, file)); } } onViewButton(file) { console.log(window.location.protocol + "//" + window.location.host + "/sistema/intranet/" + this.resourcePath + "/" + file.hash) - window.open(window.location.protocol + "//" + window.location.host + "/sistema/intranet/" + this.resourcePath + "/" + file.hash, '_blank'); + window.open(window.location.protocol + "//" + window.location.host + "/sistema/intranet/" + this.resourcePath + "/" + file.hash, '_blank'); } _getDropzoneFilesFormData() { var files = this.dropzone.files; @@ -134,6 +136,47 @@ class FileUploadDropzone { } } + _handleDownloadFiles() { + $("#loader").modal('show'); + + $.ajax({ + url: `/presupuestoadmin/download_zip`, + type: 'POST', + data: { + [this.nameId]: this.modelId + }, + xhrFields: { + responseType: 'blob' + }, + success: (blob, status, xhr) => { + const disposition = xhr.getResponseHeader('Content-Disposition'); + let filename = "archivos.zip"; + if (disposition && disposition.indexOf('attachment') !== -1) { + const match = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(disposition); + if (match != null && match[1]) { + filename = match[1].replace(/['"]/g, ''); + } + } + + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + }, + error: () => { + alertWarningMessage("Error al descargar el archivo ZIP."); + }, + complete: () => { + $("#loader").modal('hide'); + } + }); + } + + dropZoneUpdateFiles(files) { files.forEach(file => { diff --git a/httpdocs/assets/js/safekat/pages/presupuestoCliente/resumen.js b/httpdocs/assets/js/safekat/pages/presupuestoCliente/resumen.js index 4471218c..031b08fb 100644 --- a/httpdocs/assets/js/safekat/pages/presupuestoCliente/resumen.js +++ b/httpdocs/assets/js/safekat/pages/presupuestoCliente/resumen.js @@ -58,6 +58,7 @@ class Resumen { this.btnPreviewCubierta = $(this.domItem.find("#btnPreviewCubierta")); this.btnDownloadPreviewCubierta = this.domItem.find('.download-shape'); this.submitFiles = $(this.domItem.find("#submit-all-files")); + this.btnDownloadAllFiles = this.domItem.find("#download-all-files"); this.dropzone = null; this.presupuesto_id = -1; @@ -68,6 +69,7 @@ class Resumen { this.btnPreviewCubierta.on('click', this.#btnPreview.bind(this)); this.submitFiles.on('click', this.#btnUploadFiles.bind(this)); + this.btnDownloadAllFiles.on('click', this.#downloadAllFiles.bind(this)); this.downloadPreviewImage() if (presupuesto_id != -1) { this.presupuesto_id = presupuesto_id; @@ -186,6 +188,43 @@ class Resumen { }); } + #downloadAllFiles() { + $('#loader').show(); + + $.ajax({ + url: "/presupuestocliente/download_zip", + type: 'POST', + data: { presupuesto_id: this.presupuesto_id }, + xhrFields: { + responseType: 'blob' // importante para recibir un archivo binario + }, + success: function (blob, status, xhr) { + const disposition = xhr.getResponseHeader('Content-Disposition'); + let filename = "archivos_presupuesto.zip"; + if (disposition && disposition.indexOf('attachment') !== -1) { + const match = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(disposition); + if (match != null && match[1]) filename = match[1].replace(/['"]/g, ''); + } + + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + }, + error: function () { + popErrorAlert("Error al descargar el archivo ZIP"); + }, + complete: function () { + $('#loader').hide(); + } + }); + } + + #btnPreview() { @@ -384,7 +423,7 @@ class Resumen { const svgData = serializer.serializeToString(shapeSvgEl); const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' }); const svgUrl = URL.createObjectURL(svgBlob); - const img = new Image(shapeSvgEl.getBoundingClientRect().width,shapeSvgEl.getBoundingClientRect().height); + const img = new Image(shapeSvgEl.getBoundingClientRect().width, shapeSvgEl.getBoundingClientRect().height); img.onload = () => { const scaleFactor = 3; // Increase resolution (e.g., 2x, 3x) const canvas = document.createElement('canvas'); @@ -395,7 +434,7 @@ class Resumen { canvas.height = height * scaleFactor; const ctx = canvas.getContext('2d'); ctx.scale(scaleFactor, scaleFactor); - ctx.drawImage(img, 0,0,width,height); + ctx.drawImage(img, 0, 0, width, height); const pngUrl = canvas.toDataURL('image/png'); const downloadLink = document.createElement('a'); downloadLink.href = pngUrl;