13 Commits

Author SHA1 Message Date
f44da0c2b7 Merge branch 'feat/config_cabezada' into 'main'
Feat/config cabezada

See merge request jjimenez/safekat!893
2025-07-25 12:14:50 +00:00
2a3cab872b Correccion de bugs 2025-07-25 14:14:21 +02:00
e642f0520a Añadido configurador 2025-07-25 13:41:40 +02:00
200e45c898 Merge branch 'feat/iskn_presupuestos' into 'main'
Feat/iskn presupuestos

See merge request jjimenez/safekat!892
2025-07-25 10:37:25 +00:00
266241b260 Añadida a vista de presupuesto admin iskn. Asignacion automatica al confirmar presupuesto 2025-07-25 12:33:30 +02:00
94222790df Añadimos ISKN 2025-07-25 11:39:20 +02:00
e43a7b7304 Merge branch 'mod/remove_xml_export' into 'main'
Mod/remove xml export

See merge request jjimenez/safekat!891
2025-07-23 12:08:12 +00:00
5e954ae350 Implementado en Bubok 2025-07-23 14:02:59 +02:00
afe9f6e0e3 Corregida rutas 2025-07-23 11:58:18 +02:00
e65e942e58 Refactorizacion 2025-07-23 11:56:43 +02:00
065903be2f Cambio de naming en labels 2025-07-22 22:53:07 +02:00
9ed397e9ad Desacople e inyeccion de dependencias 2025-07-22 16:01:34 +02:00
a1aaa095d4 Eliminado mecanismo de pasar a XML pedido 2025-07-21 21:55:27 +02:00
43 changed files with 1337 additions and 937 deletions

View File

@ -14,4 +14,5 @@
"username": "sk_imn"
}
]
}

View File

@ -72,4 +72,21 @@ class Paths
* is used when no value is provided to `Services::renderer()`.
*/
public string $viewDirectory = __DIR__ . '/../Views';
/**
* Ruta base relativa dentro de WRITEPATH donde se almacenan
* los archivos asociados a presupuestos.
*
* Esta ruta se utiliza como base para componer las rutas
* completas tanto locales como remotas (SFTP) de ficheros
* relacionados con cada presupuesto.
*
* Ejemplo:
* Si el ID del presupuesto es 123 y el nombre del archivo es "documento.pdf",
* la ruta final será: storage/presupuestos/123/documento.pdf
*
* Se recomienda mantener esta ruta fuera de `public/` por razones de seguridad
* y utilizar controladores para servir los archivos si se desea acceso web.
*/
public string $presupuestosPath = 'storage/presupuestos';
}

View File

@ -1,29 +0,0 @@
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class PedidoXML extends BaseConfig
{
public string $host;
public int $port;
public string $username;
public string $password;
public string $base_dir; # FTP server directory
public bool $xml_enabled;
public int $id_offset;
public function __construct() {
parent::__construct();
$this->host = env("HIDRIVE_HOST","10.5.0.6");
$this->port = env("HIDRIVE_PORT",21);
$this->username = env("HIDRIVE_USER","admin");
$this->password = env("HIDRIVE_PASS","A77h3b0X4OA2rOYAf4w2");
$this->base_dir = env("HIDRIVE_PATH_ROOT","/home/admin/safekat"); # FTP server directory
$this->xml_enabled = env("FTP_XML_ENABLED",false);
$this->id_offset = env("XML_OFFSET_CUSTOMER_ID",1000000);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class PresupuestoSFTP extends BaseConfig
{
public string $host;
public int $port;
public string $username;
public string $password;
public string $base_dir;
public string $remote_base_dir = 'ficheros'; // subcarpeta específica para presupuestos
public int $id_offset;
public function __construct()
{
parent::__construct();
$this->host = env("HIDRIVE_FILES_HOST", "sftp.hidrive.ionos.com");
$this->port = (int) env("HIDRIVE_FILES_PORT", 22);
$this->username = env("HIDRIVE_FILES_USER");
$this->password = env("HIDRIVE_FILES_PASS");
$this->id_offset = (int) env("BUDGET_FILES_OFFSET_ID", 1000000);
// Directorio base remoto: /users/usuario/dominio
$domain = parse_url(env("app.baseURL"), PHP_URL_HOST);
$this->base_dir = "/users/{$this->username}/{$domain}";
}
/**
* Devuelve la ruta completa del directorio remoto para un presupuesto
*/
public function getRemoteDirForPresupuesto(int $presupuestoId): string
{
return "{$this->base_dir}/{$this->remote_base_dir}/" . ($presupuestoId + $this->id_offset);
}
}

View File

@ -500,7 +500,6 @@ $routes->group('pedidos', ['namespace' => 'App\Controllers\Pedidos'], function (
$routes->post('cambiarestado', 'Pedido::cambiarEstado', ['as' => 'cambiarEstadoPedido']);
$routes->post('update/(:any)', 'Pedido::update/$1', ['as' => 'actualizarPedido']);
$routes->post('insertfactura', 'Pedido::addFactura');
$routes->get('xml/(:num)', 'Pedido::get_xml_pedido/$1', ['as' => 'getXMLPedido']);
$routes->post('produccion/(:num)', 'Pedido::to_produccion/$1', ['as' => 'toProduccion']);
$routes->get('pedidosCliente', 'Pedido::tablaClienteForm');
$routes->get('getSumCliente/(:num)', 'Pedido::obtenerTotalPedidosCliente/$1');

View File

@ -69,4 +69,10 @@ $routes->group('importador', ['namespace' => 'App\Controllers\Presupuestos'], fu
$routes->get('getencuadernacion', 'Importadorpresupuestos::getEncuadernacionList');
$routes->get('getpresupuestodata', 'Importadorpresupuestos::getPresupuesto', ['as' => 'getPresupuesto']);
$routes->post('importar', 'Importadorpresupuestos::importarPresupuesto');
});
$routes->group('files', ['namespace' => 'App\Controllers\Presupuestos'], function($routes) {
$routes->post('get_files', 'PresupuestoFicheroController::get_files', ['as' => 'getFiles']);
$routes->post('upload_files', 'PresupuestoFicheroController::upload_files', ['as' => 'uploadFiles']);
$routes->post('download_zip', 'PresupuestoFicheroController::download_zip', ['as' => 'downloadFilesZipped']);
});

View File

@ -1,4 +1,5 @@
<?php
namespace App\Controllers\Importadores;
use App\Controllers\BaseResourceController;
@ -393,7 +394,7 @@ class ImportadorBubok extends BaseResourceController
'gramajeCubierta' => in_array($encuadernadoId, [1, 3]) ? 150 : 300, // 150 gramos para "fresado tapa dura" y "cosido tapa dura"
'solapas' => !empty($producto->cover->type->consolapas) ? 80 : 0,
'acabado' => $acabadoId,
'cabezada' => 'WHI',
'cabezada' => model('\App\Models\Configuracion\ConfigVariableModel')->getCabezadaDefault(),
'lomoRedondo' => 0
],
'guardas' => [],
@ -456,85 +457,33 @@ class ImportadorBubok extends BaseResourceController
], 400);
}
// Descarga y subida de archivos al SFTP
$presupuestoFicheroModel = model('App\Models\Presupuestos\PresupuestoFicheroModel');
$ftp = new \App\Libraries\SafekatFtpClient();
// ✅ Importar archivos desde URLs y subir al SFTP
$uploaderService = new \App\Services\PresupuestoUploaderService(
new \App\Libraries\SftpClientWrapper(config('PresupuestoSFTP')),
model(\App\Models\Presupuestos\PresupuestoFicheroModel::class),
config('PresupuestoSFTP')
);
$archivoUrls = [
'cover' => $producto->cover->file ?? null,
'body' => $producto->body->file ?? null,
];
foreach ($archivoUrls as $tipo => $url) {
if (!$url)
continue;
$resultadoArchivos = $uploaderService->importarArchivosDesdeUrlsBubok($response['sk_id'], $archivoUrls);
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());
if (!$resultadoArchivos['success']) {
log_message('warning', 'Errores al importar archivos desde Bubok: ' . print_r($resultadoArchivos['errores'], true));
}
return $this->respond([
'status' => 200,
'data' => [
'sk_id' => $response['sk_id'],
'sk_url' => $response['sk_url'] ?? null
'sk_url' => $response['sk_url'] ?? null,
'archivos_subidos' => $resultadoArchivos['archivos_subidos'],
'errores_archivos' => $resultadoArchivos['errores']
]
]);
} catch (\Throwable $e) {
return $this->respond([
'status' => 500,
@ -544,8 +493,4 @@ class ImportadorBubok extends BaseResourceController
]);
}
}
}

View File

@ -272,7 +272,7 @@ class ImportadorCatalogo extends BaseResourceController
'gramajeCubierta' => $libro->cubierta_gramaje,
'solapas' => $libro->cubierta_ancho_solapas,
'acabado' => $libro->cubierta_acabado_id,
'cabezada' => 'WHI',
'cabezada' => model('\App\Models\Configuracion\ConfigVariableModel')->getCabezadaDefault(),
'lomoRedondo' => 0
],

View File

@ -6,7 +6,6 @@ use App\Controllers\Facturacion\Facturas;
use App\Entities\Pedidos\PedidoEntity;
use App\Models\Collection;
use App\Models\Pedidos\PedidoModel;
use App\Services\PedidoXMLService;
use App\Services\ProductionService;
use Hermawan\DataTables\DataTable;
use CodeIgniter\I18n\Time;
@ -614,12 +613,7 @@ class Pedido extends \App\Controllers\BaseResourceController
$pedidoEntity->created_at_footer = $pedidoEntity->created_at ? date(' H:i d/m/Y', strtotime($pedidoEntity->created_at)) : '';
$pedidoEntity->updated_at_footer = $pedidoEntity->updated_at ? date(' H:i d/m/Y', strtotime($pedidoEntity->updated_at)) : '';
}
public function get_xml_pedido($pedido_id)
{
$data = PedidoXMLService::generate_xml($pedido_id);
// $xml_service = new PedidoXMLService($this->model);
return $this->respond($data);
}
public function to_produccion($pedido_id)
{

View File

@ -0,0 +1,184 @@
<?php
namespace App\Controllers\Presupuestos;
use App\Controllers\BaseController;
use App\Services\PresupuestoUploaderService;
use App\Libraries\SftpClientWrapper;
use App\Models\Presupuestos\PresupuestoFicheroModel;
use Config\PresupuestoSFTP;
class PresupuestoFicheroController extends BaseController
{
public function get_files()
{
// Aceptar solo POST (puedes cambiar a GET si lo necesitas)
if ($this->request->getMethod(true) !== 'POST') {
return $this->response->setStatusCode(405)->setJSON(['message' => 'Método no permitido']);
}
$presupuesto_id = $this->request->getPost('presupuesto_id') ?? 0;
$model = model('App\Models\Presupuestos\PresupuestoFicheroModel');
$files = $model->getFiles($presupuesto_id);
$result = [];
foreach ($files as $file) {
$relativePath = $file->file_path;
$fullPath = WRITEPATH . ltrim($relativePath, '/');
$relativePath = $file->file_path;
$basename = basename($relativePath); // solo el nombre del archivo
$result[] = (object) [
'name' => $file->nombre,
'size' => file_exists(WRITEPATH . $relativePath) ? filesize(WRITEPATH . $relativePath) : 0,
'hash' => $basename
];
}
return $this->response->setJSON($result);
}
public function upload_files()
{
$request = service('request');
$model = model('App\Models\Presupuestos\PresupuestoFicheroModel');
$files = $request->getFiles()['file'] ?? [];
$presupuesto_id = $request->getPost('presupuesto_id');
$old_files = json_decode($request->getPost('oldFiles') ?? '[]');
if (!is_array($files)) {
$files = [$files];
}
// Servicio de subida (con SFTP)
$service = new \App\Services\PresupuestoUploaderService(
new \App\Libraries\SftpClientWrapper(config('PresupuestoSFTP')),
$model,
config('PresupuestoSFTP')
);
// Borrar ficheros eliminados por el usuario (BD y remoto)
$service->removeFromRemote($presupuesto_id);
$numDeleted = $model->deleteMissingFiles($presupuesto_id, $old_files);
$results = [];
foreach ($files as $file) {
if (!$file->isValid()) {
$results[] = [
'name' => $file->getName(),
'status' => 'invalid',
'message' => $file->getErrorString()
];
continue;
}
$newName = $model->saveFileInBBDD(
$presupuesto_id,
$file->getClientName(),
$file->getClientExtension(),
auth()->id()
);
// Crear directorio si no existe
$uploadDir = dirname($model->getAbsolutePath($presupuesto_id, $newName));
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$file->move($uploadDir, $newName);
$results[] = [
'name' => $file->getClientName(),
'status' => 'uploaded'
];
}
// Subida al SFTP
$sftpResult = $service->uploadToRemote($presupuesto_id);
// ✅ Contar totales para mostrar en el frontend
$numUploaded = count(array_filter($results, fn($f) => $f['status'] === 'uploaded'));
$numErrores = count(array_filter($results, fn($f) => $f['status'] !== 'uploaded'));
if (!$sftpResult['success']) {
return $this->response->setJSON([
'message' => 'Error en la subida de algunos archivos.',
'summary' => [
'subidos_ok' => $numUploaded,
'errores_locales' => $numErrores,
'errores_remotos' => count(array_filter($sftpResult['files'], fn($f) => !$f['success'])),
'borrados' => $numDeleted,
],
'details' => [
'local' => $results,
'sftp' => $sftpResult['files']
]
])->setStatusCode(500);
}
return $this->response->setJSON([
'message' => 'Archivos subidos correctamente.',
'summary' => [
'subidos_ok' => $numUploaded,
'errores_locales' => $numErrores,
'errores_remotos' => 0,
'borrados' => $numDeleted
],
'details' => [
'local' => $results,
'sftp' => $sftpResult['files']
]
]);
}
public function download_zip()
{
$presupuesto_id = $this->request->getPost('presupuesto_id');
$ot_id = $this->request->getPost('ot_id');
if (!$presupuesto_id) {
return $this->response->setStatusCode(400)->setBody('Presupuesto ID requerido');
}
$prefijo = (!empty($ot_id) && is_numeric($ot_id)) ? "OT_{$ot_id}" : null;
$service = new \App\Services\PresupuestoUploaderService(
new \App\Libraries\SftpClientWrapper(config('PresupuestoSFTP')),
model('App\Models\Presupuestos\PresupuestoFicheroModel'),
config('PresupuestoSFTP')
);
$result = $service->downloadZip((int) $presupuesto_id, $prefijo);
if (!$result['success'] || empty($result['zipPath'])) {
return $this->response
->setStatusCode(500)
->setJSON(['error' => $result['message']]);
}
$zipPath = $result['zipPath'];
// Definir nombre final del ZIP para el cliente
$nombreArchivo = $prefijo
? "{$prefijo}_PRESUPUESTO_{$presupuesto_id}.zip"
: "archivos_presupuesto_{$presupuesto_id}.zip";
// Eliminar archivo ZIP tras terminar la descarga (una vez enviada la respuesta)
register_shutdown_function(function () use ($zipPath) {
if (file_exists($zipPath)) {
unlink($zipPath);
}
});
// Descargar el archivo al cliente
return $this->response
->download($zipPath, null)
->setFileName($nombreArchivo);
}
}

View File

@ -460,6 +460,8 @@ class Presupuestoadmin extends \App\Controllers\BaseResourceController
$this->viewData['tipo_impresion_id'] = $presupuestoEntity->tipo_impresion_id; // Cosido tapa blanda JJO
$this->viewData['cabezadas'] = model('\App\Models\Configuracion\ConfigVariableModel')->getCabezadasDisponibles();
$this->viewData = array_merge($this->viewData, $this->getStringsFromTipoImpresion($presupuestoEntity->tipo_impresion_id));
$this->viewData['formAction'] = route_to('updatePresupuestoAdmin', $id);
@ -587,6 +589,7 @@ class Presupuestoadmin extends \App\Controllers\BaseResourceController
$data['datosGenerales']['coleccion'] = $presupuesto->coleccion;
$data['datosGenerales']['numero_edicion'] = $presupuesto->numero_edicion;
$data['datosGenerales']['isbn'] = $presupuesto->isbn;
$data['datosGenerales']['iskn'] = $presupuesto->iskn;
$data['datosGenerales']['pais'] = $presupuesto->pais_id;
$data['datosGenerales']['pais_nombre'] = model('App\Models\Configuracion\PaisModel')->find($presupuesto->pais_id)->nombre;
$data['datosGenerales']['cliente']['id'] = $presupuesto->cliente_id;

View File

@ -133,6 +133,7 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$this->viewData['lomo_maximo_fresado_cosido'] = model('App\Models\Configuracion\ConfigVariableModel')->getVariable('lomo_maximo_fresado_cosido')->value;
$this->viewData['lomo_minimo_fresado_cosido'] = model('App\Models\Configuracion\ConfigVariableModel')->getVariable('lomo_minimo_fresado_cosido')->value;
$this->viewData['eb'] = 0;
$this->viewData['cabezadas'] = model('\App\Models\Configuracion\ConfigVariableModel')->getCabezadasDisponibles();
$this->viewData['boxTitle'] = lang('Basic.global.addNew') . ' ' . $this->viewData['pageTitle'] . ' ' . lang('Basic.global.addNewSuffix');
@ -178,6 +179,7 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$this->viewData['lomo_maximo_fresado_cosido'] = model('App\Models\Configuracion\ConfigVariableModel')->getVariable('lomo_maximo_fresado_cosido')->value;
$this->viewData['lomo_minimo_fresado_cosido'] = model('App\Models\Configuracion\ConfigVariableModel')->getVariable('lomo_minimo_fresado_cosido')->value;
$this->viewData['eb'] = $presupuestoEntity->envio_base;
$this->viewData['cabezadas'] = model('\App\Models\Configuracion\ConfigVariableModel')->getCabezadasDisponibles();
// Si se ha llamado a esta funcion porque se ha duplicado el presupuesto
// se actualiza la bbdd para que sólo ejecute algunas funciones una vez
@ -473,7 +475,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
'errors' => $errors
);
return $this->respond($data);
} else {
return $this->failUnauthorized('Invalid request', 403);
}
@ -615,7 +616,7 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
return $this->failServerError(
$return_data['exception'] . ' - ' .
$return_data['file'] . ' - ' . $return_data['line']
$return_data['file'] . ' - ' . $return_data['line']
);
}
@ -733,7 +734,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
} else {
return $return_data;
}
} catch (Exception $e) {
if ($this->request) {
if ($this->request->isAJAX())
@ -742,7 +742,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
return "Error: " . $e->getMessage();
}
}
}
@ -849,11 +848,9 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$maxSolapa = (865 - floor($anchoTotal)) / 2;
$maxSolapa = min($maxSolapa, 0.95 * $datosPedido->ancho);
return $this->respond($maxSolapa);
} else {
return $this->failUnauthorized('Invalid request', 403);
}
}
@ -874,7 +871,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
'menu' => $data,
$csrfTokenName => $newTokenHash
]);
} else {
return $this->failUnauthorized('Invalid request', 403);
}
@ -1109,7 +1105,7 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
'solapasCubierta' => intval($cubierta['solapas'] ?? 0) == 1 ? intval($cubierta['tamanioSolapas']) : 0,
'acabado' => $cubierta['acabado'] ?? 0,
'lomoRedondo' => $cubierta['lomoRedondo'] ?? 0,
'cabezada' => $cubierta['cabezada'] ?? 'WHI',
'cabezada' => $cubierta['cabezada'] ?? model('\App\Models\Configuracion\ConfigVariableModel')->getCabezadaDefault(),
];
// Sobrecubierta
@ -1301,7 +1297,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$coste_envio += ($coste_direccion->coste / $tirada[$i]);
$resultado_presupuesto['info']['totales'][$i]['coste_envio'] += $coste_direccion->coste - $coste_direccion->margen;
$resultado_presupuesto['info']['totales'][$i]['margen_envio'] += $coste_direccion->margen;
}
}
$resultado_presupuesto['coste_envio'][$i] = round($coste_envio, 2);
@ -1336,7 +1331,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
} else {
$resumen_totales = $resultado_presupuesto['info']['totales'][$i];
$resumen_totales['precio_unidad'] = round($resultado_presupuesto['precio_u'][$i], 4);
}
}
@ -1575,7 +1569,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
if (count($direccionesFP1) > 0) {
$this->guardarLineaEnvio($id, $direccionesFP1, $peso_libro, true, true, 1);
}
if (count($direccionesFP2) > 0) {
$this->guardarLineaEnvio($id, $direccionesFP2, $peso_libro, true, true, 2);
@ -1732,7 +1725,7 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$data['resumen']['base'] = $presupuesto->total_antes_descuento;
$data['resumen']['total_envio'] = round(
floatval($presupuesto->total_coste_envios) +
floatval($presupuesto->total_margen_envios),
floatval($presupuesto->total_margen_envios),
2
);
$data['resumen']['precio_unidad'] = $presupuesto->total_precio_unidad;
@ -1756,96 +1749,7 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
}
}
public function get_files()
{
// Check if the request is a POST request
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$presupuesto_id = $this->request->getPost()['presupuesto_id'] ?? 0;
$model = model('App\Models\Presupuestos\PresupuestoFicheroModel');
$files = $model->getFiles($presupuesto_id);
$result = [];
foreach ($files as $file) {
$size = filesize($file->file_path);
$splitPath = explode("presupuestos/", $file->file_path);
// se crea un objeto con el nombre del fichero y el tamaño
$obj = (object) array(
'name' => $file->nombre,
'size' => $size,
'hash' => $splitPath[1] ?? $file->file_path
);
// se añade el objeto al array
array_push($result, $obj);
}
return json_encode($result);
}
}
public function upload_files()
{
$model = model('App\Models\Presupuestos\PresupuestoFicheroModel');
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$presupuesto_id = $_POST['presupuesto_id'];
$old_files = json_decode($_POST['oldFiles']);
$ftp = new SafekatFtpClient();
// Comprobar si se han subido archivos
if (!empty($_FILES['file']) || !empty($old_files)) {
// Borrar los archivos existentes del presupuesto
$ftp->removeFiles($presupuesto_id);
$model->deleteFiles($presupuesto_id, $old_files);
if (!empty($_FILES['file'])) {
$files = $_FILES['file'];
// Iterar sobre los archivos
for ($i = 0; $i < count($files['name']); $i++) {
// Aquí puedes acceder a las propiedades del archivo
$name = $files['name'][$i];
$extension = explode('.', $files['name'][$i])[1];
$tmp_name = $files['tmp_name'][$i];
$new_name = $model->saveFileInBBDD($presupuesto_id, $name, $extension, auth()->id());
// Se sube el fichero
// Pero primero se comprueba que la carpeta presupuestos exista
if (!is_dir(WRITEPATH . 'uploads/presupuestos')) {
mkdir(WRITEPATH . 'uploads/presupuestos', 0777, true);
}
if (!is_null($new_name)) {
$path = WRITEPATH . 'uploads/presupuestos/' . $new_name;
move_uploaded_file($tmp_name, $path);
}
}
$ftp->uploadFilePresupuesto($presupuesto_id);
}
} else {
// Borrar los archivos existentes del presupuesto
$ftp->removeFiles($presupuesto_id);
$model->deleteFiles($presupuesto_id);
}
}
return json_encode(['message' => 'Archivos subidos correctamente']);
}
/***********************
*
@ -1936,6 +1840,7 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
{
if ($tipo == 'encuadernacion') {
$model = new PresupuestoEncuadernacionesModel();
$data = [
@ -2212,7 +2117,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$totalImpresion,
$margenImpresion
);
}
}
@ -2274,7 +2178,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$totalImpresion,
$margenImpresion
);
}
}
}
@ -2345,7 +2248,7 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
return $return_data;
}
$cantidad_total = intval($datosPedido->tirada);// + intval($datosPedido->merma);
$cantidad_total = intval($datosPedido->tirada); // + intval($datosPedido->merma);
// Acabado Cubierta
if (intval($datos_entrada['cubierta']['acabado']) != 0) {
@ -2385,7 +2288,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$margenServicios += round(floatval($acabadoCubierta[0]->total - $base), 2);
}
}
}
if ($lomoRedondo) {
@ -2422,7 +2324,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$base = round(floatval($base / $cantidad_total), 2) * $cantidad_total;
$totalServicios += $base;
$margenServicios += round(floatval($resultado[0]->total - $base), 2);
}
}
@ -2460,7 +2361,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$totalImpresion,
$margenImpresion
);
}
if ($coste_sobrecubierta <= 0) {
@ -2674,7 +2574,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$base = round(floatval($base / $cantidad_total), 2) * $cantidad_total;
$totalServicios += $base;
$margenServicios += round(floatval($acabadoFaja[0]->total - $base), 2);
}
}
}
@ -2729,7 +2628,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$base = round(floatval($base / $cantidad_total), 2) * $cantidad_total;
$totalServicios += $base;
$margenServicios += round(floatval($servicio->total - $base), 2);
}
$servDefectoMan = PresupuestoCLienteService::getServiciosManipuladoDefault([
@ -2872,7 +2770,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$base = round(floatval($base / $cantidad_total), 2) * $cantidad_total;
$totalServicios += $base;
$margenServicios += round(floatval($resultado[0]->total - $base), 2);
} else if ($servicio->nombre == "ferro" || $servicio->nombre == "prototipo") {
// Extra
$resultado = PresupuestoCLienteService::getServiciosExtra([
@ -2905,7 +2802,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$base = round(floatval($base / $cantidad_total), 2) * $cantidad_total;
$totalServicios += $base;
$margenServicios += round(floatval($resultado[0]->total - $base), 2);
} else if ($servicio->nombre == 'solapas_cubierta' || $servicio->nombre == 'solapas_sobrecubierta' || $servicio->nombre == 'solapas_faja') {
// Servicios manipulado
$resultado = PresupuestoCLienteService::getServiciosManipulado([
@ -2982,7 +2878,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$totalServicios += $base;
$margenServicios += round(floatval($resultado[0]->precio - $base), 2);
}
}
// Plegado de solapas grandes
@ -3222,7 +3117,6 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$margenImpresion += round($linea['margen_impresion_horas'], 2);
$margenImpresion += round($linea['margen_click_pedido'], 2);
$margenImpresion = round($margenImpresion, 2);
}
protected function calcular_lomo($lineas, $lomo_inicial)
@ -3342,8 +3236,7 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
$color = 'negro';
$model = model('App\Models\Presupuestos\PresupuestoLineaModel');
$data = $model->where('presupuesto_id', $presupuestoId)->findAll();
;
$data = $model->where('presupuesto_id', $presupuestoId)->findAll();;
foreach ($data as $linea) {
if (strpos($linea->tipo, "hq") !== false) { // $linea->tipo contains the substring "hq"
@ -3663,40 +3556,5 @@ class Presupuestocliente extends \App\Controllers\BaseResourceController
return $servicios;
}
public function download_zip()
{
$presupuesto_id = $this->request->getPost('presupuesto_id');
$ot_id = $this->request->getPost('ot_id');
if (!$presupuesto_id) {
return $this->response->setStatusCode(400)->setBody('Presupuesto ID requerido');
}
// Definir prefijo si se recibió un ot_id válido
$prefijo = (!empty($ot_id) && is_numeric($ot_id)) ? "OT_{$ot_id}" : null;
$ftpClient = new \App\Libraries\SafekatFtpClient();
try {
$zipPath = $ftpClient->downloadZipPresupuesto((int) $presupuesto_id, $prefijo);
if ($zipPath === null || !file_exists($zipPath)) {
return $this->response->setStatusCode(404)->setBody('No se encontraron archivos');
}
$nombreArchivo = $prefijo
? "{$prefijo}_PRESUPUESTO_{$presupuesto_id}.zip"
: "archivos_presupuesto_{$presupuesto_id}.zip";
return $this->response
->download($zipPath, null)
->setFileName($nombreArchivo);
} catch (\Throwable $e) {
log_message('error', $e->getMessage());
return $this->response->setStatusCode(500)->setBody('Error interno');
}
}
}

View File

@ -3,6 +3,7 @@
namespace App\Controllers\Sistema;
use CodeIgniter\Controller;
use App\Models\Presupuestos\PresupuestoFicheroModel;
class Intranet extends Controller
{
@ -11,25 +12,24 @@ class Intranet extends Controller
{
helper('file');
$resource_path = WRITEPATH . 'uploads/presupuestos/' . $resource_name;
$model = new PresupuestoFicheroModel();
$file = $model->where('file_path LIKE', "%{$resource_name}")->first();
if (file_exists($resource_path)) {
// Get the mime type of the file
$mime_type = mime_content_type($resource_path);
// Get an instance of the Response class
$response = service('response');
// Set the content type
$response->setContentType($mime_type);
// Set the output
$response->setBody(file_get_contents($resource_path));
// Send the response to the browser
$response->send();
if (!$file) {
return service('response')->setStatusCode(404)->setBody("Archivo no encontrado");
}
$resource_path = WRITEPATH . $file->file_path;
if (file_exists($resource_path)) {
$mime_type = mime_content_type($resource_path);
$response = service('response');
$response->setContentType($mime_type);
$response->setBody(file_get_contents($resource_path));
$response->send();
} else {
return service('response')->setStatusCode(404)->setBody("Archivo no encontrado");
}
}
function tickets($resource_name)
@ -54,7 +54,6 @@ class Intranet extends Controller
// Send the response to the browser
$response->send();
}
}
function orden_trabajo($ot_id, $resource_name)
{
@ -76,7 +75,6 @@ class Intranet extends Controller
// Send the response to the browser
$response->send();
}
}
function catalogo($catalogo_id, $resource_name)
@ -99,7 +97,5 @@ class Intranet extends Controller
// Send the response to the browser
$response->send();
}
}
}
}

View File

@ -15,25 +15,24 @@ use App\Models\Catalogo\CatalogoLibroModel;
use App\Services\PresupuestoService;
use CodeIgniter\Shield\Entities\User;
use App\Libraries\SftpClientWrapper;
use Config\PresupuestoSFTP;
class Test extends BaseController
{
function __construct()
{
}
function __construct() {}
public function echo()
{
echo "echo";
}
public function index()
{
}
@ -75,10 +74,8 @@ class Test extends BaseController
// Insert it
$tel_model->insert($tarifasLinea);
}
}
}
@ -224,7 +221,6 @@ class Test extends BaseController
} else {
$values = [];
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddIsknToPresupuestos extends Migration
{
public function up()
{
$this->forge->addColumn('presupuestos', [
'iskn' => [
'type' => 'VARCHAR',
'constraint' => 64,
'null' => true,
'default' => null,
'after' => 'coleccion', // o cualquier campo existente tras el cual quieras insertarlo
'collation' => 'utf8_unicode_ci',
],
]);
}
public function down()
{
$this->forge->dropColumn('presupuestos', 'iskn');
}
}

View File

@ -47,6 +47,7 @@ class PresupuestoEntity extends \CodeIgniter\Entity\Entity
"titulo" => "",
"autor" => null,
"coleccion" => null,
"iskn" => null,
"numero_edicion" => null,
"isbn" => null,
"referencia_cliente" => null,

View File

@ -35,6 +35,7 @@ return [
'coleccion' => 'Collection',
'numeroEdicion' => 'Edition number',
'isbn' => 'ISBN',
'iskn' => 'Identificador ISKN',
'referenciaCliente' => 'Customer reference',
'formatoLibro' => "Book format",
'papelFormatoId' => "Size",

View File

@ -34,7 +34,7 @@ return [
"global_prev" => "Anterior",
"global_next" => "Siguiente",
"global_save_file" => "Guardar ficheros",
"global_upload_files" => "Subir ficheros",
"global_select_files" => "Seleccionar ficheros",
"global_download_files" => "Descargar ficheros",
"global_all" => "Todos",
// LOGIN - Index

View File

@ -15,52 +15,23 @@ class SafekatFtpClient
protected string $username;
protected string $password;
protected string $base_dir;
protected bool $xml_enabled;
protected object $pedido_xml_config;
public function __construct()
{
$this->pedido_xml_config = config("PedidoXML");
$this->pedido_xml_config = config("PresupuestoSFTP");
$this->host = $this->pedido_xml_config->host;
$this->username = $this->pedido_xml_config->username;
$this->password = $this->pedido_xml_config->password;
$this->port = $this->pedido_xml_config->port;
$this->base_dir = $this->pedido_xml_config->base_dir;
$this->xml_enabled = $this->pedido_xml_config->xml_enabled;
$this->ftp = new SFTP($this->host);
}
/**
* Upload the content of $filename to the base directory declared in App\Config\FTP.php
*
* @param string $content
* @param string $filename
* @return boolean
*/
public function uploadXML(string $content, string $filename): bool
{
try {
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);
}
$this->ftp->put($remotePath . '/' . $filename, $content);
return true;
} catch (\Throwable $th) {
throw $th;
log_message('error', $th->getMessage());
return false;
}
}
public function uploadFilePresupuesto(int $presupuesto_id)
{
try {
if ($this->xml_enabled == false)
return false;
$model = model(PresupuestoFicheroModel::class);
$modelPedidoLinea = model(PedidoLineaModel::class);
$pedidoLinea = $modelPedidoLinea->findByPresupuesto($presupuesto_id);
@ -86,7 +57,6 @@ class SafekatFtpClient
public function removeFiles(int $presupuesto_id)
{
try {
// if ($this->xml_enabled == false) return false;
$model = model(PresupuestoFicheroModel::class);
$modelPedidoLinea = model(PedidoLineaModel::class);
$pedidoLinea = $modelPedidoLinea->findByPresupuesto($presupuesto_id);

View File

@ -0,0 +1,47 @@
<?php
namespace App\Libraries;
use phpseclib3\Net\SFTP;
use Config\PresupuestoSFTP;
class SftpClientWrapper
{
protected SFTP $client;
public function __construct(PresupuestoSFTP $config)
{
$this->client = new SFTP($config->host, $config->port);
$this->client->login($config->username, $config->password);
}
public function upload(string $local, string $remote): bool
{
return $this->client->put($remote, $local, SFTP::SOURCE_LOCAL_FILE);
}
public function delete(string $remote): bool
{
return $this->client->delete($remote);
}
public function exists(string $remote): bool
{
return $this->client->file_exists($remote);
}
public function mkdir(string $remote): bool
{
return $this->client->mkdir($remote, true);
}
public function chmod(string $path, int $permissions): bool
{
return $this->client->chmod($permissions, $path);
}
public function get(string $remotePath, string $localPath): bool
{
return $this->client->get($remotePath, $localPath);
}
}

View File

@ -56,4 +56,40 @@ class ConfigVariableModel extends Model
return $builder->get()->getFirstRow();
}
/**
* Devuelve solo el valor de la variable por nombre
*/
public function getValue(string $name): ?string
{
$row = $this->getVariable($name);
return $row ? $row->value : null;
}
/**
* Devuelve el valor decodificado (JSON) si aplica
*/
public function getDecodedValue(string $name): ?array
{
$value = $this->getValue($name);
$decoded = json_decode($value, true);
return (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) ? $decoded : null;
}
/**
* Devuelve las opciones disponibles de cabezadas (como array clave => langKey)
*/
public function getCabezadasDisponibles(): array
{
return $this->getDecodedValue('cabezadas_disponibles') ?? [];
}
/**
* Devuelve la cabezada por defecto, o 'WHI' si no está definida
*/
public function getCabezadaDefault(): string
{
return $this->getValue('cabezada_default') ?? 'WHI';
}
}

View File

@ -2,6 +2,8 @@
namespace App\Models\Presupuestos;
use Config\Paths;
class PresupuestoFicheroModel extends \App\Models\BaseModel
{
protected $table = "presupuesto_ficheros";
@ -23,16 +25,42 @@ class PresupuestoFicheroModel extends \App\Models\BaseModel
public static $labelField = "nombre";
/**
* Devuelve la ruta relativa del archivo dentro de WRITEPATH.
*
* @param int $presupuesto_id
* @param string $filename
* @return string
*/
public function getRelativePath(int $presupuesto_id, string $filename): string
{
return config(Paths::class)->presupuestosPath . '/' . $presupuesto_id . '/' . $filename;
}
/**
* Devuelve la ruta absoluta en el sistema de archivos del servidor.
*
* @param int $presupuesto_id
* @param string $filename
* @return string
*/
public function getAbsolutePath(int $presupuesto_id, string $filename): string
{
return WRITEPATH . $this->getRelativePath($presupuesto_id, $filename);
}
public function saveFileInBBDD($presupuesto_id, $filename, $extension, $user_id)
{
try {
$new_filename = $this->generateFileHash($filename) . '.' . $extension;
$relativePath = $this->getRelativePath($presupuesto_id, $new_filename);
$this->db->table($this->table . " t1")
->set('presupuesto_id', $presupuesto_id)
->set('nombre', $filename)
->set('file_path', WRITEPATH . 'uploads/presupuestos/' . $new_filename)
->set('file_path', $relativePath)
->set('upload_by', $user_id)
->set('upload_at', date('Y-m-d H:i:s'))
->insert();
@ -54,8 +82,9 @@ class PresupuestoFicheroModel extends \App\Models\BaseModel
// se comprueba que el $file->nombre no sea igual a ninguno de los elementos del array $old_files
if (!in_array($file->nombre, $old_files)) {
if (file_exists($file->file_path)) {
unlink($file->file_path);
$fullPath = WRITEPATH . $file->file_path;
if (file_exists($fullPath)) {
unlink($fullPath);
}
$this->db
@ -76,20 +105,23 @@ class PresupuestoFicheroModel extends \App\Models\BaseModel
->table($this->table . " t1")
->where('presupuesto_id', $presupuesto_id_origen)->get()->getResult();
if ($files) {
foreach ($files as $file) {
$hash = $this->generateFileHash($file->nombre);
// se copia el fichero a la nueva ubicación
if (!file_exists(WRITEPATH . $file->file_path)) {
copy($file->file_path, WRITEPATH . 'uploads/presupuestos/' . $hash);
$originalPath = WRITEPATH . $file->file_path;
$newPath = 'uploads/presupuestos/' . $hash;
if (file_exists($originalPath)) {
copy($originalPath, WRITEPATH . $newPath);
}
$this->db->table($this->table . " t1")
->set('presupuesto_id', $presupuesto_id_destino)
->set('nombre', $file->nombre)
->set('file_path', WRITEPATH . 'uploads/presupuestos/' . $hash)
->set('file_path', $newPath)
->set('upload_by', auth()->user()->id)
->set('upload_at', date('Y-m-d H:i:s'))
->insert();
@ -105,6 +137,30 @@ class PresupuestoFicheroModel extends \App\Models\BaseModel
->where('presupuesto_id', $presupuesto_id)->get()->getResult();
}
public function deleteMissingFiles(int $presupuesto_id, array $keepNames = [])
{
$files = $this->getFiles($presupuesto_id);
$deletedCount = 0;
foreach ($files as $file) {
if (!in_array($file->nombre, $keepNames)) {
$fullPath = WRITEPATH . $file->file_path;
if (file_exists($fullPath)) {
unlink($fullPath);
}
$this->db->table($this->table)
->where('presupuesto_id', $presupuesto_id)
->where('nombre', $file->nombre)
->delete();
$deletedCount++;
}
}
return $deletedCount;
}
/**
* Función para convertir el nombre y extensión de un fichero en un hash único
@ -117,6 +173,4 @@ class PresupuestoFicheroModel extends \App\Models\BaseModel
{
return hash('sha256', $filename);
}
}

View File

@ -67,6 +67,7 @@ class PresupuestoModel extends \App\Models\BaseModel
"titulo",
"autor",
"coleccion",
"iskn",
"numero_edicion",
"isbn",
"referencia_cliente",
@ -139,7 +140,7 @@ class PresupuestoModel extends \App\Models\BaseModel
'lomo_redondo',
'cabezada',
'envio_base',
'direcciones_fp_checks',
'direcciones_fp_checks',
];
protected $returnType = "App\Entities\Presupuestos\PresupuestoEntity";
@ -401,15 +402,27 @@ class PresupuestoModel extends \App\Models\BaseModel
}
}
function confirmarPresupuesto($presupuesto_id)
/**
* Confirma un presupuesto (cambia su estado a 'confirmado') y,
* si no tiene asignado un ISKN, lo genera y lo asigna automáticamente.
*
* @param int $presupuesto_id ID del presupuesto a confirmar.
* @return void
*/
public function confirmarPresupuesto($presupuesto_id)
{
// Cambiar el estado del presupuesto a '2' (confirmado)
$this->db
->table($this->table . " t1")
->where('t1.id', $presupuesto_id)
->set('t1.estado_id', 2)
->update();
// Si existe y aún no tiene ISKN asignado, lo generamos y asignamos
$this->asignarIskn($presupuesto_id);
}
function insertarPresupuestoCliente($id, $tirada, $data, $data_cabecera, $extra_info, $resumen_totales, $iva_reducido, $excluir_rotativa, $tiradas_alternativas)
{
@ -501,8 +514,8 @@ class PresupuestoModel extends \App\Models\BaseModel
'total_antes_descuento' => round(
$totalCostes + $totalMargenes +
$resumen_totales['coste_envio'] + $resumen_totales['margen_envio'] +
$data['envio_base'],
$resumen_totales['coste_envio'] + $resumen_totales['margen_envio'] +
$data['envio_base'],
2
),
'total_descuento' => 0,
@ -511,25 +524,25 @@ class PresupuestoModel extends \App\Models\BaseModel
'total_precio_unidad' => $resumen_totales['precio_unidad'],
'total_presupuesto' => round(
$totalCostes + $totalMargenes +
$resumen_totales['coste_envio'] + $resumen_totales['margen_envio'] +
$data['envio_base'],
$resumen_totales['coste_envio'] + $resumen_totales['margen_envio'] +
$data['envio_base'],
2
),
'total_aceptado' => round(
$totalCostes + $totalMargenes +
$resumen_totales['coste_envio'] + $resumen_totales['margen_envio'] +
$data['envio_base'],
$resumen_totales['coste_envio'] + $resumen_totales['margen_envio'] +
$data['envio_base'],
2
),
'total_factor' => round(
($totalCostes + $totalMargenes) /
$resumen_totales['sumForFactor'],
$resumen_totales['sumForFactor'],
2
),
'total_factor_ponderado' => round(
($totalCostes + $totalMargenes) /
$resumen_totales['sumForFactorPonderado'],
$resumen_totales['sumForFactorPonderado'],
2
),
@ -635,7 +648,6 @@ class PresupuestoModel extends \App\Models\BaseModel
'gramaje' => intval($data['faja']['gramaje']),
);
}
}
$json = json_encode($values);
return $json;
@ -915,5 +927,19 @@ class PresupuestoModel extends \App\Models\BaseModel
]);
}
public function asignarIskn(int $presupuesto_id): bool
{
$presupuesto = $this->find($presupuesto_id);
// Si no existe o ya tiene ISKN, no lo modificamos
if (!$presupuesto || !empty($presupuesto->iskn)) {
return false;
}
return $this->update($presupuesto_id, [
'iskn' => model('App\Models\Catalogo\IdentificadorIsknModel')->newIskn(),
'updated_at' => date('Y-m-d H:i:s'),
'user_update_id' => auth()->id(),
]);
}
}

View File

@ -1,222 +0,0 @@
<?php
namespace App\Services;
use DOMDocument;
use App\Libraries\SafekatFtpClient;
use CodeIgniter\Config\BaseService;
use App\Models\Pedidos\PedidoModel;
use App\Models\Presupuestos\PresupuestoModel;
class PedidoXMLService extends BaseService
{
public static function get_pedido_presupuesto(int $pedido_id)
{
$data_xml = [];
$pedidoModel = model(PedidoModel::class);
$presupuestoModel = model(PresupuestoModel::class);
$data_xml['pedido_cliente_presupuesto'] = $pedidoModel->getPedidoClientePresupuesto($pedido_id);
$data_xml['pedido_presupuesto_direcciones'] = $pedidoModel->getPedidoPresupuestoDirecciones($pedido_id);
$data_xml['pedido_presupuesto_lineas'] = $pedidoModel->getPedidoPresupuestoLineas($pedido_id);
$servicios = $presupuestoModel->getServiciosPresupuesto($data_xml['pedido_cliente_presupuesto']->presupuestoId);
$data_xml['servicios'] = $servicios;
$data_xml['preimpresion'] = PedidoXMLService::parse_servicio_preimpresion($servicios['preimpresion']);
$data_xml["acabado"] = PedidoXMLService::parse_servicio_acabado($servicios['acabado']);
$data_xml["binding"] = PedidoXMLService::get_binding_code($data_xml['pedido_cliente_presupuesto']->codigoTipoImpresion,$data_xml['pedido_cliente_presupuesto']->solapas);
return $data_xml;
}
protected static function parse_servicio_acabado(array $data_xml_servicios_acabado)
{
$xml_element = [];
$service_xml_key_value = [
"ShrinkWrapping" => fn($nombre) => str_contains($nombre,"retractilado"),
"Finish" => fn($nombre) => str_contains($nombre,"brillo"),
"PlakeneT" =>fn($nombre) => str_contains($nombre,"plakene traslúcido"),];
foreach($data_xml_servicios_acabado as $servicio_acabado)
{
$service_name = strtolower($servicio_acabado->nombre);
foreach ($service_xml_key_value as $key => $value) {
$xml_element[$key] = $value($service_name) ? 1 : 0 ;
}
}
return $xml_element;
}
protected static function parse_servicio_preimpresion(array $data_xml_servicios_preimpresion)
{
$xml_element = [];
$service_xml_key_value = [
"Urgent" => fn($nombre) => str_contains($nombre,"Pedido urgente"),
"Prototype" => fn($nombre) => str_contains($nombre,"Prototipo"),
"Layout" =>fn($nombre) => str_contains($nombre,"Maquetación"),
"Correction" =>fn($nombre) => str_contains($nombre,"Corrección ortográfica"),
// "Review" =>fn($nombre) => str_contains($nombre,"Revisión Profesional de archivo"),
"Design" =>fn($nombre) => str_contains($nombre,'Diseño de Cubierta'),
];
foreach($data_xml_servicios_preimpresion as $servicio_pre)
{
$service_name = $servicio_pre->nombre;
foreach ($service_xml_key_value as $key => $value) {
$value_service = $value($service_name) ? 1 : 0 ;
if( $value_service){
$xml_element[$key] = $servicio_pre->precio ;
}else if(!isset($xml_element[$key])){
$xml_element[$key] = $value_service;
}
}
}
return $xml_element;
}
public static function generate_xml($pedido_id)
{
$papel_formato_ancho = 0;
$papel_formato_alto = 0;
$data = PedidoXMLService::get_pedido_presupuesto($pedido_id);
$xml = new DOMDocument('1.0', 'utf-8');
$xml_order_el = $xml->createElement('Order');
$xml_header_el = $xml->createElement('Header');
$offset_pedido_id = env('XML_OFFSET_CUSTOMER_ID',1000000) + $data["pedido_cliente_presupuesto"]->pedidoId;
$xml_header_el->appendChild($xml->createElement('CustomerCode', $data["pedido_cliente_presupuesto"]->presupuestoClienteId));
$xml_header_el->appendChild($xml->createElement('CodeNode', env('NODE_CODE_XML','SFK')));
$xml_header_el->appendChild($xml->createElement('ExternId', $offset_pedido_id));
$xml_header_el->appendChild($xml->createElement('NumProducts', 1));
$xml_header_el->appendChild($xml->createElement('Date', now_db()));
$xml_order_el->appendChild($xml_header_el);
$xml_products_el = $xml->createElement('Products');
$xml_product_el = $xml->createElement('Product');
$xml_product_el->appendChild($xml->createElement('ItemId', $offset_pedido_id));
$xml_product_el->appendChild($xml->createElement('Quantity', $data["pedido_cliente_presupuesto"]->tirada));
$xml_product_el->appendChild($xml->createElement('Title', $data["pedido_cliente_presupuesto"]->titulo));
$xml_product_el->appendChild($xml->createElement('Pages', $data["pedido_cliente_presupuesto"]->paginas));
$xml_product_el->appendChild($xml->createElement('Reprint', $data["pedido_cliente_presupuesto"]->inc_rei ?? 0));
if ($data["pedido_cliente_presupuesto"]->papel_formato_personalizado) {
$papel_formato_ancho = $data["pedido_cliente_presupuesto"]->papelAnchoPersonalidado;
$papel_formato_alto = $data["pedido_cliente_presupuesto"]->papelAltoPersonalidado;
} else {
$papel_formato_ancho = $data["pedido_cliente_presupuesto"]->lgPapelFormatoAncho;
$papel_formato_alto = $data["pedido_cliente_presupuesto"]->lgPapelFormatoAlto;
}
$xml_product_el->appendChild($xml->createElement('Width', $papel_formato_ancho));
$xml_product_el->appendChild($xml->createElement('Height', $papel_formato_alto));
$presupuestoLineaTipoCubierta = null;
$xml_presupuesto_lineas_el = $xml->createElement('Lines');
## Iterate throught presupuesto_lineas
foreach ($data["pedido_presupuesto_lineas"] as $row) {
if (str_contains($row->tipo, "rot") || str_contains($row->tipo, "bn") || str_contains($row->tipo, "color")) {
$colorInterior = PedidoXMLService::get_color_interior($row);
$xmlInside = $xml->createElement('Inside');
$xmlInside->appendChild($xml->createElement('TypeOfPrint', $colorInterior));
$xmlInside->appendChild($xml->createElement('HQ', str_contains($row->tipo, 'hq') ? 1 : 0));
$xmlInside->appendChild($xml->createElement('Pages', $row->paginas));
$xmlInside->appendChild($xml->createElement('Paper', $row->papelCode));
$xmlInside->appendChild($xml->createElement('Weight', $row->gramaje));
$xml_presupuesto_lineas_el->appendChild($xmlInside);
} else if (str_contains($row->tipo, "lp_cubierta") ) {//|| str_contains($row->tipo, "sobrecubierta")
//? If both exists presupuestoLineaTipoCubierta is override by sobreCubierta making null and not adding
$papelCubiertaCode = $row->papelCode;
$papelCubiertaGramaje = $row->gramaje;
$presupuestoLineaTipoCubierta = $row->tipo == "lp_cubierta" ? $row : null;
}
}
$xml_product_el->appendChild($xml_presupuesto_lineas_el);
if ($presupuestoLineaTipoCubierta) {
$containsTarifaAcabadoBrillo = isset($data['acabado']['Finish']) ? true : false;
if ($containsTarifaAcabadoBrillo) {
$acabado = "brillo";
} else {
$acabado = "mate";
}
$xmlCover = $xml->createElement('Cover');
$xmlCover->appendChild($xml->createElement('Sides', $presupuestoLineaTipoCubierta->paginas / 2));
$xmlCover->appendChild($xml->createElement('Paper', $presupuestoLineaTipoCubierta->papelCode));
$xmlCover->appendChild($xml->createElement('Weight', $presupuestoLineaTipoCubierta->gramaje));
$xmlCover->appendChild($xml->createElement('Flaps', $data["pedido_cliente_presupuesto"]->solapas));
$xmlCover->appendChild($xml->createElement('WidthFlaps', $data["pedido_cliente_presupuesto"]->solapas_ancho));
$xmlCover->appendChild($xml->createElement('Finish', $acabado));
$xml_product_el->appendChild($xmlCover);
}
$xml_product_el->appendChild($xml->createElement('Binding', $data['binding']));
$xml_services_el = $xml->createElement('Services');
$xml_services_el->appendChild($xml->createElement('Bookmark', $data["pedido_cliente_presupuesto"]->marcapaginas));
foreach ($data['preimpresion'] as $key => $value) {
$xml_services_el->appendChild($xml->createElement($key, $value));
}
foreach ($data['acabado'] as $key => $value) {
$xml_services_el->appendChild($xml->createElement($key, $value));
}
$xml_product_el->appendChild($xml_services_el);
$xml_envios_el = $xml->createElement('Shipments');
foreach ($data["pedido_presupuesto_direcciones"] as $pedido_presupuesto_direccion) {
$xml_envio_el = $xml->createElement('Shipment');
$xml_envio_el->appendChild($xml->createElement('Qty', $pedido_presupuesto_direccion->cantidad));
$xml_envio_el->appendChild($xml->createElement('Price', $pedido_presupuesto_direccion->precio));
$xml_envio_el->appendChild($xml->createElement('Attention', $pedido_presupuesto_direccion->att));
$xml_envio_el->appendChild($xml->createElement('Email', $pedido_presupuesto_direccion->email));
$xml_envio_el->appendChild($xml->createElement('Address', $pedido_presupuesto_direccion->direccion));
$xml_envio_el->appendChild($xml->createElement('Province', $pedido_presupuesto_direccion->provincia));
$xml_envio_el->appendChild($xml->createElement('City', $pedido_presupuesto_direccion->municipio));
$xml_envio_el->appendChild($xml->createElement('Zip', $pedido_presupuesto_direccion->cp));
$xml_envio_el->appendChild($xml->createElement('CountryCode', $pedido_presupuesto_direccion->paisCode3));
$xml_envio_el->appendChild($xml->createElement('Telephone', $pedido_presupuesto_direccion->telefono));
$xml_envios_el->appendChild($xml_envio_el);
}
$xml_product_el->appendChild($xml_envios_el);
$xml_product_el->appendChild($xml->createElement('Comments', $data["pedido_cliente_presupuesto"]->comentarios_safekat));
$xml_product_el->appendChild($xml->createElement('CommentsClient', $data["pedido_cliente_presupuesto"]->comentarios_cliente));
$xml_products_el->appendChild($xml_product_el);
$xml_order_el->appendChild($xml_products_el);
$xml->appendChild($xml_order_el);
$file_has_suffix = hash('sha512',$offset_pedido_id);
$file_name = PedidoXMLService::generate_xml_file_name($file_has_suffix);
$ftp = new SafekatFtpClient();
$ftp->uploadXML($xml->saveXML(),$file_name);
return $data;
}
protected static function generate_xml_file_name(string $hash) : string
{
return implode("",["SafekatNew_",$hash,".xml"]);
}
protected static function get_binding_code(string $tipo_impresion_nombre,bool $solapas) : ?string
{
$solapa = $solapas ? '1' : '0';
$key = implode("_",[$tipo_impresion_nombre,$solapa]);
$xml_mapping_binding =
[
"libroFresadoTapaBlanda_0" => 'RF',
"libroFresadoTapaBlanda_1" => 'RFS',
"libroCosidoTapaBlanda_0" => 'RCHV',
"libroCosidoTapaBlanda_1" => 'RCHVS',
"libroGrapado_0" => 'CC2',
"libroGrapado_1" => 'CC2S',
"libroCosidoTapaDura_0" => 'TDC',
"libroCosidoTapaDura_1" => 'TDC',
"libroFresadoTapaDura_0" => 'RDF',
"libroFresadoTapaDura_1" => 'RDF',
"libroEspiralTapaBlanda_0" => 'ESP',
"libroEspiralTapaBlanda_1" => 'ESP',
"libroWireoTapaBlanda_0" => 'WIO',
"libroWireoTapaBlanda_1" => 'WIO',
];
return $xml_mapping_binding[$key] ?? null;
}
protected static function get_color_interior($pre_linea): ?string
{
$color_interior = null;
$bn_tipo_array = ['lp_bn', 'lp_bnhq', 'lp_rot_bn'];
$color_tipo_array = ['lp_color', 'lp_color_hq', 'lp_rot_color'];
if (in_array($pre_linea->tipo, $bn_tipo_array)) {
$color_interior = "bn";
};
if (in_array($pre_linea->tipo, $color_tipo_array)) {
$color_interior = "color";
};
return $color_interior;
}
}

View File

@ -1903,7 +1903,6 @@ class PresupuestoService extends BaseService
"user_updated_id" => auth()->user()->id,
];
$id_linea = $model_pedido_linea->insert($data_pedido_linea);
//PedidoXMLService::generate_xml($pedido_id);
}
if ($id_linea != 0 && $pedido_id != 0) {

View File

@ -0,0 +1,306 @@
<?php
namespace App\Services;
use App\Models\Presupuestos\PresupuestoFicheroModel;
use App\Libraries\SftpClientWrapper;
use Config\PresupuestoSFTP;
class PresupuestoUploaderService
{
public function __construct(
protected SftpClientWrapper $ftp,
protected PresupuestoFicheroModel $fileModel,
protected PresupuestoSFTP $config
) {}
/**
* Sube todos los archivos asociados a un presupuesto al SFTP.
*/
public function uploadToRemote(int $presupuestoId): array
{
$remoteDir = $this->config->getRemoteDirForPresupuesto($presupuestoId);
if (!$this->ftp->exists($remoteDir)) {
if (!$this->ftp->mkdir($remoteDir, true)) {
return [
'success' => false,
'message' => "No se pudo crear el directorio remoto: $remoteDir"
];
}
$this->ftp->chmod($remoteDir, 0755);
}
$files = $this->fileModel->getFiles($presupuestoId);
$results = [];
foreach ($files as $file) {
$filename = basename($file->file_path);
$localPath = WRITEPATH . $file->file_path;
$remotePath = $remoteDir . '/' . $filename;
if (!file_exists($localPath)) {
$results[] = [
'file' => $file->nombre,
'remotePath' => $remotePath,
'success' => false,
'error' => 'Archivo local no encontrado'
];
continue;
}
$ok = $this->ftp->upload($localPath, $remotePath);
$results[] = [
'file' => $file->nombre,
'remotePath' => $remotePath,
'success' => $ok
];
}
$allOk = !in_array(false, array_column($results, 'success'));
return [
'success' => $allOk,
'files' => $results
];
}
/**
* Elimina todos los archivos actuales del presupuesto del SFTP.
*/
public function removeFromRemote(int $presupuestoId): array
{
$remoteDir = $this->config->getRemoteDirForPresupuesto($presupuestoId);
$files = $this->fileModel->getFiles($presupuestoId);
$results = [];
foreach ($files as $file) {
$filename = basename($file->file_path);
$remotePath = $remoteDir . '/' . $filename;
if ($this->ftp->exists($remotePath)) {
$deleted = $this->ftp->delete($remotePath);
$results[] = [
'file' => $file->nombre,
'remotePath' => $remotePath,
'success' => $deleted,
'message' => $deleted ? 'Eliminado correctamente' : 'Falló al eliminar'
];
} else {
$results[] = [
'file' => $file->nombre,
'remotePath' => $remotePath,
'success' => false,
'message' => 'Archivo no encontrado en el SFTP'
];
}
}
return $results;
}
/**
* Elimina del SFTP los archivos que ya no existen en la lista permitida.
*/
public function removeMissingFromRemote(int $presupuestoId, array $keepFileNames): void
{
$remoteDir = $this->config->getRemoteDirForPresupuesto($presupuestoId);
$files = $this->fileModel->getFiles($presupuestoId);
foreach ($files as $file) {
if (!in_array($file->nombre, $keepFileNames)) {
$remotePath = $remoteDir . '/' . basename($file->file_path);
if ($this->ftp->exists($remotePath)) {
$this->ftp->delete($remotePath);
}
}
}
}
/**
* Descarga archivos de SFTP y genera un ZIP temporal para el presupuesto dado.
*
* @param int $presupuestoId ID del presupuesto.
* @param string|null $prefijo Prefijo para los nombres de los archivos.
* @return array Estructura: ['success' => bool, 'message' => string, 'zipPath' => string|null]
*/
public function downloadZip(int $presupuestoId, ?string $prefijo = null): array
{
$files = $this->fileModel->getFiles($presupuestoId);
if (empty($files)) {
return [
'success' => false,
'message' => "No hay archivos en la base de datos para el presupuesto ID {$presupuestoId}.",
'zipPath' => null
];
}
$remoteDir = $this->config->getRemoteDirForPresupuesto($presupuestoId);
if (!$this->ftp->exists($remoteDir)) {
return [
'success' => false,
'message' => "El directorio remoto no existe: {$remoteDir}",
'zipPath' => null
];
}
$localTempDir = WRITEPATH . 'zip_presupuestos/' . uniqid("presupuesto_");
if (!is_dir($localTempDir) && !mkdir($localTempDir, 0777, true)) {
return [
'success' => false,
'message' => "No se pudo crear el directorio temporal en: {$localTempDir}",
'zipPath' => null
];
}
$erroresDescarga = [];
foreach ($files as $file) {
$originalName = $file->nombre ?? basename($file->file_path ?? '');
$prefixedName = $prefijo ? $prefijo . '_' . $originalName : $originalName;
$localFile = $localTempDir . '/' . $prefixedName;
$remoteFile = $remoteDir . '/' . basename($file->file_path ?? '');
if (!$this->ftp->get($remoteFile, $localFile)) {
$erroresDescarga[] = "Error al descargar: {$remoteFile}";
}
}
if (count($erroresDescarga) === count($files)) {
return [
'success' => false,
'message' => "Fallo al descargar todos los archivos:\n" . implode("\n", $erroresDescarga),
'zipPath' => null
];
}
$zipPath = $localTempDir . '.zip';
$zip = new \ZipArchive();
if (!$zip->open($zipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE)) {
return [
'success' => false,
'message' => "No se pudo crear el archivo ZIP: {$zipPath}",
'zipPath' => null
];
}
foreach (glob($localTempDir . '/*') as $localFile) {
$zip->addFile($localFile, basename($localFile));
}
$zip->close();
foreach (glob($localTempDir . '/*') as $localFile) {
unlink($localFile);
}
rmdir($localTempDir);
if (!file_exists($zipPath)) {
return [
'success' => false,
'message' => "El ZIP no fue generado correctamente.",
'zipPath' => null
];
}
return [
'success' => true,
'message' => "ZIP generado correctamente.",
'zipPath' => $zipPath
];
}
public function importarArchivosDesdeUrlsBubok(int $presupuestoId, array $urls): array
{
$resultados = [];
$errores = [];
foreach ($urls as $tipo => $url) {
if (empty($url)) {
$errores[] = "URL vacía para tipo: {$tipo}";
continue;
}
try {
// Intenta descargar el contenido del archivo desde la URL
$contenido = @file_get_contents($url);
// Si no se puede descargar, se genera un archivo de error local (no se guarda en BBDD)
if ($contenido === false || strlen($contenido) === 0) {
$mensajeError = "ERROR: No se pudo descargar el archivo remoto para $tipo desde la URL: $url";
$nombreError = 'ERROR_' . strtoupper($tipo) . '.txt';
// Ruta local en la estructura estándar (no BBDD)
$rutaError = $this->fileModel->getAbsolutePath($presupuestoId, $nombreError);
// Crear el directorio si no existe
$directorio = dirname($rutaError);
if (!is_dir($directorio)) {
mkdir($directorio, 0755, true);
}
// Guardar el archivo con el mensaje de error
file_put_contents($rutaError, $mensajeError);
// Registrar el error en el array de errores
$errores[] = $mensajeError;
continue;
}
// Nombre original y extensión del archivo descargado
$nombreOriginal = basename(parse_url($url, PHP_URL_PATH));
$extension = pathinfo($nombreOriginal, PATHINFO_EXTENSION);
// Registrar el archivo en la base de datos y obtener el nombre limpio
$nombreLimpio = $this->fileModel->saveFileInBBDD(
$presupuestoId,
$nombreOriginal,
$extension,
auth()->id()
);
if (!$nombreLimpio) {
$errores[] = "No se pudo registrar '$nombreOriginal' en la base de datos.";
continue;
}
// Obtener la ruta completa donde se guardará el archivo localmente
$rutaAbsoluta = $this->fileModel->getAbsolutePath($presupuestoId, $nombreLimpio);
// Crear el directorio si no existe
$directorio = dirname($rutaAbsoluta);
if (!is_dir($directorio)) {
mkdir($directorio, 0755, true);
}
// Guardar el archivo en el sistema de archivos local
file_put_contents($rutaAbsoluta, $contenido);
// Añadir el archivo a la lista de resultados
$resultados[] = [
'nombre' => $nombreLimpio,
'tipo' => $tipo,
'ruta_local' => $rutaAbsoluta
];
} catch (\Throwable $e) {
$errores[] = "Error inesperado procesando '$tipo': " . $e->getMessage();
}
}
// Subida de todos los archivos válidos al servidor remoto SFTP
try {
$this->uploadToRemote($presupuestoId);
} catch (\Throwable $e) {
$errores[] = "Error al subir archivos al SFTP: " . $e->getMessage();
}
// Devolver el resumen del proceso
return [
'success' => empty($errores),
'archivos_subidos' => $resultados,
'errores' => $errores
];
}
}

View File

@ -24,7 +24,7 @@
</div>
<div class="col-md-12 gap-2">
<button id="<?= $id ?>_btnUploadFiles" class="btn mt-3 btn-sm btn-primary waves-effect waves-light ml-2 ">
<span class="align-middle d-sm-inline-block d-none me-sm-1"><?= lang('App.global_upload_files') ?></span>
<span class="align-middle d-sm-inline-block d-none me-sm-1"><?= lang('App.global_select_files') ?></span>
<i class="ti ti-upload ti-xs"></i>
</button>
<button id="<?= $id ?>_btnSubmitFiles" class="btn mt-3 btn-success btn-sm waves-effect waves-light ml-2">

View File

@ -15,7 +15,7 @@
<?= view('themes/_commonPartialsBs/_alertBoxes'); ?>
<table id="tableConfigVariables" class="table table-striped table-hover" style="width: 100%;">
<table id="tableConfigVariables" class="table table-striped table-hover table-responsive" style="width: 100%; word-break: break-word;">
<thead>
<tr>
<th><?= lang('ConfigVariables.datatable.columns.name') ?></th>
@ -55,11 +55,13 @@
<div class="row g-4">
<div class="col-12 mb-0">
<label for="value" class="form-label"><?= lang('ConfigVariables.form.value') ?></label>
<input type="number" min=0 id="value" class="form-control">
<div id="value-wrapper">
</div>
</div>
<div class="col-12 mb-0">
<label for="description" class="form-label"><?= lang('ConfigVariables.form.description') ?></label>
<textarea type="text" rows="4" cols="10" id="description" class="form-control"></textarea>
<textarea type="text" rows="4" cols="10" id="description" class="form-control"></textarea>
</div>
</div>
</div>
@ -75,4 +77,13 @@
<?= $this->endSection() ?>
<?= $this->section("additionalExternalJs") ?>
<script type="module" src="<?= versioned_asset('assets/js/safekat/pages/configuracion/variables.js') ?>"></script>
<script>
// Pasamos las opciones al frontend desde PHP
window.CABEZADAS_OPCIONES = <?= json_encode([
'WHI' => lang('Presupuestos.blanca'),
'GRE' => lang('Presupuestos.verde'),
'BLUE' => lang('Presupuestos.azul'),
'REDYEL' => lang('Presupuestos.rojaAmarilla'),
]) ?>;
</script>
<?= $this->endSection() ?>

View File

@ -222,46 +222,39 @@
</div>
<?php if ($tipo_impresion_id == 1 || $tipo_impresion_id == 3): ?>
<div class="col-md-12 col-lg-2 px-4">
</div>
<div class="col-md-12 col-lg-2 px-4">
</div>
<div class="col-md-12 col-lg-2 px-4">
<label id="label_compLomoRedondo" for="compLomoRedondo" class="form-label">
<?= lang('Presupuestos.lomoRedondo') ?>*
</label>
<select id="compLomoRedondo" name="lomo_redondo" service-id="<?= $serviciosAutomaticos['lomo_redondo'] ?>"
class="form-control select2bs2 comp_cubierta_items" style="width: 100%;">
<option value="0">
<p>
<?= lang('Presupuestos.no') ?>
</p>
</option>
<option value="1">
<p>
<?= lang('Presupuestos.si') ?>
</p>
</option>
</select>
</div>
<div class="col-md-12 col-lg-2 px-4">
<label for="cabezada" class="form-label">
<?= lang('Presupuestos.cabezada') ?>
</label>
<select class="form-select select2bs2" id="cabezada" name="cabezada">
<option value="WHI">
<?= lang('Presupuestos.blanca') ?>
</option>
<option value="GRE">
<?= lang('Presupuestos.verde') ?>
</option>
<option value="BLUE">
<?= lang('Presupuestos.azul') ?>
</option>
<option value="REDYEL">
<?= lang('Presupuestos.rojaAmarilla') ?>
</option>
</select>
</div>
<div class="col-md-12 col-lg-2 px-4">
<label id="label_compLomoRedondo" for="compLomoRedondo" class="form-label">
<?= lang('Presupuestos.lomoRedondo') ?>*
</label>
<select id="compLomoRedondo" name="lomo_redondo" service-id="<?= $serviciosAutomaticos['lomo_redondo'] ?>"
class="form-control select2bs2 comp_cubierta_items" style="width: 100%;">
<option value="0">
<p>
<?= lang('Presupuestos.no') ?>
</p>
</option>
<option value="1">
<p>
<?= lang('Presupuestos.si') ?>
</p>
</option>
</select>
</div>
<div class="col-md-12 col-lg-2 px-4">
<label for="cabezada" class="form-label">
<?= lang('Presupuestos.cabezada') ?>
</label>
<select class="form-select" name="cabezada" id="cabezada">
<?php foreach ($cabezadas as $key => $langKey): ?>
<option value="<?= esc($key) ?>">
<?= lang($langKey) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div>
<hr class="my-1">
@ -269,83 +262,83 @@
</div>
<?php if ($tipo_impresion_id != 5 && $tipo_impresion_id != 6 && $tipo_impresion_id != 7 && $tipo_impresion_id != 8 && $tipo_impresion_id != 21): ?>
<div class="row">
<div class="col-md-12 col-lg-2 px-4">
<p>
<?= lang('PapelImpresion.sobrecubierta') ?>
</p>
<div class="row">
<div class="col-md-12 col-lg-2 px-4">
<p>
<?= lang('PapelImpresion.sobrecubierta') ?>
</p>
</div>
<div class="col-md-12 col-lg-2 px-4">
<select id="compSobrecubierta" name="comp_sobrecubierta"
class="form-control select2bs2 comp_sobrecubierta_items" style="width: 100%;">
<option value="0">
<?= lang('Presupuestos.no') ?>
</option>
<option value="1">
<?= lang('Presupuestos.si') ?>
</option>
</select>
</div>
<div class="col-md-12 col-lg-6 px-4">
<select disabled id="compPapelSobrecubierta" name="comp_papel_sobrecubierta"
class="form-control select2bs2 comp_sobrecubierta_items" style="width: 100%;">
</select>
</div>
<div class="col-md-12 col-lg-2 px-4">
<select disabled id="compGramajeSobrecubierta" name="comp_gramaje_sobrecubierta"
class="form-control select2bs2 comp_sobrecubierta_items" style="width: 100%;">
</select>
</div>
<div>
<hr class="my-1">
</div>
</div>
<div class="col-md-12 col-lg-2 px-4">
<select id="compSobrecubierta" name="comp_sobrecubierta"
class="form-control select2bs2 comp_sobrecubierta_items" style="width: 100%;">
<option value="0">
<?= lang('Presupuestos.no') ?>
</option>
<option value="1">
<?= lang('Presupuestos.si') ?>
</option>
</select>
</div>
<div class="col-md-12 col-lg-6 px-4">
<select disabled id="compPapelSobrecubierta" name="comp_papel_sobrecubierta"
class="form-control select2bs2 comp_sobrecubierta_items" style="width: 100%;">
</select>
</div>
<div class="col-md-12 col-lg-2 px-4">
<select disabled id="compGramajeSobrecubierta" name="comp_gramaje_sobrecubierta"
class="form-control select2bs2 comp_sobrecubierta_items" style="width: 100%;">
</select>
</div>
<div>
<hr class="my-1">
</div>
</div>
<?php endif; ?>
<?php if ($tipo_impresion_id == 1 || $tipo_impresion_id == 3 || $tipo_impresion_id == 5 || $tipo_impresion_id == 7): ?>
<div class="row">
<div class="col-md-12 col-lg-2 px-4">
<p>
<?= lang('PapelImpresion.guardas') ?>
</p>
</div>
<div class="col-md-12 col-lg-2 px-4">
<select id="compCarasGuardas" name="comp_paginas_guardas"
class="form-control select2bs2 comp_guardas_items" style="width: 100%;">
<option value="0">
<p>
<?= lang('Presupuestos.sinImpresion') ?>
</p>
</option>
<option value="4">
<p>
<?= lang('Presupuestos.unaCara') ?>
</p>
</option>
<option value="8">
<?php if ($tipo_impresion_id != 5 && $tipo_impresion_id != 6 && $tipo_impresion_id != 7 && $tipo_impresion_id != 8): ?>
<p>
<?= lang('Presupuestos.dosCaras') ?>
</p>
<?php endif ?>
</option>
</select>
</div>
<div class="col-md-12 col-lg-6 px-4">
<select id="compPapelGuardas" name="comp_papel_guardas"
class="form-control select2bs2 comp_guardas_items" style="width: 100%;">
</select>
</div>
<div class="col-md-12 col-lg-2 px-4">
<select id="compGramajeGuardas" name="comp_gramaje_guardas"
class="form-control select2bs2 comp_guardas_items" style="width: 100%;">
</select>
</div>
<div class="row">
<div class="col-md-12 col-lg-2 px-4">
<p>
<?= lang('PapelImpresion.guardas') ?>
</p>
</div>
<div class="col-md-12 col-lg-2 px-4">
<select id="compCarasGuardas" name="comp_paginas_guardas"
class="form-control select2bs2 comp_guardas_items" style="width: 100%;">
<option value="0">
<p>
<?= lang('Presupuestos.sinImpresion') ?>
</p>
</option>
<option value="4">
<p>
<?= lang('Presupuestos.unaCara') ?>
</p>
</option>
<option value="8">
<?php if ($tipo_impresion_id != 5 && $tipo_impresion_id != 6 && $tipo_impresion_id != 7 && $tipo_impresion_id != 8): ?>
<p>
<?= lang('Presupuestos.dosCaras') ?>
</p>
<?php endif ?>
</option>
</select>
</div>
<div class="col-md-12 col-lg-6 px-4">
<select id="compPapelGuardas" name="comp_papel_guardas"
class="form-control select2bs2 comp_guardas_items" style="width: 100%;">
</select>
</div>
<div class="col-md-12 col-lg-2 px-4">
<select id="compGramajeGuardas" name="comp_gramaje_guardas"
class="form-control select2bs2 comp_guardas_items" style="width: 100%;">
</select>
</div>
<div>
<hr class="my-1">
<div>
<hr class="my-1">
</div>
</div>
</div>
<?php endif; ?>
<div class="row">
@ -733,102 +726,102 @@
<!----------------------------------------------------------------------------->
<?php if ($tipo_impresion_id == 1 || $tipo_impresion_id == 3 || $tipo_impresion_id == 5 || $tipo_impresion_id == 7): ?>
<div class="accordion mt-3" id="accordionCompGuardas">
<div class="card accordion-item active">
<h2 class="accordion-header" id="headingOne">
<button id="accordion-button-int-guardas" type="button" class="accordion-button"
data-bs-toggle="collapse" data-bs-target="#accordionCompGuardasTip"
aria-expanded="false" aria-controls="accordionCompGuardasTip">
<h6 id="title_guardas">
<?= lang("Presupuestos.Guardas") ?>
</h6>
</button>
</h2>
<div class="accordion mt-3" id="accordionCompGuardas">
<div class="card accordion-item active">
<h2 class="accordion-header" id="headingOne">
<button id="accordion-button-int-guardas" type="button" class="accordion-button"
data-bs-toggle="collapse" data-bs-target="#accordionCompGuardasTip"
aria-expanded="false" aria-controls="accordionCompGuardasTip">
<h6 id="title_guardas">
<?= lang("Presupuestos.Guardas") ?>
</h6>
</button>
</h2>
<div id="accordionCompGuardasTip" class="accordion-collapse collapse"
data-bs-parent="#accordionCompGuardasTip">
<div class="accordion-body">
<div id="accordionCompGuardasTip" class="accordion-collapse collapse"
data-bs-parent="#accordionCompGuardasTip">
<div class="accordion-body">
<table id="tableCompGuardas" class="comparator-table table dt-responsive dataTable"
style="width: 100%;">
<thead>
<tr>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.tipo') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.paginas') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.papel') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.gramaje') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.marca') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.maquina') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.numeroPliegos') ?>
</th>
<th style="padding-right: 0.75em; max-width:80px">
<?= lang('Presupuestos.pliegosPedido') ?>
</th>
<th style="padding-right: 0.75em; max-width:80px">
<?= lang('Presupuestos.precioPliego') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.libro') ?>
</th>
<th style="padding-right: 0.75em; max-width:80px">
<?= lang('Presupuestos.totalPapelPedido') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.lomo') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.peso') ?>
</th>
<th style="padding-right: 0.75em; max-width:80px">
<?= lang('Presupuestos.horas') ?>
</th>
<th style="padding-right: 0.75em; max-width:80px">
<?= lang('Presupuestos.precioImpresion') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.total') ?>
</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td class="dt-result dt-result-text" colspan="14">
<?= lang('Presupuestos.total') ?>
<?= lang("Presupuestos.Guardas") ?>:
</td>
<td id="total_comp_guardas" class="dt-result dt-result-value" colspan="2">
0.00 </td>
</tr>
</tfoot>
</table>
<table id="tableCompGuardas" class="comparator-table table dt-responsive dataTable"
style="width: 100%;">
<thead>
<tr>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.tipo') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.paginas') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.papel') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.gramaje') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.marca') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.maquina') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.numeroPliegos') ?>
</th>
<th style="padding-right: 0.75em; max-width:80px">
<?= lang('Presupuestos.pliegosPedido') ?>
</th>
<th style="padding-right: 0.75em; max-width:80px">
<?= lang('Presupuestos.precioPliego') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.libro') ?>
</th>
<th style="padding-right: 0.75em; max-width:80px">
<?= lang('Presupuestos.totalPapelPedido') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.lomo') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.peso') ?>
</th>
<th style="padding-right: 0.75em; max-width:80px">
<?= lang('Presupuestos.horas') ?>
</th>
<th style="padding-right: 0.75em; max-width:80px">
<?= lang('Presupuestos.precioImpresion') ?>
</th>
<th style="padding-right: 0.75em;">
<?= lang('Presupuestos.total') ?>
</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td class="dt-result dt-result-text" colspan="14">
<?= lang('Presupuestos.total') ?>
<?= lang("Presupuestos.Guardas") ?>:
</td>
<td id="total_comp_guardas" class="dt-result dt-result-value" colspan="2">
0.00 </td>
</tr>
</tfoot>
</table>
<div>
<button type="button" name="insertarGuardasBtn" id="insertarGuardasBtn"
class="btn btn-primary float-end me-sm-3 me-1 mb-3 insertarLinea d-none">
<?= lang('Presupuestos.insertarLinea') . ' ' . strtoupper(lang('Presupuestos.Guardas')) ?>
</button>
<div>
<button type="button" name="insertarGuardasBtn" id="insertarGuardasBtn"
class="btn btn-primary float-end me-sm-3 me-1 mb-3 insertarLinea d-none">
<?= lang('Presupuestos.insertarLinea') . ' ' . strtoupper(lang('Presupuestos.Guardas')) ?>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
<div class="accordion mt-3" id="accordionCompFaja">

View File

@ -25,7 +25,7 @@
<label for="created_at" class="form-label">
<?= lang('Presupuestos.created_at') ?>
</label>
<?php if(str_contains($formAction,'edit')): ?>
<?php if (str_contains($formAction, 'edit')): ?>
<input readonly style="background: #E8E8E8;" id="updated_at" name="updated_at" maxLength="12" class="form-control" value="">
<?php else: ?>
<input readonly style="background: #E8E8E8;" id="created_at" name="created_at" maxLength="12" class="form-control" value="">
@ -39,12 +39,12 @@
<?= lang('Presupuestos.presupuestoEstado') ?>
</label>
<select disabled id="estado_id" name="estado_id" maxLength="11" style="background-color: #E8E8E8;" class="form-control select2bs" value="">
<option value=1 >
<?=lang('Presupuestos.presupuestoEstadoBorrador') ?>
</option>
<option value=2 >
<?=lang('Presupuestos.presupuestoEstadoAceptado') ?>
</option>
<option value=1>
<?= lang('Presupuestos.presupuestoEstadoBorrador') ?>
</option>
<option value=2>
<?= lang('Presupuestos.presupuestoEstadoAceptado') ?>
</option>
</select>
</div><!--//.mb-3 -->
</div><!--//.col -->
@ -71,7 +71,7 @@
<div class="col-md-12 col-lg-6 px-4">
<div class="mb-3">
<label for="titulo" class="form-label">
<?=lang('Presupuestos.titulo') ?>*
<?= lang('Presupuestos.titulo') ?>*
</label>
<input type="text" id="titulo" name="titulo" maxLength="300" class="form-control" value="">
</div><!--//.mb-3 -->
@ -81,7 +81,7 @@
<div class="col-md-12 col-lg-6 px-4">
<div class="mb-3">
<label for="autor" class="form-label">
<?=lang('Presupuestos.autor') ?>
<?= lang('Presupuestos.autor') ?>
</label>
<input type="text" id="autor" name="autor" maxLength="150" class="form-control" value="">
</div><!--//.mb-3 -->
@ -95,37 +95,37 @@
<div class="col-md-12 col-lg-3 px-4">
<div class="mb-3">
<label for="coleccion" class="form-label">
<?=lang('Presupuestos.coleccion') ?>
<?= lang('Presupuestos.coleccion') ?>
</label>
<input type="text" id="coleccion" name="coleccion" maxLength="255" class="form-control" value="">
</div><!--//.mb-3 -->
</div>
<div class="col-md-12 col-lg-3 px-4">
<div class="col-md-12 col-lg-3 px-4">
<div class="mb-3">
<label for="numeroEdicion" class="form-label">
<?=lang('Presupuestos.numeroEdicion') ?>
<?= lang('Presupuestos.numeroEdicion') ?>
</label>
<input type="text" id="numeroEdicion" name="numero_edicion" maxLength="50" class="form-control" value="">
</div><!--//.mb-3 -->
</div>
<div class="col-md-12 col-lg-3 px-4">
<div class="col-md-12 col-lg-3 px-4">
<div class="mb-3">
<label for="isbn" class="form-label">
<?=lang('Presupuestos.isbn') ?>
<?= lang('Presupuestos.isbn') ?>
</label>
<input type="text" id="isbn" name="isbn" maxLength="50" class="form-control" value="">
<input type="text" id="isbn" name="isbn" maxLength="50" class="form-control" value="">
</div><!--//.mb-3 -->
</div>
<div class="col-md-12 col-lg-3 px-4">
<div class="col-md-12 col-lg-3 px-4">
<div class="mb-3">
<label for="paisId" class="form-label">
<?=lang('Presupuestos.paisId') ?>
<?= lang('Presupuestos.paisId') ?>
</label>
<select id="paisId" name="pais_id" class="form-control select2bs" style="width: 100%;" >
<?php if(isset($pais_default)): ?>
<select id="paisId" name="pais_id" class="form-control select2bs" style="width: 100%;">
<?php if (isset($pais_default)): ?>
<option value="<?= $pais_default_id ?>" selected>
<?= $pais_default ?>
</option>
@ -133,31 +133,43 @@
</select>
</div><!--//.mb-3 -->
</div>
</div> <!--//.row -->
<!-- Fila 3 -->
<div class="row">
<!-- Fila 4 -->
<div class="row">
<div class="col-md-12 col-lg-6 px-4">
<div class="mb-3">
<label for="clienteId" class="form-label">
<?= lang('Presupuestos.clienteId') ?>*
<?php if(isset($cliente_id)):?>
<a href="<?= route_to('editarCliente', $cliente_id); ?>" target="_blank" ><i class="ti ti-file-search ti-sm mx-2""></i></a>
<?php if (isset($cliente_id)): ?>
<a href="<?= route_to('editarCliente', $cliente_id); ?>" target="_blank"><i class="ti ti-file-search ti-sm mx-2""></i></a>
<?php endif; ?>
</label>
<select id="clienteId" name="cliente_id" class="form-control select2bs2" style="width: 100%;">
</select>
</select>
</div><!--//.mb-3 -->
</div><!--//.col -->
<div class="col-md-12 col-lg-6 px-4">
<div class="col-md-12 col-lg-3 px-4">
<div class="mb-3">
<label for="referenciaCliente" class="form-label">
<?=lang('Presupuestos.referenciaCliente') ?>
<?= lang('Presupuestos.referenciaCliente') ?>
</label>
<input type="text" id="referenciaCliente" name="referencia_cliente" maxLength="100" class="form-control" value="">
<input type="text" id="referenciaCliente" name="referencia_cliente" maxLength="100" class="form-control" value="">
</div><!--//.mb-3 -->
</div><!--//.col -->
<div class="col-md-12 col-lg-3 px-4">
<div class="mb-3">
<label for="iskn" class="form-label">
<?= lang('Presupuestos.iskn') ?>
</label>
<input type="text" id="iskn" name="iskn" class="form-control" readonly
value="" maxlength="64"
style="background: #E8E8E8;">
</div><!--//.mb-3 -->
</div><!--//.col -->
@ -167,4 +179,4 @@
</div> <!-- //.accordion-body -->
</div> <!-- //.accordion-collapse -->
</div> <!-- //.accordion-item -->
</div> <!-- //.accordion -->
</div> <!-- //.accordion -->

View File

@ -136,10 +136,11 @@
<?= lang('Presupuestos.cabezada') ?>
</label>
<select class="form-select select2bs2" id="cabezada" name="cabezada">
<option value="WHI"><?= lang('Presupuestos.blanca') ?></option>
<option value="GRE"><?= lang('Presupuestos.verde') ?></option>
<option value="BLUE"><?= lang('Presupuestos.azul') ?></option>
<option value="REDYEL"><?= lang('Presupuestos.rojaAmarilla') ?></option>
<?php foreach ($cabezadas as $key => $langKey): ?>
<option value="<?= esc($key) ?>">
<?= lang($langKey) ?>
</option>
<?php endforeach; ?>
</select>
</div>

View File

@ -206,7 +206,7 @@
</div>
</div>
<button id="btnUploadFile" class="btn mt-3 btn-primary btn-submit waves-effect waves-light ml-2 ">
<span class="align-middle d-sm-inline-block d-none me-sm-1"><?= lang('App.global_upload_files') ?></span>
<span class="align-middle d-sm-inline-block d-none me-sm-1"><?= lang('App.global_select_files') ?></span>
<i class="ti ti-upload ti-xs"></i>
</button>
<button id="submit-all-files" class="btn mt-3 btn-success btn-submit waves-effect waves-light ml-2">

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
</body>
</html>

BIN
httpdocs/assets/img/pdf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -130,4 +130,19 @@ export const alertWarning = (value, target = 'body',options = {}) => {
timerProgressBar: true,
...options
})
}
}
export const alertFileUploadSuccess = (message = "Archivos subidos correctamente.") => {
return Swal.fire({
icon: "success",
title: "Carga finalizada",
text: message,
showConfirmButton: false,
timer: 3000,
customClass: {
popup: 'p-4',
title: 'fs-4 fw-bold text-success',
htmlContainer: 'text-muted',
}
});
};

View File

@ -1,10 +1,8 @@
import Modal from "./modal.js";
import Ajax from "./ajax.js";
class ConfigVariableDatatable
{
constructor(domItem)
{
class ConfigVariableDatatable {
constructor(domItem) {
this.domItem = domItem
this.datatableItem = this.domItem
this.modalItem = $("#modalConfigVariableForm")
@ -14,76 +12,69 @@ class ConfigVariableDatatable
this.formEdit = this.modalItem.find("#formEditConfigVariable")
}
init(){
init() {
this.datatable = this.datatableItem.DataTable({
processing: true,
dom: 'Blrtip',
serverSide: true,
lengthMenu: [ 25, 50, 100, 200 ],
lengthMenu: [25, 50, 100, 200],
pageLength: 50,
language: {
url: "/themes/vuexy/vendor/libs/datatables-sk/plugins/i18n/es-ES.json"
},
columns : [
{data : 'name',searchable:true,sortable:false},
{data : 'value',searchable:true,sortable:false},
{data : 'description',searchable:true,sortable:false},
{data : 'action',sortable:false,searchable:false,
render : (d,t) =>{
columns: [
{ data: 'name', searchable: true, sortable: false },
{ data: 'value', searchable: true, sortable: false },
{ data: 'description', searchable: true, sortable: false },
{
data: 'action', sortable: false, searchable: false,
render: (d, t) => {
return `
<div class="btn-group btn-group-sm">
<a href="javascript:void(0)" data-id="${d}" class="edit-variable"><i class="ti ti-pencil ti-sm mx-2"></i></a>
</div>
`
}
}
},
],
ajax: '/configuracion/variables/datatable'
});
}
events()
{
this.modalItem.on("click",".btn-update-variable",this.handleUpdateVariable.bind(this))
this.datatableItem.on("click",".edit-variable",(e)=> {
events() {
this.modalItem.on("click", ".btn-update-variable", this.handleUpdateVariable.bind(this))
this.datatableItem.on("click", ".edit-variable", (e) => {
e.preventDefault()
this.variableId = $(e.currentTarget).data("id")
this.handleGetVariable()
})
}
handleGetVariable()
{
handleGetVariable() {
const url = `/configuracion/variables/find/${this.variableId}`
let ajax = new Ajax(
url,null,null,
url, null, null,
this.handleGetVariableSuccess.bind(this),
this.handleGetVariableError.bind(this)
)
ajax.get()
}
handleGetVariableSuccess(data){
handleGetVariableSuccess(data) {
this.formEdit[0].reset()
this.modalEdit.toggle()
this.nameInput = this.formEdit
.find("#name")
this.nameInput = this.formEdit.find("#name")
this.nameInput.val(data.name)
this.valueInput = this.formEdit
.find("#value")
this.valueInput.val(data.value)
this.descriptionInput = this.formEdit
.find("#description")
this.renderValueField(data.name, data.value)
this.descriptionInput = this.formEdit.find("#description")
this.descriptionInput.val(data.description)
}
handleGetVariableError(err){}
handleUpdateVariable()
{
handleGetVariableError(err) { }
handleUpdateVariable() {
const url = `/configuracion/variables/edit/${this.variableId}`
const data = {
value : this.valueInput.val(),
description : this.descriptionInput.val(),
value: this.valueInput.val(),
description: this.descriptionInput.val(),
}
let ajax = new Ajax(
url,
@ -94,26 +85,49 @@ class ConfigVariableDatatable
)
ajax.post()
}
handleUpdateVariableSucess(data){
handleUpdateVariableSucess(data) {
this.modalEdit.toggle()
this.datatable.ajax.reload()
}
handleUpdateVariableError(err){}
handleUpdateVariableError(err) { }
handleDeleteVariable()
{
handleDeleteVariable() {
const url = `/configuracion/variables/delete/${this.variableId}`
let ajax = new Ajax(
url,null,null,
url, null, null,
this.handleDeleteVariableSucess.bind(this),
this.handleDeleteVariableError.bind(this)
)
ajax.post()
}
handleDeleteVariableSucess(data){
handleDeleteVariableSucess(data) {
this.datatable.reload()
}
handleDeleteVariableError(err){}
handleDeleteVariableError(err) { }
renderValueField(name, currentValue) {
const wrapper = this.formEdit.find("#value-wrapper");
let html = '';
if (name === 'cabezadas_disponibles') {
html = `<textarea id="value" rows="6" class="form-control">${currentValue}</textarea>`;
} else if (name === 'cabezada_default') {
const options = window.CABEZADAS_OPCIONES || {};
html = `<select id="value" class="form-select">`;
for (const [key, label] of Object.entries(options)) {
const selected = key === currentValue ? 'selected' : '';
html += `<option value="${key}" ${selected}>${label}</option>`;
}
html += `</select>`;
} else {
html = `<input type="text" id="value" class="form-control" value="${currentValue}">`;
}
wrapper.html(html);
this.valueInput = this.formEdit.find("#value"); // Actualiza referencia
}
}
export default ConfigVariableDatatable;

View File

@ -1,73 +1,84 @@
// Importación de utilidades AJAX y alertas personalizadas
import Ajax from '../ajax.js';
import { alertSuccessMessage, alertWarningMessage } from '../alerts/sweetAlert.js'
import { alertFileUploadSuccess, alertWarningMessage } from '../alerts/sweetAlert.js'
// Template HTML para la vista previa de cada archivo en Dropzone
const PREVIEW_TEMPLATE = `
<div class="dz-preview dz-file-preview">
<div class="dz-details">
<div class="dz-thumbnail">
<!---<img data-dz-thumbnail>
<span class="dz-nopreview">No preview</span> --->
<!-- Miniatura de imagen o PDF -->
<div class="dz-success-mark"></div>
<div class="dz-error-mark"></div>
<div class="dz-error-message"><span data-dz-errormessage></span></div>
<div class="progress">
<div class="progress-bar progress-bar-primary" role="progressbar" aria-valuemin="0" aria-valuemax="100" data-dz-uploadprogress></div>
<div class="progress-bar progress-bar-primary" role="progressbar" aria-valuemin="0" aria-valuemax="100" data-dz-uploadprogress></div>
</div>
</div>
<div class="dz-filename" data-dz-name></div>
<!-- Estilo uniforme con Eliminar / Ver -->
<a class="dz-download dz-remove" href="javascript:void(0);" style="text-align:center;">Descargar</a>
<!-- Botón para descargar -->
<a class="dz-download dz-remove" href="javascript:void(0);" style="text-align:center;">Descargar</a>
<div class="dz-size" data-dz-size></div>
</div>
</div>
`;
// Clase principal que maneja el flujo de Dropzone + AJAX
class FileUploadDropzone {
constructor({ domElement, nameId = "presupuesto_id", getUri = null, postUri = null, resourcePath = "presupuestos", otId = null }) {
Dropzone.autoDiscover = false;
this.domElement = domElement
this.jqElement = $(domElement)
this.modelId = this.jqElement.data('id')
this.btnSelectFiles = $(`#${domElement.replace('#', '')}_btnUploadFiles`)
this.btnSubmitFile = $(`#${domElement.replace('#', '')}_btnSubmitFiles`)
Dropzone.autoDiscover = false; // Desactiva la auto inicialización de Dropzone
this.domElement = domElement;
this.jqElement = $(domElement); // Referencia jQuery al elemento
this.modelId = this.jqElement.data('id'); // ID que asocia los archivos a un modelo (presupuesto, pedido, etc.)
// Botones asociados
this.btnSelectFiles = $(`#${domElement.replace('#', '')}_btnUploadFiles`);
this.btnSubmitFile = $(`#${domElement.replace('#', '')}_btnSubmitFiles`);
this.btnDownloadFiles = $(`#${domElement.replace('#', '')}_btnDownloadFiles`);
this.dataPost = {}
this.nameId = nameId;
this.otId = otId;
this.getUri = getUri
this.postUri = postUri
this.dataPost[nameId] = this.modelId;
this.resourcePath = resourcePath
this.getUri = getUri;
this.postUri = postUri;
this.resourcePath = resourcePath;
this.dataPost = {};
this.dataPost[nameId] = this.modelId;
}
// Inicializa Dropzone y los eventos externos
init() {
if (this.jqElement.length > 0) {
this.btnSubmitFile.on('click', this._handleUploadFiles.bind(this))
this.btnSelectFiles.on('click', () => {
this.jqElement.trigger('click')
})
this.btnDownloadFiles.on('click', this._handleDownloadFiles.bind(this))
// Vincula botones externos
this.btnSubmitFile.on('click', this._handleUploadFiles.bind(this));
this.btnSelectFiles.on('click', () => this.jqElement.trigger('click'));
this.btnDownloadFiles.on('click', this._handleDownloadFiles.bind(this));
// Inicializa Dropzone
this.dropzone = new Dropzone(this.domElement, {
url: this.postUri,
addRemoveLinks: true,
previewTemplate: PREVIEW_TEMPLATE,
paramName: "file",
uploadMultiple: true,
parallelUploads: 4, // Ajusta este número al máximo número de archivos que esperas subir a la vez
maxFiles: 5, // Ajusta este número al máximo número de archivos que esperas subir a la vez
parallelUploads: 4,
maxFiles: 5,
autoProcessQueue: true,
dictRemoveFile: "Eliminar",
acceptedFiles: 'image/*, application/pdf',
maxFilesize: 5e+7, // Bytes
init: this._handleGetFiles.bind(this)
maxFilesize: 5e+7, // 50 MB
init: this._handleGetFiles.bind(this) // Carga inicial de archivos
});
// Cuando se añade un archivo (manual o programático)
this.dropzone.on("addedfile", this._handleAddedFile.bind(this));
}
}
// Botones "Ver" y "Descargar" para cada archivo
_handleAddedFile(file) {
if (file.hash) {
// Botón Ver
@ -90,75 +101,122 @@ class FileUploadDropzone {
}
}
// Acción del botón "Ver"
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');
const url = `${window.location.protocol}//${window.location.host}/sistema/intranet/${this.resourcePath}/${file.hash}`;
window.open(url, '_blank');
}
// Prepara el objeto FormData para el envío
_getDropzoneFilesFormData() {
var files = this.dropzone.files;
const files = this.dropzone.files;
const formData = new FormData();
const oldFiles = [];
var formData = new FormData();
var oldFiles = [];
var counter = 0;
for (var i = 0; i < files.length; i++) {
if (files[i].upload) {
var file = files[i];
for (let file of files) {
if (file.upload) {
formData.append('file[]', file);
counter += 1;
}
else {
oldFiles.push(files[i].name);
} else {
oldFiles.push(file.name);
}
}
formData.append('oldFiles', JSON.stringify(oldFiles));
formData.append('oldFiles', JSON.stringify(oldFiles));
formData.append(this.nameId, this.modelId);
return formData;
}
// Acción al hacer clic en "Subir archivos"
_handleUploadFiles() {
$("#loader").modal('show')
let ajax = new Ajax(this.postUri,
$("#loader").modal('show');
const ajax = new Ajax(
this.postUri,
this._getDropzoneFilesFormData(),
null,
this._handleUploadFilesSuccess.bind(this),
null)
null
);
ajax.ajaxForm("POST");
}
// Éxito tras subir archivos
_handleUploadFilesSuccess(response) {
this.dropZoneClean()
this._handleGetFiles()
alertSuccessMessage(response?.message ?? "Archivos subidos correctamente");
}
_handleUploadFilesError(errors) { }
this.dropZoneClean();
this._handleGetFiles();
const summary = response?.summary || {};
const numOk = summary.subidos_ok || 0;
const numErrLocal = summary.errores_locales || 0;
const numErrRemote = summary.errores_remotos || 0;
const numDeleted = summary.borrados || 0;
const partes = [];
if (numOk > 0) {
partes.push(`Se subió${numOk === 1 ? '' : 'ron'} ${numOk} archivo${numOk === 1 ? '' : 's'} correctamente.`);
}
if (numDeleted > 0) {
partes.push(`Se eliminaron ${numDeleted} archivo${numDeleted === 1 ? '' : 's'} obsoleto${numDeleted === 1 ? '' : 's'}.`);
}
if (numErrLocal > 0) {
partes.push(`${numErrLocal} archivo${numErrLocal === 1 ? '' : 's'} falló${numErrLocal === 1 ? '' : 'n'} en el sistema.`);
}
if (numErrRemote > 0) {
partes.push(`${numErrRemote} fallo${numErrRemote === 1 ? '' : 's'} en la transferencia.`);
}
const mensaje = partes.length > 0
? partes.join(' ')
: response?.message ?? "Archivos actualizados correctamente.";
alertFileUploadSuccess(mensaje);
}
_handleUploadFilesError(errors) {
// No implementado aún
}
// Carga inicial de archivos existentes desde el servidor
_handleGetFiles() {
const ajax = new Ajax(
this.getUri,
this.dataPost,
null,
this._handelGetFilesSuccess.bind(this),
null,
)
ajax.post()
null
);
ajax.post();
}
// Manejo de respuesta del servidor al cargar archivos
_handelGetFilesSuccess(response) {
try {
$("#loader").modal('hide')
const files = JSON.parse(response)
this.dropZoneUpdateFiles(files)
$("#loader").modal('hide');
const files = Array.isArray(response)
? response
: typeof response === 'string'
? JSON.parse(response)
: [];
this.dropZoneUpdateFiles(files);
} catch (error) {
$("#loader").modal('hide')
console.error("Error parseando respuesta:", error);
$("#loader").modal('hide');
}
}
// Manejo del botón "Descargar archivos ZIP"
_handleDownloadFiles() {
$("#loader").modal('show');
$.ajax({
url: `/presupuestoadmin/download_zip`,
url: `/files/download_zip`,
type: 'POST',
data: {
[this.nameId]: this.modelId,
@ -172,7 +230,7 @@ class FileUploadDropzone {
let filename = "archivos.zip";
if (disposition && disposition.indexOf('attachment') !== -1) {
const match = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(disposition);
if (match != null && match[1]) {
if (match && match[1]) {
filename = match[1].replace(/['"]/g, '');
}
}
@ -195,28 +253,57 @@ class FileUploadDropzone {
});
}
// Carga archivos simulados (mock) al Dropzone visual
dropZoneUpdateFiles(files) {
files.forEach(file => {
this.dropZoneAddFile(file)
//console.log("Iterando archivo:", file.name);
this.dropZoneAddFile(file);
});
}
// Limpia todos los archivos de Dropzone visualmente
dropZoneClean() {
this.dropzone.files.forEach(file => {
this.dropzone.removeFile(file);
})
});
}
dropZoneAddFile(mockFile) {
this.dropzone.files.push(mockFile); // add to files array
this.dropzone.emit("addedfile", mockFile);
this.dropzone.emit("thumbnail", mockFile, window.location.host + "/sistema/intranet/" + this.resourcePath + "/" + mockFile.hash);
this.dropzone.emit("complete", mockFile);
this.dropzone.options.success.call(this.dropzone, mockFile);
// Inserta un archivo en Dropzone manualmente (mock)
dropZoneAddFile(mockFileData) {
const extension = mockFileData.name.split('.').pop().toLowerCase();
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(extension);
const isPDF = extension === 'pdf';
const fileUrl = `${window.location.protocol}//${window.location.host}/sistema/intranet/${this.resourcePath}/${mockFileData.hash}`;
const mockFile = {
name: mockFileData.name,
size: mockFileData.size,
type: isImage ? `image/${extension === 'jpg' ? 'jpeg' : extension}` : 'application/pdf',
hash: mockFileData.hash,
upload: false // Impide que se vuelva a subir
};
this.dropzone.emit("addedfile", mockFile);
// Espera a que Dropzone genere el DOM para modificar la miniatura
setTimeout(() => {
if (isImage) {
this.dropzone.emit("thumbnail", mockFile, fileUrl);
} else if (isPDF) {
const preview = mockFile.previewElement?.querySelector('.dz-thumbnail');
if (preview) {
preview.innerHTML = `<img src="/assets/img/pdf.png" alt="PDF" style="width:100%; height:auto;" />`;
}
}
this.dropzone.emit("complete", mockFile);
}, 10);
this.dropzone.files.push(mockFile);
}
}
export default FileUploadDropzone;
// Exporta la clase para usarla en otros módulos JS
export default FileUploadDropzone;

View File

@ -2,4 +2,4 @@ import ConfigVariableDatatable from "../../components/configVariableDatatable.js
const item = new ConfigVariableDatatable($("#tableConfigVariables"))
item.init()
item.events()
item.events()

View File

@ -65,8 +65,8 @@ class PresupuestoAdminEdit {
this.configUploadDropzone = {
domElement: '#dropzone-presupuesto-admin-files',
nameId: "presupuesto_id",
getUri: '/presupuestos/presupuestocliente/get_files',
postUri: '/presupuestos/presupuestocliente/upload_files'
getUri: '/files/get_files',
postUri: '/files/upload_files'
}
if ($(this.configUploadDropzone.domElement).length > 0) {
this.fileUploadDropzone = new FileUploadDropzone(this.configUploadDropzone)
@ -234,6 +234,7 @@ class PresupuestoAdminEdit {
titulo: this.datosGenerales.titulo.val(),
autor: this.datosGenerales.autor.val(),
isbn: this.datosGenerales.isbn.val(),
iskn: this.datosGenerales.iskn.val(),
pais_id: this.datosGenerales.pais.getVal(),
coleccion: this.datosGenerales.coleccion.val(),
numero_edicion: this.datosGenerales.numeroEdicion.val(),

View File

@ -15,6 +15,7 @@ class DatosGenerales{
this.coleccion = this.domItem.find('#coleccion');
this.numeroEdicion = this.domItem.find('#numeroEdicion');
this.isbn = this.domItem.find('#isbn');
this.iskn = this.domItem.find('#iskn');
this.cliente = new ClassSelect($('#clienteId'), '/clientes/cliente/getSelect2', 'Seleccione cliente');
@ -46,6 +47,7 @@ class DatosGenerales{
this.coleccion.val(datos.coleccion);
this.numeroEdicion.val(datos.numero_edicion);
this.isbn.val(datos.isbn);
this.iskn.val(datos.iskn);
this.cliente.setOption(datos.cliente.id, datos.cliente.nombre);

View File

@ -99,7 +99,7 @@ class Resumen {
</div>`;
this.dropzone = new Dropzone('#dropzone-multi', {
url: "/presupuestos/presupuestocliente/upload_files",
url: "/files/upload_files",
addRemoveLinks: true,
previewTemplate: previewTemplate,
paramName: "file",
@ -115,24 +115,24 @@ class Resumen {
$('#loader').show();
$.ajax({
url: "/presupuestos/presupuestocliente/get_files",
url: "/files/get_files",
type: 'POST',
data: { presupuesto_id: id }
}).done(function (response) {
if (response == null || response == "") {
return;
}
let values = JSON.parse(response);
for (var i = 0; i < values.length; i++) {
var mockFile = { name: values[i].name, size: values[i].size, hash: values[i].hash };
if (!response) return;
thisDropzone.files.push(mockFile); // add to files array
thisDropzone.emit("addedfile", mockFile);
thisDropzone.emit("thumbnail", mockFile, window.location.host + "/sistema/intranet/presupuestos/" + values[i].hash);
thisDropzone.emit("complete", mockFile);
thisDropzone.options.success.call(thisDropzone, mockFile);
};
let files;
try {
files = Array.isArray(response)
? response
: typeof response === 'string'
? JSON.parse(response)
: [];
} catch (e) {
console.error("Error parseando respuesta:", e);
files = [];
}
}).always(function () {
$('#loader').hide();
});
@ -176,7 +176,7 @@ class Resumen {
formData.append('presupuesto_id', this.presupuesto_id);
$.ajax({
url: "/presupuestos/presupuestocliente/upload_files",
url: "/files/upload_files",
type: 'POST',
data: formData,
processData: false, // Indicar a jQuery que no procese los datos
@ -192,7 +192,7 @@ class Resumen {
$('#loader').show();
$.ajax({
url: "/presupuestocliente/download_zip",
url: "/files/download_zip",
type: 'POST',
data: { presupuesto_id: this.presupuesto_id },
xhrFields: {

View File

@ -72,8 +72,8 @@ class OrdenTrabajo {
domElement: '#dropzone-ot-files',
nameId: "presupuesto_id",
otId: this.otId,
getUri: '/presupuestos/presupuestocliente/get_files',
postUri: '/presupuestos/presupuestocliente/upload_files'
getUri: '/files/get_files',
postUri: '/files/upload_files'
}
if ($(this.configUploadDropzone.domElement).length > 0) {
this.fileUploadDropzone = new FileUploadDropzone(this.configUploadDropzone)