1materialAdmin 2 3 //================================================= 4 // ITEMS BATCH UPLOAD 5 //================================================= 6 .controller('itemsBatchUploadCtrl', function ($scope, $location, growlService, Item, Utils, Session) { 7 8 var _this = this; 9 10 $scope.primaryButtonHandler = (addonName) => Utils.requestAddon(addonName); 11 const user = Session.get(); 12 $scope.isSalesReportEnabled = user?.company?.addons?.includes("stock"); 13 14 // Inicializar formulario 15 _this.form = {}; 16 _this.uploading = false; 17 _this.uploadResult = null; 18 _this.errorCsvContent = null; 19 20 // Función para procesar el archivo CSV 21 this.submit = function (csvForm) { 22 if (csvForm.$invalid) { 23 growlService.growlError('Por favor seleccione un archivo CSV válido.'); 24 return; 25 } 26 27 if (!_this.form.csvFile) { 28 growlService.growlError('Por favor seleccione un archivo CSV.'); 29 return; 30 } 31 32 _this.uploading = true; 33 _this.uploadResult = null; 34 35 // Llamar al servicio de carga masiva 36 Item.batchUpload(_this.form.csvFile).then(function (response) { 37 _this.uploading = false; 38 _this.uploadResult = response; 39 40 // Si hay errores, generar el CSV con errores marcados 41 if (response.error_rows && response.error_rows.length > 0) { 42 _this.generateErrorCsv(response); 43 } 44 45 // Mostrar notificaciones 46 if (response.success_count > 0) { 47 growlService.growl( 48 response.success_count + ' items procesados correctamente.', 49 'success' 50 ); 51 } 52 53 if (response.error_count > 0) { 54 growlService.growlError( 55 'Se encontraron ' + response.error_count + ' errores durante el procesamiento.' 56 ); 57 } 58 59 // Si todo fue exitoso, mostrar opción de volver 60 if (response.error_count === 0 && response.success_count > 0) { 61 setTimeout(function () { 62 swal({ 63 title: "¡Procesamiento Completado!", 64 text: response.success_count + " items fueron procesados exitosamente.\n\n¿Desea volver al listado de items?", 65 type: "success", 66 showCancelButton: true, 67 confirmButtonClass: "btn-primary", 68 confirmButtonText: "Ir a Items", 69 cancelButtonText: "Quedarse aquÃ", 70 closeOnConfirm: true 71 }, function (goToItems) { 72 if (goToItems) { 73 $location.path('items'); 74 $scope.$apply(); 75 } 76 }); 77 }, 500); 78 } 79 80 }).catch(function (error) { 81 _this.uploading = false; 82 83 var errorMessage = 'Error al procesar el archivo CSV'; 84 if (error.data && error.data.error) { 85 errorMessage += ': ' + error.data.error; 86 } else if (error.data && error.data.details) { 87 errorMessage += ': ' + error.data.details; 88 } 89 90 growlService.growlError(errorMessage); 91 92 // Mostrar error detallado si está disponible 93 if (error.data && (error.data.error || error.data.details)) { 94 swal("Error de Procesamiento", errorMessage, "error"); 95 } 96 }); 97 }; 98 99 // Función para limpiar el formulario y resultados 100 this.clearResults = function () { 101 _this.uploadResult = null; 102 _this.form.csvFile = null; 103 _this.errorCsvContent = null; 104 }; 105 106 // Función para generar CSV con errores marcados 107 this.generateErrorCsv = function (response) { 108 if (!response.error_rows || response.error_rows.length === 0) { 109 return; 110 } 111 112 var headers = response.headers || ['ID', 'Tipo', 'Categoria', 'SKU', 'Nombre', 'Precio', 'Stock', 'Valor minimo', 'Moneda', 'Detalle', 'Impuesto']; 113 var csvContent = headers.join(',') + ',ERRORES\n'; 114 115 response.error_rows.forEach(function (errorRow) { 116 console.log('error_rows', errorRow); 117 var row = errorRow.row_data; 118 var line = ''; 119 120 headers.forEach(function (header, index) { 121 if (index > 0) line += ','; 122 123 var value = row[header] || ''; 124 125 // Escapar comillas y envolver en comillas si contiene comas
126 value = String(value).replace(/"/g, '""'); 127 if (value.includes(',') || value.includes('"') || value.includes('\n')) { 128 value = '"' + value + '"'; 129 } 130 131 line += value; 132 }); 133 134 // Agregar columna de descripción del error 135 var errorMsg = ''; 136 if (errorRow.error_type === 'missing_required_fields') { 137 errorMsg = 'Faltan campos: ' + errorRow.missing_fields.join(', '); 138 } else if (errorRow.error_type === 'invalid_currency') { 139 errorMsg = 'Moneda inválida'; 140 } else if (errorRow.error_type === 'invalid_category') { 141 errorMsg = 'CategorÃa inválida'; 142 } else if (errorRow.error_message) { 143 errorMsg = errorRow.error_message; 144 } 145 146 line += ',"' + errorMsg + '"'; 147 csvContent += line + '\n'; 148 }); 149 150 _this.errorCsvContent = csvContent; 151 }; 152 153 // Función para descargar CSV con errores 154 this.downloadErrorCsv = function () { 155 if (!_this.errorCsvContent) { 156 growlService.growlError('No hay CSV de errores disponible'); 157 return; 158 } 159 160 var blob = new Blob([_this.errorCsvContent], { type: 'text/csv;charset=utf-8;' }); 161 var link = document.createElement('a'); 162 163 if (link.download !== undefined) { 164 var url = URL.createObjectURL(blob); 165 link.setAttribute('href', url); 166 link.setAttribute('download', 'items_con_errores.csv'); 167 link.style.visibility = 'hidden'; 168 document.body.appendChild(link); 169 link.click(); 170 document.body.removeChild(link); 171 URL.revokeObjectURL(url); 172 173 growlService.growl('CSV con errores descargado exitosamente', 'success'); 174 } else { 175 growlService.growlError('Su navegador no soporta la descarga automática'); 176 } 177 }; 178 179 // Función para navegar de vuelta 180 this.goBack = function () { 181 $location.path('items'); 182 }; 183 184 // Variable para controlar el estado de descarga 185 _this.downloadingCSV = false; 186 187 // Función para descargar CSV con todos los items de la BD 188 this.downloadExampleCSV = function () { 189 _this.downloadingCSV = true; 190 191 // Obtener todos los items de la base de datos 192 Item.findAll().then(function (response) { 193 var csvContent = "ID,Tipo,Categoria,SKU,Nombre,Precio,Stock,Valor minimo,Moneda,Detalle,Impuesto\n"; 194 195 // Formatear cada item en el CSV 196 if (response.items && response.items.length > 0) { 197 response.items.forEach(function (item) { 198 var id = item.id || ''; 199 var type = item.is_product ? 'Producto' : 'Servicio'; 200 var categoria = item.category && item.category.name ? item.category.name : ''; 201 var codigo = item.code || ''; 202 var nombre = item.type || ''; 203 var precio = item.price || '0.00'; 204 var stock = item.available || '0'; 205 var valorMinimo = item.critic_amount || '10'; 206 var moneda = item.currency && item.currency.code ? item.currency.code : 'UYU'; 207 var detalle = item.details || ''; 208 var impuesto = item.tax?.name || ''; 209 210 // Escapar comillas dobles en los campos 211 categoria = categoria.replace(/"/g, '""'); 212 codigo = codigo.replace(/"/g, '""'); 213 nombre = nombre.replace(/"/g, '""'); 214 detalle = detalle.replace(/"/g, '""'); 215 216 // Agregar la lÃnea al CSV, envolviendo en comillas los campos que puedan contener comas 217 csvContent += id + ',' + 218 '"' + type + '",' + 219 '"' + categoria + '",' + 220 '"' + codigo + '",' + 221 '"' + nombre + '",' + 222 precio + ',' + 223 stock + ',' + 224 valorMinimo + ',' + 225 moneda + ',' + 226 '"' + detalle + '",' + 227 '"' + impuesto + '"\n'; 228 }); 229 } else { 230 // Si no hay items, agregar una lÃnea de ejemplo 231 csvContent += ""; 232 } 233 234 // Crear blob y URL para descarga 235 var blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); 236 var link = document.createElement('a'); 237 238 if (link.download !== undefined) { 239 // Navegadores modernos 240 var url = URL.createObjectURL(blob); 241 link.setAttribute('href', url); 242 link.setAttribute('download', 'items_completo.csv'); 243 link.style.visibility = 'hidden'; 244 document.body.appendChild(link); 245 link.click(); 246 document.body.removeChild(link); 247 URL.revokeObjectURL(url); 248 249 _this.downloadingCSV = false;
250 var itemCount = response.items ? response.items.length : 0; 251 growlService.growl('CSV con ' + itemCount + ' items descargado exitosamente', 'success'); 252 } else { 253 // Fallback para navegadores más antiguos 254 _this.downloadingCSV = false; 255 growlService.growlError('Su navegador no soporta la descarga automática. Por favor use el enlace directo.'); 256 } 257 258 }).catch(function (error) { 259 _this.downloadingCSV = false; 260 261 var errorMessage = 'Error al obtener los items de la base de datos'; 262 if (error.data && error.data.error) { 263 errorMessage += ': ' + error.data.error; 264 } 265 266 growlService.growlError(errorMessage); 267 }); 268 }; 269 270 });
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.