Changeset 3783588
- Timestamp:
- 11/04/2026 09:54:16 AM (15 hours ago)
- Location:
- wp-facturaone/trunk
- Files:
-
- 5 edited
-
FONE_imagenes.php (modified) (2 diffs)
-
FONE_index.php (modified) (4 diffs)
-
ajax.php (modified) (7 diffs)
-
readme.txt (modified) (1 diff)
-
wp-facturaone.php (modified) (17 diffs)
Legend:
- Unmodified
- Added
- Removed
-
wp-facturaone/trunk/FONE_imagenes.php
r3608362 r3783588 1 1 <?php 2 /* INDICE DE CAMBIOS GPT 3 * GPT0000000001 - Al retirar variaciones, conservar adjuntos usados por el principal u otros productos. 4 */ 2 5 if ( ! defined( 'ABSPATH' ) ) exit; 3 6 … … 856 859 857 860 if ($img_id > 0) { 858 wp_delete_attachment($img_id, true); 861 // GPT0000000001 - La imagen de una variacion puede ser tambien la del principal o su galeria. 862 FONE_delete_attachment_safe($img_id, $variation_id); 859 863 } 860 864 -
wp-facturaone/trunk/FONE_index.php
r3764464 r3783588 9 9 * GPT0000000006 - 2026-10-25: configurar en Avanzado el envio de pedidos con importe cero, desactivado por defecto. 10 10 * GPT0000000007 - 2026-10-25: mover Pedidos con importe 0 al final, despues de Personalizar Texto Reserva. 11 * GPT0000000008 - Identificar los productos con error y abrir su detalle desde Mas informacion. 11 12 */ 12 13 ?> … … 415 416 function FONE_importPopup(options) { 416 417 var popup = window.Swal || window.swal; 417 if (popup && typeof popup.fire === 'function') popup.fire(options); 418 } 418 // GPT0000000008 - Permitir el detalle en la propia pagina si el dialogo no esta disponible. 419 if (popup && typeof popup.fire === 'function') { 420 try { popup.fire(options); return true; } catch (e) {} 421 } 422 return false; 423 } 424 // GPT0000000008 - Mostrar solo los campos identificativos y el error que devuelve el servidor. 425 window.FONE_import_issue_details = ''; 426 function FONE_importErrorDetails(diagnostic) { 427 var rows = Array.isArray(diagnostic.error_rows) ? diagnostic.error_rows : []; 428 if (!rows.length) return ''; 429 var html = '<div style="text-align:left;overflow-wrap:anywhere;line-height:1.5;">'; 430 if (diagnostic.code === 'worker_item_errors') html += '<p>La importación sigue en curso y continúa con los demás elementos.</p>'; 431 if (diagnostic.code === 'worker_finished_with_errors') html += '<p>El proceso ha terminado con errores en los elementos indicados.</p>'; 432 for (var i=0; i<rows.length && i<20; i++) { 433 var row = rows[i] || {}, product = row.product || {}, exception = row.exception || {}; 434 html += '<div style="border:1px solid #ddd;border-radius:5px;padding:10px 12px;margin:10px 0;">' 435 + '<p style="margin:0 0 8px;"><strong>Elemento ' + FONE_escapeHtml(String(row.seq || '')) + ' (' + FONE_escapeHtml(row.func || '') + ')</strong></p>'; 436 function detail(label, value) { 437 if (value !== undefined && value !== null && value !== '') html += '<div><strong>' + label + ':</strong> <span style="white-space:pre-wrap;">' + FONE_escapeHtml(String(value)) + '</span></div>'; 438 } 439 detail('Nombre', product.name); 440 detail('SKU / referencia', product.sku); 441 detail('ID en FacturaONE', product.erp_id); 442 detail('ID en WooCommerce', product.woo_id); 443 detail('ID padre', product.parent_id); 444 if (!product.name && !product.sku && !product.erp_id && !product.woo_id && !product.parent_id) html += '<p>No se guardó la identificación del producto en este registro.</p>'; 445 detail('Tarea', row.task_file); 446 detail('Motivo', row.error); 447 detail('Tipo de error', exception.type); 448 detail('Archivo', exception.file); 449 detail('Línea', exception.line); 450 html += '</div>'; 451 } 452 if (parseInt(diagnostic.errors,10) > Math.min(rows.length,20)) html += '<p>Se muestran los primeros ' + Math.min(rows.length,20) + ' de ' + FONE_escapeHtml(String(diagnostic.errors)) + ' errores registrados.</p>'; 453 return html + '</div>'; 454 } 455 function FONE_openImportDetails() { 456 if (!window.FONE_import_issue_details) return; 457 var html = '<div style="max-height:65vh;overflow:auto;">' + window.FONE_import_issue_details + '</div>'; 458 if (FONE_importPopup({title:'Detalle de los productos con error', html:html, width:800})) return; 459 var box = document.getElementById('FONE-import-diagnostic'); 460 if (!box) return; 461 var detail = document.getElementById('FONE-import-error-details'); 462 if (!detail) { 463 detail = document.createElement('div'); 464 detail.id = 'FONE-import-error-details'; 465 box.appendChild(detail); 466 } 467 detail.innerHTML = html; 468 detail.style.display = 'block'; 469 } 470 document.addEventListener('click', function(e) { 471 var button = e.target; 472 while (button && button !== document && !(button.getAttribute && button.getAttribute('data-fone-import-action') === 'details')) button = button.parentNode; 473 if (!button || button === document) return; 474 var scope = button.parentNode; 475 while (scope && scope !== document && !(scope.getAttribute && scope.getAttribute('data-fone-import-details-root') === '1')) scope = scope.parentNode; 476 if (!scope || scope === document) return; 477 e.preventDefault(); 478 FONE_openImportDetails(); 479 }); 419 480 function FONE_showImportIssue(info, key) { 420 481 var box = document.getElementById('FONE-import-diagnostic'); … … 427 488 target.insertBefore(box, target.firstChild); 428 489 } 490 // GPT0000000008 - Actualizar el detalle incluso cuando el aviso ya se notifico durante el progreso. 491 window.FONE_import_issue_details = info.details_html || ''; 492 var content = info.html; 493 if (window.FONE_import_issue_details) content += '<p data-fone-import-details-root="1"><button type="button" class="button" data-fone-import-action="details">Más información</button></p>'; 429 494 box.style.display = 'block'; 430 box.innerHTML = '<p><strong>' + FONE_escapeHtml(info.title) + '</strong></p>' + info.html;495 box.innerHTML = '<p><strong>' + FONE_escapeHtml(info.title) + '</strong></p>' + content; 431 496 var watch = window.FONE_import_watch; 432 497 if (watch && key && watch.reported[key]) return; 433 498 if (watch && key) watch.reported[key] = true; 434 FONE_importPopup({type:'error', title:info.title, html: info.html, width:760});499 FONE_importPopup({type:'error', title:info.title, html:content, width:760}); 435 500 } 436 501 function FONE_clearImportIssue() { 502 // GPT0000000008 - No reutilizar el detalle de una importacion anterior. 503 window.FONE_import_issue_details = ''; 437 504 var box = document.getElementById('FONE-import-diagnostic'); 438 505 if (box) box.style.display = 'none'; … … 572 639 field('Pendientes', diagnostic.pending); 573 640 field('Errores', diagnostic.errors || info.queue_errors); 574 var rows = diagnostic.error_rows || []; 641 // GPT0000000008 - Identificacion breve en el aviso y detalle de todos los errores disponibles. 642 var rows = Array.isArray(diagnostic.error_rows) ? diagnostic.error_rows : []; 575 643 for (var i=0; i<rows.length && i<5; i++) { 644 var product = rows[i].product || {}, identity = []; 645 if (product.name) identity.push(String(product.name)); 646 if (product.sku) identity.push('SKU: ' + String(product.sku)); 647 if (product.erp_id) identity.push('ID FacturaONE: ' + String(product.erp_id)); 648 if (product.woo_id) identity.push('ID WooCommerce: ' + String(product.woo_id)); 649 field('Producto del elemento ' + rows[i].seq, identity.join(' | ')); 576 650 field('Elemento ' + rows[i].seq + ' (' + rows[i].func + ')', rows[i].error); 577 651 } 578 652 var html = '<pre style="white-space:pre-wrap;overflow-wrap:anywhere;text-align:left;font-size:12px;max-height:340px;overflow:auto;">' + FONE_escapeHtml(lines.join('\n').substring(0,6000)) + '</pre>'; 579 653 if (raw) html += '<details><summary>Respuesta recibida de WordPress</summary><pre style="white-space:pre-wrap;overflow-wrap:anywhere;text-align:left;font-size:12px;max-height:200px;overflow:auto;">' + FONE_escapeHtml(raw.substring(0,2000)) + '</pre></details>'; 580 return {title: stage === 'Consulta de progreso' ? 'Error al consultar el progreso' : 'Error al importar productos', html:html };654 return {title: stage === 'Consulta de progreso' ? 'Error al consultar el progreso' : 'Error al importar productos', html:html, details_html:FONE_importErrorDetails(diagnostic)}; 581 655 } 582 656 function execbackground(phpfile,accion,e){ -
wp-facturaone/trunk/ajax.php
r3758931 r3783588 3 3 * GPT0000000001 - Exponer errores y falta de avance del proceso al consultar su progreso. 4 4 * GPT0000000002 - Consultar el resumen final sin cache antes de informar de una cola vacia. 5 * GPT0000000003 - Conservar producto y excepcion en los errores y mostrar hasta veinte detalles al finalizar. 5 6 */ 6 7 if ( ! defined('ABSPATH') ) { … … 33 34 }} 34 35 36 // GPT0000000003 - Contexto de la fila actual; no se guarda el payload ni datos de conexion. 37 if (!function_exists('FONE_ajax_product_context')) { 38 function FONE_ajax_product_context($product) { 39 if (!isset($GLOBALS['FONE_import_error_context']) || !is_array($product)) { return; } 40 foreach (array('erp_id', 'name', 'sku', 'woo_id', 'parent_id') as $key) { 41 if (isset($product[$key]) && is_scalar($product[$key])) { 42 $GLOBALS['FONE_import_error_context'][$key] = $product[$key]; 43 } 44 } 45 }} 46 if (!function_exists('FONE_ajax_diagnostic_text')) { 47 function FONE_ajax_diagnostic_text($text, $limit = 2000) { 48 if (!is_scalar($text)) { return ''; } 49 $text = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', (string)$text); 50 $secret = (string)get_option('FacturaONE_APIKEY', ''); 51 if ($secret !== '') { $text = str_replace($secret, '[API Key oculta]', $text); } 52 return preg_match('/^.{0,'.(int)$limit.'}/us', $text, $match) ? $match[0] : '[Texto no valido en UTF-8]'; 53 }} 54 if (!function_exists('FONE_ajax_error_detail')) { 55 function FONE_ajax_error_detail($error, $product = array(), $exception = array()) { 56 $detail = array('version'=>1, 'error'=>FONE_ajax_diagnostic_text($error), 'product'=>array(), 'exception'=>array()); 57 foreach (array('erp_id'=>64, 'name'=>300, 'sku'=>150, 'woo_id'=>64, 'parent_id'=>64) as $key=>$limit) { 58 if (isset($product[$key])) { $detail['product'][$key] = FONE_ajax_diagnostic_text($product[$key], $limit); } 59 } 60 foreach (array('type'=>150, 'file'=>180, 'line'=>12) as $key=>$limit) { 61 if (isset($exception[$key])) { $detail['exception'][$key] = FONE_ajax_diagnostic_text($exception[$key], $limit); } 62 } 63 return $detail; 64 }} 65 if (!function_exists('FONE_ajax_error_rows')) { 66 function FONE_ajax_error_rows($job_id) { 67 global $wpdb; 68 $table = $wpdb->prefix . 'fone_jobs'; 69 $rows = $wpdb->get_results($wpdb->prepare("SELECT seq, func, task_file, LEFT(error, 16000) AS error FROM $table WHERE job_id=%d AND status=3 ORDER BY seq ASC LIMIT 20", (int)$job_id), ARRAY_A); 70 if (!is_array($rows)) { return array(); } 71 foreach ($rows as &$row) { 72 $stored = (string)($row['error'] ?? ''); 73 $detail = null; 74 if (strpos($stored, 'FONE_IMPORT_DETAIL:') === 0) { 75 $parsed = json_decode(substr($stored, strlen('FONE_IMPORT_DETAIL:')), true); 76 if (is_array($parsed) && ($parsed['version'] ?? 0) === 1 && isset($parsed['error'])) { 77 $detail = FONE_ajax_error_detail($parsed['error'], (array)($parsed['product'] ?? array()), (array)($parsed['exception'] ?? array())); 78 } 79 } 80 if ($detail === null) { $detail = FONE_ajax_error_detail($stored); } 81 $row['error'] = $detail['error']; 82 $row['product'] = $detail['product']; 83 $row['exception'] = $detail['exception']; 84 $row['task_file'] = basename((string)($row['task_file'] ?? '')); 85 } 86 unset($row); 87 return $rows; 88 }} 89 35 90 if (!function_exists('FONE_ajax_finish_job')) { 36 91 function FONE_ajax_finish_job($job_id, $backgroundtask = '') { … … 56 111 57 112 if ($summary['errors'] > 0) { 58 $summary['error_rows'] = $wpdb->get_results( 59 $wpdb->prepare("SELECT seq, func, LEFT(error, 250) AS error FROM $table WHERE job_id=%d AND status=3 ORDER BY seq ASC LIMIT 20", $job_id), 60 ARRAY_A 61 ); 62 } 63 } 64 65 update_option('_facturaone_job_last_summary', wp_json_encode($summary)); 113 // GPT0000000003 - Conservar identificadores antes de limpiar la cola. 114 $summary['error_rows'] = FONE_ajax_error_rows($job_id); 115 } 116 } 117 118 // GPT0000000003 - Cargar el informe detallado solo cuando se consulte, no en cada visita. 119 update_option('_facturaone_job_last_summary', wp_json_encode($summary), false); 66 120 update_option('_facturaone_status_backgroundid', '0'); 67 121 update_option('_facturaone_status_realizado', ''); … … 191 245 if ($errors > 0) { 192 246 $import_diagnostic = array('code'=>'worker_finished_with_errors', 'stage'=>'worker', 'job_id'=>$summary_job, 193 'msg'=>'La importacion ha terminado con errores.', 'errors'=>$errors, 'error_rows'=>array_slice((array)($summary['error_rows'] ?? array()), 0, 5)); 247 // GPT0000000003 - Resumen completo para el boton Mas informacion. 248 'msg'=>'La importacion ha terminado con errores.', 'tasks'=>$total, 'done_or_error'=>$done_or_error, 'pending'=>0, 'errors'=>$errors, 'error_rows'=>array_slice((array)($summary['error_rows'] ?? array()), 0, 20)); 194 249 } 195 250 } elseif ($status_backgroundid > 0 && (!$requested_job || $requested_job === $status_backgroundid)) { 196 251 $dispatch_error = (string)get_option('_facturaone_job_dispatch_error_'.$status_backgroundid, ''); 197 252 if ($errors > 0) { 198 $error_rows = $wpdb->get_results($wpdb->prepare("SELECT seq, func, LEFT(error, 250) AS error FROM $table WHERE job_id=%d AND status=3 ORDER BY seq ASC LIMIT 5", $status_backgroundid), ARRAY_A); 253 // GPT0000000003 - Mismo detalle durante el proceso y tras finalizar. 254 $error_rows = FONE_ajax_error_rows($status_backgroundid); 199 255 $import_diagnostic = array('code'=>'worker_item_errors', 'msg'=>'El proceso ha registrado errores al importar elementos.', 'error_rows'=>$error_rows); 200 256 } elseif ($dispatch_error !== '') { … … 405 461 $ok = false; 406 462 $err = null; 463 // GPT0000000003 - Reiniciar por fila para no atribuir un error al producto anterior. 464 $GLOBALS['FONE_import_error_context'] = array(); 465 $error_exception = array(); 407 466 408 467 try { … … 434 493 } catch (Throwable $e) { 435 494 $err = $e->getMessage(); 495 // GPT0000000003 - Solo clase, archivo y linea; nunca argumentos ni traza completa. 496 $error_exception = array('type'=>get_class($e), 'file'=>basename($e->getFile()), 'line'=>$e->getLine()); 436 497 } catch (Exception $e) { 437 498 $err = $e->getMessage(); 499 $error_exception = array('type'=>get_class($e), 'file'=>basename($e->getFile()), 'line'=>$e->getLine()); 438 500 } 439 501 … … 448 510 } 449 511 } else { 450 $wpdb->update($table, array('status' => 3, 'error' => $err), array('id' => (int) $r['id']), array('%d', '%s'), array('%d')); 512 // GPT0000000003 - El fallo afecta a esta fila; el foreach sigue con las siguientes. 513 $detail = FONE_ajax_error_detail($err, $GLOBALS['FONE_import_error_context'], $error_exception); 514 $encoded_error = wp_json_encode($detail, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); 515 $row_error = $encoded_error !== false ? 'FONE_IMPORT_DETAIL:'.$encoded_error : FONE_ajax_diagnostic_text($err); 516 $wpdb->update($table, array('status' => 3, 'error' => $row_error), array('id' => (int) $r['id']), array('%d', '%s'), array('%d')); 451 517 error_log('FONE job row error job_id=' . $job_id . ' seq=' . (int) $r['seq'] . ' func=' . (string) $r['func'] . ' error=' . (string) $err); 452 518 } 453 519 520 // GPT0000000003 - El contexto solo existe durante la ejecucion de cada fila. 521 unset($GLOBALS['FONE_import_error_context']); 454 522 update_option('_facturaone_job_last_run_' . $job_id, time()); 455 523 -
wp-facturaone/trunk/readme.txt
r3764464 r3783588 6 6 WC requires at least: 8.0 7 7 WC tested up to: 10.0 8 Stable tag: 5.5 38 Stable tag: 5.54 9 9 License: GPLv3 10 10 License URI: https://www.gnu.org/licenses/gpl-3.0.html -
wp-facturaone/trunk/wp-facturaone.php
r3764464 r3783588 4 4 Plugin URI: https://www.facturaone.com/ 5 5 Description: La forma más fácil de trabajar con tu tienda online desde nuestro ERP FacturaONE. 6 Version: 5.5 36 Version: 5.54 7 7 Author: FacturaONE 8 8 Author URI: https://www.facturaone.com/ … … 20 20 * Indice de cambios GPT: 21 21 * GPT0000000001 - 2026-10-25: opcion para omitir pedidos de importe cero por defecto en envios y emision. 22 * GPT0000000002 - Identificar el producto o variacion de los errores de importacion sin alterar su guardado. 23 * GPT0000000003 - Recuperar como simple un principal ERP asociado a una variacion sin padre, conservando su ID. 24 * GPT0000000004 - Sincronizar el tipo simple, variable o compuesto segun ERP antes de guardar y renovar su cache. 22 25 */ 23 26 if ( ! defined( 'ABSPATH' ) ) exit; … … 545 548 } 546 549 550 // GPT0000000004 - Usar la clase del tipo ERP, aunque WooCommerce conserve otro tipo anterior. 551 function FONE_new_product_for_erp($product_id, $product_type) { 552 $product_id = (int)$product_id; 553 $classname = WC_Product_Factory::get_product_classname($product_id, $product_type); 554 $product = new $classname($product_id); 555 if (!($product instanceof WC_Product) || !$product->is_type($product_type)) { 556 throw new Exception('WooCommerce no dispone de una clase compatible con el tipo ERP ' 557 .$product_type.' para el producto '.$product_id.'. Revisar la extension de productos correspondiente.'); 558 } 559 return $product; 560 } 561 562 // GPT0000000004 - Evitar que una lectura posterior (stock/SEO) reutilice el tipo anterior. 563 function FONE_set_erp_product_type($product_id, $product_type) { 564 $product_id = (int)$product_id; 565 $result = wp_set_object_terms($product_id, $product_type, 'product_type', false); 566 if (is_wp_error($result)) { 567 throw new Exception('No se ha podido establecer el tipo '.$product_type.' del producto WooCommerce ' 568 .$product_id.': '.$result->get_error_message()); 569 } 570 clean_post_cache($product_id); 571 if (class_exists('WC_Cache_Helper')) { 572 WC_Cache_Helper::invalidate_cache_group('product_'.$product_id); 573 } 574 wp_cache_delete('product_type_'.$product_id, 'products'); 575 wp_cache_delete($product_id, 'products'); 576 wc_delete_product_transients($product_id); 577 } 578 547 579 function FONE_subeproducto($baseurlfact, $deletevariaciones, $item_lookup_id, $item_name, $item_description, $item_description_web, $urlimage, $item_price, $tarifa1, $tarifa2, $tarifa3, $tarifa4, $tarifa5, $item_kgs_neto, $item_kgs_bruto, $familia_id, $subfamilia_id, $item_activado, $tax_rate_id, $variaciones, $compuestos, $item_txt_alternativo, $marca, $ean, $item_clientid, $item_onorder, $item_wp_stock_control, $item_wp_stock_visible, $item_seo_fraseobjetivo, $item_seo_metadescripcion, $item_wp_visibilidad, $item_wp_destacado, $item_slug, $item_description_web_corta, $item_wp_backorders, $item_categorias, $item_sku, $familia_web_activate, $marca_web_activate, $largo='', $ancho='', $alto='', $item_minquantity='', $item_maxquantity='', $item_stepquantity='', $item_stock=0){ 548 580 581 // GPT0000000002 - Identificar el articulo incluso si falla la busqueda/carga en WooCommerce. 582 if (function_exists('FONE_ajax_product_context')) { 583 FONE_ajax_product_context(array('erp_id'=>$item_lookup_id, 'name'=>$item_name, 'sku'=>$item_sku, 'woo_id'=>'', 'parent_id'=>'')); 584 } 549 585 static $cfg = null; 550 586 static $tax_cache = []; … … 564 600 565 601 $_product_id = (int) fone_get_product_id_by_idfactura($item_lookup_id, $item_sku); 602 // GPT0000000002 - Incluir el ID que WooCommerce intenta cargar. 603 if (function_exists('FONE_ajax_product_context')) { FONE_ajax_product_context(array('woo_id'=>$_product_id)); } 566 604 $itemnameorig = $item_name; 567 605 … … 578 616 if (trim((string)$item_name) == '') { $item_name = $itemnameorig; } 579 617 618 // GPT0000000004 - El tipo actual del ERP manda en altas y actualizaciones de cualquier principal. 619 $hasVar = FONE_has_data($variaciones); 620 $hasCom = FONE_has_data($compuestos); 621 $fone_product_type = $hasVar ? 'variable' : ($hasCom ? 'composite' : 'simple'); 622 580 623 // --------------------------- 581 624 // UPDATE producto existente … … 583 626 if ($_product_id > 0) { 584 627 585 //comprueba si es simple, variable o compuesto 586 $hasVar = FONE_has_data($variaciones); 587 $hasCom = FONE_has_data($compuestos); 588 if (!$hasVar && !$hasCom) { 589 $objProduct = new WC_Product_Simple((int)$_product_id); 590 } else { 591 $objProduct = wc_get_product((int)$_product_id); // variable o composite 592 } 593 594 if (!$objProduct) { return 0; } 628 // GPT0000000003 - Solo recuperar una variacion huerfana vinculada por su ID ERP exacto. 629 // FONE_subeproducto recibe principales; una variacion con padre exige revisar la asociacion. 630 if (get_post_type($_product_id) === 'product_variation') { 631 $fone_parent_id = (int) wp_get_post_parent_id($_product_id); 632 $fone_linked_erp_id = get_post_meta($_product_id, 'fone_idfactura', true); 633 $fone_exact_erp_link = is_scalar($fone_linked_erp_id) 634 && trim((string)$fone_linked_erp_id) === trim((string)$item_lookup_id) 635 && trim((string)$item_lookup_id) !== ''; 636 if (function_exists('FONE_ajax_product_context')) { 637 FONE_ajax_product_context(array('parent_id'=>$fone_parent_id)); 638 } 639 if ($fone_parent_id !== 0 || $hasVar || $hasCom || !$fone_exact_erp_link) { 640 throw new Exception('El articulo principal ERP '.$item_lookup_id.' esta asociado a la variacion WooCommerce ' 641 .$_product_id.' (padre: '.$fone_parent_id.'). Revisar la asociacion; no se ha convertido a producto simple.'); 642 } 643 644 // Incluir el nombre ERP permite recuperar tambien el registro con titulo vacio. 645 $fone_recovered_id = wp_update_post(wp_slash(array( 646 'ID' => $_product_id, 647 'post_type' => 'product', 648 'post_parent' => 0, 649 'post_title' => (string)$item_name, 650 )), true); 651 if (is_wp_error($fone_recovered_id) || (int)$fone_recovered_id !== $_product_id) { 652 $fone_recovery_error = is_wp_error($fone_recovered_id) 653 ? $fone_recovered_id->get_error_message() : 'WordPress no ha confirmado el ID actualizado.'; 654 throw new Exception('No se ha podido recuperar el producto WooCommerce '.$_product_id.': '.$fone_recovery_error); 655 } 656 FONE_set_erp_product_type($_product_id, 'simple'); 657 } 658 659 // GPT0000000004 - Guardar directamente con la clase de destino; no reutilizar la anterior. 660 $objProduct = FONE_new_product_for_erp($_product_id, $fone_product_type); 595 661 596 662 // Precio (comparación mínima) … … 801 867 // CREATE nuevo producto 802 868 // --------------------------- 803 $objProduct = new WC_Product_Simple(); 869 // GPT0000000004 - Aplicar la misma seleccion de tipo al crear. 870 $objProduct = FONE_new_product_for_erp(0, $fone_product_type); 804 871 $objProduct->set_name($item_name); 805 872 … … 941 1008 $product_id = $objProduct->save(); 942 1009 $_product_id = (int) $product_id; 1010 // GPT0000000002 - Incluir tambien el ID de un producto recien creado. 1011 if (function_exists('FONE_ajax_product_context')) { FONE_ajax_product_context(array('woo_id'=>$_product_id)); } 943 1012 944 1013 // slug old … … 1089 1158 1090 1159 if ($hasVar){ 1091 // 1) Cambiar taxonomy product_type 1092 wp_remove_object_terms($_product_id, 'simple', 'product_type'); 1093 if (get_term_by('slug','composite','product_type')) { 1094 wp_remove_object_terms($_product_id, 'composite', 'product_type'); 1095 } 1096 wp_set_object_terms($_product_id, 'variable', 'product_type', false); 1097 1098 // 2) Limpia caches ANTES de tocar el objeto 1099 clean_post_cache($_product_id); 1100 wc_delete_product_transients($_product_id); 1101 wp_cache_delete($_product_id, 'products'); 1160 // GPT0000000004 - Fijar tipo y limpiar tambien la cache del tipo antes de cargar el objeto. 1161 FONE_set_erp_product_type($_product_id, 'variable'); 1102 1162 1103 1163 // 3) Usa SIEMPRE el tipo correcto … … 1127 1187 FONE_delete_product_image_variaciones($_product_id); 1128 1188 1129 wp_remove_object_terms($_product_id, 'simple', 'product_type'); 1130 wp_remove_object_terms($_product_id, 'variable', 'product_type'); 1131 1132 if (get_term_by('slug','composite','product_type')) { 1133 wp_set_object_terms($_product_id, 'composite', 'product_type', false); 1134 } 1189 // GPT0000000004 - El objeto ya se guardo como compuesto; mantener tipo y cache coherentes. 1190 FONE_set_erp_product_type($_product_id, 'composite'); 1135 1191 1136 1192 FONE_componentes($_product_id, $compuestos); … … 1149 1205 FONE_delete_product_image_variaciones($_product_id); 1150 1206 1151 wp_remove_object_terms($_product_id, 'variable', 'product_type'); 1152 1153 if (get_term_by('slug','composite','product_type')) { 1154 wp_remove_object_terms($_product_id, 'composite', 'product_type'); 1155 } 1156 1157 wp_set_object_terms($_product_id, 'simple', 'product_type', false); 1207 // GPT0000000004 - Conversion general a simple, tambien al venir de variable o compuesto. 1208 FONE_set_erp_product_type($_product_id, 'simple'); 1158 1209 delete_post_meta($_product_id, 'wooco_components'); 1159 1210 … … 1497 1548 AND p.post_parent = %d 1498 1549 AND p.post_status IN ('publish','draft','private','pending') 1550 1499 1551 AND pm.meta_key = 'fone_idfactura' 1500 1552 AND pm.meta_value = %s … … 1703 1755 FONE_ensure_parent_attributes_and_terms($_product_id, $terms_by_attr); 1704 1756 1757 // GPT0000000002 - Conservar el contexto del padre para restaurarlo tras las variaciones. 1758 $fone_import_parent_context = $GLOBALS['FONE_import_error_context'] ?? null; 1705 1759 // 6) Procesa variaciones entrantes (update/create) + aplica lógica de activación 1706 1760 $incoming_ids = []; … … 1794 1848 } 1795 1849 1850 // GPT0000000002 - La clave sku de esta lista es el identificador ERP de la variacion. 1851 if (is_array($fone_import_parent_context) && function_exists('FONE_ajax_product_context')) { 1852 FONE_ajax_product_context(array('erp_id'=>$idf, 'name'=>($fone_import_parent_context['name'] ?? '').' / '.implode(' / ', $attr_values), 1853 'sku'=>'', 'woo_id'=>$existing_by_idfactura[$idf] ?? '', 'parent_id'=>$_product_id)); 1854 } 1796 1855 // UPDATE 1797 1856 if (isset($existing_by_idfactura[$idf])) { … … 1877 1936 } 1878 1937 1938 // GPT0000000002 - Los pasos posteriores vuelven a corresponder al producto padre. 1939 if (is_array($fone_import_parent_context)) { $GLOBALS['FONE_import_error_context'] = $fone_import_parent_context; } 1879 1940 // 7) Variaciones que EXISTEN en Woo pero ya NO vienen del ERP => borrar definitivamente 1880 1941 // IMPORTANTE: … … 2105 2166 // Creating the product variation 2106 2167 $variation_id = wp_insert_post( $variation_post ); 2168 // GPT0000000002 - Identificar una variacion recien creada antes de cargarla. 2169 if (function_exists('FONE_ajax_product_context') && !is_wp_error($variation_id)) { FONE_ajax_product_context(array('woo_id'=>(int)$variation_id)); } 2107 2170 2108 2171 // Get an instance of the WC_Product_Variation object … … 2354 2417 $name=preg_replace('/\s+/u',' ',$name); 2355 2418 $n=function_exists('mb_strtolower')?mb_strtolower($name,'UTF-8'):strtolower($name); 2419 2356 2420 2357 2421 $terms=get_terms(['taxonomy'=>$taxonomy,'hide_empty'=>false,'parent'=>$parent_id,'number'=>0]);
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)