Corregidos bugs y homegeneizado

This commit is contained in:
imnavajas
2025-05-13 13:28:54 +02:00
parent 88d6b69857
commit fa77952ea6
4 changed files with 242 additions and 71 deletions

View File

@ -32,6 +32,7 @@ $routes->group('importador', ['namespace' => 'App\Controllers\Importadores'], fu
/**====================== /**======================
* AJAX * AJAX
*========================**/ *========================**/
$routes->post('validar-fila', 'ImportadorBubok::validarFila');
$routes->post('importar-fila', 'ImportadorBubok::importarFila'); $routes->post('importar-fila', 'ImportadorBubok::importarFila');
}); });

View File

@ -7,7 +7,7 @@ use CodeIgniter\Router\RouteCollection;
/* Rutas para tarifas */ /* Rutas para tarifas */
$routes->group('scripts', ['namespace' => 'App\Controllers\Scripts'], function ($routes) { $routes->group('scripts', ['namespace' => 'App\Controllers\Scripts'], function ($routes) {
$routes->get('completar-identidades', 'UsersIntegrity::completarIdentidades'); //$routes->get('completar-identidades', 'UsersIntegrity::completarIdentidades');
}); });

View File

@ -3,6 +3,7 @@ namespace App\Controllers\Importadores;
use App\Controllers\BaseResourceController; use App\Controllers\BaseResourceController;
use App\Controllers\Presupuestos\Presupuestocliente; use App\Controllers\Presupuestos\Presupuestocliente;
use App\Models\Presupuestos\PresupuestoModel;
use App\Services\PresupuestoService; use App\Services\PresupuestoService;
class ImportadorBubok extends BaseResourceController class ImportadorBubok extends BaseResourceController
@ -50,6 +51,73 @@ class ImportadorBubok extends BaseResourceController
return view(static::$viewPath . 'viewImportadorBubokTool', $viewData); return view(static::$viewPath . 'viewImportadorBubokTool', $viewData);
} }
public function validarFila()
{
$json = $this->request->getJSON();
if (!$json || empty($json->producto) || empty($json->pedido)) {
return $this->response->setJSON([
'apto' => false,
'reason' => 'Datos incompletos'
]);
}
$producto = $json->producto;
$pedido = $json->pedido;
// Validar existencia de ID de producto
if (empty($producto->id)) {
return $this->response->setJSON([
'apto' => false,
'reason' => 'ID de producto no proporcionado'
]);
}
$refCliente = $pedido->orderNumber . '-' . $producto->id;
// Validar formato Ref. Cliente
if (strpos($refCliente, '-') === false || strlen($refCliente) < 5) {
return $this->response->setJSON([
'apto' => false,
'reason' => 'Ref. cliente inválido'
]);
}
// 1. Verificar si ya fue importado
$presupuestoModel = new PresupuestoModel();
$yaExiste = $presupuestoModel->where('referencia_cliente', $refCliente)->first();
if ($yaExiste) {
return $this->response->setJSON([
'apto' => false,
'reason' => 'Referencia ya importada'
]);
}
// 2. Validación básica del producto (puedes expandir con más reglas si lo necesitas)
$errores = [];
if (empty($producto->title))
$errores[] = 'Falta título';
if (empty($producto->body->pages))
$errores[] = 'Faltan páginas';
if (empty($producto->amount))
$errores[] = 'Falta tirada';
if (!empty($errores)) {
return $this->response->setJSON([
'apto' => false,
'reason' => implode(', ', $errores)
]);
}
// 3. Producto considerado apto
return $this->response->setJSON([
'apto' => true
]);
}
public function importarFila() public function importarFila()
{ {
@ -83,7 +151,7 @@ class ImportadorBubok extends BaseResourceController
'message' => 'Número de orden o ID del producto no reconocidos.' 'message' => 'Número de orden o ID del producto no reconocidos.'
]); ]);
} }
$refCliente = "$orderNumber - $productId"; $refCliente = "$orderNumber-$productId";
// Titulo // Titulo
$titulo = $producto->title ?? null; $titulo = $producto->title ?? null;
@ -331,12 +399,12 @@ class ImportadorBubok extends BaseResourceController
'ivaReducido' => 1, 'ivaReducido' => 1,
]; ];
return $this->respond([ /*return $this->respond([
'status' => 400, 'status' => 400,
'message' => $dataToImport, 'message' => $dataToImport,
'interiorTipo' => $interiorTipo, 'interiorTipo' => $interiorTipo,
'isColor' => $isColor 'isColor' => $isColor
]); ]);*/
// 5. Guardar // 5. Guardar
try { try {
@ -368,11 +436,11 @@ class ImportadorBubok extends BaseResourceController
]; ];
} }
} }
// confirmar y crear pedido y ot // confirmar y crear pedido y ot
$presupuestoModel->confirmarPresupuesto($response['sk_id']); $presupuestoModel->confirmarPresupuesto($response['sk_id']);
PresupuestoService::crearPedido($response['sk_id'],isImported:true); PresupuestoService::crearPedido($response['sk_id'], isImported: true);
if (!isset($response['sk_id'])) { if (!isset($response['sk_id'])) {
return $this->respond([ return $this->respond([

View File

@ -1,8 +1,11 @@
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
let dataTable; let dataTable;
let productosOriginales = []; let productosOriginales = [];
let datosComunesPedido = {}; let datosComunesPedido = {};
document.getElementById('importBtn').disabled = true;
document.getElementById('xmlFile').addEventListener('change', function (e) { document.getElementById('xmlFile').addEventListener('change', function (e) {
const file = e.target.files[0]; const file = e.target.files[0];
if (!file || !file.name.endsWith('.zip')) { if (!file || !file.name.endsWith('.zip')) {
@ -10,10 +13,19 @@ document.addEventListener('DOMContentLoaded', function () {
return; return;
} }
document.getElementById('importBtn').disabled = true;
const reader = new FileReader(); const reader = new FileReader();
reader.onload = async function (event) { reader.onload = async function (event) {
try { try {
const zip = await JSZip.loadAsync(event.target.result); const zip = await JSZip.loadAsync(event.target.result);
// Reset tabla y memoria de productos
if (dataTable) {
dataTable.clear().draw();
}
productosOriginales = [];
const xmlFiles = Object.values(zip.files).filter(f => const xmlFiles = Object.values(zip.files).filter(f =>
f.name.endsWith('.xml') && !f.name.startsWith('__MACOSX/') f.name.endsWith('.xml') && !f.name.startsWith('__MACOSX/')
); );
@ -23,11 +35,20 @@ document.addEventListener('DOMContentLoaded', function () {
return; return;
} }
Swal.fire({
title: 'Procesando...',
text: 'Cargando XMLs...',
allowOutsideClick: false,
showDenyButton: false,
didOpen: () => Swal.showLoading()
});
for (const file of xmlFiles) { for (const file of xmlFiles) {
const content = await file.async('text'); const content = await file.async('text');
parseXmlAndLoadTable(content); await parseXmlAndLoadTable(content);
} }
Swal.close();
Swal.fire('Éxito', `${xmlFiles.length} archivos XML cargados.`, 'success'); Swal.fire('Éxito', `${xmlFiles.length} archivos XML cargados.`, 'success');
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@ -38,8 +59,8 @@ document.addEventListener('DOMContentLoaded', function () {
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file);
}); });
function parseXmlAndLoadTable(xmlText) { async function parseXmlAndLoadTable(xmlText) {
let parser = new DOMParser(); const parser = new DOMParser();
let xmlDoc; let xmlDoc;
try { try {
@ -50,7 +71,7 @@ document.addEventListener('DOMContentLoaded', function () {
return; return;
} }
datosComunesPedido = { const pedidoActual = {
orderNumber: xmlDoc.querySelector('orderNumber')?.textContent ?? '', orderNumber: xmlDoc.querySelector('orderNumber')?.textContent ?? '',
shipping: { shipping: {
address: xmlDoc.querySelector('shippingData > address')?.textContent ?? '', address: xmlDoc.querySelector('shippingData > address')?.textContent ?? '',
@ -65,13 +86,19 @@ document.addEventListener('DOMContentLoaded', function () {
const products = Array.from(xmlDoc.getElementsByTagName('product')); const products = Array.from(xmlDoc.getElementsByTagName('product'));
const offset = productosOriginales.length; const offset = productosOriginales.length;
const rows = [];
products.forEach((prod, idx) => { for (let i = 0; i < products.length; i++) {
productosOriginales.push({ index: offset + idx, data: prod }); const product = products[i];
}); const globalIndex = offset + i;
// Asociar producto con su pedido
productosOriginales.push({
index: globalIndex,
data: product,
pedido: { ...pedidoActual }
});
const rows = products.map((product, index) => {
const globalIndex = offset + index;
const id = product.querySelector('id')?.textContent ?? ''; const id = product.querySelector('id')?.textContent ?? '';
const title = product.querySelector('title')?.textContent ?? ''; const title = product.querySelector('title')?.textContent ?? '';
const pages = product.querySelector('body > pages')?.textContent ?? ''; const pages = product.querySelector('body > pages')?.textContent ?? '';
@ -97,40 +124,65 @@ document.addEventListener('DOMContentLoaded', function () {
} }
}); });
return [ // Obtener referencia del pedido
`<input type="checkbox" class="form-check-input select-row" checked>`, const refCliente = `${pedidoActual.orderNumber}-${id}`;
`${datosComunesPedido.orderNumber} - ${id}`,
title, // Validar fila con su propio pedido
tamano, const result = await validarProductoXml(product, pedidoActual);
pages,
tirada, let checkboxHtml = '';
interior, let notaHtml = '';
'', let actionBtnsHtml = '';
`<button type="button" class="btn btn-sm btn-outline-success importRow">Importar</button>
<button type="button" class="btn btn-sm btn-outline-danger deleteRow">Eliminar</button>` if (result.apto) {
]; checkboxHtml = `<input type="checkbox" class="form-check-input select-row" checked>`;
}); notaHtml = '';
actionBtnsHtml = `
<div class="d-flex flex-column align-items-start">
<button type="button" class="btn btn-outline-success btn-sm mb-1 importRow">
<i class="fas fa-file-import me-1"></i> Importar
</button>
<button type="button" class="btn btn-outline-danger btn-sm deleteRow">
<i class="fas fa-trash-alt me-1"></i> Eliminar
</button>
</div>
`;
} else {
checkboxHtml = `<input type="checkbox" class="form-check-input select-row" disabled>`;
notaHtml = `<span class="badge bg-danger">${result.reason}</span>`;
actionBtnsHtml = `
<div class="d-flex flex-column align-items-start">
<span class="badge rounded-pill bg-danger mb-2">No Apto</span>
<button type="button" class="btn btn-outline-danger btn-sm deleteRow">
<i class="fas fa-trash-alt me-1"></i> Eliminar
</button>
</div>
`;
}
rows.push([checkboxHtml, refCliente, title, tamano, pages, tirada, interior, notaHtml, actionBtnsHtml]);
}
if (!$.fn.DataTable.isDataTable('#xmlTable')) { if (!$.fn.DataTable.isDataTable('#xmlTable')) {
const headerHtml = ` $('#xmlTable').html(`
<thead> <thead>
<tr> <tr>
<th><input type="checkbox" id="selectAll" /></th> <th><input type="checkbox" id="selectAll" /></th>
<th>Referencia</th> <th>Referencia</th>
<th>Título</th> <th>Título</th>
<th>Tamaño</th> <th>Tamaño</th>
<th>Páginas</th> <th>Páginas</th>
<th>Tirada</th> <th>Tirada</th>
<th>Interior</th> <th>Interior</th>
<th>Notas</th> <th>Notas</th>
<th>Acciones</th> <th>Acciones</th>
</tr> </tr>
<tr> <tr>
<th></th><th></th><th></th><th></th><th></th> <th></th><th></th><th></th><th></th><th></th>
<th></th><th></th><th></th><th></th> <th></th><th></th><th></th><th></th>
</tr> </tr>
</thead>`; </thead>
$('#xmlTable').html(headerHtml); `);
} }
if (!dataTable) { if (!dataTable) {
@ -141,6 +193,8 @@ document.addEventListener('DOMContentLoaded', function () {
responsive: true, responsive: true,
scrollX: true, scrollX: true,
dom: 'lfrtip', dom: 'lfrtip',
pageLength: 25,
lengthMenu: [5, 10, 25, 50, 100, 250, 500],
language: { language: {
url: "/themes/vuexy/vendor/libs/datatables-sk/plugins/i18n/es-ES.json" url: "/themes/vuexy/vendor/libs/datatables-sk/plugins/i18n/es-ES.json"
}, },
@ -163,8 +217,36 @@ document.addEventListener('DOMContentLoaded', function () {
rows.forEach(row => dataTable.row.add(row)); rows.forEach(row => dataTable.row.add(row));
dataTable.draw(false); dataTable.draw(false);
} }
if (rows.length > 0) {
document.getElementById('importBtn').disabled = false;
}
} }
async function validarProductoXml(productoXml, pedido) {
try {
const response = await fetch('/importador/bubok/validar-fila', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '<?= csrf_hash() ?>'
},
body: JSON.stringify({
producto: xmlToJson(productoXml),
pedido: pedido
})
});
return await response.json();
} catch (error) {
console.error('Error validando producto:', error);
return { apto: false, reason: 'Error conexión' };
}
}
function setupEventListeners() { function setupEventListeners() {
$('#xmlTable tbody').off('click', '.deleteRow').on('click', '.deleteRow', function () { $('#xmlTable tbody').off('click', '.deleteRow').on('click', '.deleteRow', function () {
dataTable.row($(this).parents('tr')).remove().draw(); dataTable.row($(this).parents('tr')).remove().draw();
@ -173,9 +255,12 @@ document.addEventListener('DOMContentLoaded', function () {
$('#xmlTable tbody').off('click', '.importRow').on('click', '.importRow', async function () { $('#xmlTable tbody').off('click', '.importRow').on('click', '.importRow', async function () {
const $row = $(this).closest('tr'); const $row = $(this).closest('tr');
const rowIndex = dataTable.row($row).index(); const rowIndex = dataTable.row($row).index();
const xmlProduct = productosOriginales.find(p => p.index === rowIndex)?.data; const productoObj = productosOriginales.find(p => p.index === rowIndex);
if (!xmlProduct) return; if (!productoObj) return;
const xmlProduct = productoObj.data;
const pedido = productoObj.pedido;
try { try {
const response = await fetch('/importador/bubok/importar-fila', { const response = await fetch('/importador/bubok/importar-fila', {
@ -186,32 +271,46 @@ document.addEventListener('DOMContentLoaded', function () {
}, },
body: JSON.stringify({ body: JSON.stringify({
producto: xmlToJson(xmlProduct), producto: xmlToJson(xmlProduct),
pedido: datosComunesPedido pedido: pedido
}) })
}); });
const result = await response.json(); const result = await response.json();
const rowData = dataTable.row($row).data(); const rowData = dataTable.row($row).data();
if (response.ok && result.status === 200) { if (response.ok && result.status === 200) {
const skUrl = result.data?.sk_url?.replace('presupuestocliente', 'presupuestoadmin') ?? null; const skUrl = result.data?.sk_url?.replace('presupuestocliente', 'presupuestoadmin') ?? null;
const htmlLink = skUrl const skId = result.data?.sk_id ?? '';
? `<a href="${skUrl}" target="_blank" class="btn btn-sm btn-primary">Ver presupuesto</a>`
: 'Importado'; rowData[7] = skUrl
rowData[7] = htmlLink; ? `<a href="${skUrl}" target="_blank" class="btn btn-sm btn-primary">Ver presupuesto (${skId})</a>`
: `<span class="badge bg-success">Importado (${skId})</span>`;
dataTable.row($row).data(rowData).draw(false); dataTable.row($row).data(rowData).draw(false);
let html = skUrl
? `La fila se importó exitosamente.<br><br><a href="${skUrl}" target="_blank" class="btn btn-primary mt-2">Ver presupuesto (${skId})</a>`
: `La fila se importó exitosamente. (${skId})`;
let icon = 'success';
if (result.price_warning) {
html = skUrl
? `La fila se importó con éxito, pero el precio fue ajustado por debajo de costes.<br><br><a href="${skUrl}" target="_blank" class="btn btn-primary mt-2">Ver presupuesto (${skId})</a>`
: `La fila se importó con advertencia de coste. (${skId})`;
icon = 'warning';
}
Swal.fire({ Swal.fire({
title: 'Importado correctamente', title: 'Importado correctamente',
html: skUrl html: html,
? `Se importó correctamente.<br><a href="${skUrl}" target="_blank" class="btn btn-primary mt-2">Ver presupuesto</a>` icon: icon,
: 'Importación realizada.',
icon: 'success',
confirmButtonText: 'Aceptar', confirmButtonText: 'Aceptar',
customClass: { confirmButton: 'btn btn-success' } customClass: { confirmButton: 'btn btn-success' }
}); });
} else { } else {
rowData[7] = `<span class="badge bg-danger">${result.message ?? 'Error inesperado'}</span>`; rowData[7] = `<span class="badge bg-danger">${result.message ?? 'Error desconocido'}</span>`;
dataTable.row($row).data(rowData).draw(false); dataTable.row($row).data(rowData).draw(false);
Swal.fire({ Swal.fire({
@ -222,15 +321,14 @@ document.addEventListener('DOMContentLoaded', function () {
customClass: { confirmButton: 'btn btn-danger' } customClass: { confirmButton: 'btn btn-danger' }
}); });
} }
} catch (error) {
dataTable.row($row).data(rowData).draw(false); console.error('Importación fallida:', error);
} catch (err) {
console.error(err);
Swal.fire('Error', 'Error de comunicación con el servidor.', 'error'); Swal.fire('Error', 'Error de comunicación con el servidor.', 'error');
} }
}); });
$('#selectAll').off('change').on('change', function () { $('#selectAll').off('change').on('change', function () {
const checked = $(this).is(':checked'); const checked = $(this).is(':checked');
$('#xmlTable tbody input.select-row:enabled').prop('checked', checked); $('#xmlTable tbody input.select-row:enabled').prop('checked', checked);
@ -329,8 +427,11 @@ document.addEventListener('DOMContentLoaded', function () {
if (!result.isConfirmed) return; if (!result.isConfirmed) return;
for (const i of filasSeleccionadas) { for (const i of filasSeleccionadas) {
const productXml = productosOriginales.find(p => p.index === i)?.data; const productoObj = productosOriginales.find(p => p.index === i);
if (!productXml) continue; if (!productoObj) continue;
const productXml = productoObj.data;
const pedido = productoObj.pedido;
try { try {
const response = await fetch('/importador/bubok/importar-fila', { const response = await fetch('/importador/bubok/importar-fila', {
@ -341,7 +442,7 @@ document.addEventListener('DOMContentLoaded', function () {
}, },
body: JSON.stringify({ body: JSON.stringify({
producto: xmlToJson(productXml), producto: xmlToJson(productXml),
pedido: datosComunesPedido pedido: pedido
}) })
}); });
@ -351,9 +452,10 @@ document.addEventListener('DOMContentLoaded', function () {
if (response.ok && result.status === 200) { if (response.ok && result.status === 200) {
const skUrl = result.data?.sk_url?.replace('presupuestocliente', 'presupuestoadmin') ?? null; const skUrl = result.data?.sk_url?.replace('presupuestocliente', 'presupuestoadmin') ?? null;
const skId = result.data?.sk_id ?? '';
rowData[7] = skUrl rowData[7] = skUrl
? `<a href="${skUrl}" target="_blank" class="btn btn-sm btn-primary">Ver presupuesto</a>` ? `<a href="${skUrl}" target="_blank" class="btn btn-sm btn-primary">Ver presupuesto (${skId})</a>`
: '<span class="badge bg-success">Importado</span>'; : `<span class="badge bg-success">Importado (${skId})</span>`;
} else { } else {
rowData[7] = `<span class="badge bg-danger">${result.message ?? 'Error desconocido'}</span>`; rowData[7] = `<span class="badge bg-danger">${result.message ?? 'Error desconocido'}</span>`;
} }