Merge branch 'main' into feat/ot-new-features

This commit is contained in:
amazuecos
2025-05-01 06:15:26 +02:00
24 changed files with 1189 additions and 77 deletions

View File

@ -0,0 +1,344 @@
document.addEventListener('DOMContentLoaded', function () {
let dataTable;
let productosOriginales = [];
let datosComunesPedido = {};
document.getElementById('xmlFile').addEventListener('change', function (e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function (event) {
const xmlText = event.target.result;
parseXmlAndLoadTable(xmlText);
};
reader.readAsText(file);
});
function parseXmlAndLoadTable(xmlText) {
let parser = new DOMParser();
let xmlDoc;
try {
xmlDoc = parser.parseFromString(xmlText, 'application/xml');
if (xmlDoc.getElementsByTagName('parsererror').length > 0) throw new Error('XML inválido');
} catch (e) {
Swal.fire('Error', 'No se pudo leer el XML.', 'error');
return;
}
datosComunesPedido = {
orderNumber: xmlDoc.querySelector('orderNumber')?.textContent ?? '',
shipping: {
address: xmlDoc.querySelector('shippingData > address')?.textContent ?? '',
city: xmlDoc.querySelector('shippingData > city')?.textContent ?? '',
country: xmlDoc.querySelector('shippingData > country')?.textContent ?? '',
postalCode: xmlDoc.querySelector('shippingData > postalCode')?.textContent ?? '',
name: xmlDoc.querySelector('shippingData > name')?.textContent ?? '',
phone: xmlDoc.querySelector('shippingData > phone')?.textContent ?? ''
},
labelUrl: xmlDoc.querySelector('urlPdfSeur')?.textContent ?? ''
};
const products = Array.from(xmlDoc.getElementsByTagName('product'));
productosOriginales = products.map((prod, idx) => ({ index: idx, data: prod }));
const rows = products.map((product, index) => {
const id = product.querySelector('id')?.textContent ?? '';
const title = product.querySelector('title')?.textContent ?? '';
const pages = product.querySelector('body > pages')?.textContent ?? '';
const tirada = product.querySelector('amount')?.textContent ?? '';
let interior = 'Desconocido';
const colorNode = product.querySelector('body > color');
if (colorNode) {
const monochrome = colorNode.querySelector('Monochrome')?.textContent ?? '0';
const cmyk = colorNode.querySelector('CMYK')?.textContent ?? '0';
const semicolor = colorNode.querySelector('Semicolor')?.textContent ?? '0';
if (monochrome === '1') interior = 'Negro';
else if (cmyk === '1') interior = 'Color';
else if (semicolor === '1') interior = 'Semicolor';
}
let tamano = 'Desconocido';
const sizeTags = product.querySelectorAll('size > *');
sizeTags.forEach(tag => {
if (tag.textContent === '1') {
tamano = tag.tagName.replace(/^size/, '');
}
});
return [
`<input type="checkbox" class="form-check-input select-row" checked>`,
`${datosComunesPedido.orderNumber} - ${id}`,
title,
tamano,
pages,
tirada,
interior,
'',
`<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 (!$.fn.DataTable.isDataTable('#xmlTable')) {
const headerHtml = `
<thead>
<tr>
<th><input type="checkbox" id="selectAll" /></th>
<th>Referencia</th>
<th>Título</th>
<th>Tamaño</th>
<th>Páginas</th>
<th>Tirada</th>
<th>Interior</th>
<th>Notas</th>
<th>Acciones</th>
</tr>
<tr>
<th></th><th></th><th></th><th></th><th></th>
<th></th><th></th><th></th><th></th>
</tr>
</thead>`;
$('#xmlTable').html(headerHtml);
}
dataTable = $('#xmlTable').DataTable({
destroy: true,
data: rows,
orderCellsTop: true,
responsive: true,
scrollX: true,
dom: 'lfrtip',
language: {
url: "/themes/vuexy/vendor/libs/datatables-sk/plugins/i18n/es-ES.json"
},
order: [[1, 'asc']]
});
$('#xmlTable thead tr:eq(1) th').each(function (i) {
if (![0, 7, 8].includes(i)) {
$(this).html('<input type="text" class="form-control form-control-sm" placeholder="Filtrar..." />');
$('input', this).on('keyup change', function () {
if (dataTable.column(i).search() !== this.value) {
dataTable.column(i).search(this.value).draw();
}
});
}
});
setupEventListeners();
}
function setupEventListeners() {
$('#xmlTable tbody').off('click', '.deleteRow').on('click', '.deleteRow', function () {
dataTable.row($(this).parents('tr')).remove().draw();
});
$('#xmlTable tbody').off('click', '.importRow').on('click', '.importRow', async function () {
const $row = $(this).closest('tr');
const rowIndex = dataTable.row($row).index();
const xmlProduct = productosOriginales.find(p => p.index === rowIndex)?.data;
if (!xmlProduct) return;
try {
const response = await fetch('/importador/bubok/importar-fila', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '<?= csrf_hash() ?>'
},
body: JSON.stringify({
producto: xmlToJson(xmlProduct),
pedido: datosComunesPedido
})
});
const result = await response.json();
const rowData = dataTable.row($row).data();
if (response.ok && result.status === 200) {
const skUrl = result.data?.sk_url?.replace('presupuestocliente', 'presupuestoadmin') ?? null;
const htmlLink = skUrl
? `<a href="${skUrl}" target="_blank" class="btn btn-sm btn-primary">Ver presupuesto</a>`
: 'Importado';
rowData[7] = htmlLink;
dataTable.row($row).data(rowData).draw(false);
Swal.fire({
title: 'Importado correctamente',
html: skUrl
? `Se importó correctamente.<br><a href="${skUrl}" target="_blank" class="btn btn-primary mt-2">Ver presupuesto</a>`
: 'Importación realizada.',
icon: 'success',
confirmButtonText: 'Aceptar',
customClass: { confirmButton: 'btn btn-success' }
});
} else {
rowData[7] = `<span class="badge bg-danger">${result.message ?? 'Error inesperado'}</span>`;
dataTable.row($row).data(rowData).draw(false);
Swal.fire({
title: 'Error',
text: result.message ?? 'Hubo un error al importar esta fila.',
icon: 'error',
confirmButtonText: 'Aceptar',
customClass: { confirmButton: 'btn btn-danger' }
});
}
dataTable.row($row).data(rowData).draw(false);
} catch (err) {
console.error(err);
Swal.fire('Error', 'Error de comunicación con el servidor.', 'error');
}
});
$('#selectAll').off('change').on('change', function () {
const checked = $(this).is(':checked');
$('#xmlTable tbody input.select-row:enabled').prop('checked', checked);
});
}
function xmlToJson(xmlNode) {
// Si es nodo de texto
if (xmlNode.nodeType === 3) {
return xmlNode.nodeValue.trim();
}
let obj = {};
// Procesar atributos si existen
if (xmlNode.attributes && xmlNode.attributes.length > 0) {
for (let attr of xmlNode.attributes) {
obj[`@${attr.name}`] = attr.value;
}
}
// Procesar hijos
if (xmlNode.hasChildNodes()) {
const children = Array.from(xmlNode.childNodes).filter(n => n.nodeType !== 8); // ignorar comentarios
// Si el único hijo es texto, devolver como string
if (children.length === 1 && children[0].nodeType === 3) {
return children[0].nodeValue.trim();
}
// Procesar nodos hijos normalmente
children.forEach(child => {
const name = child.nodeName;
const value = xmlToJson(child);
if (obj[name]) {
if (!Array.isArray(obj[name])) {
obj[name] = [obj[name]];
}
obj[name].push(value);
} else {
obj[name] = value;
}
});
}
return obj;
}
document.getElementById('importBtn').addEventListener('click', async function () {
if (!dataTable || dataTable.rows().count() === 0) {
Swal.fire({
title: 'Atención',
text: 'Primero debes cargar un archivo XML válido.',
icon: 'warning',
confirmButtonText: 'Aceptar',
customClass: { confirmButton: 'btn btn-warning' }
});
return;
}
const filasSeleccionadas = [];
dataTable.rows().every(function () {
const node = this.node();
const checkbox = $(node).find('input.select-row');
if (checkbox.length > 0 && checkbox.is(':checked') && !checkbox.is(':disabled')) {
filasSeleccionadas.push(this.index());
}
});
if (filasSeleccionadas.length === 0) {
Swal.fire({
title: 'Atención',
text: 'No hay filas seleccionadas.',
icon: 'warning',
confirmButtonText: 'Aceptar',
customClass: { confirmButton: 'btn btn-warning' }
});
return;
}
Swal.fire({
title: '¿Importar seleccionados?',
text: `Se van a importar ${filasSeleccionadas.length} filas.`,
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Sí, importar',
cancelButtonText: 'Cancelar',
reverseButtons: true,
customClass: {
confirmButton: 'btn btn-primary',
cancelButton: 'btn btn-secondary'
}
}).then(async (result) => {
if (!result.isConfirmed) return;
for (const i of filasSeleccionadas) {
const productXml = productosOriginales.find(p => p.index === i)?.data;
if (!productXml) continue;
try {
const response = await fetch('/importador/bubok/importar-fila', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '<?= csrf_hash() ?>'
},
body: JSON.stringify({
producto: xmlToJson(productXml),
pedido: datosComunesPedido
})
});
const result = await response.json();
const rowNode = dataTable.row(i).node();
const rowData = dataTable.row(i).data();
if (response.ok && result.status === 200) {
const skUrl = result.data?.sk_url?.replace('presupuestocliente', 'presupuestoadmin') ?? null;
rowData[7] = skUrl
? `<a href="${skUrl}" target="_blank" class="btn btn-sm btn-primary">Ver presupuesto</a>`
: '<span class="badge bg-success">Importado</span>';
} else {
rowData[7] = `<span class="badge bg-danger">${result.message ?? 'Error desconocido'}</span>`;
}
dataTable.row(rowNode).data(rowData).draw(false);
} catch (error) {
console.error('Importación fallida:', error);
}
}
Swal.fire({
title: 'Importación finalizada',
text: 'Se han procesado todas las filas seleccionadas.',
icon: 'success',
confirmButtonText: 'Aceptar',
customClass: { confirmButton: 'btn btn-success' }
});
});
});
});

View File

@ -169,12 +169,21 @@ document.addEventListener('DOMContentLoaded', function () {
dataTable.row($row).data(rowData).draw(false); // Redibujar la fila SIN perder scroll ni filtros
}
let html = skUrl
? `La fila se importó exitosamente.<br><br><a href="${skUrl}" target="_blank" class="btn btn-primary mt-2">Ver presupuesto</a>`
: 'La fila se importó exitosamente.';
let icon = 'success';
if (result.price_warning) {
html = skUrl ? `La fila se importó exitosamente, pero el precio se ha ajustado debajo de costes.<br><br><a href="${skUrl}" target="_blank" class="btn btn-primary mt-2">Ver presupuesto</a>`
: 'La fila se importó exitosamente, pero el precio se ha ajustado debajo de costes.';
icon = 'warning';
}
Swal.fire({
title: 'Importado correctamente',
html: skUrl
? `La fila se importó exitosamente.<br><br><a href="${skUrl}" target="_blank" class="btn btn-primary mt-2">Ver presupuesto</a>`
: 'La fila se importó exitosamente.',
icon: 'success',
html: html,
icon: icon,
confirmButtonText: 'Aceptar',
buttonsStyling: true,
customClass: { confirmButton: 'btn btn-success' }
@ -252,6 +261,9 @@ document.addEventListener('DOMContentLoaded', function () {
}).then(async (result) => {
if (!result.isConfirmed) return;
// array para contener los IDs que no se han podido ajustar
let idsNoAjustados = [];
for (const fila of filasAptas) {
try {
const response = await fetch('/importador/catalogo/importar-fila', {
@ -268,6 +280,10 @@ document.addEventListener('DOMContentLoaded', function () {
if (response.ok && result.status === 200) {
const skUrl = result.data?.sk_url?.replace('presupuestocliente', 'presupuestoadmin') ?? null;
if (result.price_warning) {
idsNoAjustados.push(result.data.sk_id);
}
if (skUrl) {
fila.rowData[6] = `<a href="${skUrl}" target="_blank" class="btn btn-sm btn-primary">Ver presupuesto</a>`;
} else {
@ -286,10 +302,16 @@ document.addEventListener('DOMContentLoaded', function () {
}
}
let text = 'Se han procesado todas las filas seleccionadas.';
let icon = 'success';
if (idsNoAjustados.length > 0) {
text = 'Se han procesado todas las filas seleccionadas, pero se han ajustado los siguientes presupuestos por debajo de costes: ' + idsNoAjustados.join(', ');
icon = 'warning';
}
Swal.fire({
title: 'Importación finalizada',
text: 'Se han procesado todas las filas seleccionadas.',
icon: 'success',
text: text,
icon: icon,
confirmButtonText: 'Aceptar',
buttonsStyling: true,
customClass: { confirmButton: 'btn btn-success' }

View File

@ -1540,6 +1540,12 @@ class LineasPresupuesto {
cliente_id: $('#clienteId').find(":selected").val(),
};
if($('#alert-datosLibro').html().includes(window.language.Presupuestos.validation.no_lp_for_merma) &&
(uso == 'interior' || uso == 'interior_rot')){
datos.calcular_merma = 1;
}
if (datos.ancho == 0 || datos.alto == 0 || datos.ancho == '' || datos.alto == '' || isNaN(datos.ancho) || isNaN(datos.alto)) {
return;
}

View File

@ -20,7 +20,12 @@ class Resumen {
});
$('#total_descuentoPercent').on('change', function () {
this.updateTotales({ updateLP: false, updateServicios: false, updateEnvio: false }, false);
let autoTotalAceptado = AutoNumeric.getAutoNumericElement($('#total_aceptado_revisado')[0]);
let total_aceptado_revisado = autoTotalAceptado.getNumber();
if(total_aceptado_revisado && total_aceptado_revisado != 0)
this.updateTotales({ updateLP: false, updateServicios: false, updateEnvio: false }, true);
else
this.updateTotales({ updateLP: false, updateServicios: false, updateEnvio: false }, false);
}.bind(this));
$("#totalDespuesDecuento").on('change', this.updateToastSummary.bind(this))
@ -354,7 +359,7 @@ class Resumen {
$('#total_descuentoPercent').val(0)
}
let totalAntesDescuento = totalCostes + totalMargenes + totalEnvios_base;
let totalDescuento = totalAntesDescuento * parseInt($('#total_descuentoPercent').val() || 0) / 100
let totalDescuento = totalAntesDescuento * parseFloat($('#total_descuentoPercent').val() || 0) / 100
let totalPresupuesto = totalAntesDescuento - totalDescuento; // para el calculo del precio_u solo se tiene en cuenta el base
let precioUnidad = totalPresupuesto / parseInt($('#tirada').val())