'factura', 'plural' => 'facturas', 'ajax' => false, ) ); } public function get_columns(): array { return array( 'cb' => '', 'invoice_number' => __( 'Nº Factura', '8digits-factura-for-woocommerce' ), 'order_id' => __( 'Pedido', '8digits-factura-for-woocommerce' ), 'customer' => __( 'Cliente', '8digits-factura-for-woocommerce' ), 'invoice_date' => __( 'Fecha', '8digits-factura-for-woocommerce' ), 'total' => __( 'Total', '8digits-factura-for-woocommerce' ), 'tax' => __( 'IVA', '8digits-factura-for-woocommerce' ), 'status' => __( 'Estado', '8digits-factura-for-woocommerce' ), 'actions' => __( 'Acciones', '8digits-factura-for-woocommerce' ), ); } public function get_sortable_columns(): array { return array( 'invoice_number' => array( 'invoice_number', false ), 'order_id' => array( 'order_id', false ), 'invoice_date' => array( 'invoice_date', true ), 'total' => array( 'total', false ), ); } protected function get_bulk_actions(): array { return array( 'void' => __( 'Anular', '8digits-factura-for-woocommerce' ), 'regenerate' => __( 'Regenerar PDFs', '8digits-factura-for-woocommerce' ), 'zip' => __( 'Descargar ZIP', '8digits-factura-for-woocommerce' ), 'resend' => __( 'Reenviar emails', '8digits-factura-for-woocommerce' ), ); } public function prepare_items(): void { $per_page = 20; // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only list filters and ordering. $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; $status_filter = isset( $_GET['eightdigits_factura_status'] ) ? sanitize_text_field( wp_unslash( $_GET['eightdigits_factura_status'] ) ) : ''; $date_from = isset( $_GET['date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['date_from'] ) ) : ''; $date_to = isset( $_GET['date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['date_to'] ) ) : ''; $paged = isset( $_GET['paged'] ) ? max( 1, absint( wp_unslash( $_GET['paged'] ) ) ) : 1; $allowed_orderby = array( 'invoice_number', 'order_id', 'invoice_date', 'total' ); $orderby = isset( $_GET['orderby'] ) && in_array( sanitize_key( wp_unslash( $_GET['orderby'] ) ), $allowed_orderby, true ) ? sanitize_key( wp_unslash( $_GET['orderby'] ) ) : 'invoice_date'; $order = isset( $_GET['order'] ) && 'asc' === strtolower( sanitize_key( wp_unslash( $_GET['order'] ) ) ) ? 'ASC' : 'DESC'; // phpcs:enable WordPress.Security.NonceVerification.Recommended list( $items, $total ) = self::query_invoices( array( 'per_page' => $per_page, 'paged' => $paged, 'search' => $search, 'status_filter' => $status_filter, 'date_from' => $date_from, 'date_to' => $date_to, 'orderby' => $orderby, 'order' => $order, ) ); $this->items = $items; $this->set_pagination_args( array( 'total_items' => $total, 'per_page' => $per_page, 'total_pages' => (int) ceil( $total / $per_page ), ) ); $this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() ); } private static function query_invoices( array $args ): array { global $wpdb; $table = esc_sql( $wpdb->prefix . 'eightdigits_factura_invoices' ); $wheres = array( "invoice_type = 'invoice'" ); $params = array(); if ( '' !== $args['search'] ) { $like = '%' . $wpdb->esc_like( $args['search'] ) . '%'; $wheres[] = '(invoice_number LIKE %s OR order_id LIKE %s)'; $params[] = $like; $params[] = $like; } if ( '' !== $args['status_filter'] ) { $wheres[] = 'status = %s'; $params[] = $args['status_filter']; } if ( '' !== $args['date_from'] ) { $wheres[] = 'DATE(invoice_date) >= %s'; $params[] = $args['date_from']; } if ( '' !== $args['date_to'] ) { $wheres[] = 'DATE(invoice_date) <= %s'; $params[] = $args['date_to']; } $where_sql = 'WHERE ' . implode( ' AND ', $wheres ); $orderby = in_array( $args['orderby'], array( 'invoice_number', 'order_id', 'invoice_date', 'total' ), true ) ? $args['orderby'] : 'invoice_date'; $order = 'ASC' === $args['order'] ? 'ASC' : 'DESC'; $per_page = (int) $args['per_page']; $paged = max( 1, (int) $args['paged'] ); $offset = ( $paged - 1 ) * max( 0, $per_page ); $total = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Safe table and placeholder usage. $wpdb->prepare( "SELECT COUNT(*) FROM {$table} {$where_sql}", $params ) ); $item_params = $params; $item_params[] = $per_page; $item_params[] = $offset; $items = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Safe table and allowlisted ordering. $wpdb->prepare( "SELECT * FROM {$table} {$where_sql} ORDER BY {$orderby} {$order} LIMIT %d OFFSET %d", $item_params ), ARRAY_A ); return array( $items ?: array(), $total ); } public static function get_export_items( array $args = array() ): array { $defaults = array( 'search' => '', 'status_filter' => '', 'date_from' => '', 'date_to' => '', 'orderby' => 'invoice_date', 'order' => 'DESC', 'per_page' => 999999, 'paged' => 1, ); $query_args = wp_parse_args( $args, $defaults ); list( $items ) = self::query_invoices( $query_args ); return $items; } protected function column_cb( $item ): string { return sprintf( '', (int) $item['id'] ); } protected function column_invoice_number( array $item ): string { return '' . esc_html( $item['invoice_number'] ) . ''; } protected function column_order_id( array $item ): string { $order = wc_get_order( (int) $item['order_id'] ); if ( $order ) { return sprintf( '#%s', esc_url( $order->get_edit_order_url() ), esc_html( (string) $order->get_id() ) ); } return '#' . (int) $item['order_id']; } protected function column_customer( array $item ): string { $order = wc_get_order( (int) $item['order_id'] ); if ( ! $order ) { return '-'; } $name = trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() ); $email = $order->get_billing_email(); if ( $name ) { return esc_html( $name ) . ( $email ? '
' . esc_html( $email ) . '' : '' ); } return $email ? esc_html( $email ) : '-'; } protected function column_invoice_date( array $item ): string { return esc_html( wp_date( 'd/m/Y', strtotime( $item['invoice_date'] ) ) ); } protected function column_total( array $item ): string { return wc_price( (float) $item['total'] ); } protected function column_tax( array $item ): string { return wc_price( (float) $item['tax'] ); } protected function column_status( array $item ): string { $labels = array( 'generated' => __( 'Generada', '8digits-factura-for-woocommerce' ), 'sent' => __( 'Enviada', '8digits-factura-for-woocommerce' ), 'void' => __( 'Anulada', '8digits-factura-for-woocommerce' ), ); $status = $item['status']; $label = $labels[ $status ] ?? ucfirst( $status ); return sprintf( '%s', esc_attr( $status ), esc_html( $label ) ); } protected function column_actions( array $item ): string { $order = wc_get_order( (int) $item['order_id'] ); $download_url = EightDigits_Factura_Invoice::get_download_url( $item ); $resend_nonce = wp_create_nonce( 'eightdigits_factura_resend_' . $item['order_id'] ); $void_nonce = wp_create_nonce( 'eightdigits_factura_void_' . $item['id'] ); $regenerate_nonce = wp_create_nonce( 'eightdigits_factura_regenerate_' . $item['id'] ); $html = '
'; $html .= sprintf( '%s ', esc_url( $download_url ), esc_attr__( 'Descargar PDF', '8digits-factura-for-woocommerce' ), esc_html__( 'PDF', '8digits-factura-for-woocommerce' ) ); if ( $order ) { $html .= sprintf( ' ', (int) $item['order_id'], esc_attr( $resend_nonce ), esc_attr__( 'Reenviar email con factura', '8digits-factura-for-woocommerce' ), esc_html__( 'Reenviar', '8digits-factura-for-woocommerce' ) ); $html .= sprintf( '%s ', esc_url( $order->get_edit_order_url() ), esc_attr__( 'Ver pedido', '8digits-factura-for-woocommerce' ), esc_html__( 'Pedido', '8digits-factura-for-woocommerce' ) ); } if ( in_array( $item['status'], array( 'generated', 'sent' ), true ) ) { $html .= sprintf( ' ', (int) $item['id'], esc_attr( $item['invoice_number'] ), esc_attr( $void_nonce ), esc_attr__( 'Anular factura', '8digits-factura-for-woocommerce' ), esc_html__( 'Anular', '8digits-factura-for-woocommerce' ) ); } $html .= sprintf( '', (int) $item['id'], esc_attr( $item['invoice_number'] ), esc_attr( $regenerate_nonce ), esc_attr__( 'Regenerar PDF', '8digits-factura-for-woocommerce' ), esc_html__( 'Regenerar PDF', '8digits-factura-for-woocommerce' ) ); $html .= '
'; return $html; } protected function extra_tablenav( $which ): void { if ( 'top' !== $which ) { return; } // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only filter params. $status = isset( $_GET['eightdigits_factura_status'] ) ? sanitize_text_field( wp_unslash( $_GET['eightdigits_factura_status'] ) ) : ''; $from = isset( $_GET['date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['date_from'] ) ) : ''; $to = isset( $_GET['date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['date_to'] ) ) : ''; $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; // phpcs:enable WordPress.Security.NonceVerification.Recommended echo '
'; echo ''; printf( '', esc_attr( $from ) ); printf( '', esc_attr( $to ) ); submit_button( __( 'Filtrar', '8digits-factura-for-woocommerce' ), 'button', 'filter_action', false ); $export_url = wp_nonce_url( add_query_arg( array( 'action' => 'eightdigits_factura_export_csv', 'eightdigits_factura_status' => $status, 'date_from' => $from, 'date_to' => $to, 's' => $search, ), admin_url( 'admin-ajax.php' ) ), 'eightdigits_factura_export_csv', 'nonce' ); printf( '%s', esc_url( $export_url ), esc_html__( 'Exportar CSV', '8digits-factura-for-woocommerce' ) ); echo '
'; } protected function get_views(): array { $base = admin_url( 'admin.php?page=factura-for-woocommerce' ); $current = isset( $_GET['eightdigits_factura_status'] ) ? sanitize_text_field( wp_unslash( $_GET['eightdigits_factura_status'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $total = EightDigits_Factura_Invoice::count(); $counts = EightDigits_Factura_Invoice::get_status_counts(); $views = array(); $views['all'] = sprintf( '%s (%d)', esc_url( $base ), '' === $current ? ' class="current"' : '', esc_html__( 'Todas', '8digits-factura-for-woocommerce' ), (int) $total ); foreach ( array( 'generated' => __( 'Generadas', '8digits-factura-for-woocommerce' ), 'sent' => __( 'Enviadas', '8digits-factura-for-woocommerce' ), 'void' => __( 'Anuladas', '8digits-factura-for-woocommerce' ), ) as $status => $label ) { $views[ $status ] = sprintf( '%s (%d)', esc_url( add_query_arg( 'eightdigits_factura_status', $status, $base ) ), $current === $status ? ' class="current"' : '', esc_html( $label ), (int) ( $counts[ $status ] ?? 0 ) ); } return $views; } } class EightDigits_Factura_Admin { public function __construct() { add_action( 'admin_menu', array( $this, 'register_menus' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_action( 'wp_dashboard_setup', array( $this, 'register_dashboard_widget' ) ); add_action( 'admin_post_eightdigits_factura_invoice_bulk', array( $this, 'handle_bulk_action_request' ) ); add_action( 'wp_ajax_eightdigits_factura_bulk_zip', array( $this, 'ajax_bulk_zip' ) ); add_action( 'wp_ajax_eightdigits_factura_void_invoice', array( $this, 'ajax_void_invoice' ) ); add_action( 'wp_ajax_eightdigits_factura_regenerate_pdf', array( $this, 'ajax_regenerate_pdf' ) ); add_action( 'wp_ajax_eightdigits_factura_export_csv', array( $this, 'ajax_export_csv' ) ); add_filter( 'plugin_action_links_' . EIGHTDIGITS_FACTURA_PLUGIN_BASENAME, array( $this, 'add_plugin_action_links' ) ); } public function register_menus(): void { add_menu_page( __( '8Digits Factura', '8digits-factura-for-woocommerce' ), __( '8Digits Factura', '8digits-factura-for-woocommerce' ), 'manage_woocommerce', 'factura-for-woocommerce', array( $this, 'render_invoice_list' ), 'dashicons-media-spreadsheet', 56 ); add_submenu_page( 'factura-for-woocommerce', __( 'Facturas', '8digits-factura-for-woocommerce' ), __( 'Facturas', '8digits-factura-for-woocommerce' ), 'manage_woocommerce', 'factura-for-woocommerce', array( $this, 'render_invoice_list' ) ); add_submenu_page( 'factura-for-woocommerce', __( 'Configuracion', '8digits-factura-for-woocommerce' ), __( 'Configuracion', '8digits-factura-for-woocommerce' ), 'manage_options', 'eightdigits-factura-settings', array( $this, 'render_settings' ) ); } public function enqueue_assets( string $hook ): void { $plugin_pages = array( 'toplevel_page_factura-for-woocommerce', 'factura-for-woocommerce_page_eightdigits-factura-settings' ); if ( ! in_array( $hook, $plugin_pages, true ) && ! $this->is_order_page() ) { return; } wp_enqueue_style( 'eightdigits-factura-admin', EIGHTDIGITS_FACTURA_PLUGIN_URL . 'assets/css/admin.css', array(), EIGHTDIGITS_FACTURA_VERSION ); wp_enqueue_script( 'eightdigits-factura-admin', EIGHTDIGITS_FACTURA_PLUGIN_URL . 'assets/js/admin.js', array( 'jquery' ), EIGHTDIGITS_FACTURA_VERSION, true ); wp_localize_script( 'eightdigits-factura-admin', 'eightdigitsFacturaAdmin', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'bulkZipNonce' => wp_create_nonce( 'eightdigits_factura_bulk_zip' ), 'strings' => array( 'resendSuccess' => __( 'Email reenviado correctamente.', '8digits-factura-for-woocommerce' ), 'resendError' => __( 'Error al reenviar el email.', '8digits-factura-for-woocommerce' ), 'generateSuccess' => __( 'Factura generada.', '8digits-factura-for-woocommerce' ), 'generateError' => __( 'Error al generar la factura.', '8digits-factura-for-woocommerce' ), 'zipError' => __( 'Error al generar el ZIP.', '8digits-factura-for-woocommerce' ), 'selectRows' => __( 'Seleccione al menos una factura.', '8digits-factura-for-woocommerce' ), 'voidError' => __( 'Error al anular la factura.', '8digits-factura-for-woocommerce' ), 'voidConfirm' => __( '¿Anular factura %s? Esta accion no se puede deshacer.', '8digits-factura-for-woocommerce' ), 'regenerateError' => __( 'Error al regenerar el PDF.', '8digits-factura-for-woocommerce' ), 'nifInvalid' => __( 'Formato de NIF/CIF/NIE no valido.', '8digits-factura-for-woocommerce' ), 'nifValid' => __( 'Formato correcto.', '8digits-factura-for-woocommerce' ), ), ) ); } private function is_order_page(): bool { global $pagenow; $screen = get_current_screen(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only screen detection. if ( 'post.php' === $pagenow && isset( $_GET['post'] ) ) { return 'shop_order' === get_post_type( absint( wp_unslash( $_GET['post'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended } return $screen && in_array( $screen->id, array( 'woocommerce_page_wc-orders', 'shop_order' ), true ); } public function render_invoice_list(): void { require_once EIGHTDIGITS_FACTURA_PLUGIN_DIR . 'admin/views/invoice-list.php'; } public function render_settings(): void { require_once EIGHTDIGITS_FACTURA_PLUGIN_DIR . 'admin/views/settings-page.php'; } public function handle_bulk_action_request(): void { if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'Sin permiso.', '8digits-factura-for-woocommerce' ) ); } check_admin_referer( 'eightdigits_factura_bulk_action', 'eightdigits_factura_bulk_nonce' ); if ( empty( $_POST['invoice_ids'] ) ) { $this->redirect_after_bulk_action( array() ); } $bulk_action = ''; if ( isset( $_POST['action'] ) && '-1' !== $_POST['action'] ) { $bulk_action = sanitize_text_field( wp_unslash( $_POST['action'] ) ); } elseif ( isset( $_POST['action2'] ) && '-1' !== $_POST['action2'] ) { $bulk_action = sanitize_text_field( wp_unslash( $_POST['action2'] ) ); } if ( '' === $bulk_action || 'zip' === $bulk_action ) { $this->redirect_after_bulk_action( array() ); } $invoice_ids = array_map( 'absint', (array) wp_unslash( $_POST['invoice_ids'] ) ); if ( 'resend' === $bulk_action ) { $processed = $this->bulk_resend_invoices( $invoice_ids ); $this->redirect_after_bulk_action( array( 'bulk_resent' => $processed > 0 ? '1' : '0', ) ); } if ( 'void' === $bulk_action ) { $processed = $this->bulk_void_invoices( $invoice_ids ); $this->redirect_after_bulk_action( array( 'bulk_voided' => $processed, ) ); } if ( 'regenerate' === $bulk_action ) { $processed = $this->bulk_regenerate_invoices( $invoice_ids ); $this->redirect_after_bulk_action( array( 'bulk_regenerated' => $processed, ) ); } } private function bulk_resend_invoices( array $invoice_ids ): int { $mailer = WC()->mailer(); $mails = $mailer->get_emails(); $email_id = 'customer_processing_order'; $processed = 0; foreach ( $invoice_ids as $invoice_id ) { $invoice = EightDigits_Factura_Invoice::get( $invoice_id ); if ( ! $invoice ) { continue; } $order = wc_get_order( (int) $invoice['order_id'] ); if ( ! $order ) { continue; } if ( isset( $mails[ $email_id ] ) ) { $mails[ $email_id ]->trigger( $order->get_id(), $order ); } global $wpdb; $table = $wpdb->prefix . 'eightdigits_factura_invoices'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->update( $table, array( 'status' => 'sent' ), array( 'id' => $invoice_id ), array( '%s' ), array( '%d' ) ); $processed++; } return $processed; } private function bulk_void_invoices( array $invoice_ids ): int { $processed = 0; foreach ( $invoice_ids as $invoice_id ) { $invoice = EightDigits_Factura_Invoice::get( $invoice_id ); if ( ! $invoice ) { continue; } $result = EightDigits_Factura_Invoice::void( $invoice_id ); if ( is_wp_error( $result ) ) { continue; } $order = wc_get_order( (int) $invoice['order_id'] ); if ( $order ) { $order->add_order_note( sprintf( __( '8Digits Factura: Factura %s anulada.', '8digits-factura-for-woocommerce' ), $invoice['invoice_number'] ) ); } $processed++; } return $processed; } private function bulk_regenerate_invoices( array $invoice_ids ): int { $processed = 0; foreach ( $invoice_ids as $invoice_id ) { $invoice = EightDigits_Factura_Invoice::get( $invoice_id ); if ( ! $invoice ) { continue; } $result = EightDigits_Factura_Invoice::regenerate_pdf( $invoice_id ); if ( is_wp_error( $result ) ) { continue; } $order = wc_get_order( (int) $invoice['order_id'] ); if ( $order ) { $order->add_order_note( sprintf( __( '8Digits Factura: PDF de la factura %s regenerado.', '8digits-factura-for-woocommerce' ), $invoice['invoice_number'] ) ); } $processed++; } return $processed; } private function redirect_after_bulk_action( array $args ): void { $query_args = array( 'page' => 'factura-for-woocommerce', ); // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce is verified in handle_bulk_action_request() before redirect context is read. foreach ( array( 's', 'orderby', 'order', 'paged', 'date_from', 'date_to', 'eightdigits_factura_status' ) as $key ) { if ( isset( $_POST[ $key ] ) ) { $query_args[ $key ] = sanitize_text_field( wp_unslash( $_POST[ $key ] ) ); } } // phpcs:enable WordPress.Security.NonceVerification.Missing $redirect = add_query_arg( array_merge( $query_args, $args ), admin_url( 'admin.php' ) ); wp_safe_redirect( $redirect ); exit; } public function register_dashboard_widget(): void { if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } wp_add_dashboard_widget( 'eightdigits_factura_dashboard_widget', __( '8Digits Factura', '8digits-factura-for-woocommerce' ), array( $this, 'render_dashboard_widget' ) ); } public function render_dashboard_widget(): void { if ( ! current_user_can( 'manage_woocommerce' ) ) { return; } $stats = get_transient( 'eightdigits_factura_dashboard_stats' ); if ( false === $stats ) { $stats = EightDigits_Factura_Invoice::get_dashboard_stats(); set_transient( 'eightdigits_factura_dashboard_stats', $stats, HOUR_IN_SECONDS ); } $status_counts = $stats['status_counts'] ?? array(); $invoice_list_url = admin_url( 'admin.php?page=factura-for-woocommerce' ); ?>



__( 'Sin permiso.', '8digits-factura-for-woocommerce' ) ) ); } $ids = isset( $_POST['invoice_ids'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['invoice_ids'] ) ) : array(); if ( empty( $ids ) ) { wp_send_json_error( array( 'message' => __( 'No se han seleccionado facturas.', '8digits-factura-for-woocommerce' ) ) ); } if ( ! class_exists( 'ZipArchive' ) ) { wp_send_json_error( array( 'message' => __( 'ZipArchive no disponible en este servidor.', '8digits-factura-for-woocommerce' ) ) ); } $tmp_dir = get_temp_dir(); $zip_name = 'eightdigits-factura-' . gmdate( 'Ymd-His' ) . '.zip'; $zip_path = $tmp_dir . $zip_name; $zip = new ZipArchive(); $opened = $zip->open( $zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE ); if ( true !== $opened ) { wp_send_json_error( array( 'message' => __( 'No se pudo crear el archivo ZIP.', '8digits-factura-for-woocommerce' ) ) ); } $added = 0; foreach ( $ids as $invoice_id ) { $invoice = EightDigits_Factura_Invoice::get( $invoice_id ); if ( ! $invoice || empty( $invoice['pdf_path'] ) || ! file_exists( $invoice['pdf_path'] ) ) { continue; } $zip->addFile( $invoice['pdf_path'], basename( $invoice['pdf_path'] ) ); $added++; } $zip->close(); if ( 0 === $added ) { wp_send_json_error( array( 'message' => __( 'No se encontraron archivos PDF para descargar.', '8digits-factura-for-woocommerce' ) ) ); } $zip_data = base64_encode( file_get_contents( $zip_path ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reads a just-created local ZIP for download. wp_delete_file( $zip_path ); wp_send_json_success( array( 'filename' => $zip_name, 'data_b64' => $zip_data, 'message' => sprintf( __( 'ZIP con %d facturas listo.', '8digits-factura-for-woocommerce' ), $added ), ) ); } public function ajax_void_invoice(): void { $invoice_id = absint( wp_unslash( $_POST['invoice_id'] ?? 0 ) ); check_ajax_referer( 'eightdigits_factura_void_' . $invoice_id, 'nonce' ); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array( 'message' => __( 'Sin permiso.', '8digits-factura-for-woocommerce' ) ) ); } $invoice = EightDigits_Factura_Invoice::get( $invoice_id ); if ( ! $invoice ) { wp_send_json_error( array( 'message' => __( 'Factura no encontrada.', '8digits-factura-for-woocommerce' ) ) ); } $result = EightDigits_Factura_Invoice::void( $invoice_id ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'Factura anulada correctamente.', '8digits-factura-for-woocommerce' ), 'status' => __( 'Anulada', '8digits-factura-for-woocommerce' ), ) ); } public function ajax_regenerate_pdf(): void { $invoice_id = absint( wp_unslash( $_POST['invoice_id'] ?? 0 ) ); check_ajax_referer( 'eightdigits_factura_regenerate_' . $invoice_id, 'nonce' ); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_send_json_error( array( 'message' => __( 'Sin permiso.', '8digits-factura-for-woocommerce' ) ) ); } $result = EightDigits_Factura_Invoice::regenerate_pdf( $invoice_id ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'PDF regenerado correctamente.', '8digits-factura-for-woocommerce' ), 'download_url' => EightDigits_Factura_Invoice::get_download_url( $result ), ) ); } public function add_plugin_action_links( array $links ): array { $pro_link = sprintf( '%s', esc_url( 'https://8digits.es/factura-for-woocommerce/' ), esc_html__( 'Go Pro', '8digits-factura-for-woocommerce' ) ); array_unshift( $links, $pro_link ); return $links; } public function ajax_export_csv(): void { check_ajax_referer( 'eightdigits_factura_export_csv', 'nonce' ); if ( ! current_user_can( 'manage_woocommerce' ) ) { wp_die( esc_html__( 'Sin permiso.', '8digits-factura-for-woocommerce' ) ); } $items = EightDigits_Factura_Invoice_List_Table::get_export_items( array( 'search' => isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '', 'status_filter' => isset( $_GET['eightdigits_factura_status'] ) ? sanitize_text_field( wp_unslash( $_GET['eightdigits_factura_status'] ) ) : '', 'date_from' => isset( $_GET['date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['date_from'] ) ) : '', 'date_to' => isset( $_GET['date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['date_to'] ) ) : '', ) ); $filename = 'eightdigits-factura-export-' . gmdate( 'Ymd-His' ) . '.csv'; nocache_headers(); header( 'Content-Type: text/csv; charset=UTF-8' ); header( 'Content-Disposition: attachment; filename="' . sanitize_file_name( $filename ) . '"' ); $handle = fopen( 'php://output', 'w' ); if ( false === $handle ) { wp_die( esc_html__( 'No se pudo generar el CSV.', '8digits-factura-for-woocommerce' ) ); } echo "\xEF\xBB\xBF"; fputcsv( $handle, array( __( 'Nº Factura', '8digits-factura-for-woocommerce' ), __( 'Fecha', '8digits-factura-for-woocommerce' ), __( 'Cliente', '8digits-factura-for-woocommerce' ), __( 'NIF', '8digits-factura-for-woocommerce' ), __( 'Base Imponible', '8digits-factura-for-woocommerce' ), __( 'IVA %', '8digits-factura-for-woocommerce' ), __( 'IVA Importe', '8digits-factura-for-woocommerce' ), __( 'Total', '8digits-factura-for-woocommerce' ), __( 'Estado', '8digits-factura-for-woocommerce' ), ), ';' ); $status_labels = array( 'generated' => __( 'Generada', '8digits-factura-for-woocommerce' ), 'sent' => __( 'Enviada', '8digits-factura-for-woocommerce' ), 'void' => __( 'Anulada', '8digits-factura-for-woocommerce' ), ); foreach ( $items as $invoice ) { $order = wc_get_order( (int) $invoice['order_id'] ); $customer_name = ''; $nif = ''; if ( $order ) { $customer_name = trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() ); if ( '' === $customer_name ) { $customer_name = $order->get_billing_email(); } $nif = (string) ( $order->get_meta( 'billing_nif' ) ?: $order->get_meta( '_billing_nif' ) ); } $tax_total = (float) $invoice['tax']; $grand_total = (float) $invoice['total']; $taxable_base = $grand_total - $tax_total; $tax_rate = 0.0 !== $taxable_base ? round( ( $tax_total / $taxable_base ) * 100, 2 ) : 0.0; fputcsv( $handle, array( $invoice['invoice_number'], wp_date( 'd/m/Y', strtotime( $invoice['invoice_date'] ) ), $customer_name, $nif, number_format( $taxable_base, 2, ',', '' ), number_format( $tax_rate, 2, ',', '' ), number_format( $tax_total, 2, ',', '' ), number_format( $grand_total, 2, ',', '' ), $status_labels[ $invoice['status'] ] ?? $invoice['status'], ), ';' ); } fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closes the CSV stream. exit; } }