Numeración de pedidos secuenciales para Woocommerce con prefijo/sufijo

Autor: Luís Muñoz
php code
<?php // === EMP - Numeración de pedidos Woo con prefijo/sufijo === // Snippet para WPCodeBox o functions.php del child theme. if ( ! defined('ABSPATH') ) { exit; } if ( ! class_exists('EMP_Custom_Order_Numbering') ) : class EMP_Custom_Order_Numbering { const OPTION_START = 'emp_con_start_number'; const OPTION_NEXT = 'emp_con_next_number'; const OPTION_PREFIX = 'emp_con_prefix'; const OPTION_SUFFIX = 'emp_con_suffix'; const META_KEY = '_emp_custom_order_number'; public function __construct() { add_action('admin_menu', [$this, 'add_menu']); add_action('init', [$this, 'maybe_init_defaults']); add_action('woocommerce_new_order', [$this, 'assign_order_number'], 10, 1); add_filter('woocommerce_order_number', [$this, 'filter_order_number'], 10, 2); add_filter('manage_edit-shop_order_columns', [$this, 'add_admin_column'], 20); add_action('manage_shop_order_posts_custom_column', [$this, 'render_admin_column'], 10, 2); // Búsqueda en listado de pedidos add_filter('request', [$this, 'enable_search_by_custom_number']); } public function maybe_init_defaults() { if ( get_option(self::OPTION_START, null) === null ) { add_option(self::OPTION_START, 1000); } if ( get_option(self::OPTION_NEXT, null) === null ) { add_option(self::OPTION_NEXT, (int) get_option(self::OPTION_START, 1000)); } if ( get_option(self::OPTION_PREFIX, null) === null ) { add_option(self::OPTION_PREFIX, ''); } if ( get_option(self::OPTION_SUFFIX, null) === null ) { add_option(self::OPTION_SUFFIX, ''); } } public function add_menu() { if ( ! current_user_can('manage_woocommerce') ) return; add_submenu_page( 'woocommerce', 'Numeración de pedidos', 'Numeración de pedidos', 'manage_woocommerce', 'emp-custom-order-numbering', [$this, 'render_settings_page'] ); } public function render_settings_page() { if ( isset($_POST['emp_con_nonce']) && wp_verify_nonce($_POST['emp_con_nonce'], 'emp_con_save') && current_user_can('manage_woocommerce') ) { $start = isset($_POST['emp_con_start']) ? (int) sanitize_text_field($_POST['emp_con_start']) : 0; $next = isset($_POST['emp_con_next']) ? (int) sanitize_text_field($_POST['emp_con_next']) : 0; $prefix = isset($_POST['emp_con_prefix']) ? sanitize_text_field($_POST['emp_con_prefix']) : ''; $suffix = isset($_POST['emp_con_suffix']) ? sanitize_text_field($_POST['emp_con_suffix']) : ''; if ( $start > 0 ) { update_option(self::OPTION_START, $start); if ( (int) get_option(self::OPTION_NEXT, 0) < $start ) { update_option(self::OPTION_NEXT, $start); } } if ( $next > 0 ) { update_option(self::OPTION_NEXT, $next); } update_option(self::OPTION_PREFIX, $prefix); update_option(self::OPTION_SUFFIX, $suffix); echo '<div class="updated notice"><p>Ajustes guardados.</p></div>'; } $start = (int) get_option(self::OPTION_START, 1000); $next = (int) get_option(self::OPTION_NEXT, $start); $prefix = (string) get_option(self::OPTION_PREFIX, ''); $suffix = (string) get_option(self::OPTION_SUFFIX, ''); ?> <div class="wrap"> <h1>Numeración personalizada de pedidos</h1> <form method="post"> <?php wp_nonce_field('emp_con_save', 'emp_con_nonce'); ?> <table class="form-table" role="presentation"> <tr> <th scope="row"><label for="emp_con_start">Número de inicio</label></th> <td> <input name="emp_con_start" id="emp_con_start" type="number" min="1" value="<?php echo esc_attr($start); ?>" class="regular-text" /> <p class="description">Mínimo de la secuencia. Si lo subes, el siguiente número se ajusta si hace falta.</p> </td> </tr> <tr> <th scope="row"><label for="emp_con_next">Número del próximo pedido</label></th> <td> <input name="emp_con_next" id="emp_con_next" type="number" min="1" value="<?php echo esc_attr($next); ?>" class="regular-text" /> <p class="description">Úsalo solo si quieres saltar la secuencia aun número mayor manualmente.</p> </td> </tr> <tr> <th scope="row"><label for="emp_con_prefix">Prefijo (opcional)</label></th> <td> <input name="emp_con_prefix" id="emp_con_prefix" type="text" value="<?php echo esc_attr($prefix); ?>" class="regular-text" /> <p class="description">Ej.: <code>EMP-</code> o <code>2025-</code></p> </td> </tr> <tr> <th scope="row"><label for="emp_con_suffix">Sufijo (opcional)</label></th> <td> <input name="emp_con_suffix" id="emp_con_suffix" type="text" value="<?php echo esc_attr($suffix); ?>" class="regular-text" /> <p class="description">Ej.: <code>-ES</code></p> </td> </tr> </table> <?php submit_button('Guardar cambios'); ?> </form> </div> <?php } public function assign_order_number( $order_id ) { if ( ! $order_id ) return; if ( get_post_meta($order_id, self::META_KEY, true) ) return; // ya asignado $start = (int) get_option(self::OPTION_START, 1000); $next = (int) get_option(self::OPTION_NEXT, $start); if ( $next < $start ) $next = $start; update_post_meta($order_id, self::META_KEY, $next); update_option(self::OPTION_NEXT, $next + 1); } public function filter_order_number( $order_number, $order ) { $id = is_a($order, 'WC_Order') ? $order->get_id() : (int) $order; $raw = get_post_meta($id, self::META_KEY, true); return $raw ? $this->format_number((int) $raw) : $order_number; } private function format_number( $n ) { $prefix = (string) get_option(self::OPTION_PREFIX, ''); $suffix = (string) get_option(self::OPTION_SUFFIX, ''); return $prefix . $n . $suffix; } public function add_admin_column( $columns ) { $new = []; foreach ( $columns as $key => $label ) { $new[$key] = $label; if ( 'order_number' === $key ) { $new['emp_con_number'] = 'Nº personalizado'; } } return $new; } public function render_admin_column( $column, $post_id ) { if ( 'emp_con_number' === $column ) { $raw = get_post_meta($post_id, self::META_KEY, true); echo $raw ? esc_html( $this->format_number((int)$raw) ) : '—'; } } public function enable_search_by_custom_number( $vars ) { global $pagenow, $typenow; if ( 'edit.php' === $pagenow && 'shop_order' === $typenow && ! empty($_GET['s']) ) { $term = trim(wp_unslash($_GET['s'])); // 1) Si es solo dígitos, busca por número crudo. if ( ctype_digit($term) ) { $order_id = $this->get_order_id_by_custom_number( (int) $term ); if ( $order_id ) { $vars['post__in'] = [$order_id]; $vars['s'] = ''; return $vars; } } // 2) Intentar “prefijo + número + sufijo” $prefix = (string) get_option(self::OPTION_PREFIX, ''); $suffix = (string) get_option(self::OPTION_SUFFIX, ''); $p = preg_quote($prefix, '/'); $s = preg_quote($suffix, '/'); $pattern = '/^' . $p . '(\d+)' . $s . '$/'; if ( preg_match($pattern, $term, $m) ) { $num = (int) $m[1]; $order_id = $this->get_order_id_by_custom_number( $num ); if ( $order_id ) { $vars['post__in'] = [$order_id]; $vars['s'] = ''; } } } return $vars; } private function get_order_id_by_custom_number( $number ) { global $wpdb; $meta = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s LIMIT 1", self::META_KEY, (string) $number ) ); return $meta ? (int) $meta : 0; } } new EMP_Custom_Order_Numbering(); endif;

Crea una numeración personalizada para pedidos de WooCommerce con prefijo y sufijo, independiente del ID nativo. Muestra ese número en el admin, lo usa en la vista pública y permite buscar pedidos por ese número.

Cómo funciona

  • Al activar el snippet, añade un submenú en WooCommerce → Numeración de pedidos con estos ajustes:
    • Número de inicio (mínimo de la secuencia).
    • Próximo número (para saltar manualmente a un valor mayor).
    • Prefijo (p. ej. EMP- o 2025-).
    • Sufijo (p. ej. -ES).
  • En cada nuevo pedido:
    • Guarda el número interno en el metadato _emp_custom_order_number.
    • Incrementa el próximo número para el siguiente pedido.
  • Filtra el número visible del pedido (frontend y backend) para mostrar prefijo + número + sufijo.
  • En el listado de pedidos del admin añade la columna “Nº personalizado”.
  • En el buscador del listado de pedidos puedes escribir:
    • Solo dígitos (busca por el número interno crudo).
    • O el formato completo con prefijo/sufijo (p. ej. EMP-1023-ES).

Notas

  • No cambia números ya asignados; solo aplica a pedidos nuevos.
  • Si subes el número de inicio, el sistema ajusta el próximo si fuese menor.
  • Recomendado para child theme o como snippet en WPCodeBox.