Plugin Directory

Changeset 3463326


Ignore:
Timestamp:
02/17/2026 10:18:40 AM (14 hours ago)
Author:
Picaland
Message:

Added: DB advisory lock helpers based on MySQL/MariaDB GET_LOCK/RELEASE_LOCK to prevent concurrent duplicate create/send/upload actions.
Fixed: Fatture in Cloud send flow now requires explicit docID and wfc-issued_send_document=false; removed implicit docID fallback from stored document meta.
Fixed: Fatture in Cloud create flow now uses atomic lock guard to avoid duplicate issued documents under concurrent Ajax/Cron execution.
Fixed: Added channel guards to SDI/PEPPOL/FIC status callbacks so native/addon flows run only when their integration channel is effectively active.
Fixed: Hardened _invoice_sent writes across channels (SDI native, PEPPOL, Aruba, SDI via PEC, FIC) by requiring minimum success/coherence conditions before setting sent.
Fixed: Added recurring cron cleanup for inactive/disabled upload schedules (FIC, Aruba, SDI via PEC) to avoid orphan scheduled events.
Fixed: Fatture table invoice number rendering now only shows dismiss icon when both order_number_invoice and formatted number are missing, with recovery from formatted where possible.
Added: Native SDI/PEPPOL timeout-pending flow with explicit admin recovery actions in order list (Confirm UUID / Cancel timeout).
Added: New admin AJAX actions confirmNativeSendWithUuid and cancelNativeTimeoutPending to finalize or clear timeout-pending state safely.
Added: Native timeout helper set (meta keys/channel prefix/timeout detection) and centralized timeout pending metadata management.
Fixed: SDI and PEPPOL create/send flows now mark timeout-pending on timeout-like provider responses and clear pending state on successful UUID save.
Fixed: SDI and PEPPOL create/send error responses now include readable provider detail extracted from raw payload data when available.
Change: Timeout pending UUID input in native actions UI is now full-width with explicit spacing before recovery buttons.
Fixed: getNextInvoiceNumber now aligns stale next numeration values to max(next, last_assigned+1) for the active series, preventing duplicates and backward numbering.
Added: series-aware duplicate check now uses WooCommerce order queries (HPOS compatible), comparing type/prefix/year context against the latest assigned order.
Added: provider-level series hooks for PMPro and Cozmos addons via wc_el_inv-next_number_already_assigned and wc_el_inv-next_number_last_assigned_for_series.
Added: Extended unit test coverage for timeout helpers, native error detail extraction, resolver hardening edge cases, and wrapper/adapter behavior.
Fixed: Resolver hardening for invalid filter outputs (channels status/priority), empty priorities, non-string priority entries, and normalized shop country handling.

Location:
woopop-electronic-invoice-free/trunk
Files:
1 added
52 edited

Legend:

Unmodified
Added
Removed
  • woopop-electronic-invoice-free/trunk/addon/for/cozmos/inc/filters.php

    r3320156 r3463326  
    137137            ),
    138138        ),
    139         'filter' => array()
     139        'filter' => array(
     140            array(
     141                'filter'        => 'wc_el_inv-next_number_already_assigned',
     142                'callback'      => 'WooPoPCozmosLabsPMS\\Functions\\Utils::cozmosNextNumberAlreadyAssigned',
     143                'priority'      => 20,
     144                'accepted_args' => 3,
     145            ),
     146            array(
     147                'filter'        => 'wc_el_inv-next_number_last_assigned_for_series',
     148                'callback'      => 'WooPoPCozmosLabsPMS\\Functions\\Utils::cozmosLastAssignedNumberForSeries',
     149                'priority'      => 20,
     150                'accepted_args' => 2,
     151            ),
     152        )
    140153    ),
    141154);
  • woopop-electronic-invoice-free/trunk/addon/for/cozmos/inc/filtersFront.php

    r3341137 r3463326  
    214214                'accepted_args' => 1,
    215215            ),
     216            array(
     217                'filter'        => 'wc_el_inv-next_number_already_assigned',
     218                'callback'      => 'WooPoPCozmosLabsPMS\\Functions\\Utils::cozmosNextNumberAlreadyAssigned',
     219                'priority'      => 20,
     220                'accepted_args' => 3,
     221            ),
     222            array(
     223                'filter'        => 'wc_el_inv-next_number_last_assigned_for_series',
     224                'callback'      => 'WooPoPCozmosLabsPMS\\Functions\\Utils::cozmosLastAssignedNumberForSeries',
     225                'priority'      => 20,
     226                'accepted_args' => 2,
     227            ),
    216228        ),
    217229    ),
  • woopop-electronic-invoice-free/trunk/addon/for/cozmos/src/Functions/Utils.php

    r3248026 r3463326  
    33namespace WooPoPCozmosLabsPMS\Functions;
    44
     5use WcElectronInvoice\Providers\OrderQuery;
     6
    57defined( 'ABSPATH' ) || die( 'Not allowed.' );
    68
    7 class Utils {}
     9class Utils {
     10    /**
     11     * Extend duplicate-check lookup for Cozmos numeration.
     12     *
     13     * @param bool   $exists
     14     * @param int    $number
     15     * @param string $optionKeyName
     16     *
     17     * @return bool
     18     */
     19    public static function cozmosNextNumberAlreadyAssigned( $exists, $number, $optionKeyName ) {
     20        if ( $exists || (int) $number < 1 ) {
     21            return (bool) $exists;
     22        }
     23
     24        $lastAssigned = self::cozmosLastAssignedNumberForSeries( 0, $optionKeyName );
     25
     26        return ( $lastAssigned > 0 && $lastAssigned === (int) $number );
     27    }
     28
     29    /**
     30     * Contribute Cozmos last assigned number for current series.
     31     *
     32     * @param int    $lastAssigned
     33     * @param string $optionKeyName
     34     *
     35     * @return int
     36     */
     37    public static function cozmosLastAssignedNumberForSeries( $lastAssigned, $optionKeyName ) {
     38        if ( 'number_next_invoice' !== $optionKeyName && 'number_next_receipt' !== $optionKeyName ) {
     39            return (int) $lastAssigned;
     40        }
     41
     42        try {
     43            $provider = OrderQuery::instance()->get_provider( 'cozmoslabspms' );
     44            if ( ! $provider || ! method_exists( $provider, 'getOrders' ) ) {
     45                return (int) $lastAssigned;
     46            }
     47
     48            $limit = (int) apply_filters(
     49                'wc_el_inv-next_number_recent_provider_orders_limit',
     50                200,
     51                'cozmoslabspms',
     52                $optionKeyName,
     53                0
     54            );
     55            if ( $limit < 1 ) {
     56                $limit = 1;
     57            }
     58
     59            $orders = $provider->getOrders( [
     60                'status'  => [ 'completed', 'processing' ],
     61                'limit'   => $limit,
     62                'orderby' => 'date',
     63                'order'   => 'DESC',
     64            ] );
     65            if ( ! is_array( $orders ) ) {
     66                return (int) $lastAssigned;
     67            }
     68
     69            $seriesContext = \WcElectronInvoice\Functions\getNextNumberSeriesContext( $optionKeyName );
     70            foreach ( $orders as $order ) {
     71                if ( ! is_object( $order ) || ! method_exists( $order, 'get_meta' ) ) {
     72                    continue;
     73                }
     74
     75                $invoiceNumber = (int) $order->get_meta( 'order_number_invoice', true );
     76                if ( $invoiceNumber < 1 ) {
     77                    continue;
     78                }
     79
     80                $choiceType = (string) $order->get_meta( '_billing_choice_type', true );
     81                $isReceipt  = ( 'receipt' === $choiceType );
     82                if ( 'number_next_receipt' === $optionKeyName && ! $isReceipt ) {
     83                    continue;
     84                }
     85                if ( 'number_next_invoice' === $optionKeyName && $isReceipt ) {
     86                    continue;
     87                }
     88
     89                if ( \WcElectronInvoice\Functions\isOrderInNextNumberSeries( $order, $seriesContext ) ) {
     90                    return max( (int) $lastAssigned, $invoiceNumber );
     91                }
     92
     93                return (int) $lastAssigned;
     94            }
     95        } catch ( \Throwable $e ) {
     96            return (int) $lastAssigned;
     97        }
     98
     99        return (int) $lastAssigned;
     100    }
     101}
  • woopop-electronic-invoice-free/trunk/addon/for/cozmos/vendor/composer/installed.php

    r3460902 r3463326  
    44        'pretty_version' => 'dev-main',
    55        'version' => 'dev-main',
    6         'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     6        'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => 'dev-main',
    1515            'version' => 'dev-main',
    16             'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     16            'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
  • woopop-electronic-invoice-free/trunk/addon/for/pmpro/inc/filtersAlways.php

    r3418379 r3463326  
    122122            ),
    123123        ),
    124         'filter' => array(),
     124        'filter' => array(
     125            array(
     126                'filter'        => 'wc_el_inv-next_number_already_assigned',
     127                'callback'      => 'POPxPMPro\\Functions\\Utils::pmproNextNumberAlreadyAssigned',
     128                'priority'      => 20,
     129                'accepted_args' => 3,
     130            ),
     131            array(
     132                'filter'        => 'wc_el_inv-next_number_last_assigned_for_series',
     133                'callback'      => 'POPxPMPro\\Functions\\Utils::pmproLastAssignedNumberForSeries',
     134                'priority'      => 20,
     135                'accepted_args' => 2,
     136            ),
     137        ),
    125138    ),
    126139);
  • woopop-electronic-invoice-free/trunk/addon/for/pmpro/src/Functions/Utils.php

    r3409381 r3463326  
    189189
    190190        return max( 0, $netAmount );
     191    }
     192
     193    /**
     194     * Extend duplicate-check lookup for PMPro numeration.
     195     *
     196     * @param bool   $exists
     197     * @param int    $number
     198     * @param string $optionKeyName
     199     *
     200     * @return bool
     201     */
     202    public static function pmproNextNumberAlreadyAssigned( $exists, $number, $optionKeyName ) {
     203        if ( $exists || (int) $number < 1 ) {
     204            return (bool) $exists;
     205        }
     206
     207        $lastAssigned = self::pmproLastAssignedNumberForSeries( 0, $optionKeyName );
     208
     209        return ( $lastAssigned > 0 && $lastAssigned === (int) $number );
     210    }
     211
     212    /**
     213     * Contribute PMPro last assigned number for current series.
     214     *
     215     * @param int    $lastAssigned
     216     * @param string $optionKeyName
     217     *
     218     * @return int
     219     */
     220    public static function pmproLastAssignedNumberForSeries( $lastAssigned, $optionKeyName ) {
     221        if ( 'number_next_invoice' !== $optionKeyName && 'number_next_receipt' !== $optionKeyName ) {
     222            return (int) $lastAssigned;
     223        }
     224
     225        try {
     226            $provider = OrderQuery::instance()->get_provider( 'pmpro' );
     227            if ( ! $provider || ! method_exists( $provider, 'getOrders' ) ) {
     228                return (int) $lastAssigned;
     229            }
     230
     231            $limit = (int) apply_filters(
     232                'wc_el_inv-next_number_recent_provider_orders_limit',
     233                200,
     234                'pmpro',
     235                $optionKeyName,
     236                0
     237            );
     238            if ( $limit < 1 ) {
     239                $limit = 1;
     240            }
     241
     242            $orders = $provider->getOrders( [
     243                'status'  => [ 'completed', 'processing' ],
     244                'limit'   => $limit,
     245                'orderby' => 'date',
     246                'order'   => 'DESC',
     247            ] );
     248            if ( ! is_array( $orders ) ) {
     249                return (int) $lastAssigned;
     250            }
     251
     252            $seriesContext = \WcElectronInvoice\Functions\getNextNumberSeriesContext( $optionKeyName );
     253            foreach ( $orders as $order ) {
     254                if ( ! is_object( $order ) || ! method_exists( $order, 'get_meta' ) ) {
     255                    continue;
     256                }
     257
     258                $invoiceNumber = (int) $order->get_meta( 'order_number_invoice', true );
     259                if ( $invoiceNumber < 1 ) {
     260                    continue;
     261                }
     262
     263                $choiceType = (string) $order->get_meta( '_billing_choice_type', true );
     264                $isReceipt  = ( 'receipt' === $choiceType );
     265                if ( 'number_next_receipt' === $optionKeyName && ! $isReceipt ) {
     266                    continue;
     267                }
     268                if ( 'number_next_invoice' === $optionKeyName && $isReceipt ) {
     269                    continue;
     270                }
     271
     272                if ( \WcElectronInvoice\Functions\isOrderInNextNumberSeries( $order, $seriesContext ) ) {
     273                    return max( (int) $lastAssigned, $invoiceNumber );
     274                }
     275
     276                return (int) $lastAssigned;
     277            }
     278        } catch ( \Throwable $e ) {
     279            return (int) $lastAssigned;
     280        }
     281
     282        return (int) $lastAssigned;
    191283    }
    192284
  • woopop-electronic-invoice-free/trunk/addon/for/pmpro/vendor/composer/installed.php

    r3460902 r3463326  
    44        'pretty_version' => 'dev-main',
    55        'version' => 'dev-main',
    6         'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     6        'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => 'dev-main',
    1515            'version' => 'dev-main',
    16             'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     16            'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
  • woopop-electronic-invoice-free/trunk/addon/to/aruba/languages/waf-it_IT.po

    r3253418 r3463326  
    33"Project-Id-Version: POP to Aruba\n"
    44"POT-Creation-Date: 2025-03-08 10:54+0100\n"
    5 "PO-Revision-Date: 2025-03-08 10:54+0100\n"
     5"PO-Revision-Date: 2026-02-16 13:58+0100\n"
    66"Last-Translator: Alfio <[email protected]>\n"
    77"Language-Team: \n"
     
    1111"Content-Transfer-Encoding: 8bit\n"
    1212"Plural-Forms: nplurals=2; plural=(n != 1);\n"
    13 "X-Generator: Poedit 3.5\n"
     13"X-Generator: Poedit 3.8\n"
    1414"X-Poedit-Basepath: ..\n"
    15 "X-Poedit-KeywordsList: __;_e;_n:1,2;__ngettext_noop:1,2;_n_noop:1,2;_c;"
    16 "_nc:4c,1,2;_x:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;_ex:1,2c;esc_attr__;esc_attr_e;"
    17 "esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n"
     15"X-Poedit-KeywordsList: "
     16"__;_e;_n:1,2;__ngettext_noop:1,2;_n_noop:1,2;_c;_nc:4c,1,2;_x:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;_ex:1,2c;esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n"
    1817"X-Poedit-SearchPathExcluded-3: assets\n"
    1918"X-Poedit-SourceCharset: UTF-8\n"
     
    3433msgstr "Fatture Aruba azioni"
    3534
    36 #: inc/localizeScripts.php:40 src/Functions/Api.php:507
    37 #: src/Functions/Api.php:647
     35#: inc/localizeScripts.php:40 src/Functions/Api.php:507 src/Functions/Api.php:647
    3836msgid "Message:"
    3937msgstr "Messaggio:"
     
    147145msgstr "Richiesta aggiornamento token non valida"
    148146
    149 #: src/Functions/Api.php:409 src/Functions/Api.php:495
    150 #: src/Functions/Api.php:503 src/Functions/Api.php:643
     147#: src/Functions/Api.php:409 src/Functions/Api.php:495 src/Functions/Api.php:503
     148#: src/Functions/Api.php:643
    151149msgid "Oops, something is wrong!"
    152150msgstr "Ops, qualcosa non va!"
     
    166164#: src/Functions/Api.php:505 src/Functions/Api.php:645
    167165msgid ""
    168 "The invoice could not be created, we recommend that you check your order "
    169 "data and try again"
    170 msgstr ""
    171 "Impossibile creare la fattura, ti consigliamo di controllare i dati "
    172 "dell’ordine e riprovare"
     166"The invoice could not be created, we recommend that you check your order data and try again"
     167msgstr ""
     168"Impossibile creare la fattura, ti consigliamo di controllare i dati dell’ordine e riprovare"
    173169
    174170#: src/Functions/Api.php:508 src/Functions/Api.php:648
     
    188184msgstr "Errore di caricamento della fattura!"
    189185
    190 #: src/Functions/Api.php:627 src/Functions/Api.php:817
    191 #: src/Functions/Api.php:1100 src/Jobs.php:474 src/Jobs.php:530
    192 #: src/Jobs.php:598
     186#: src/Functions/Api.php:627 src/Functions/Api.php:817 src/Functions/Api.php:1100
     187#: src/Jobs.php:474 src/Jobs.php:530 src/Jobs.php:598
    193188msgid "Unauthorized: the Token seems to be invalid, update it!"
    194189msgstr "Non autorizzato: il Token sembra non essere valido, aggiornalo!"
     
    240235#: src/Functions/Utils.php:258
    241236msgid "WooPOP Addon: You do not have the permissions to manage the add-on"
    242 msgstr ""
    243 "WooPOP Addon: Non si dispone dei permessi per gestire il componente "
    244 "aggiuntivo"
     237msgstr "WooPOP Addon: Non si dispone dei permessi per gestire il componente aggiuntivo"
    245238
    246239#: src/Functions/Utils.php:267
     
    270263#: src/Functions/Utils.php:315
    271264msgid ""
    272 "Enable automatic sending of invoices for completed orders not sent in the "
    273 "last 7 days. (work 2 times a day)"
    274 msgstr ""
    275 "Abilita l'invio automatico delle fatture per gli ordini completati e non "
    276 "inviati negli ultimi 7 giorni. (invia 2 volte al giorno)"
     265"Enable automatic sending of invoices for completed orders not sent in the last 7 days. (work "
     266"2 times a day)"
     267msgstr ""
     268"Abilita l'invio automatico delle fatture per gli ordini completati e non inviati negli ultimi "
     269"7 giorni. (invia 2 volte al giorno)"
    277270
    278271#: src/Functions/Utils.php:321
     
    310303#: src/Functions/Utils.php:367
    311304msgid "Enter this code in wp-config.php to activate the event log."
    312 msgstr ""
    313 "Inserisci questo codice in wp-config.php per attivare il registro eventi."
     305msgstr "Inserisci questo codice in wp-config.php per attivare il registro eventi."
    314306
    315307#: src/Functions/Utils.php:377
     
    369361msgid "The PDF invoice has already been sent manually"
    370362msgstr "La fattura PDF è già stata inviata manualmente"
     363
     364msgid "Action skipped: another upload process is already running for this order."
     365msgstr "Azione saltata: un altro processo di caricamento è già in esecuzione per questo ordine."
  • woopop-electronic-invoice-free/trunk/addon/to/aruba/src/Functions/Api.php

    r3409381 r3463326  
    434434    $scheduledCronJob = get_option('waf_scheduled_events');
    435435    $checkSent        = \WcElectronInvoice\Functions\getPostMeta('_invoice_sent', null, $orderID, true, 'order', $provider);
    436     if ('on' === $scheduledCronJob && 'feCronJobUploadRequest' === $ajaxAction && 'sent' === $checkSent) {
     436    if ('on' === $scheduledCronJob &&
     437        \WcElectronInvoice\Functions\isBackgroundExecutionMode($ajaxAction, ['feCronJobUploadRequest']) &&
     438        'sent' === $checkSent
     439    ) {
    437440        return;
    438441    }
     
    442445
    443446    // auto refreshOAuthToken
    444     if ($ajaxAction !== 'feCronJobUploadRequest' &&
     447    if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($ajaxAction, ['feCronJobUploadRequest']) &&
    445448        (! $apiData->expireAccessToken || (int)$apiData->expireAccessToken < time())
    446449    ) {
     
    471474        $isXmlFromRemoTe = false;
    472475        $validXML        = false;
    473         if('feCronJobUploadRequest' !== $ajaxAction) {
     476        if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($ajaxAction,
     477                ['feCronJobUploadRequest'])) {
    474478            $url = esc_url_raw(add_query_arg([
    475479                CreateXml::LIST_TYPE => $orderID,
     
    500504        // Not valid ?
    501505        if (! $isXml->valid) {
     506            $uploadError = \WcElectronInvoice\Functions\mapUploadPreconditionError('aruba', 'xml_invalid');
    502507            $noXmlResponse = array(
    503508                'title'   => __('Oops, something is wrong!', WP_FATT_ARUBA_TEXTDOMAIN),
     
    505510                'xml'     => $isXml->xml,
    506511                'message' => __('Request error: xml not valid', WP_FATT_ARUBA_TEXTDOMAIN),
    507                 'code'    => 501,
     512                'code'    => $uploadError['code'],
    508513            );
    509514
     
    524529        }
    525530
     531        $lockKey = \WcElectronInvoice\Functions\buildDbLockKey('aruba', 'upload_invoice', (int)$orderID,
     532            (string)$provider, (string)$choiceType);
     533        $lockDecision = \WcElectronInvoice\Functions\evaluateLockBusyDecision(
     534            \WcElectronInvoice\Functions\acquireDbLock($lockKey, 0),
     535            \WcElectronInvoice\Functions\isBackgroundExecutionMode($ajaxAction, ['feCronJobUploadRequest'])
     536        );
     537        if (! $lockDecision['continue']) {
     538            if ($lockDecision['respond_error']) {
     539                $lockError = \WcElectronInvoice\Functions\mapLockBusyError('aruba', 'upload_invoice');
     540                wp_send_json(array(
     541                    'message' => __('Action skipped: another upload process is already running for this order.',
     542                        WP_FATT_ARUBA_TEXTDOMAIN),
     543                    'orderID' => $orderID,
     544                    'code'    => $lockError['code'],
     545                ));
     546                die();
     547            }
     548
     549            return;
     550        }
     551
    526552        try {
    527553            // XML in Base64
    528554            $xmlB64 = base64_encode($xml);
    529555
    530             $body = array(
    531                 'dataFile'        => $xmlB64,
    532                 'credential'      => '',
    533                 'domain'          => '',
    534                 'senderPIVA'      => '',
    535                 'skipExtraSchema' => false,
    536             );
     556            $body = \WcElectronInvoice\Functions\buildArubaUploadPayload($xmlB64);
    537557
    538558            $baseUri = getActiveApi()->apiWsUrl;
     
    556576                // Synchronous Controls
    557577                // @see https://fatturazioneelettronica.aruba.it/apidoc/docs.html#synchronous-checks
    558                 if ("0000" !== $responseData->errorCode && $responseData->uploadFileName === '') {
     578                if ("0000" !== $responseData->errorCode ||
     579                    ! \WcElectronInvoice\Functions\canMarkInvoiceSentWithUploadFileName($responseData->uploadFileName)
     580                ) {
     581                    $uploadError = \WcElectronInvoice\Functions\mapUploadPreconditionError('aruba', 'upload_failed');
    559582                    $responseArgs = array(
    560583                        'body'           => $responseData,
    561                         'message'        => $responseData->errorDescription,
     584                        'message'        => ! empty($responseData->errorDescription) ? $responseData->errorDescription : __('Invoice upload error!',
     585                            WP_FATT_ARUBA_TEXTDOMAIN),
    562586                        'orderID'        => $orderID,
    563587                        'jobForceSignIn' => $jobForceSignIn,
    564588                        'requestURI'     => $uri,
    565                         'code'           => $response->getStatusCode(),
     589                        'code'           => $uploadError['code'],
    566590                    );
    567591                } else {
    568592                    $order->update_meta('waf-upload_file_name', $responseData->uploadFileName);
    569                     $order->update_meta('_invoice_sent', 'sent');
     593                    if (\WcElectronInvoice\Functions\canMarkInvoiceSentWithUploadFileName($responseData->uploadFileName)) {
     594                        $order->update_meta('_invoice_sent', 'sent');
     595                    }
    570596                    // Save
    571597                    $order->save();
     
    584610                }
    585611            } else {
     612                $uploadError = \WcElectronInvoice\Functions\mapUploadPreconditionError('aruba', 'upload_failed');
    586613                $responseArgs = array(
    587614                    'body'           => $responseData,
     
    590617                    'jobForceSignIn' => $jobForceSignIn,
    591618                    'requestURI'     => $uri,
    592                     'code'           => $response->getStatusCode(),
     619                    'code'           => $uploadError['code'],
    593620                );
    594621            }
     
    619646            // Cron Job return data
    620647            if ('on' === $scheduledCronJob &&
    621                 'feCronJobUploadRequest' === $ajaxAction &&
     648                \WcElectronInvoice\Functions\isBackgroundExecutionMode($ajaxAction, ['feCronJobUploadRequest']) &&
    622649                'sent' !== $checkSent
    623650            ) {
     651                \WcElectronInvoice\Functions\releaseDbLock($lockKey);
    624652                return (object)array(
    625653                    'fileName' => property_exists($responseData, 'uploadFileName') ? $responseData->uploadFileName : null,
     
    628656            }
    629657
     658            \WcElectronInvoice\Functions\releaseDbLock($lockKey);
    630659            wp_send_json($responseArgs);
    631660            die();
     
    661690                    true));
    662691
     692            \WcElectronInvoice\Functions\releaseDbLock($lockKey);
    663693            wp_send_json($dataException);
    664694            die();
  • woopop-electronic-invoice-free/trunk/addon/to/aruba/src/Jobs.php

    r3426093 r3463326  
    302302        $data = getActiveApi();
    303303        if (! $data->active) {
     304            wp_clear_scheduled_hook('twiceDailyWooPoPToFattureArubaJobsScheduledUpload');
    304305            return;
    305306        }
     
    368369                }
    369370            });
     371        } else {
     372            wp_clear_scheduled_hook('twiceDailyWooPoPToFattureArubaJobsScheduledUpload');
    370373        }
    371374    }
  • woopop-electronic-invoice-free/trunk/addon/to/aruba/vendor/composer/installed.php

    r3460902 r3463326  
    44        'pretty_version' => 'dev-main',
    55        'version' => 'dev-main',
    6         'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     6        'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => 'dev-main',
    1515            'version' => 'dev-main',
    16             'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     16            'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
  • woopop-electronic-invoice-free/trunk/addon/to/fattureincloud-stock/vendor/composer/installed.php

    r3460902 r3463326  
    44        'pretty_version' => 'dev-main',
    55        'version' => 'dev-main',
    6         'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     6        'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => 'dev-main',
    1515            'version' => 'dev-main',
    16             'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     16            'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
  • woopop-electronic-invoice-free/trunk/addon/to/fattureincloud/inc/filtersAdmin.php

    r3382415 r3463326  
    113113            array(
    114114                'filter'        => 'woocommerce_order_status_changed',
    115                 'callback'      => 'WooPoPToFattureInCloud\\Jobs::changeStatus',
     115                'callback'      => function ($orderID, $from, $to) {
     116                    if (! \WcElectronInvoice\Integrations::isInvoiceChannelActive(\WcElectronInvoice\Integrations::CHANNEL_FIC)) {
     117                        return;
     118                    }
     119
     120                    \WooPoPToFattureInCloud\Jobs::changeStatus($orderID, $from, $to);
     121                },
    116122                'priority'      => 10,
    117123                'accepted_args' => 3,
  • woopop-electronic-invoice-free/trunk/addon/to/fattureincloud/inc/filtersFront.php

    r3369717 r3463326  
    5252                    'woocommerce_order_status_processing',
    5353                ),
    54                 'callback'      => 'WooPoPToFattureInCloud\\Jobs::autoCompleted',
     54                'callback'      => function ($orderID) {
     55                    if (! \WcElectronInvoice\Integrations::isInvoiceChannelActive(\WcElectronInvoice\Integrations::CHANNEL_FIC)) {
     56                        return;
     57                    }
     58
     59                    \WooPoPToFattureInCloud\Jobs::autoCompleted($orderID);
     60                },
    5561                'priority'      => 10,
    5662                'accepted_args' => 1,
  • woopop-electronic-invoice-free/trunk/addon/to/fattureincloud/languages/wfc-it_IT.po

    r3382415 r3463326  
    33"Project-Id-Version: POP to Fattureincloud\n"
    44"POT-Creation-Date: 2025-10-02 12:08+0200\n"
    5 "PO-Revision-Date: 2025-10-02 12:08+0200\n"
     5"PO-Revision-Date: 2026-02-16 13:58+0100\n"
    66"Last-Translator: Alfio <[email protected]>\n"
    77"Language-Team: \n"
     
    1111"Content-Transfer-Encoding: 8bit\n"
    1212"Plural-Forms: nplurals=2; plural=(n != 1);\n"
    13 "X-Generator: Poedit 3.7\n"
     13"X-Generator: Poedit 3.8\n"
    1414"X-Poedit-Basepath: ..\n"
    1515"X-Poedit-KeywordsList: "
     
    3737msgstr "Caricare su Fatture in Cloud per sincronizzare il numero di fattura"
    3838
    39 #: inc/localizeScripts.php:40 src/Functions/Api.php:1269
    40 #: src/Functions/Api.php:2616
     39#: inc/localizeScripts.php:40 src/Functions/Api.php:1269 src/Functions/Api.php:2616
    4140msgid "Message:"
    4241msgstr "Messaggio:"
     
    5352msgid "Time out, click Connect again to generate another verification code"
    5453msgstr ""
    55 "Tempo scaduto, fare di nuovo click su Connetti per generare un altro codice "
    56 "di verificawhi"
     54"Tempo scaduto, fare di nuovo click su Connetti per generare un altro codice di verificawhi"
    5755
    5856#: src/Api.php:347 src/Api.php:410 src/Api.php:556 src/Functions/Api.php:499
    59 #: src/Functions/Api.php:2429 src/Functions/Api.php:2588
    60 #: src/Functions/Api.php:2718 src/Functions/Api.php:2991 src/Jobs.php:489
     57#: src/Functions/Api.php:2429 src/Functions/Api.php:2588 src/Functions/Api.php:2718
     58#: src/Functions/Api.php:2991 src/Jobs.php:489
    6159msgid "Unauthorized: the Token seems to be invalid, update it!"
    6260msgstr "Non autorizzato: il Token sembra non essere valido, aggiornalo!"
     
    9290#: src/Filters.php:168
    9391msgid ""
    94 "Permissions allow you to create invoices or orders, *credit notes, *clients "
    95 "and  *receipts (optionals) on Fatture in Cloud"
    96 msgstr ""
    97 "I permessi permettono di creare fatture oppure ordini, *note di credito, "
    98 "*clienti e *ricevute (opzionali) su Fatture in Cloud"
     92"Permissions allow you to create invoices or orders, *credit notes, *clients and  *receipts "
     93"(optionals) on Fatture in Cloud"
     94msgstr ""
     95"I permessi permettono di creare fatture oppure ordini, *note di credito, *clienti e "
     96"*ricevute (opzionali) su Fatture in Cloud"
    9997
    10098#: src/Filters.php:173 src/Filters.php:294
     
    188186#: src/Filters.php:329
    189187msgid ""
    190 "(*) If you do not connect the company you will not be able to send invoices "
    191 "through the Fatture In Cloud API"
    192 msgstr ""
    193 "(*) Se non colleghi l’azienda non potrai inviare fatture tramite l’API "
    194 "Fatture In Cloud "
     188"(*) If you do not connect the company you will not be able to send invoices through the "
     189"Fatture In Cloud API"
     190msgstr ""
     191"(*) Se non colleghi l’azienda non potrai inviare fatture tramite l’API Fatture In Cloud "
    195192
    196193#: src/Filters.php:346 src/Filters.php:359
     
    208205#: src/Filters.php:375
    209206msgid "select the payment gateway and a payment account for each method"
    210 msgstr ""
    211 "selezionare il gateway di pagamento e un conto di pagamento per ciascun "
    212 "metodo"
     207msgstr "selezionare il gateway di pagamento e un conto di pagamento per ciascun metodo"
    213208
    214209#: src/Filters.php:396
     
    313308msgstr "Errore nel caricamento. controllare i dati e riprovare"
    314309
    315 #: src/Functions/Api.php:191 src/Functions/Api.php:254
    316 #: src/Functions/Api.php:387 src/Functions/Api.php:419
    317 #: src/Functions/Api.php:498 src/Functions/Api.php:513
    318 #: src/Functions/Api.php:567 src/Functions/Api.php:596
    319 #: src/Functions/Api.php:628 src/Functions/Api.php:747
    320 #: src/Functions/Api.php:817 src/Functions/Api.php:830
    321 #: src/Functions/Api.php:842 src/Functions/Api.php:1023
    322 #: src/Functions/Api.php:1040 src/Functions/Api.php:1068
    323 #: src/Functions/Api.php:1108 src/Functions/Api.php:1123
    324 #: src/Functions/Api.php:1142 src/Functions/Api.php:1197
    325 #: src/Functions/Api.php:1211 src/Functions/Api.php:1257
    326 #: src/Functions/Api.php:1265 src/Functions/Api.php:1514
    327 #: src/Functions/Api.php:1521 src/Functions/Api.php:1537
    328 #: src/Functions/Api.php:2131 src/Functions/Api.php:2152
    329 #: src/Functions/Api.php:2277 src/Functions/Api.php:2369
    330 #: src/Functions/Api.php:2428 src/Functions/Api.php:2443
    331 #: src/Functions/Api.php:2467 src/Functions/Api.php:2480
    332 #: src/Functions/Api.php:2587 src/Functions/Api.php:2602
    333 #: src/Functions/Api.php:2612 src/Functions/Api.php:2717
    334 #: src/Functions/Api.php:2732 src/Functions/Api.php:2808
    335 #: src/Functions/Api.php:2990 src/Functions/Api.php:3016
     310#: src/Functions/Api.php:191 src/Functions/Api.php:254 src/Functions/Api.php:387
     311#: src/Functions/Api.php:419 src/Functions/Api.php:498 src/Functions/Api.php:513
     312#: src/Functions/Api.php:567 src/Functions/Api.php:596 src/Functions/Api.php:628
     313#: src/Functions/Api.php:747 src/Functions/Api.php:817 src/Functions/Api.php:830
     314#: src/Functions/Api.php:842 src/Functions/Api.php:1023 src/Functions/Api.php:1040
     315#: src/Functions/Api.php:1068 src/Functions/Api.php:1108 src/Functions/Api.php:1123
     316#: src/Functions/Api.php:1142 src/Functions/Api.php:1197 src/Functions/Api.php:1211
     317#: src/Functions/Api.php:1257 src/Functions/Api.php:1265 src/Functions/Api.php:1514
     318#: src/Functions/Api.php:1521 src/Functions/Api.php:1537 src/Functions/Api.php:2131
     319#: src/Functions/Api.php:2152 src/Functions/Api.php:2277 src/Functions/Api.php:2369
     320#: src/Functions/Api.php:2428 src/Functions/Api.php:2443 src/Functions/Api.php:2467
     321#: src/Functions/Api.php:2480 src/Functions/Api.php:2587 src/Functions/Api.php:2602
     322#: src/Functions/Api.php:2612 src/Functions/Api.php:2717 src/Functions/Api.php:2732
     323#: src/Functions/Api.php:2808 src/Functions/Api.php:2990 src/Functions/Api.php:3016
    336324#: src/Functions/Api.php:3200
    337325msgid "Oops, something is wrong!"
     
    397385msgid "Invalid Settings for Mapping Payment account whit method"
    398386msgstr ""
    399 "Impostazioni non valide per la mappatura del conto di pagamento e del metodo "
    400 "di pagamento"
     387"Impostazioni non valide per la mappatura del conto di pagamento e del metodo di pagamento"
    401388
    402389#: src/Functions/Api.php:656
     
    406393#: src/Functions/Api.php:707
    407394msgid "Mapping Payment Gateway whit method ID set successfully!"
    408 msgstr ""
    409 "Mappatura del Gateway di pagamento con l’ID del metodo impostato con "
    410 "successo!"
     395msgstr "Mappatura del Gateway di pagamento con l’ID del metodo impostato con successo!"
    411396
    412397#: src/Functions/Api.php:748 src/Functions/Api.php:818
     
    462447#: src/Functions/Api.php:1199
    463448msgid ""
    464 "You cannot send the credit note because the reference to the linked document "
    465 "is\n"
    466 "                    missing. (NB: before sending the credit note, the "
    467 "invoice must be sent)."
    468 msgstr ""
    469 "Non è possibile inviare la nota di credito perché manca il riferimento al "
    470 "documento collegato\n"
    471 "                    (NB: prima di inviare la nota di credito, è necessario "
    472 "inviare la fattura)."
     449"You cannot send the credit note because the reference to the linked document is\n"
     450"                    missing. (NB: before sending the credit note, the invoice must be sent)."
     451msgstr ""
     452"Non è possibile inviare la nota di credito perché manca il riferimento al documento "
     453"collegato\n"
     454"                    (NB: prima di inviare la nota di credito, è necessario inviare la "
     455"fattura)."
    473456
    474457#: src/Functions/Api.php:1213
    475458msgid "You cannot send a credit note from an order set to receive a receipt"
    476459msgstr ""
    477 "Non è possibile inviare una nota di credito da un ordine impostato per "
    478 "ricevere una ricevuta"
     460"Non è possibile inviare una nota di credito da un ordine impostato per ricevere una ricevuta"
    479461
    480462#: src/Functions/Api.php:1260
     
    482464msgstr "Errore di richiesta: xml non valido"
    483465
    484 #: src/Functions/Api.php:1266 src/Functions/Api.php:2370
    485 #: src/Functions/Api.php:2468 src/Functions/Api.php:2613
     466#: src/Functions/Api.php:1266 src/Functions/Api.php:2370 src/Functions/Api.php:2468
     467#: src/Functions/Api.php:2613
    486468msgid "Order: "
    487469msgstr "Ordine: "
    488470
    489 #: src/Functions/Api.php:1267 src/Functions/Api.php:2371
    490 #: src/Functions/Api.php:2469 src/Functions/Api.php:2614
    491 msgid ""
    492 "The invoice could not be created, we recommend that you check your order "
    493 "data and try again"
    494 msgstr ""
    495 "Non è stato possibile creare la fattura, si consiglia di controllare i dati "
    496 "dell'ordine e riprovare"
    497 
    498 #: src/Functions/Api.php:1270 src/Functions/Api.php:2373
    499 #: src/Functions/Api.php:2471
     471#: src/Functions/Api.php:1267 src/Functions/Api.php:2371 src/Functions/Api.php:2469
     472#: src/Functions/Api.php:2614
     473msgid ""
     474"The invoice could not be created, we recommend that you check your order data and try again"
     475msgstr ""
     476"Non è stato possibile creare la fattura, si consiglia di controllare i dati dell'ordine e "
     477"riprovare"
     478
     479#: src/Functions/Api.php:1270 src/Functions/Api.php:2373 src/Functions/Api.php:2471
    500480msgid "Errors:"
    501481msgstr "Errori:"
    502482
    503 #: src/Functions/Api.php:1331 src/Functions/Api.php:1333
    504 #: src/Functions/Api.php:1336
     483#: src/Functions/Api.php:1331 src/Functions/Api.php:1333 src/Functions/Api.php:1336
    505484msgid "Shipping: "
    506485msgstr "Spedizione: "
     
    511490
    512491#: src/Functions/Api.php:1396
    513 msgid ""
    514 "The payment method of the order has been excluded from sending to Fatture in "
    515 "Cloud"
    516 msgstr ""
    517 "Il metodo di pagamento dell'ordine è stato escluso dall'invio a Fatture in "
    518 "Cloud"
     492msgid "The payment method of the order has been excluded from sending to Fatture in Cloud"
     493msgstr "Il metodo di pagamento dell'ordine è stato escluso dall'invio a Fatture in Cloud"
    519494
    520495#: src/Functions/Api.php:1406
     
    592567#: src/Functions/Api.php:2896
    593568msgid ""
    594 "Error in generating the PDF, download it from your reserved area, or ask the "
    595 "seller for it"
    596 msgstr ""
    597 "Errore nel generare il PDF, scaricarlo dalla propria area riservata o "
    598 "richiederlo al venditore"
     569"Error in generating the PDF, download it from your reserved area, or ask the seller for it"
     570msgstr ""
     571"Errore nel generare il PDF, scaricarlo dalla propria area riservata o richiederlo al "
     572"venditore"
    599573
    600574#: src/Functions/Api.php:2936
     
    613587#: src/Functions/Utils.php:258
    614588msgid "WooPOP Addon: You do not have the permissions to manage the add-on"
    615 msgstr ""
    616 "WooPOP Addon: Non si dispone dei permessi per gestire il componente "
    617 "aggiuntivo"
     589msgstr "WooPOP Addon: Non si dispone dei permessi per gestire il componente aggiuntivo"
    618590
    619591#: src/Functions/Utils.php:287
     
    635607#: src/Functions/Utils.php:301
    636608msgid ""
    637 "Go to <b>settings > balance account</b> and create a balance account, eg: "
    638 "<b>\"Standard account\"</b>"
    639 msgstr ""
    640 "Vai su <b>impostazioni > conto saldo</b> e crea un conto saldo, ad esempio: "
    641 "<b>”Conto standard”</b> "
     609"Go to <b>settings > balance account</b> and create a balance account, eg: <b>\"Standard "
     610"account\"</b>"
     611msgstr ""
     612"Vai su <b>impostazioni > conto saldo</b> e crea un conto saldo, ad esempio: <b>”Conto "
     613"standard”</b> "
    642614
    643615#: src/Functions/Utils.php:303
    644616msgid ""
    645 "Go to <b>settings > payment methods</b> and create these basic methods: "
    646 "<b>(Wire transfer, Check, Cash, Payment card)</b>"
    647 msgstr ""
    648 "Vai su <b>impostazioni > metodi di pagamento</b> e crea questi metodi di "
    649 "base: <b>(Bonifico, Assegno, Contanti, Carta di pagamento)</b>"
     617"Go to <b>settings > payment methods</b> and create these basic methods: <b>(Wire transfer, "
     618"Check, Cash, Payment card)</b>"
     619msgstr ""
     620"Vai su <b>impostazioni > metodi di pagamento</b> e crea questi metodi di base: "
     621"<b>(Bonifico, Assegno, Contanti, Carta di pagamento)</b>"
    650622
    651623#: src/Functions/Utils.php:308
     
    655627#: src/Functions/Utils.php:310
    656628msgid ""
    657 "<b>By clicking on Connect</b>, a popup will appear with a <b>code and a "
    658 "link</b> to authorize the connection. When you click, a page will open where "
    659 "you will have to authorize the connection by entering the code in the popup "
    660 "<u>(do not close the popup until the procedure is complete)</u>"
    661 msgstr ""
    662 "<b>Facendo clic su Connetti</b>, apparirà un popup con un <b>codice e un "
    663 "link</b> per autorizzare la connessione. Facendo clic, si aprirà una pagina "
    664 "in cui si dovrà autorizzare la connessione inserendo il codice presente nel "
    665 "popup <u>(non chiudere il popup fino al completamento della procedura)</u>"
     629"<b>By clicking on Connect</b>, a popup will appear with a <b>code and a link</b> to "
     630"authorize the connection. When you click, a page will open where you will have to authorize "
     631"the connection by entering the code in the popup <u>(do not close the popup until the "
     632"procedure is complete)</u>"
     633msgstr ""
     634"<b>Facendo clic su Connetti</b>, apparirà un popup con un <b>codice e un link</b> per "
     635"autorizzare la connessione. Facendo clic, si aprirà una pagina in cui si dovrà autorizzare "
     636"la connessione inserendo il codice presente nel popup <u>(non chiudere il popup fino al "
     637"completamento della procedura)</u>"
    666638
    667639#: src/Functions/Utils.php:312
    668640msgid ""
    669 "Once the connection procedure with Fatture In Cloud is finished, <b>go back "
    670 "to this page</b> where you will find the answer of <b>\"Access Token "
    671 "successful!\"</b>"
    672 msgstr ""
    673 "Una volta terminata la procedura di connessione con Fatture In Cloud, "
    674 "<b>torna in questa pagina</b> dove troverai la risposta di <b>”Token di "
    675 "accesso riuscito!”</b>"
     641"Once the connection procedure with Fatture In Cloud is finished, <b>go back to this page</"
     642"b> where you will find the answer of <b>\"Access Token successful!\"</b>"
     643msgstr ""
     644"Una volta terminata la procedura di connessione con Fatture In Cloud, <b>torna in questa "
     645"pagina</b> dove troverai la risposta di <b>”Token di accesso riuscito!”</b>"
    676646
    677647#: src/Functions/Utils.php:318
    678648msgid ""
    679 "Si consiglia di verificare con molta attenzione che i dati per la "
    680 "Fatturazione elettronica inviati a\n"
    681 "                        Fattura in Cloud siano corretti. L'autore del plugin "
    682 "declina ogni responsabilità per errori o mancanze nella generazione della "
    683 "Fattura elettronica."
     649"Si consiglia di verificare con molta attenzione che i dati per la Fatturazione elettronica "
     650"inviati a\n"
     651"                        Fattura in Cloud siano corretti. L'autore del plugin declina ogni "
     652"responsabilità per errori o mancanze nella generazione della Fattura elettronica."
    684653msgstr ""
    685654
     
    689658
    690659#: src/Functions/Utils.php:328
    691 msgid ""
    692 "Every time the plugin is deactivated, the authentication data is removed."
    693 msgstr ""
    694 "Ogni volta che il plugin viene disattivato, i dati di autenticazione vengono "
    695 "rimossi."
     660msgid "Every time the plugin is deactivated, the authentication data is removed."
     661msgstr "Ogni volta che il plugin viene disattivato, i dati di autenticazione vengono rimossi."
    696662
    697663#: src/Functions/Utils.php:330
     
    725691#: src/Functions/Utils.php:363
    726692msgid ""
    727 "Enable automatic sending of invoices for completed orders not sent in the "
    728 "last 7 days. (work 2 times a day)"
    729 msgstr ""
    730 "Abilita l'invio automatico delle fatture per gli ordini completati e non "
    731 "inviati negli ultimi 7 giorni. (invia 2 volte al giorno)"
     693"Enable automatic sending of invoices for completed orders not sent in the last 7 days. "
     694"(work 2 times a day)"
     695msgstr ""
     696"Abilita l'invio automatico delle fatture per gli ordini completati e non inviati negli "
     697"ultimi 7 giorni. (invia 2 volte al giorno)"
    732698
    733699#: src/Functions/Utils.php:368
     
    737703#: src/Functions/Utils.php:369
    738704msgid ""
    739 "if active, the invoice / receipt documents will be created but the invoices "
    740 "will not be sent to the SDI"
    741 msgstr ""
    742 "se attivo, i documenti fattura/ricevuta saranno creati ma le fatture non "
    743 "verranno inviate allo SDI"
     705"if active, the invoice / receipt documents will be created but the invoices will not be "
     706"sent to the SDI"
     707msgstr ""
     708"se attivo, i documenti fattura/ricevuta saranno creati ma le fatture non verranno inviate "
     709"allo SDI"
    744710
    745711#: src/Functions/Utils.php:375
     
    756722
    757723#: src/Functions/Utils.php:396
    758 msgid ""
    759 "Use the \"Fatture in Cloud\" PDF document as an attachment to the Invoice "
    760 "email"
    761 msgstr ""
    762 "Utilizza il documento PDF di “Fatture in Cloud” come allegato all’e-mail di "
    763 "Fatturazione"
     724msgid "Use the \"Fatture in Cloud\" PDF document as an attachment to the Invoice email"
     725msgstr ""
     726"Utilizza il documento PDF di “Fatture in Cloud” come allegato all’e-mail di Fatturazione"
    764727
    765728#: src/Functions/Utils.php:402
     
    768731
    769732#: src/Functions/Utils.php:407
    770 msgid ""
    771 "Enables the insertion of the notes inserted during the checkout phase on the "
    772 "invoice"
    773 msgstr ""
    774 "Abilita inserimento in fattura delle note inserite durante la fase di "
    775 "checkout"
     733msgid "Enables the insertion of the notes inserted during the checkout phase on the invoice"
     734msgstr "Abilita inserimento in fattura delle note inserite durante la fase di checkout"
    776735
    777736#: src/Functions/Utils.php:413
     
    789748#: src/Functions/Utils.php:428
    790749msgid ""
    791 "Revenue centers can be used to group revenue related to a particular "
    792 "activity. (for example \"e-commerce\")"
    793 msgstr ""
    794 "I centri di ricavo possono essere utilizzati per raggruppare le entrate "
    795 "relative a una particolare attività. (ad esempio \"e-commerce\")"
     750"Revenue centers can be used to group revenue related to a particular activity. (for example "
     751"\"e-commerce\")"
     752msgstr ""
     753"I centri di ricavo possono essere utilizzati per raggruppare le entrate relative a una "
     754"particolare attività. (ad esempio \"e-commerce\")"
    796755
    797756#: src/Functions/Utils.php:435
     
    829788#: src/Functions/Utils.php:468
    830789msgid "Enter this code in wp-config.php to activate the event log."
    831 msgstr ""
    832 "Inserisci questo codice nel wp-config.php per attivare il registro degli "
    833 "eventi."
     790msgstr "Inserisci questo codice nel wp-config.php per attivare il registro degli eventi."
    834791
    835792#: src/Functions/Utils.php:477
     
    886843msgstr "Vuoi imbrogliare eh?"
    887844
     845msgid "Action skipped: another create process is already running for this order."
     846msgstr "Azione saltata: un altro processo di creazione è già in esecuzione per questo ordine."
     847
     848msgid "Action not allowed: another integration channel is active."
     849msgstr "Azione non consentita: un altro canale di integrazione è attivo."
     850
     851msgid "Missing issued document id, create the invoice first."
     852msgstr "ID documento emesso mancante, crea prima la fattura."
     853
     854msgid "Missing document id parameter for send request."
     855msgstr "Parametro ID documento mancante nella richiesta di invio."
     856
     857msgid "Invalid document id for send request."
     858msgstr "ID documento non valido per la richiesta di invio."
     859
     860msgid "Action skipped: another send process is already running for this order."
     861msgstr "Azione saltata: un altro processo di invio è già in esecuzione per questo ordine."
     862
    888863#~ msgid "Sended"
    889864#~ msgstr "Inviato"
  • woopop-electronic-invoice-free/trunk/addon/to/fattureincloud/src/Functions/Api.php

    r3460146 r3463326  
    3434use GuzzleHttp\Exception\GuzzleException;
    3535use WcElectronInvoice\Admin\Settings\OptionPage;
     36use WcElectronInvoice\Integrations;
    3637use WcElectronInvoice\WooCommerce\Fields\GeneralFields;
    3738use WcElectronInvoice\WooCommerce\Fields\TaxFields;
     
    10851086    $jobEvent = filterInput($_POST, 'fcCreateInvoiceJobEvent', FILTER_UNSAFE_RAW);
    10861087
     1088    if (! Integrations::isInvoiceChannelActive(Integrations::CHANNEL_FIC)) {
     1089        if (! $jobEvent) {
     1090            wp_send_json(array(
     1091                'title'   => __('Oops, something is wrong!', WP_FATT_CLOUD_TEXTDOMAIN),
     1092                'message' => esc_html__('Action not allowed: another integration channel is active.',
     1093                    WP_FATT_CLOUD_TEXTDOMAIN),
     1094                'code'    => 422,
     1095            ));
     1096            die();
     1097        }
     1098
     1099        return;
     1100    }
     1101
    10871102    // auto refreshOAuthToken
    10881103    if (! $jobEvent && ! getActiveApi()->expireAccessToken || (int)getActiveApi()->expireAccessToken < time()) {
     
    11161131    }
    11171132
    1118     // Return if order not completed and processing status
    1119     if ('processing' !== $order->get_status() &&
    1120         'completed' !== $order->get_status()
    1121     ) {
     1133    $statusAllowed = in_array((string)$order->get_status(), ['processing', 'completed'], true);
     1134    $disableInvoiceNumber = \WcElectronInvoice\Functions\disableInvoiceOnOrderTotalZero($order);
     1135    $createDecision = \WcElectronInvoice\Functions\evaluateFicCreateDecision(
     1136        $statusAllowed,
     1137        true === apply_filters('wfc-create-invoice_send_invoice_zero', $disableInvoiceNumber, $order->get_total())
     1138    );
     1139
     1140    if (! $createDecision['allowed'] && 'status_not_allowed' === $createDecision['reason']) {
     1141        $createError = \WcElectronInvoice\Functions\mapFicCreateDecisionError($createDecision['reason']);
    11221142        wp_send_json(array(
    11231143            'title'   => __('Oops, something is wrong!', WP_FATT_CLOUD_TEXTDOMAIN),
    11241144            'body'    => null,
    11251145            'message' => sprintf(__("OrderID %s - not completed or not processing status."), $order->get_id()),
    1126             'code'    => 501,
     1146            'code'    => $createError['code'],
    11271147        ));
    11281148        // Log
     
    11331153    }
    11341154
    1135     // Return if total is 0
    1136     $disableInvoiceNumber = \WcElectronInvoice\Functions\disableInvoiceOnOrderTotalZero($order);
    1137     if (true === apply_filters('wfc-create-invoice_send_invoice_zero',
    1138             $disableInvoiceNumber,
    1139             $order->get_total()
    1140         )) {
     1155    if (! $createDecision['allowed'] && 'total_zero_not_allowed' === $createDecision['reason']) {
     1156        $createError = \WcElectronInvoice\Functions\mapFicCreateDecisionError($createDecision['reason']);
    11411157        wp_send_json(array(
    11421158            'title'   => __('Oops, something is wrong!', WP_FATT_CLOUD_TEXTDOMAIN),
    11431159            'body'    => null,
    11441160            'message' => __('The order total is 0 (zero) you cannot send the invoice', WP_FATT_CLOUD_TEXTDOMAIN),
    1145             'code'    => 501,
     1161            'code'    => $createError['code'],
    11461162        ));
    11471163        // Log
     
    16431659            $dutyValue = $order->get_total() > floatval('77.47') ? 2 : 0;
    16441660        }
     1661
     1662        $lockKey = null;
    16451663
    16461664        try {
     
    20942112            }
    20952113
    2096             $body['data']['show_totals']              = 'all';
    2097             $body['data']['show_paypal_button']       = false;
    2098             $body['data']['show_notification_button'] = false;
    2099             $body['data']['accompanying_invoice']     = false;
    2100             $body['data']['show_payment_method']      = true;
    2101             $body['data']['show_payments']            = true;
     2114            $body['data'] = array_merge(
     2115                $body['data'],
     2116                \WcElectronInvoice\Functions\buildFicDocumentDisplayOptions()
     2117            );
    21022118            if (false === apply_filters('wfc-disabled_data_date_document', false, $jobEvent)) {
    21032119                // data: data di emissione documento
    21042120                $body['data']['date'] = $docDateNow->format('Y-m-d');
    21052121            }
    2106             // od_date: documento originario (operazione per fatture, data fattura collegata per NC)
    2107             $body['data']['ei_data']['od_date'] = $odDateValue;
    2108 
    2109             // invoice_date: per NC è la data della fattura stornata; per le fatture impostiamo la data emissione
    2110             if ('credit_note' === $documentType && $linkedInvDate) {
    2111                 $body['data']['ei_data']['invoice_date'] = $linkedInvDate;
    2112             } else {
    2113                 $body['data']['ei_data']['invoice_date'] = $docDateNow->format('Y-m-d');
    2114             }
    2115 
    2116             if ('credit_note' === $documentType) {
    2117                 $body['data']['ei_data']['invoice_number'] = $creditNoteLinkedInvoiceNumber;
    2118             }
    2119 
    2120             if ($iban) {
    2121                 $body['data']['ei_data']['bank_iban']        = $iban;
    2122                 $body['data']['ei_data']['bank_name']        = $bankName;
    2123                 $body['data']['ei_data']['bank_beneficiary'] = $accountName;
    2124             }
     2122            $invoiceDate = ('credit_note' === $documentType && $linkedInvDate) ? $linkedInvDate : $docDateNow->format('Y-m-d');
     2123            $eiInvoiceNumber = 'credit_note' === $documentType ? (string)$creditNoteLinkedInvoiceNumber : null;
     2124            $bankData        = $iban ? [
     2125                'iban'        => $iban,
     2126                'name'        => $bankName,
     2127                'beneficiary' => $accountName,
     2128            ] : null;
     2129
     2130            $body['data']['ei_data'] = array_merge(
     2131                $body['data']['ei_data'],
     2132                \WcElectronInvoice\Functions\buildFicEiDataPayload(
     2133                    (string)$odDateValue,
     2134                    (string)$invoiceDate,
     2135                    $eiInvoiceNumber,
     2136                    $bankData
     2137                )
     2138            );
    21252139
    21262140            // Log
     
    21542168            }
    21552169
     2170            $lockKey = \WcElectronInvoice\Functions\buildDbLockKey('fic', 'create_invoice', (int)$orderID,
     2171                (string)$provider, (string)$documentType);
     2172            $lockDecision = \WcElectronInvoice\Functions\evaluateLockBusyDecision(
     2173                \WcElectronInvoice\Functions\acquireDbLock($lockKey, 0),
     2174                (bool)$jobEvent
     2175            );
     2176            if (! $lockDecision['continue']) {
     2177                if ($lockDecision['respond_error']) {
     2178                    $lockError = \WcElectronInvoice\Functions\mapLockBusyError('fic', 'create_invoice');
     2179                    wp_send_json(array(
     2180                        'title'   => __('Oops, something is wrong!', WP_FATT_CLOUD_TEXTDOMAIN),
     2181                        'message' => esc_html__('Action skipped: another create process is already running for this order.',
     2182                            WP_FATT_CLOUD_TEXTDOMAIN),
     2183                        'code'    => $lockError['code'],
     2184                    ));
     2185                    die();
     2186                }
     2187
     2188                return;
     2189            }
     2190
    21562191            // Check issued document exist
    21572192            $documentID = \WcElectronInvoice\Functions\getPostMeta("wfc-issued_document_id", null, $orderID, true,
    21582193                'order', $provider);
    2159             if (isset($documentID) && '' !== $documentID) {
     2194            $createDecision = \WcElectronInvoice\Functions\evaluateFicCreateDecision(true, false, $documentID);
     2195            if (! $createDecision['allowed'] && 'already_created' === $createDecision['reason']) {
    21602196                if (! $jobEvent) {
     2197                    \WcElectronInvoice\Functions\releaseDbLock($lockKey);
     2198                    $createError = \WcElectronInvoice\Functions\mapFicCreateDecisionError($createDecision['reason']);
    21612199                    wp_send_json(array(
    21622200                        'title'   => __('Oops, something is wrong!', WP_FATT_CLOUD_TEXTDOMAIN),
    21632201                        'message' => esc_html__('The document has already been created', WP_FATT_CLOUD_TEXTDOMAIN),
    2164                         'code'    => 422,
     2202                        'code'    => $createError['code'],
    21652203                    ));
    21662204                    \WooPoPToFattureInCloud\Functions\log("OrderID {$order->get_id()} - The document has already been created: {$documentID}" . "\n");
    21672205                    die();
    21682206                } else {
     2207                    \WcElectronInvoice\Functions\releaseDbLock($lockKey);
    21692208                    return;
    21702209                }
     
    24282467
    24292468            if (! $jobEvent) {
     2469                \WcElectronInvoice\Functions\releaseDbLock($lockKey);
    24302470                wp_send_json($issuedDocumentResponse);
    24312471                die();
    24322472            } else {
     2473                \WcElectronInvoice\Functions\releaseDbLock($lockKey);
    24332474                return true;
    24342475            }
     
    24822523            sendEmailToAdminForInvoiceCreateError($emailBody, $orderID);
    24832524
     2525            if (! empty($lockKey)) {
     2526                \WcElectronInvoice\Functions\releaseDbLock($lockKey);
     2527            }
    24842528            wp_send_json($dataException);
    24852529            die();
     
    27892833    $id       = filterInput($_POST, 'id', FILTER_VALIDATE_INT);
    27902834    $provider = filterInput($_POST, 'provider', FILTER_UNSAFE_RAW);
     2835    $docID    = (int)$docID;
     2836
     2837    if (! Integrations::isInvoiceChannelActive(Integrations::CHANNEL_FIC)) {
     2838        if (! $jobEvent) {
     2839            wp_send_json(array(
     2840                'title'   => __('Oops, something is wrong!', WP_FATT_CLOUD_TEXTDOMAIN),
     2841                'message' => esc_html__('Action not allowed: another integration channel is active.',
     2842                    WP_FATT_CLOUD_TEXTDOMAIN),
     2843                'code'    => 422,
     2844            ));
     2845            die();
     2846        }
     2847
     2848        return;
     2849    }
    27912850
    27922851    $order = \WcElectronInvoice\Providers\OrderQuery::instance()->getProviderOrder($id, $provider);
     
    28112870    }
    28122871
    2813     // Invoice sent check
    2814     $checkSent = \WcElectronInvoice\Functions\getPostMeta('_invoice_sent', null, $id, true, 'order', $provider);
    2815     if (isset($checkSent) && 'sent' === $checkSent) {
     2872    $issuedDocumentID = (int)\WcElectronInvoice\Functions\getPostMeta('wfc-issued_document_id', null, $id, true,
     2873        'order',
     2874        $provider);
     2875    $issuedSendDocument = \WcElectronInvoice\Functions\getPostMeta('wfc-issued_send_document', null, $id, true,
     2876        'order',
     2877        $provider);
     2878    $checkSent          = \WcElectronInvoice\Functions\getPostMeta('_invoice_sent', null, $id, true, 'order',
     2879        $provider);
     2880    $sendDecision       = \WcElectronInvoice\Functions\evaluateFicSendDecision(
     2881        (int)$docID,
     2882        (int)$issuedDocumentID,
     2883        $issuedSendDocument,
     2884        is_string($checkSent) ? $checkSent : null
     2885    );
     2886    $alreadySentOnFic   = \WcElectronInvoice\Functions\normalizeBooleanMeta($issuedSendDocument);
     2887
     2888    if (! $sendDecision['allowed']) {
     2889        $sendError = \WcElectronInvoice\Functions\mapFicSendDecisionError($sendDecision['reason']);
     2890        if ('doc_id_mismatch' === $sendDecision['reason']) {
     2891            \WooPoPToFattureInCloud\Functions\log('sendInvoice - docID mismatch, abort send: ' . print_r([
     2892                    'docID'            => $docID,
     2893                    'issuedDocumentID' => $issuedDocumentID,
     2894                    'orderID'          => $id,
     2895                ], true));
     2896        }
     2897
    28162898        if (! $jobEvent) {
     2899            $message = esc_html__('Action not allowed.', WP_FATT_CLOUD_TEXTDOMAIN);
     2900            if ('missing_issued_document' === $sendDecision['reason']) {
     2901                $message = esc_html__('Missing issued document id, create the invoice first.', WP_FATT_CLOUD_TEXTDOMAIN);
     2902            } elseif ('missing_doc_id' === $sendDecision['reason']) {
     2903                $message = esc_html__('Missing document id parameter for send request.', WP_FATT_CLOUD_TEXTDOMAIN);
     2904            } elseif ('doc_id_mismatch' === $sendDecision['reason']) {
     2905                $message = esc_html__('Invalid document id for send request.', WP_FATT_CLOUD_TEXTDOMAIN);
     2906            } elseif ('already_sent_fic' === $sendDecision['reason'] || 'already_sent_meta' === $sendDecision['reason']) {
     2907                $message = esc_html__('The document has already been sent', WP_FATT_CLOUD_TEXTDOMAIN);
     2908            }
     2909
    28172910            wp_send_json(array(
    28182911                'title'   => __('Oops, something is wrong!', WP_FATT_CLOUD_TEXTDOMAIN),
    2819                 'message' => esc_html__('The document has already been sent', WP_FATT_CLOUD_TEXTDOMAIN),
    2820                 'code'    => 422,
     2912                'message' => $message,
     2913                'code'    => $sendError['code'],
    28212914            ));
    28222915            die();
    2823         } else {
    2824             return;
    2825         }
     2916        }
     2917
     2918        return;
     2919    }
     2920
     2921    $lockKey = \WcElectronInvoice\Functions\buildDbLockKey('fic', 'send_invoice', (int)$id, (string)$provider,
     2922        (string)$docID);
     2923    $lockDecision = \WcElectronInvoice\Functions\evaluateLockBusyDecision(
     2924        \WcElectronInvoice\Functions\acquireDbLock($lockKey, 0),
     2925        (bool)$jobEvent
     2926    );
     2927    if (! $lockDecision['continue']) {
     2928        if ($lockDecision['respond_error']) {
     2929            $lockError = \WcElectronInvoice\Functions\mapLockBusyError('fic', 'send_invoice');
     2930            wp_send_json(array(
     2931                'title'   => __('Oops, something is wrong!', WP_FATT_CLOUD_TEXTDOMAIN),
     2932                'message' => esc_html__('Action skipped: another send process is already running for this order.',
     2933                    WP_FATT_CLOUD_TEXTDOMAIN),
     2934                'code'    => $lockError['code'],
     2935            ));
     2936            die();
     2937        }
     2938
     2939        return;
    28262940    }
    28272941
     
    28402954
    28412955        $orderID = $order->get_id();
    2842         if ($sendEInvoice->valid()) {
     2956        if (\WcElectronInvoice\Functions\canMarkInvoiceSentWithFicSend((bool)$sendEInvoice->valid(), (int)$docID,
     2957                $alreadySentOnFic)) {
    28432958            // Log
    28442959            \WooPoPToFattureInCloud\Functions\log('sendInvoice Valid : ' . print_r($sendEInvoice->valid(), true));
     
    29893104
    29903105        if (! $jobEvent) {
     3106            \WcElectronInvoice\Functions\releaseDbLock($lockKey);
    29913107            wp_send_json($response);
    29923108            die();
    29933109        } else {
     3110            \WcElectronInvoice\Functions\releaseDbLock($lockKey);
    29943111            return true;
    29953112        }
     
    30363153        \WooPoPToFattureInCloud\Functions\log(print_r($dataException, true));
    30373154
     3155        \WcElectronInvoice\Functions\releaseDbLock($lockKey);
    30383156        wp_send_json($dataException);
    30393157        die();
  • woopop-electronic-invoice-free/trunk/addon/to/fattureincloud/src/Jobs.php

    r3370892 r3463326  
    516516        $data = getActiveApi();
    517517        if (! $data->active) {
     518            wp_clear_scheduled_hook('twiceDailyWooPoPToFattureInCloudJobsScheduledUpload');
    518519            return;
    519520        }
  • woopop-electronic-invoice-free/trunk/addon/to/fattureincloud/vendor/composer/installed.php

    r3460902 r3463326  
    44        'pretty_version' => 'dev-main',
    55        'version' => 'dev-main',
    6         'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     6        'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => 'dev-main',
    1515            'version' => 'dev-main',
    16             'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     16            'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
  • woopop-electronic-invoice-free/trunk/addon/to/sdi-pec/languages/popsdipec-it_IT.po

    r3253418 r3463326  
    44"Project-Id-Version: POP to SdI\n"
    55"POT-Creation-Date: 2025-03-08 10:54+0100\n"
    6 "PO-Revision-Date: 2025-03-08 10:54+0100\n"
     6"PO-Revision-Date: 2026-02-16 13:57+0100\n"
    77"Last-Translator: \n"
    88"Language-Team: \n"
     
    1212"Content-Transfer-Encoding: 8bit\n"
    1313"Plural-Forms: nplurals=2; plural=(n != 1);\n"
    14 "X-Generator: Poedit 3.5\n"
     14"X-Generator: Poedit 3.8\n"
    1515"X-Poedit-Basepath: ..\n"
    16 "X-Poedit-KeywordsList: __;_e;_n:1,2;__ngettext_noop:1,2;_n_noop:1,2;_c;"
    17 "_nc:4c,1,2;_x:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;_ex:1,2c;esc_attr__;esc_attr_e;"
    18 "esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n"
     16"X-Poedit-KeywordsList: "
     17"__;_e;_n:1,2;__ngettext_noop:1,2;_n_noop:1,2;_c;_nc:4c,1,2;_x:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;_ex:1,2c;esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n"
    1918"X-Poedit-SearchPathExcluded-3: assets\n"
    2019"X-Poedit-SourceCharset: UTF-8\n"
     
    5150msgstr "Azioni Fatture SdI PEC"
    5251
    53 #: inc/localizeScripts.php:40 src/Functions/Api.php:318
    54 #: src/Functions/Api.php:429 src/Functions/Api.php:465
     52#: inc/localizeScripts.php:40 src/Functions/Api.php:318 src/Functions/Api.php:429
     53#: src/Functions/Api.php:465
    5554msgid "Message:"
    5655msgstr "Messaggio:"
     
    9493#: src/Filters.php:104
    9594msgid ""
    96 "Set the expiration date for the password used for authentication. This field "
    97 "is optional. Make sure to create an Application Password required for two-"
    98 "factor authentication."
    99 msgstr ""
    100 "Imposta la data di scadenza per la password utilizzata per l’autenticazione. "
    101 "Questo campo è facoltativo. Assicurati di creare una password "
    102 "dell’applicazione richiesta per l’autenticazione a due fattori."
     95"Set the expiration date for the password used for authentication. This field is optional. "
     96"Make sure to create an Application Password required for two-factor authentication."
     97msgstr ""
     98"Imposta la data di scadenza per la password utilizzata per l’autenticazione. Questo campo è "
     99"facoltativo. Assicurati di creare una password dell’applicazione richiesta per "
     100"l’autenticazione a due fattori."
    103101
    104102#: src/Filters.php:106
     
    130128msgstr "Salvato. Credenziali non valide o configurazione errata."
    131129
    132 #: src/Functions/Api.php:193 src/Functions/Api.php:306
    133 #: src/Functions/Api.php:314 src/Functions/Api.php:355
    134 #: src/Functions/Api.php:425 src/Functions/Api.php:454
     130#: src/Functions/Api.php:193 src/Functions/Api.php:306 src/Functions/Api.php:314
     131#: src/Functions/Api.php:355 src/Functions/Api.php:425 src/Functions/Api.php:454
    135132#: src/Functions/Api.php:461
    136133msgid "Oops, something is wrong!"
     
    157154msgstr "Errore di richiesta: xml non valido"
    158155
    159 #: src/Functions/Api.php:315 src/Functions/Api.php:356
    160 #: src/Functions/Api.php:426 src/Functions/Api.php:462
     156#: src/Functions/Api.php:315 src/Functions/Api.php:356 src/Functions/Api.php:426
     157#: src/Functions/Api.php:462
    161158msgid "Order: "
    162159msgstr "Ordine: "
     
    164161#: src/Functions/Api.php:316 src/Functions/Api.php:427
    165162msgid ""
    166 "The invoice could not be created, we recommend that you check your order "
    167 "data and try again"
    168 msgstr ""
    169 "Impossibile creare la fattura, ti consigliamo di controllare i dati "
    170 "dell’ordine e riprovare"
     163"The invoice could not be created, we recommend that you check your order data and try again"
     164msgstr ""
     165"Impossibile creare la fattura, ti consigliamo di controllare i dati dell’ordine e riprovare"
    171166
    172167#: src/Functions/Api.php:319 src/Functions/Api.php:466
     
    179174
    180175#: src/Functions/Api.php:350
    181 msgid ""
    182 "You are in development mode set the constant WP_FATT_SDI_DEV_PEC_TO for "
    183 "testing"
    184 msgstr ""
    185 "Sei in modalità di sviluppo, impostare la costante WP_FATT_SDI_DEV_PEC_TO "
    186 "per i test"
     176msgid "You are in development mode set the constant WP_FATT_SDI_DEV_PEC_TO for testing"
     177msgstr "Sei in modalità di sviluppo, impostare la costante WP_FATT_SDI_DEV_PEC_TO per i test"
    187178
    188179#: src/Functions/Api.php:357
    189180#, php-format
    190181msgid ""
    191 "The invoice could not be send because sender or receiver is not a valid "
    192 "email addresses: %s -> %s"
    193 msgstr ""
    194 "Non è stato possibile inviare la fattura perché il mittente o il "
    195 "destinatario non sono indirizzi email validi: %s -> %s"
     182"The invoice could not be send because sender or receiver is not a valid email addresses: %s "
     183"-> %s"
     184msgstr ""
     185"Non è stato possibile inviare la fattura perché il mittente o il destinatario non sono "
     186"indirizzi email validi: %s -> %s"
    196187
    197188#: src/Functions/Api.php:398
     
    223214#: src/Functions/Api.php:463
    224215msgid "The invoice could not be sent because PhpMailer throws an exception"
    225 msgstr ""
    226 "Non è stato possibile inviare la fattura perché PhpMailer genera un’eccezione"
     216msgstr "Non è stato possibile inviare la fattura perché PhpMailer genera un’eccezione"
    227217
    228218#: src/Functions/Api.php:515
     
    232222#: src/Functions/Utils.php:359
    233223msgid "WooPOP Addon: You do not have the permissions to manage the add-on"
    234 msgstr ""
    235 "WooPOP Addon: Non si dispone dei permessi per gestire il componente "
    236 "aggiuntivo"
     224msgstr "WooPOP Addon: Non si dispone dei permessi per gestire il componente aggiuntivo"
    237225
    238226#: src/Functions/Utils.php:368
     
    262250#: src/Functions/Utils.php:415
    263251msgid ""
    264 "Enable automatic sending of invoices for completed orders not sent in the "
    265 "last 7 days. (work 2 times a day)"
    266 msgstr ""
    267 "Abilita l'invio automatico delle fatture per gli ordini completati e non "
    268 "inviati negli ultimi 7 giorni. (funziona 2 volte al giorno)"
     252"Enable automatic sending of invoices for completed orders not sent in the last 7 days. (work "
     253"2 times a day)"
     254msgstr ""
     255"Abilita l'invio automatico delle fatture per gli ordini completati e non inviati negli ultimi "
     256"7 giorni. (funziona 2 volte al giorno)"
    269257
    270258#: src/Functions/Utils.php:421
     
    277265
    278266#: src/Functions/Utils.php:428
    279 msgid ""
    280 "Se hai ricevuto un indirizzo email destintario differente, puoi sostituirlo "
    281 "qui"
     267msgid "Se hai ricevuto un indirizzo email destintario differente, puoi sostituirlo qui"
    282268msgstr ""
    283269
     
    288274#: src/Functions/Utils.php:435
    289275msgid "Set the email with the constant WP_FATT_SDI_DEV_PEC_TO"
    290 msgstr ""
    291 "Sei in modalità di sviluppo, impostare la costante WP_FATT_SDI_DEV_PEC_TO "
    292 "per i test"
     276msgstr "Sei in modalità di sviluppo, impostare la costante WP_FATT_SDI_DEV_PEC_TO per i test"
    293277
    294278#: src/Functions/Utils.php:441
     
    322306#: src/Functions/Utils.php:480
    323307msgid "Enter this code in wp-config.php to activate the event log."
    324 msgstr ""
    325 "Inserisci questo codice nel wp-config.php per attivare il registro degli "
    326 "eventi."
     308msgstr "Inserisci questo codice nel wp-config.php per attivare il registro degli eventi."
    327309
    328310#: src/Functions/Utils.php:490
     
    366348#, php-format
    367349msgid ""
    368 "This is a reminder that your password will expire on %s. Please update it to "
    369 "avoid interruptions in the PEC invoice sending system."
    370 msgstr ""
    371 "Questo è un promemoria che la tua password scadrà il %s. Aggiornala per "
    372 "evitare interruzioni nel sistema di invio delle fatture PEC."
     350"This is a reminder that your password will expire on %s. Please update it to avoid "
     351"interruptions in the PEC invoice sending system."
     352msgstr ""
     353"Questo è un promemoria che la tua password scadrà il %s. Aggiornala per evitare interruzioni "
     354"nel sistema di invio delle fatture PEC."
    373355
    374356#: src/Functions/Utils.php:738
    375 msgid ""
    376 "If your password expires, the system for sending invoices via PEC will stop "
    377 "functioning."
    378 msgstr ""
    379 "Se la tua password scade, il sistema di invio delle fatture tramite PEC "
    380 "smetterà di funzionare."
     357msgid "If your password expires, the system for sending invoices via PEC will stop functioning."
     358msgstr ""
     359"Se la tua password scade, il sistema di invio delle fatture tramite PEC smetterà di "
     360"funzionare."
    381361
    382362#: src/Functions/Utils.php:740
     
    387367msgid "Cheatin&#8217; huh?"
    388368msgstr "Vuoi imbrogliare eh?"
     369
     370msgid "Action skipped: another upload process is already running for this order."
     371msgstr "Azione saltata: un altro processo di caricamento è già in esecuzione per questo ordine."
     372
     373msgid "Invoice send error: empty invoice file name!"
     374msgstr "Errore invio fattura: nome file fattura vuoto!"
  • woopop-electronic-invoice-free/trunk/addon/to/sdi-pec/src/Functions/Api.php

    r3366302 r3463326  
    221221    $checkSent        = \WcElectronInvoice\Functions\getPostMeta('_invoice_sent', null, $orderID, true, 'order',
    222222        $provider);
    223     if ('on' === $scheduledCronJob && 'feCronJobUploadRequest' === $ajaxAction && 'sent' === $checkSent) {
     223    if ('on' === $scheduledCronJob &&
     224        \WcElectronInvoice\Functions\isBackgroundExecutionMode($ajaxAction, ['feCronJobUploadRequest']) &&
     225        'sent' === $checkSent
     226    ) {
    224227        return;
    225228    }
     
    239242        )
    240243    ) {
    241         if ('feCronJobUploadRequest' !== $ajaxAction) {
     244        if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($ajaxAction,
     245                ['feCronJobUploadRequest'])) {
    242246            wp_send_json(array(
    243247                'title'   => __('Payment method excluded for sending.', WP_FATT_SDI_PEC_TEXTDOMAIN),
     
    284288        $isXmlFromRemoTe = false;
    285289        $validXML        = false;
    286         if ('feCronJobUploadRequest' !== $ajaxAction) {
     290        if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($ajaxAction,
     291                ['feCronJobUploadRequest'])) {
    287292            $url = esc_url_raw(add_query_arg([
    288293                CreateXml::LIST_TYPE => $orderID,
     
    313318        // Not valid ?
    314319        if (! $isXml->valid) {
     320            $uploadError = \WcElectronInvoice\Functions\mapUploadPreconditionError('sdi_pec', 'xml_invalid');
    315321            $noXmlResponse = array(
    316322                'title'   => __('Oops, something is wrong!', WP_FATT_SDI_PEC_TEXTDOMAIN),
     
    318324                'xml'     => $isXml->xml,
    319325                'message' => __('Request error: xml not valid', WP_FATT_SDI_PEC_TEXTDOMAIN),
    320                 'code'    => 501,
     326                'code'    => $uploadError['code'],
    321327            );
    322328
     
    351357            ! \PHPMailer\PHPMailer\PHPMailer::validateAddress($smtpPecTo)
    352358        ) {
     359            $uploadError = \WcElectronInvoice\Functions\mapUploadPreconditionError('sdi_pec',
     360                'invalid_email_recipient');
    353361            $noSentResponse = array(
    354362                'message' => __('Sender or recipient not valid!', WP_FATT_SDI_PEC_TEXTDOMAIN),
    355363                'orderID' => $orderID,
    356                 'code'    => 501,
     364                'code'    => $uploadError['code'],
    357365            );
    358366
     
    380388        }
    381389
     390        $lockKey = \WcElectronInvoice\Functions\buildDbLockKey('sdi_pec', 'upload_invoice', (int)$orderID,
     391            (string)$provider, (string)$choiceType);
     392        $lockDecision = \WcElectronInvoice\Functions\evaluateLockBusyDecision(
     393            \WcElectronInvoice\Functions\acquireDbLock($lockKey, 0),
     394            \WcElectronInvoice\Functions\isBackgroundExecutionMode($ajaxAction, ['feCronJobUploadRequest'])
     395        );
     396        if (! $lockDecision['continue']) {
     397            if ($lockDecision['respond_error']) {
     398                $lockError = \WcElectronInvoice\Functions\mapLockBusyError('sdi_pec', 'upload_invoice');
     399                wp_send_json(array(
     400                    'message' => __('Action skipped: another upload process is already running for this order.',
     401                        WP_FATT_SDI_PEC_TEXTDOMAIN),
     402                    'orderID' => $orderID,
     403                    'code'    => $lockError['code'],
     404                ));
     405                die();
     406            }
     407
     408            return;
     409        }
     410
    382411        try {
    383412            $mail          = new \PHPMailer\PHPMailer\PHPMailer(true);
     
    427456
    428457            if (! $mail->send()) {
     458                $uploadError = \WcElectronInvoice\Functions\mapUploadPreconditionError('sdi_pec', 'upload_failed');
    429459                $noSentResponse = array(
    430460                    'message' => __('Invoice send error!', WP_FATT_SDI_PEC_TEXTDOMAIN),
    431461                    'orderID' => $orderID,
    432                     'code'    => 501,
     462                    'code'    => $uploadError['code'],
    433463                );
    434464
     
    445475                        true));
    446476
     477                \WcElectronInvoice\Functions\releaseDbLock($lockKey);
    447478                wp_send_json($noSentResponse);
    448479                die();
    449480            }
    450481
    451             $order->update_meta('_invoice_sent', 'sent');
    452             $order->save();
    453 
    454             $responseArgs = array(
    455                 'message' => sprintf('%s:%s - %s',
    456                     __('Invoice', WP_FATT_SDI_PEC_TEXTDOMAIN),
    457                     $fileName,
    458                     __('Invoice sent successfully!', WP_FATT_SDI_PEC_TEXTDOMAIN)
    459                 ),
    460                 'orderID' => $orderID,
    461                 'code'    => 200,
    462             );
     482            if (\WcElectronInvoice\Functions\canMarkInvoiceSentWithUploadFileName($fileName)) {
     483                $order->update_meta('_invoice_sent', 'sent');
     484                $order->save();
     485
     486                $responseArgs = array(
     487                    'message' => sprintf('%s:%s - %s',
     488                        __('Invoice', WP_FATT_SDI_PEC_TEXTDOMAIN),
     489                        $fileName,
     490                        __('Invoice sent successfully!', WP_FATT_SDI_PEC_TEXTDOMAIN)
     491                    ),
     492                    'orderID' => $orderID,
     493                    'code'    => 200,
     494                );
     495            } else {
     496                $uploadError = \WcElectronInvoice\Functions\mapUploadPreconditionError('sdi_pec',
     497                    'empty_upload_filename');
     498                $responseArgs = array(
     499                    'message' => __('Invoice send error: empty invoice file name!', WP_FATT_SDI_PEC_TEXTDOMAIN),
     500                    'orderID' => $orderID,
     501                    'code'    => $uploadError['code'],
     502                );
     503            }
    463504        } catch (\Exception $e) {
     505            $uploadError = \WcElectronInvoice\Functions\mapUploadPreconditionError('sdi_pec', 'upload_failed');
    464506            $noSentResponse = array(
    465507                'title'   => __('Oops, something is wrong!', WP_FATT_SDI_PEC_TEXTDOMAIN),
    466508                'errors'  => [$e->getMessage()],
    467509                'message' => __('Request error: invoice could not be sent', WP_FATT_SDI_PEC_TEXTDOMAIN),
    468                 'code'    => 501,
     510                'code'    => $uploadError['code'],
    469511            );
    470512
     
    481523            \WooPoPToSdIPec\Functions\log(print_r($noSentResponse, true));
    482524
     525            \WcElectronInvoice\Functions\releaseDbLock($lockKey);
    483526            wp_send_json($noSentResponse);
    484527            die();
     
    509552        // Cron Job return data
    510553        if ('on' === $scheduledCronJob &&
    511             'feCronJobUploadRequest' === $ajaxAction &&
     554            \WcElectronInvoice\Functions\isBackgroundExecutionMode($ajaxAction, ['feCronJobUploadRequest']) &&
    512555            'sent' !== $checkSent
    513556        ) {
     557            \WcElectronInvoice\Functions\releaseDbLock($lockKey);
    514558            return (object)array(
    515559                'fileName' => $fileName,
     
    518562        }
    519563
     564        \WcElectronInvoice\Functions\releaseDbLock($lockKey);
    520565        wp_send_json($responseArgs);
    521566        die();
  • woopop-electronic-invoice-free/trunk/addon/to/sdi-pec/src/Jobs.php

    r3370892 r3463326  
    276276        $data = getActiveApi();
    277277        if (! $data->smtpConnected) {
     278            wp_clear_scheduled_hook('twiceDailyWooPoPToSdIPecJobsScheduledUpload');
    278279            return;
    279280        }
     
    343344                }
    344345            });
     346        } else {
     347            wp_clear_scheduled_hook('twiceDailyWooPoPToSdIPecJobsScheduledUpload');
    345348        }
    346349    }
  • woopop-electronic-invoice-free/trunk/addon/to/sdi-pec/vendor/composer/installed.php

    r3460902 r3463326  
    44        'pretty_version' => 'dev-main',
    55        'version' => 'dev-main',
    6         'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     6        'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => 'dev-main',
    1515            'version' => 'dev-main',
    16             'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     16            'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
  • woopop-electronic-invoice-free/trunk/assets/js/admin.js

    r3444920 r3463326  
    15681568            });
    15691569
     1570            /**
     1571             * Native timeout pending actions - confirm UUID
     1572             */
     1573            document.querySelectorAll('.sdi_pop_actions a.api-action.confirm-timeout-uuid, .peppol_pop_actions a.api-action.confirm-timeout-uuid').forEach(function (btn) {
     1574                btn.addEventListener('click', function (e) {
     1575                    e.preventDefault();
     1576                    e.stopImmediatePropagation();
     1577
     1578                    const provider = btn.dataset.provider;
     1579                    const id = btn.dataset.id;
     1580                    const channel = btn.dataset.channel;
     1581                    const documentType = btn.dataset.documentType || 'invoice';
     1582                    const wrapper = btn.closest('.sdi_pop_actions, .peppol_pop_actions');
     1583                    const uuidInput = wrapper ? wrapper.querySelector('.native-timeout-uuid-input') : null;
     1584                    const uuid = uuidInput ? uuidInput.value.trim() : '';
     1585
     1586                    if (!uuid) {
     1587                        showToastAndReload(wc_el_inv_admin.timeout_uuid_required, null, 'error');
     1588                        return;
     1589                    }
     1590
     1591                    this.setAttribute('disabled', 'disabled');
     1592
     1593                    let loader = document.getElementById('loading-overlay');
     1594                    if (!loader) {
     1595                        loader = document.createElement('span');
     1596                        loader.id = 'loading-overlay';
     1597                        document.body.appendChild(loader);
     1598                    }
     1599                    loader.classList.remove('hidden');
     1600                    loader.classList.add('loading');
     1601
     1602                    fetch(ajaxurl, {
     1603                        method: 'POST',
     1604                        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
     1605                        body: new URLSearchParams({
     1606                            action: 'confirmNativeSendWithUuid',
     1607                            provider: provider,
     1608                            id: id,
     1609                            channel: channel,
     1610                            document_type: documentType,
     1611                            uuid: uuid
     1612                        })
     1613                    })
     1614                        .then(response => response.json())
     1615                        .then(data => {
     1616                            if ('dev' === wc_el_inv_admin.mode) {
     1617                                console.log('Response:', data);
     1618                            }
     1619                            showToastAndReload(data.data.message, data.data.raw, data.success ? 'success' : 'error', 6000);
     1620                        })
     1621                        .catch(error => {
     1622                            console.error('AJAX error:', error);
     1623                            showToastAndReload(error.message, null, 'error');
     1624                        }).finally(() => {
     1625                        this.removeAttribute('disabled');
     1626                        loader.classList.remove('loading');
     1627                        loader.classList.add('hidden');
     1628                    });
     1629                });
     1630            });
     1631
     1632            /**
     1633             * Native timeout pending actions - cancel timeout
     1634             */
     1635            document.querySelectorAll('.sdi_pop_actions a.api-action.cancel-timeout-pending, .peppol_pop_actions a.api-action.cancel-timeout-pending').forEach(function (btn) {
     1636                btn.addEventListener('click', function (e) {
     1637                    e.preventDefault();
     1638                    e.stopImmediatePropagation();
     1639
     1640                    if (!window.confirm(wc_el_inv_admin.timeout_cancel_confirm)) {
     1641                        return;
     1642                    }
     1643
     1644                    const provider = btn.dataset.provider;
     1645                    const id = btn.dataset.id;
     1646                    const channel = btn.dataset.channel;
     1647
     1648                    this.setAttribute('disabled', 'disabled');
     1649
     1650                    let loader = document.getElementById('loading-overlay');
     1651                    if (!loader) {
     1652                        loader = document.createElement('span');
     1653                        loader.id = 'loading-overlay';
     1654                        document.body.appendChild(loader);
     1655                    }
     1656                    loader.classList.remove('hidden');
     1657                    loader.classList.add('loading');
     1658
     1659                    fetch(ajaxurl, {
     1660                        method: 'POST',
     1661                        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
     1662                        body: new URLSearchParams({
     1663                            action: 'cancelNativeTimeoutPending',
     1664                            provider: provider,
     1665                            id: id,
     1666                            channel: channel
     1667                        })
     1668                    })
     1669                        .then(response => response.json())
     1670                        .then(data => {
     1671                            if ('dev' === wc_el_inv_admin.mode) {
     1672                                console.log('Response:', data);
     1673                            }
     1674                            showToastAndReload(data.data.message, data.data.raw, data.success ? 'success' : 'error', 6000);
     1675                        })
     1676                        .catch(error => {
     1677                            console.error('AJAX error:', error);
     1678                            showToastAndReload(error.message, null, 'error');
     1679                        }).finally(() => {
     1680                        this.removeAttribute('disabled');
     1681                        loader.classList.remove('loading');
     1682                        loader.classList.add('hidden');
     1683                    });
     1684                });
     1685            });
     1686
    15701687        });
    15711688
  • woopop-electronic-invoice-free/trunk/assets/js/admin.min.js

    r3444920 r3463326  
    2424 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
    2525 */
    26 !function(e,t,n){function o(e,t,n){var o=document.createElement("a");o.setAttribute("id",e),o.setAttribute("href","javascript:;"),o.innerHTML='<i class="dashicons dashicons-edit"></i>',t.insertAdjacentElement(n,o)}function i(e,t,o){var i=document.createElement("button");i.setAttribute("id",e),i.setAttribute("name",e),i.setAttribute("class","button"),i.innerText=n.text_save,t.insertAdjacentElement(o,i)}function a(e,t,n){var o=document.createElement("a");o.setAttribute("id",e),o.setAttribute("href","javascript:;"),o.innerHTML='<i class="dashicons dashicons-no"></i>',t.insertAdjacentElement("beforeend",o)}function r(e){var t=e.split(/[^0-9]/),n=t[0]+"-"+t[1]+"-"+t[2]+"T"+t[3]+":"+t[4]+":"+t[5]+"+0000";return new Date(n.toString()).getTime()/1e3}function d(e){var t,n=null,o=null,i=null,a=document.getElementById("date_in"),d=document.getElementById("date_out"),c=document.getElementById("filter_type"),s=document.getElementById("filter_state"),l=document.getElementById("filter_provider");if(c&&(n=c.options[c.selectedIndex].value),s&&(o=s.options[s.selectedIndex].value),l&&(i=l.options[l.selectedIndex].value),a.value||d.value){var u=a.value+"T00:00:00",_=d.value+"T23:59:59";u=u.split(" - ").map((function(e){return r(e)})).join(" - "),_=_.split(" - ").map((function(e){return r(e)})).join(" - "),a.value&&""===d.value&&(t=e+"&date_in="+u),""===a.value&&d.value&&(t=e+"&date_out="+_),a.value&&d.value&&(t=e+"&date_in="+u+"&date_out="+_)}else t=e;n&&(t=t+"&filter_type="+n),o&&(t=t+"&filter_state="+o),i&&(t=t+"&filter_provider="+i);if(e&&e.indexOf("filtered")>-1){var m={};if(e.replace(/[?&]+([^=&]+)=([^&]*)/gi,(function(e,t,n){m[t]=n})),"no"===m.filtered)return e}return t}function c(e,t){var n,o=t;return window.location.href.indexOf(e)>-1&&(o=(n={},window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi,(function(e,t,o){n[t]=o})),n)[e]),o}function s(e,t,o,i){i||(i=4e3),"dev"===n.mode&&(console.log("message",e),console.log("raw",t));const a=document.createElement("div");a.className="toast "+o;let r="";"error"===o&&(r+='<span class="close" title="close">X</span>'),r+=e;let d="";t?.response?.data?.violations?t.response.data.violations.forEach((e=>{d+="<hr><b>",d+=`- ${n.sdi_notice_path}: ${e.propertyPath}\n`,d+="</b>",d+=`- ${n.sdi_notice_content}: ${e.propertyContent}\n`,d+=`- ${n.sdi_notice_message}: ${e.message}\n`})):t?.detail?.detail?d+=`- ${t.detail.detail}\n`:t?.error&&(d+=`- ${t.error}\n`),d&&(r+="<small>",r+="\n"+n.sdi_notice_details+":\n",r+=d,r+="</small>"),a.innerHTML=r.replace(/\n/g,"<br>"),window.requestAnimationFrame((()=>{document.body.appendChild(a),a.style.opacity="1","error"===o?a.querySelector(".close").addEventListener("click",(function(){a.remove(),location.reload()})):setTimeout((()=>{a.remove(),"success"!==o&&"notify"!==o||location.reload()}),i)}))}document.addEventListener("scroll",(function(){const e=document.querySelector(".wc_el_inv__header");e&&(window.scrollY>20?e.classList.add("is-scrolled"):e.classList.remove("is-scrolled"))})),window.addEventListener("load",(function(){var r=t("#store_invoice-description");r&&t(r).prev().addClass("store_invoice_title");var s,l,u,_,m,v,p,f,y,b,h,g,w,E,A=t("#store_invoice_transmitter-description");A&&t(A).prev().addClass("store_invoice_transmitter_title");t("a.edit_address").on("click",(function(e){t(".order_data_column > p").addClass("hide")})),s=c("tab"),(l=document.querySelectorAll("#toplevel_page_wc_el_inv-options-page ul.wp-submenu li a"))&&l.forEach((function(e){e.parentElement.classList.contains("wp-first-item")&&e.parentElement.remove(),e.parentElement.classList.remove("current"),-1!==e.getAttribute("href").indexOf(s)&&e.parentElement.classList.add("current")})),function(){var e=document.getElementById("wc_el_inv-settings-number_next_invoice");if(e){""!==e.value&&e.setAttribute("disabled","disabled"),o("edit_invoice_next_number",e,"afterend");var t=document.getElementById("edit_invoice_next_number");t.addEventListener("click",(function(){t.hasAttribute("readonly")||(t.style.display="none",e.removeAttribute("disabled"),i("save_invoice_next_number",e,"afterend"))}))}}(),function(){var e=document.getElementById("wc_el_inv-settings-number_next_receipt");if(e){""!==e.value&&e.setAttribute("disabled","disabled"),o("edit_receipt_next_number",e,"afterend");var t=document.getElementById("edit_receipt_next_number");t.addEventListener("click",(function(){t.hasAttribute("readonly")||(t.style.display="none",e.removeAttribute("disabled"),i("save_receipt_next_number",e,"afterend"))}))}}(),function(){var e=document.getElementById("wc_el_inv-settings-number_next_credit_note");if(e){""!==e.value&&e.setAttribute("disabled","disabled"),o("edit_credit_note_next_number",e,"afterend");var t=document.getElementById("edit_credit_note_next_number");t.addEventListener("click",(function(){t.hasAttribute("readonly")||(t.style.display="none",e.removeAttribute("disabled"),i("save_credit_note_next_number",e,"afterend"))}));var n=document.getElementById("wc_el_inv-settings-number_next_credit_note_receipt");if(n){""!==n.value&&n.setAttribute("disabled","disabled"),o("edit_credit_note_receipt_next_number",n,"afterend");var a=document.getElementById("edit_credit_note_receipt_next_number");a.addEventListener("click",(function(){t.hasAttribute("readonly")||(a.style.display="none",n.removeAttribute("disabled"),i("save_credit_note_receipt_next_number",n,"afterend"))}))}}}(),function(){var t=document.querySelectorAll(".wc_el_inv-order_fields");if(0!==t.length){e.forEach(t,(function(e){""!==e.value&&e.setAttribute("disabled","disabled")}));var n=document.querySelector(".wc_el_inv__general-order h3"),i=document.querySelector(".wc_el_inv__general-order--hidden-fields"),r=document.querySelector(".wc_el_inv__general-order--text-data");if(n&&i&&r){o("edit_invoice_next_number",n,"beforeend");var d=document.getElementById("edit_invoice_next_number");a("close_invoice_next_number",n);var c=document.getElementById("close_invoice_next_number");c.style.display="none",d.addEventListener("click",(function(){i.style.display="block",r.style.display="none",this.style.display="none",c.style.display="block",e.forEach(t,(function(e){e.removeAttribute("disabled")}))})),c.addEventListener("click",(function(){i.style.display="none",r.style.display="block",c.style.display="none",d.style.display="",e.forEach(t,(function(e){e.setAttribute("disabled","disabled")}))}))}}}(),u=document.querySelectorAll(".wc_el_inv__refund-invoice[data-order_refund_id]"),0!==(_=document.querySelectorAll(".wc_el_inv-order_fields")).length&&(e.forEach(_,(function(e){""!==e.value&&e.setAttribute("disabled","disabled")})),e.forEach(u,(function(t,n){var i=t.querySelector(".wc_el_inv__refund-invoice td h3"),r=t.querySelector(".wc_el_inv__refund-invoice--hidden-fields"),d=t.querySelector(".wc_el_inv__refund-invoice--text-data");if(i&&r&&d){o("edit_refund_invoice_next_number-"+n,i,"beforeend");var c=document.getElementById("edit_refund_invoice_next_number-"+n);a("close_refund_invoice_next_number-"+n,i);var s=document.getElementById("close_refund_invoice_next_number-"+n);s.style.display="none",s.addEventListener("click",(function(){r.style.display="none",d.style.display="block",s.style.display="none",c.style.display="",e.forEach(_,(function(e){e.setAttribute("disabled","disabled")}))})),c.addEventListener("click",(function(){r.style.display="block",d.style.display="none",this.style.display="none",s.style.display="block",e.forEach(_,(function(e){e.removeAttribute("disabled")}))}))}}))),function(){var t=[document.querySelector(".save-all-csv")];if([]!==t){var n=document.querySelector(".filter");if(n){var o=n.getAttribute("href");n.addEventListener("click",(function(e){e.preventDefault(),e.stopImmediatePropagation();var t=d(o);t&&(window.location=t)}))}t&&e.forEach(t,(function(e){e&&e.addEventListener("click",(function(e){e.preventDefault(),e.stopImmediatePropagation();var t=d(e.target.href);window.open(t,"_blank")}))}))}}(),m=document.querySelector("#woocommerce-order-items .inside .refund-items"),(v=document.querySelectorAll("#order_refunds .actions .mark_trigger"))&&e.forEach(v,(function(e){e.classList.contains("mark_as_sent")&&m&&(m.setAttribute("disabled",!0),m.innerText=n.refund_item_disabled_text)})),p=document.querySelector("#woocommerce-order-items .inside input#refund_amount"),f=document.querySelector(".wc_el_inv__general-order .actions .mark_trigger"),p&&f&&f.classList.contains("mark_as_sent")&&(p.setAttribute("readonly",!0),p.insertAdjacentHTML("afterend",'<p id="readonly-info">'+n.refund_amount_read_only_info_text+"</p>")),y=document.getElementById("wc_el_inv_order_search"),b=document.querySelector(".wc_el_inv_order_search_trigger"),y&&b&&(y.addEventListener("change",(function(e){e.preventDefault(),e.stopImmediatePropagation(),"dev"===n.mode&&console.log("search:",y.value),""!==y.value?window.location=b.href+"&order_search="+y.value:window.location=b.href+"&order_search"})),b.addEventListener("click",(function(e){e.preventDefault(),e.stopImmediatePropagation(),"dev"===n.mode&&console.log("search:",y.value),""===y.value?alert(n.search_by_id):window.location=b.href+"&order_search="+y.value}))),(h=document.getElementById("action_bulk"))&&h.addEventListener("change",(function(t){var o=t.target.value,i=document.querySelectorAll('input[name="pop-invoice[]"]'),a=!1;if(e.forEach(i,(function(e){!0===e.checked&&(a=e.checked)})),a||""===o){var r=!1;if("sent"===o?window.confirm(n.invoice_sent_confirm)&&(r=!0):"no_sent"===o&&window.confirm(n.invoice_undo_confirm)&&(r=!0),r){var d=document.getElementById("wp-list-table-invoice-form");if(d){var c=document.createElement("input");c.setAttribute("type","hidden"),c.setAttribute("id","bulk-sent"),c.setAttribute("name","bulk-sent"),c.setAttribute("value",o),d.appendChild(c),d.submit()}}}else alert(n.bulk_invoice_cb)})),(g=document.querySelectorAll(".doc-type-input"))&&e.forEach(g,(function(e){e.addEventListener("change",(function(e){this.parentElement.parentElement.parentElement.querySelectorAll(".choice_type--current")[0].setAttribute("value",this.value)}))})),(w=document.querySelectorAll(".action-endpoint"))&&e.forEach(w,(function(e){e.addEventListener("click",(function(e){e.preventDefault(),e.stopImmediatePropagation();var t=this.parentElement.querySelector(".choice_type--current"),n=this.href;t&&(n=n+"&choice_type="+t.value),window.open(n)}))})),function(){if("undefined"!=typeof inlineEditPost){var e=inlineEditPost.edit;inlineEditPost.edit=function(n){e.apply(this,arguments);var o=0;if("object"==typeof n&&(o=parseInt(this.getId(n))),o>0){var i=t("#edit-"+o),a=t("#post-"+o),r=!!t(".column-reverse_charge > *",a).prop("checked");t(':input[name="active_reverse_charge"]',i).prop("checked",r)}}}}(),function(){var e=document.querySelectorAll(".nav_section_advanced#wc_output_fields_nav ul li a");function t(t){e.forEach((function(e){e.getAttribute("href")==="#"+t?e.classList.add("active"):e.classList.remove("active")}));var n=document.querySelector(".wc_el_inv-form .form-table tr.import_collections"),o=document.querySelector(".wc_el_inv-form .form-table tr.export_collections"),i=document.querySelector(".wc_el_inv-form .form-table tr.reverse_charge"),a=document.querySelector(".wc_el_inv-form .form-table tr.automatic_sending"),r=document.querySelector(".wc_el_inv-form .form-table tr.pop_webhook");"reverse-charge"===t&&(o&&(o.style.display="none"),n&&(n.style.display="none"),i&&(i.style.display="table-row"),a&&(a.style.display="none"),r&&(r.style.display="none")),"import-export"===t&&(o&&(o.style.display="table-row"),n&&(n.style.display="table-row"),i&&(i.style.display="none"),a&&(a.style.display="none"),r&&(r.style.display="none")),"automatic-sending"===t&&(o&&(o.style.display="none"),n&&(n.style.display="none"),i&&(i.style.display="none"),a&&(a.style.display="table-row"),r&&(r.style.display="none")),"webhook"===t&&(o&&(o.style.display="none"),n&&(n.style.display="none"),i&&(i.style.display="none"),a&&(a.style.display="none"),r&&(r.style.display="table-row"))}e&&("free"===n.user_level?t("automatic-sending"):"IT"!==n.shop_country?t("webhook"):"#reverse-charge"!==window.location.hash&&window.location.hash?"#import-export"===window.location.hash?t("import-export"):"#automatic-sending"===window.location.hash?t("automatic-sending"):"#webhook"===window.location.hash&&t("webhook"):t("reverse-charge"),e.forEach((function(n){n.addEventListener("click",(function(o){e.forEach((function(e){e.classList.remove("active")})),n.classList.add("active"),t(o.target.hash.replace("#",""))}))})))}(),(E=document.querySelectorAll(".wc_el_inv-fic-addon.current-setting_section_numeration .form-table .number_digits input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .reset_numerations input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .suffix_invoice input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .suffix_year input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .suffix_year_format input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .number_next_invoice input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .number_next_receipt input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .numeration_credit_note input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .number_next_credit_note input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .numeration_credit_note_receipt input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .number_next_credit_note_receipt input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table #edit_invoice_next_number,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table #edit_receipt_next_number,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table #edit_credit_note_next_number,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table #edit_credit_note_receipt_next_number"))&&E.forEach((function(e){e.setAttribute("disabled","disabled"),e.setAttribute("readonly","readonly")})),function(){var e=document.querySelectorAll(".rc-nature-select");function t(e,t){var o=e.options[e.selectedIndex].text.split(" - "),i=2===o.length?o[0]:"",a=2===o.length?o[1]:"",r=null;("N7"===i&&(a="IVA assolta in altro stato UE (vendite a distanza ex art. 40 c. 3 e 4 e art. 41 c. 1 lett. b, DL 331/93; prestazione di servizi di telecomunicazioni, teleradiodiffusione ed elettronici ex art. 7-sexies lett. f, g, art. 74-sexies DPR 633/72)"),"wc_el_inv-ue_private_nature_code"===e.getAttribute("id")?(r=document.getElementById("wc_el_inv-ue_private_normative_reference"),t&&("Personalizzato"===a?(r.setAttribute("value",""),r.value=""):(r.setAttribute("value",a),r.value=a))):"wc_el_inv-ue_company_nature_code"===e.getAttribute("id")?(r=document.getElementById("wc_el_inv-ue_company_normative_reference"),t&&("Personalizzato"===a?(r.setAttribute("value",""),r.value=""):(r.setAttribute("value",a),r.value=a))):"wc_el_inv-ue_company_no_vies_nature_code"===e.getAttribute("id")?(r=document.getElementById("wc_el_inv-ue_company_no_vies_normative_reference"),t&&("Personalizzato"===a?(r.setAttribute("value",""),r.value=""):(r.setAttribute("value",a),r.value=a))):"wc_el_inv-extra_ue_private_nature_code"===e.getAttribute("id")?(r=document.getElementById("wc_el_inv-extra_ue_private_normative_reference"),t&&("Personalizzato"===a?(r.setAttribute("value",""),r.value=""):(r.setAttribute("value",a),r.value=a))):"wc_el_inv-extra_ue_company_nature_code"===e.getAttribute("id")&&(r=document.getElementById("wc_el_inv-extra_ue_company_normative_reference"),t&&("Personalizzato"===a?(r.setAttribute("value",""),r.value=""):(r.setAttribute("value",a),r.value=a))),r)&&(r.closest("tr").style.display="Personalizzato"===a?"table-row":"none");"dev"===n.mode&&console.log("CODE:",i,"REF:",a)}e.length>0&&e.forEach((function(e){t(e,!1),e.addEventListener("change",(function(n){t(e,!0)}))}))}(),t(document).ajaxComplete((function(){var e=document.querySelector("#woocommerce-order-items .inside input#refund_amount"),t=document.querySelector(".wc_el_inv__general-order .actions .mark_trigger");e&&t&&(!e.hasAttribute("readonly")&&t.classList.contains("mark_as_sent")?(e.setAttribute("readonly",!0),e.insertAdjacentHTML("afterend",'<p id="readonly-info">'+n.refund_amount_read_only_info_text+"</p>")):e.hasAttribute("readonly")&&!t.classList.contains("mark_as_sent")&&e.removeAttribute("readonly"))}))})),document.body.classList.add("disable-clicks"),document.addEventListener("DOMContentLoaded",(function(){var o;(o=document.querySelectorAll(".mark_trigger"))&&e.forEach(o,(function(e){e.addEventListener("click",(function(e){e.preventDefault(),e.stopImmediatePropagation();var o=!1;this.classList.contains("mark_as_sent")?window.confirm(n.invoice_sent_confirm)&&(o=!0):this.classList.contains("mark_undo")&&window.confirm(n.invoice_undo_confirm)&&(o=!0),o&&t.ajax({url:n.ajax_url,method:"POST",cache:!1,data:{action:"markInvoice",action_url:this.href,nonce:n.ajax_nonce},beforeSend:function(){}.bind(this),complete:function(e,t){"dev"===n.mode&&console.log(e,t)}.bind(this),error:function(e,t,n){console.warn("markInvoice "+n,t)}.bind(this),success:function(e){console.log(e),window.location.reload()}.bind(this)})}))})),document.body.classList.remove("disable-clicks")})),document.addEventListener("DOMContentLoaded",(function(){document.querySelectorAll(".sdi_pop_actions a.api-action.create").forEach((function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopImmediatePropagation();const o=e.closest(".sdi_pop_actions").querySelector("a.api-action");if(!o)return;const i=o.dataset.provider,a=o.dataset.id,r=o.dataset.uuid;this.setAttribute("disabled","disabled");let d=document.getElementById("loading-overlay");d||(d=document.createElement("span"),d.id="loading-overlay",document.body.appendChild(d)),d.classList.remove("hidden"),d.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"createAndSendInvoice",method:"ajax",provider:i,id:a,uuid:r})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,e.data.raw,e.success?"success":"error",6e3)})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{this.removeAttribute("disabled"),d.classList.remove("loading"),d.classList.add("hidden")}))}))})),document.querySelectorAll(".sdi_pop_actions a.api-action.notifications").forEach((function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopImmediatePropagation();const o=e.closest(".sdi_pop_actions").querySelector("a.api-action");if(!o)return;const i=o.dataset.provider,a=o.dataset.id,r=o.dataset.uuid;let d=document.getElementById("loading-overlay");d||(d=document.createElement("span"),d.id="loading-overlay",document.body.appendChild(d)),d.classList.remove("hidden"),d.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"notificationsInvoice",method:"ajax",provider:i,id:a,uuid:r})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,null,e.success?"notify":"error")})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{d.classList.remove("loading"),d.classList.add("hidden")}))}))})),document.querySelectorAll(".sdi_pop_actions a.api-action.preserve").forEach((function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopImmediatePropagation();const o=e.closest(".sdi_pop_actions").querySelector("a.api-action");if(!o)return;const i=o.dataset.provider,a=o.dataset.id,r=o.dataset.uuid;let d=document.getElementById("loading-overlay");d||(d=document.createElement("span"),d.id="loading-overlay",document.body.appendChild(d)),d.classList.remove("hidden"),d.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"preserveInvoice",method:"ajax",provider:i,id:a,uuid:r})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,null,e.success?"preserve":"error")})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{d.classList.remove("loading"),d.classList.add("hidden")}))}))})),document.querySelectorAll(".peppol_pop_actions a.api-action.create").forEach((function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopImmediatePropagation();const o=e.closest(".peppol_pop_actions").querySelector("a.api-action");if(!o)return;const i=o.dataset.provider,a=o.dataset.id,r=o.dataset.uuid;this.setAttribute("disabled","disabled");let d=document.getElementById("loading-overlay");d||(d=document.createElement("span"),d.id="loading-overlay",document.body.appendChild(d)),d.classList.remove("hidden"),d.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"createAndSendUblInvoice",method:"ajax",provider:i,id:a,uuid:r})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,e.data.raw,e.success?"success":"error",6e3)})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{this.removeAttribute("disabled"),d.classList.remove("loading"),d.classList.add("hidden")}))}))})),document.querySelectorAll(".peppol_pop_actions a.api-action.state").forEach((function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopImmediatePropagation();const o=e.closest(".peppol_pop_actions").querySelector("a.api-action");if(!o)return;const i=o.dataset.provider,a=o.dataset.id,r=o.dataset.uuid;let d=document.getElementById("loading-overlay");d||(d=document.createElement("span"),d.id="loading-overlay",document.body.appendChild(d)),d.classList.remove("hidden"),d.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"stateInvoice",method:"ajax",provider:i,id:a,uuid:r})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,null,e.success?"success":"notify")})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{d.classList.remove("loading"),d.classList.add("hidden")}))}))}))})),document.addEventListener("DOMContentLoaded",(function(){document.querySelectorAll(".pop_to_webhook_actions a.api-action.send-webhook").forEach((function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopImmediatePropagation();const o=e.closest(".pop_to_webhook_actions").querySelector("a.api-action");if(!o)return;const i=o.dataset.provider,a=o.dataset.id;this.setAttribute("disabled","disabled");let r=document.getElementById("loading-overlay");r||(r=document.createElement("span"),r.id="loading-overlay",document.body.appendChild(r)),r.classList.remove("hidden"),r.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"sendWebhook",method:"ajax",provider:i,id:a})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,e.data.raw,e.success?"success":"error",6e3)})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{this.removeAttribute("disabled"),r.classList.remove("loading"),r.classList.add("hidden")}))}))}))}));let l=0,u=n.count_filtered;function _(e){localStorage.setItem("downloadOffset",e),localStorage.setItem("downloadStarted","1")}function m(){localStorage.removeItem("downloadOffset"),localStorage.removeItem("downloadStarted")}function v(){l>=u?m():t.ajax({url:n.ajax_url,method:"POST",data:{action:"processFileBatch",offset:l,limit:5},success:function(e){if(e.success){e.data.total&&(u=e.data.total),l+=5,l>u&&(l=u),_(l),t(".pop-progress span").remove();let n=Math.min(l/u*100,100);t("#progress-bar").css("width",n+"%").text(Math.round(n)+"%"),e.data.done?(m(),t(this).removeClass("disabled"),t("#progress-bar").css("width","100%").text("100%"),window.location.href=e.data.zip_url):v()}else alert("Errore: "+e.data.message)}})}t(document).ready((function(){const e={offset:parseInt(localStorage.getItem("downloadOffset")||"0"),started:"1"===localStorage.getItem("downloadStarted")};e.started&&(l=e.offset,v()),t("#start-download-btn").on("click",(function(e){t(this).addClass("disabled"),e.preventDefault(),e.stopImmediatePropagation(),t(".pop-progress span").text(n.start_download),l=0,_(l),t("#progress-bar").css("width","0%"),v()}))}));let p=document.getElementById("change_api_key");p&&p.addEventListener("click",(()=>{let e=document.querySelector(".wc_el_inv-form table.form-table .license_key");if(!e)return;let t=e.classList.contains("hidden");e.classList.toggle("hidden");let n=e.querySelector('td label[for="wc_el_inv-settings-license_key"] input[type="text"]');n&&(t?n.removeAttribute("readonly"):n.setAttribute("readonly","readonly"),document.getElementById("save_license_key")||i("save_license_key",n,"afterend"))}))}(window._,window.jQuery,window.wc_el_inv_admin);
     26!function(e,t,n){function o(e,t,n){var o=document.createElement("a");o.setAttribute("id",e),o.setAttribute("href","javascript:;"),o.innerHTML='<i class="dashicons dashicons-edit"></i>',t.insertAdjacentElement(n,o)}function i(e,t,o){var i=document.createElement("button");i.setAttribute("id",e),i.setAttribute("name",e),i.setAttribute("class","button"),i.innerText=n.text_save,t.insertAdjacentElement(o,i)}function a(e,t,n){var o=document.createElement("a");o.setAttribute("id",e),o.setAttribute("href","javascript:;"),o.innerHTML='<i class="dashicons dashicons-no"></i>',t.insertAdjacentElement("beforeend",o)}function r(e){var t=e.split(/[^0-9]/),n=t[0]+"-"+t[1]+"-"+t[2]+"T"+t[3]+":"+t[4]+":"+t[5]+"+0000";return new Date(n.toString()).getTime()/1e3}function d(e){var t,n=null,o=null,i=null,a=document.getElementById("date_in"),d=document.getElementById("date_out"),c=document.getElementById("filter_type"),s=document.getElementById("filter_state"),l=document.getElementById("filter_provider");if(c&&(n=c.options[c.selectedIndex].value),s&&(o=s.options[s.selectedIndex].value),l&&(i=l.options[l.selectedIndex].value),a.value||d.value){var u=a.value+"T00:00:00",_=d.value+"T23:59:59";u=u.split(" - ").map((function(e){return r(e)})).join(" - "),_=_.split(" - ").map((function(e){return r(e)})).join(" - "),a.value&&""===d.value&&(t=e+"&date_in="+u),""===a.value&&d.value&&(t=e+"&date_out="+_),a.value&&d.value&&(t=e+"&date_in="+u+"&date_out="+_)}else t=e;n&&(t=t+"&filter_type="+n),o&&(t=t+"&filter_state="+o),i&&(t=t+"&filter_provider="+i);if(e&&e.indexOf("filtered")>-1){var m={};if(e.replace(/[?&]+([^=&]+)=([^&]*)/gi,(function(e,t,n){m[t]=n})),"no"===m.filtered)return e}return t}function c(e,t){var n,o=t;return window.location.href.indexOf(e)>-1&&(o=(n={},window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi,(function(e,t,o){n[t]=o})),n)[e]),o}function s(e,t,o,i){i||(i=4e3),"dev"===n.mode&&(console.log("message",e),console.log("raw",t));const a=document.createElement("div");a.className="toast "+o;let r="";"error"===o&&(r+='<span class="close" title="close">X</span>'),r+=e;let d="";t?.response?.data?.violations?t.response.data.violations.forEach((e=>{d+="<hr><b>",d+=`- ${n.sdi_notice_path}: ${e.propertyPath}\n`,d+="</b>",d+=`- ${n.sdi_notice_content}: ${e.propertyContent}\n`,d+=`- ${n.sdi_notice_message}: ${e.message}\n`})):t?.detail?.detail?d+=`- ${t.detail.detail}\n`:t?.error&&(d+=`- ${t.error}\n`),d&&(r+="<small>",r+="\n"+n.sdi_notice_details+":\n",r+=d,r+="</small>"),a.innerHTML=r.replace(/\n/g,"<br>"),window.requestAnimationFrame((()=>{document.body.appendChild(a),a.style.opacity="1","error"===o?a.querySelector(".close").addEventListener("click",(function(){a.remove(),location.reload()})):setTimeout((()=>{a.remove(),"success"!==o&&"notify"!==o||location.reload()}),i)}))}document.addEventListener("scroll",(function(){const e=document.querySelector(".wc_el_inv__header");e&&(window.scrollY>20?e.classList.add("is-scrolled"):e.classList.remove("is-scrolled"))})),window.addEventListener("load",(function(){var r=t("#store_invoice-description");r&&t(r).prev().addClass("store_invoice_title");var s,l,u,_,m,p,v,f,y,h,b,g,w,E,A=t("#store_invoice_transmitter-description");A&&t(A).prev().addClass("store_invoice_transmitter_title");t("a.edit_address").on("click",(function(e){t(".order_data_column > p").addClass("hide")})),s=c("tab"),(l=document.querySelectorAll("#toplevel_page_wc_el_inv-options-page ul.wp-submenu li a"))&&l.forEach((function(e){e.parentElement.classList.contains("wp-first-item")&&e.parentElement.remove(),e.parentElement.classList.remove("current"),-1!==e.getAttribute("href").indexOf(s)&&e.parentElement.classList.add("current")})),function(){var e=document.getElementById("wc_el_inv-settings-number_next_invoice");if(e){""!==e.value&&e.setAttribute("disabled","disabled"),o("edit_invoice_next_number",e,"afterend");var t=document.getElementById("edit_invoice_next_number");t.addEventListener("click",(function(){t.hasAttribute("readonly")||(t.style.display="none",e.removeAttribute("disabled"),i("save_invoice_next_number",e,"afterend"))}))}}(),function(){var e=document.getElementById("wc_el_inv-settings-number_next_receipt");if(e){""!==e.value&&e.setAttribute("disabled","disabled"),o("edit_receipt_next_number",e,"afterend");var t=document.getElementById("edit_receipt_next_number");t.addEventListener("click",(function(){t.hasAttribute("readonly")||(t.style.display="none",e.removeAttribute("disabled"),i("save_receipt_next_number",e,"afterend"))}))}}(),function(){var e=document.getElementById("wc_el_inv-settings-number_next_credit_note");if(e){""!==e.value&&e.setAttribute("disabled","disabled"),o("edit_credit_note_next_number",e,"afterend");var t=document.getElementById("edit_credit_note_next_number");t.addEventListener("click",(function(){t.hasAttribute("readonly")||(t.style.display="none",e.removeAttribute("disabled"),i("save_credit_note_next_number",e,"afterend"))}));var n=document.getElementById("wc_el_inv-settings-number_next_credit_note_receipt");if(n){""!==n.value&&n.setAttribute("disabled","disabled"),o("edit_credit_note_receipt_next_number",n,"afterend");var a=document.getElementById("edit_credit_note_receipt_next_number");a.addEventListener("click",(function(){t.hasAttribute("readonly")||(a.style.display="none",n.removeAttribute("disabled"),i("save_credit_note_receipt_next_number",n,"afterend"))}))}}}(),function(){var t=document.querySelectorAll(".wc_el_inv-order_fields");if(0!==t.length){e.forEach(t,(function(e){""!==e.value&&e.setAttribute("disabled","disabled")}));var n=document.querySelector(".wc_el_inv__general-order h3"),i=document.querySelector(".wc_el_inv__general-order--hidden-fields"),r=document.querySelector(".wc_el_inv__general-order--text-data");if(n&&i&&r){o("edit_invoice_next_number",n,"beforeend");var d=document.getElementById("edit_invoice_next_number");a("close_invoice_next_number",n);var c=document.getElementById("close_invoice_next_number");c.style.display="none",d.addEventListener("click",(function(){i.style.display="block",r.style.display="none",this.style.display="none",c.style.display="block",e.forEach(t,(function(e){e.removeAttribute("disabled")}))})),c.addEventListener("click",(function(){i.style.display="none",r.style.display="block",c.style.display="none",d.style.display="",e.forEach(t,(function(e){e.setAttribute("disabled","disabled")}))}))}}}(),u=document.querySelectorAll(".wc_el_inv__refund-invoice[data-order_refund_id]"),0!==(_=document.querySelectorAll(".wc_el_inv-order_fields")).length&&(e.forEach(_,(function(e){""!==e.value&&e.setAttribute("disabled","disabled")})),e.forEach(u,(function(t,n){var i=t.querySelector(".wc_el_inv__refund-invoice td h3"),r=t.querySelector(".wc_el_inv__refund-invoice--hidden-fields"),d=t.querySelector(".wc_el_inv__refund-invoice--text-data");if(i&&r&&d){o("edit_refund_invoice_next_number-"+n,i,"beforeend");var c=document.getElementById("edit_refund_invoice_next_number-"+n);a("close_refund_invoice_next_number-"+n,i);var s=document.getElementById("close_refund_invoice_next_number-"+n);s.style.display="none",s.addEventListener("click",(function(){r.style.display="none",d.style.display="block",s.style.display="none",c.style.display="",e.forEach(_,(function(e){e.setAttribute("disabled","disabled")}))})),c.addEventListener("click",(function(){r.style.display="block",d.style.display="none",this.style.display="none",s.style.display="block",e.forEach(_,(function(e){e.removeAttribute("disabled")}))}))}}))),function(){var t=[document.querySelector(".save-all-csv")];if([]!==t){var n=document.querySelector(".filter");if(n){var o=n.getAttribute("href");n.addEventListener("click",(function(e){e.preventDefault(),e.stopImmediatePropagation();var t=d(o);t&&(window.location=t)}))}t&&e.forEach(t,(function(e){e&&e.addEventListener("click",(function(e){e.preventDefault(),e.stopImmediatePropagation();var t=d(e.target.href);window.open(t,"_blank")}))}))}}(),m=document.querySelector("#woocommerce-order-items .inside .refund-items"),(p=document.querySelectorAll("#order_refunds .actions .mark_trigger"))&&e.forEach(p,(function(e){e.classList.contains("mark_as_sent")&&m&&(m.setAttribute("disabled",!0),m.innerText=n.refund_item_disabled_text)})),v=document.querySelector("#woocommerce-order-items .inside input#refund_amount"),f=document.querySelector(".wc_el_inv__general-order .actions .mark_trigger"),v&&f&&f.classList.contains("mark_as_sent")&&(v.setAttribute("readonly",!0),v.insertAdjacentHTML("afterend",'<p id="readonly-info">'+n.refund_amount_read_only_info_text+"</p>")),y=document.getElementById("wc_el_inv_order_search"),h=document.querySelector(".wc_el_inv_order_search_trigger"),y&&h&&(y.addEventListener("change",(function(e){e.preventDefault(),e.stopImmediatePropagation(),"dev"===n.mode&&console.log("search:",y.value),""!==y.value?window.location=h.href+"&order_search="+y.value:window.location=h.href+"&order_search"})),h.addEventListener("click",(function(e){e.preventDefault(),e.stopImmediatePropagation(),"dev"===n.mode&&console.log("search:",y.value),""===y.value?alert(n.search_by_id):window.location=h.href+"&order_search="+y.value}))),(b=document.getElementById("action_bulk"))&&b.addEventListener("change",(function(t){var o=t.target.value,i=document.querySelectorAll('input[name="pop-invoice[]"]'),a=!1;if(e.forEach(i,(function(e){!0===e.checked&&(a=e.checked)})),a||""===o){var r=!1;if("sent"===o?window.confirm(n.invoice_sent_confirm)&&(r=!0):"no_sent"===o&&window.confirm(n.invoice_undo_confirm)&&(r=!0),r){var d=document.getElementById("wp-list-table-invoice-form");if(d){var c=document.createElement("input");c.setAttribute("type","hidden"),c.setAttribute("id","bulk-sent"),c.setAttribute("name","bulk-sent"),c.setAttribute("value",o),d.appendChild(c),d.submit()}}}else alert(n.bulk_invoice_cb)})),(g=document.querySelectorAll(".doc-type-input"))&&e.forEach(g,(function(e){e.addEventListener("change",(function(e){this.parentElement.parentElement.parentElement.querySelectorAll(".choice_type--current")[0].setAttribute("value",this.value)}))})),(w=document.querySelectorAll(".action-endpoint"))&&e.forEach(w,(function(e){e.addEventListener("click",(function(e){e.preventDefault(),e.stopImmediatePropagation();var t=this.parentElement.querySelector(".choice_type--current"),n=this.href;t&&(n=n+"&choice_type="+t.value),window.open(n)}))})),function(){if("undefined"!=typeof inlineEditPost){var e=inlineEditPost.edit;inlineEditPost.edit=function(n){e.apply(this,arguments);var o=0;if("object"==typeof n&&(o=parseInt(this.getId(n))),o>0){var i=t("#edit-"+o),a=t("#post-"+o),r=!!t(".column-reverse_charge > *",a).prop("checked");t(':input[name="active_reverse_charge"]',i).prop("checked",r)}}}}(),function(){var e=document.querySelectorAll(".nav_section_advanced#wc_output_fields_nav ul li a");function t(t){e.forEach((function(e){e.getAttribute("href")==="#"+t?e.classList.add("active"):e.classList.remove("active")}));var n=document.querySelector(".wc_el_inv-form .form-table tr.import_collections"),o=document.querySelector(".wc_el_inv-form .form-table tr.export_collections"),i=document.querySelector(".wc_el_inv-form .form-table tr.reverse_charge"),a=document.querySelector(".wc_el_inv-form .form-table tr.automatic_sending"),r=document.querySelector(".wc_el_inv-form .form-table tr.pop_webhook");"reverse-charge"===t&&(o&&(o.style.display="none"),n&&(n.style.display="none"),i&&(i.style.display="table-row"),a&&(a.style.display="none"),r&&(r.style.display="none")),"import-export"===t&&(o&&(o.style.display="table-row"),n&&(n.style.display="table-row"),i&&(i.style.display="none"),a&&(a.style.display="none"),r&&(r.style.display="none")),"automatic-sending"===t&&(o&&(o.style.display="none"),n&&(n.style.display="none"),i&&(i.style.display="none"),a&&(a.style.display="table-row"),r&&(r.style.display="none")),"webhook"===t&&(o&&(o.style.display="none"),n&&(n.style.display="none"),i&&(i.style.display="none"),a&&(a.style.display="none"),r&&(r.style.display="table-row"))}e&&("free"===n.user_level?t("automatic-sending"):"IT"!==n.shop_country?t("webhook"):"#reverse-charge"!==window.location.hash&&window.location.hash?"#import-export"===window.location.hash?t("import-export"):"#automatic-sending"===window.location.hash?t("automatic-sending"):"#webhook"===window.location.hash&&t("webhook"):t("reverse-charge"),e.forEach((function(n){n.addEventListener("click",(function(o){e.forEach((function(e){e.classList.remove("active")})),n.classList.add("active"),t(o.target.hash.replace("#",""))}))})))}(),(E=document.querySelectorAll(".wc_el_inv-fic-addon.current-setting_section_numeration .form-table .number_digits input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .reset_numerations input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .suffix_invoice input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .suffix_year input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .suffix_year_format input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .number_next_invoice input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .number_next_receipt input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .numeration_credit_note input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .number_next_credit_note input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .numeration_credit_note_receipt input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table .number_next_credit_note_receipt input,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table #edit_invoice_next_number,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table #edit_receipt_next_number,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table #edit_credit_note_next_number,\n.wc_el_inv-fic-addon.current-setting_section_numeration .form-table #edit_credit_note_receipt_next_number"))&&E.forEach((function(e){e.setAttribute("disabled","disabled"),e.setAttribute("readonly","readonly")})),function(){var e=document.querySelectorAll(".rc-nature-select");function t(e,t){var o=e.options[e.selectedIndex].text.split(" - "),i=2===o.length?o[0]:"",a=2===o.length?o[1]:"",r=null;("N7"===i&&(a="IVA assolta in altro stato UE (vendite a distanza ex art. 40 c. 3 e 4 e art. 41 c. 1 lett. b, DL 331/93; prestazione di servizi di telecomunicazioni, teleradiodiffusione ed elettronici ex art. 7-sexies lett. f, g, art. 74-sexies DPR 633/72)"),"wc_el_inv-ue_private_nature_code"===e.getAttribute("id")?(r=document.getElementById("wc_el_inv-ue_private_normative_reference"),t&&("Personalizzato"===a?(r.setAttribute("value",""),r.value=""):(r.setAttribute("value",a),r.value=a))):"wc_el_inv-ue_company_nature_code"===e.getAttribute("id")?(r=document.getElementById("wc_el_inv-ue_company_normative_reference"),t&&("Personalizzato"===a?(r.setAttribute("value",""),r.value=""):(r.setAttribute("value",a),r.value=a))):"wc_el_inv-ue_company_no_vies_nature_code"===e.getAttribute("id")?(r=document.getElementById("wc_el_inv-ue_company_no_vies_normative_reference"),t&&("Personalizzato"===a?(r.setAttribute("value",""),r.value=""):(r.setAttribute("value",a),r.value=a))):"wc_el_inv-extra_ue_private_nature_code"===e.getAttribute("id")?(r=document.getElementById("wc_el_inv-extra_ue_private_normative_reference"),t&&("Personalizzato"===a?(r.setAttribute("value",""),r.value=""):(r.setAttribute("value",a),r.value=a))):"wc_el_inv-extra_ue_company_nature_code"===e.getAttribute("id")&&(r=document.getElementById("wc_el_inv-extra_ue_company_normative_reference"),t&&("Personalizzato"===a?(r.setAttribute("value",""),r.value=""):(r.setAttribute("value",a),r.value=a))),r)&&(r.closest("tr").style.display="Personalizzato"===a?"table-row":"none");"dev"===n.mode&&console.log("CODE:",i,"REF:",a)}e.length>0&&e.forEach((function(e){t(e,!1),e.addEventListener("change",(function(n){t(e,!0)}))}))}(),t(document).ajaxComplete((function(){var e=document.querySelector("#woocommerce-order-items .inside input#refund_amount"),t=document.querySelector(".wc_el_inv__general-order .actions .mark_trigger");e&&t&&(!e.hasAttribute("readonly")&&t.classList.contains("mark_as_sent")?(e.setAttribute("readonly",!0),e.insertAdjacentHTML("afterend",'<p id="readonly-info">'+n.refund_amount_read_only_info_text+"</p>")):e.hasAttribute("readonly")&&!t.classList.contains("mark_as_sent")&&e.removeAttribute("readonly"))}))})),document.body.classList.add("disable-clicks"),document.addEventListener("DOMContentLoaded",(function(){var o;(o=document.querySelectorAll(".mark_trigger"))&&e.forEach(o,(function(e){e.addEventListener("click",(function(e){e.preventDefault(),e.stopImmediatePropagation();var o=!1;this.classList.contains("mark_as_sent")?window.confirm(n.invoice_sent_confirm)&&(o=!0):this.classList.contains("mark_undo")&&window.confirm(n.invoice_undo_confirm)&&(o=!0),o&&t.ajax({url:n.ajax_url,method:"POST",cache:!1,data:{action:"markInvoice",action_url:this.href,nonce:n.ajax_nonce},beforeSend:function(){}.bind(this),complete:function(e,t){"dev"===n.mode&&console.log(e,t)}.bind(this),error:function(e,t,n){console.warn("markInvoice "+n,t)}.bind(this),success:function(e){console.log(e),window.location.reload()}.bind(this)})}))})),document.body.classList.remove("disable-clicks")})),document.addEventListener("DOMContentLoaded",(function(){document.querySelectorAll(".sdi_pop_actions a.api-action.create").forEach((function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopImmediatePropagation();const o=e.closest(".sdi_pop_actions").querySelector("a.api-action");if(!o)return;const i=o.dataset.provider,a=o.dataset.id,r=o.dataset.uuid;this.setAttribute("disabled","disabled");let d=document.getElementById("loading-overlay");d||(d=document.createElement("span"),d.id="loading-overlay",document.body.appendChild(d)),d.classList.remove("hidden"),d.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"createAndSendInvoice",method:"ajax",provider:i,id:a,uuid:r})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,e.data.raw,e.success?"success":"error",6e3)})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{this.removeAttribute("disabled"),d.classList.remove("loading"),d.classList.add("hidden")}))}))})),document.querySelectorAll(".sdi_pop_actions a.api-action.notifications").forEach((function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopImmediatePropagation();const o=e.closest(".sdi_pop_actions").querySelector("a.api-action");if(!o)return;const i=o.dataset.provider,a=o.dataset.id,r=o.dataset.uuid;let d=document.getElementById("loading-overlay");d||(d=document.createElement("span"),d.id="loading-overlay",document.body.appendChild(d)),d.classList.remove("hidden"),d.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"notificationsInvoice",method:"ajax",provider:i,id:a,uuid:r})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,null,e.success?"notify":"error")})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{d.classList.remove("loading"),d.classList.add("hidden")}))}))})),document.querySelectorAll(".sdi_pop_actions a.api-action.preserve").forEach((function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopImmediatePropagation();const o=e.closest(".sdi_pop_actions").querySelector("a.api-action");if(!o)return;const i=o.dataset.provider,a=o.dataset.id,r=o.dataset.uuid;let d=document.getElementById("loading-overlay");d||(d=document.createElement("span"),d.id="loading-overlay",document.body.appendChild(d)),d.classList.remove("hidden"),d.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"preserveInvoice",method:"ajax",provider:i,id:a,uuid:r})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,null,e.success?"preserve":"error")})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{d.classList.remove("loading"),d.classList.add("hidden")}))}))})),document.querySelectorAll(".peppol_pop_actions a.api-action.create").forEach((function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopImmediatePropagation();const o=e.closest(".peppol_pop_actions").querySelector("a.api-action");if(!o)return;const i=o.dataset.provider,a=o.dataset.id,r=o.dataset.uuid;this.setAttribute("disabled","disabled");let d=document.getElementById("loading-overlay");d||(d=document.createElement("span"),d.id="loading-overlay",document.body.appendChild(d)),d.classList.remove("hidden"),d.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"createAndSendUblInvoice",method:"ajax",provider:i,id:a,uuid:r})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,e.data.raw,e.success?"success":"error",6e3)})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{this.removeAttribute("disabled"),d.classList.remove("loading"),d.classList.add("hidden")}))}))})),document.querySelectorAll(".peppol_pop_actions a.api-action.state").forEach((function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopImmediatePropagation();const o=e.closest(".peppol_pop_actions").querySelector("a.api-action");if(!o)return;const i=o.dataset.provider,a=o.dataset.id,r=o.dataset.uuid;let d=document.getElementById("loading-overlay");d||(d=document.createElement("span"),d.id="loading-overlay",document.body.appendChild(d)),d.classList.remove("hidden"),d.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"stateInvoice",method:"ajax",provider:i,id:a,uuid:r})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,null,e.success?"success":"notify")})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{d.classList.remove("loading"),d.classList.add("hidden")}))}))})),document.querySelectorAll(".sdi_pop_actions a.api-action.confirm-timeout-uuid, .peppol_pop_actions a.api-action.confirm-timeout-uuid").forEach((function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopImmediatePropagation();const o=e.dataset.provider,i=e.dataset.id,a=e.dataset.channel,r=e.dataset.documentType||"invoice",d=e.closest(".sdi_pop_actions, .peppol_pop_actions"),c=d?d.querySelector(".native-timeout-uuid-input"):null,l=c?c.value.trim():"";if(!l)return void s(n.timeout_uuid_required,null,"error");this.setAttribute("disabled","disabled");let u=document.getElementById("loading-overlay");u||(u=document.createElement("span"),u.id="loading-overlay",document.body.appendChild(u)),u.classList.remove("hidden"),u.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"confirmNativeSendWithUuid",provider:o,id:i,channel:a,document_type:r,uuid:l})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,e.data.raw,e.success?"success":"error",6e3)})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{this.removeAttribute("disabled"),u.classList.remove("loading"),u.classList.add("hidden")}))}))})),document.querySelectorAll(".sdi_pop_actions a.api-action.cancel-timeout-pending, .peppol_pop_actions a.api-action.cancel-timeout-pending").forEach((function(e){e.addEventListener("click",(function(t){if(t.preventDefault(),t.stopImmediatePropagation(),!window.confirm(n.timeout_cancel_confirm))return;const o=e.dataset.provider,i=e.dataset.id,a=e.dataset.channel;this.setAttribute("disabled","disabled");let r=document.getElementById("loading-overlay");r||(r=document.createElement("span"),r.id="loading-overlay",document.body.appendChild(r)),r.classList.remove("hidden"),r.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"cancelNativeTimeoutPending",provider:o,id:i,channel:a})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,e.data.raw,e.success?"success":"error",6e3)})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{this.removeAttribute("disabled"),r.classList.remove("loading"),r.classList.add("hidden")}))}))}))})),document.addEventListener("DOMContentLoaded",(function(){document.querySelectorAll(".pop_to_webhook_actions a.api-action.send-webhook").forEach((function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopImmediatePropagation();const o=e.closest(".pop_to_webhook_actions").querySelector("a.api-action");if(!o)return;const i=o.dataset.provider,a=o.dataset.id;this.setAttribute("disabled","disabled");let r=document.getElementById("loading-overlay");r||(r=document.createElement("span"),r.id="loading-overlay",document.body.appendChild(r)),r.classList.remove("hidden"),r.classList.add("loading"),fetch(ajaxurl,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({action:"sendWebhook",method:"ajax",provider:i,id:a})}).then((e=>e.json())).then((e=>{"dev"===n.mode&&console.log("Response:",e),s(e.data.message,e.data.raw,e.success?"success":"error",6e3)})).catch((e=>{console.error("AJAX error:",e),s(e.message,null,"error")})).finally((()=>{this.removeAttribute("disabled"),r.classList.remove("loading"),r.classList.add("hidden")}))}))}))}));let l=0,u=n.count_filtered;function _(e){localStorage.setItem("downloadOffset",e),localStorage.setItem("downloadStarted","1")}function m(){localStorage.removeItem("downloadOffset"),localStorage.removeItem("downloadStarted")}function p(){l>=u?m():t.ajax({url:n.ajax_url,method:"POST",data:{action:"processFileBatch",offset:l,limit:5},success:function(e){if(e.success){e.data.total&&(u=e.data.total),l+=5,l>u&&(l=u),_(l),t(".pop-progress span").remove();let n=Math.min(l/u*100,100);t("#progress-bar").css("width",n+"%").text(Math.round(n)+"%"),e.data.done?(m(),t(this).removeClass("disabled"),t("#progress-bar").css("width","100%").text("100%"),window.location.href=e.data.zip_url):p()}else alert("Errore: "+e.data.message)}})}t(document).ready((function(){const e={offset:parseInt(localStorage.getItem("downloadOffset")||"0"),started:"1"===localStorage.getItem("downloadStarted")};e.started&&(l=e.offset,p()),t("#start-download-btn").on("click",(function(e){t(this).addClass("disabled"),e.preventDefault(),e.stopImmediatePropagation(),t(".pop-progress span").text(n.start_download),l=0,_(l),t("#progress-bar").css("width","0%"),p()}))}));let v=document.getElementById("change_api_key");v&&v.addEventListener("click",(()=>{let e=document.querySelector(".wc_el_inv-form table.form-table .license_key");if(!e)return;let t=e.classList.contains("hidden");e.classList.toggle("hidden");let n=e.querySelector('td label[for="wc_el_inv-settings-license_key"] input[type="text"]');n&&(t?n.removeAttribute("readonly"):n.setAttribute("readonly","readonly"),document.getElementById("save_license_key")||i("save_license_key",n,"afterend"))}))}(window._,window.jQuery,window.wc_el_inv_admin);
  • woopop-electronic-invoice-free/trunk/changelog.txt

    r3460902 r3463326  
     1= 6.7.0 - 17/02/2026 =
     2Added: DB advisory lock helpers based on MySQL/MariaDB GET_LOCK/RELEASE_LOCK to prevent concurrent duplicate create/send/upload actions.
     3Fixed: Fatture in Cloud send flow now requires explicit docID and wfc-issued_send_document=false; removed implicit docID fallback from stored document meta.
     4Fixed: Fatture in Cloud create flow now uses atomic lock guard to avoid duplicate issued documents under concurrent Ajax/Cron execution.
     5Fixed: Added channel guards to SDI/PEPPOL/FIC status callbacks so native/addon flows run only when their integration channel is effectively active.
     6Fixed: Hardened _invoice_sent writes across channels (SDI native, PEPPOL, Aruba, SDI via PEC, FIC) by requiring minimum success/coherence conditions before setting sent.
     7Fixed: Added recurring cron cleanup for inactive/disabled upload schedules (FIC, Aruba, SDI via PEC) to avoid orphan scheduled events.
     8Fixed: Fatture table invoice number rendering now only shows dismiss icon when both order_number_invoice and formatted number are missing, with recovery from formatted where possible.
     9Added: Native SDI/PEPPOL timeout-pending flow with explicit admin recovery actions in order list (Confirm UUID / Cancel timeout).
     10Added: New admin AJAX actions confirmNativeSendWithUuid and cancelNativeTimeoutPending to finalize or clear timeout-pending state safely.
     11Added: Native timeout helper set (meta keys/channel prefix/timeout detection) and centralized timeout pending metadata management.
     12Fixed: SDI and PEPPOL create/send flows now mark timeout-pending on timeout-like provider responses and clear pending state on successful UUID save.
     13Fixed: SDI and PEPPOL create/send error responses now include readable provider detail extracted from raw payload data when available.
     14Change: Timeout pending UUID input in native actions UI is now full-width with explicit spacing before recovery buttons.
     15Fixed: getNextInvoiceNumber now aligns stale next numeration values to max(next, last_assigned+1) for the active series, preventing duplicates and backward numbering.
     16Added: series-aware duplicate check now uses WooCommerce order queries (HPOS compatible), comparing type/prefix/year context against the latest assigned order.
     17Added: provider-level series hooks for PMPro and Cozmos addons via wc_el_inv-next_number_already_assigned and wc_el_inv-next_number_last_assigned_for_series.
     18Added: Extended unit test coverage for timeout helpers, native error detail extraction, resolver hardening edge cases, and wrapper/adapter behavior.
     19Fixed: Resolver hardening for invalid filter outputs (channels status/priority), empty priorities, non-string priority entries, and normalized shop country handling.
     20
    121= 6.6.2 - 13/02/2026 =
    222Fixed: WooCommerce Blocks checkout now reliably saves POP additional billing fields into order meta during checkout completion.
  • woopop-electronic-invoice-free/trunk/inc/filtersAdmin.php

    r3444920 r3463326  
    434434                'priority' => 10,
    435435            ),
     436            array(
     437                'filter'   => 'wp_ajax_confirmNativeSendWithUuid',
     438                'callback' => '\\WcElectronInvoice\\InvoiceApi::confirmNativeSendWithUuid',
     439                'priority' => 10,
     440            ),
     441            array(
     442                'filter'   => 'wp_ajax_cancelNativeTimeoutPending',
     443                'callback' => '\\WcElectronInvoice\\InvoiceApi::cancelNativeTimeoutPending',
     444                'priority' => 10,
     445            ),
    436446
    437447            /**
  • woopop-electronic-invoice-free/trunk/inc/localizeScripts.php

    r3460902 r3463326  
    130130                WC_EL_INV_TEXTDOMAIN
    131131            ),
     132            'timeout_uuid_required'             => esc_html__(
     133                'UUID is required.',
     134                WC_EL_INV_TEXTDOMAIN
     135            ),
     136            'timeout_cancel_confirm'            => esc_html__(
     137                'Confirm only if POP support has explicitly verified that the document was NOT created/sent. This action re-enables send and may create duplicates if verification is incorrect.',
     138                WC_EL_INV_TEXTDOMAIN
     139            ),
    132140        ),
    133141        'wc_el_inv_integration' => array(
  • woopop-electronic-invoice-free/trunk/inc/wc/filtersAdmin.php

    r3382415 r3463326  
    282282                'filter'        => 'woocommerce_order_status_changed',
    283283                'callback'      => function ($orderID, $from, $to) {
     284                    if (! \WcElectronInvoice\Integrations::isInvoiceChannelActive(\WcElectronInvoice\Integrations::CHANNEL_SDIPOP)) {
     285                        return;
     286                    }
     287
    284288                    \WcElectronInvoice\InvoiceApi::changeStatus($orderID, $from, $to);
     289                },
     290                'priority'      => 10,
     291                'accepted_args' => 3,
     292            ),
     293
     294            /**
     295             * PEPPOL via POP
     296             * Cron Jobs on order status change
     297             */
     298            array(
     299                'filter'        => 'woocommerce_order_status_changed',
     300                'callback'      => function ($orderID, $from, $to) {
     301                    if (! \WcElectronInvoice\Integrations::isInvoiceChannelActive(\WcElectronInvoice\Integrations::CHANNEL_PEPPOL)) {
     302                        return;
     303                    }
     304
     305                    \WcElectronInvoice\PeppolApi::changeStatus($orderID, $from, $to);
    285306                },
    286307                'priority'      => 10,
  • woopop-electronic-invoice-free/trunk/inc/wc/filtersFront.php

    r3460902 r3463326  
    235235                ),
    236236                'callback'      => function ($orderID) {
     237                    if (! \WcElectronInvoice\Integrations::isInvoiceChannelActive(\WcElectronInvoice\Integrations::CHANNEL_SDIPOP)) {
     238                        return;
     239                    }
     240
    237241                    \WcElectronInvoice\InvoiceApi::autoCompleted($orderID);
     242                },
     243                'priority'      => 10,
     244                'accepted_args' => 1,
     245            ),
     246
     247            /**
     248             * PEPPOL via POP
     249             * Cron Jobs on order auto completed
     250             */
     251            array(
     252                'filter'        => array(
     253                    'woocommerce_payment_complete',
     254                    'woocommerce_payment_complete_order_status_completed',
     255                    'woocommerce_payment_complete_order_status_processing',
     256                    'woocommerce_order_status_completed',
     257                    'woocommerce_order_status_processing',
     258                ),
     259                'callback'      => function ($orderID) {
     260                    if (! \WcElectronInvoice\Integrations::isInvoiceChannelActive(\WcElectronInvoice\Integrations::CHANNEL_PEPPOL)) {
     261                        return;
     262                    }
     263
     264                    \WcElectronInvoice\PeppolApi::autoCompleted($orderID);
    238265                },
    239266                'priority'      => 10,
  • woopop-electronic-invoice-free/trunk/index.php

    r3460902 r3463326  
    77 * Description: POP automatically configures your e-commerce to comply with European tax regulations. Your e-commerce can generate electronic invoices in XML format and, thanks to our APIs, automatically transmit them to your accounting software and tax authorities.
    88 *
    9  * Version: 6.6.2
     9 * Version: 6.7.0
    1010 * Author: POP
    1111 * Author URI: https://popapi.io/
     
    5252define('WC_EL_INV_NAME', 'POP Electronic Invoice');
    5353define('WC_EL_INV_TEXTDOMAIN', 'el-inv');
    54 define('WC_EL_INV_VERSION', '6.6.2');
     54define('WC_EL_INV_VERSION', '6.7.0');
    5555define('WC_EL_INV_VERSION_CLASS', str_replace('.', '_', WC_EL_INV_VERSION));
    5656define('WC_EL_INV_PLUGIN_DIR', basename(plugin_dir_path(__FILE__)));
  • woopop-electronic-invoice-free/trunk/languages/el-inv-es_ES.po

    r3460146 r3463326  
    33"Project-Id-Version: POP Electronic Invoice\n"
    44"POT-Creation-Date: 2025-12-11 10:11+0100\n"
    5 "PO-Revision-Date: 2026-02-11 11:00+0100\n"
     5"PO-Revision-Date: 2026-02-16 13:57+0100\n"
    66"Last-Translator: \n"
    77"Language-Team: Angelo Giammarresi - [email protected]\n"
     
    33173317msgstr "Referencia normativa para el reverse charge guardada con este pedido."
    33183318
     3319msgid "Action not allowed: another integration channel is active."
     3320msgstr "Acción no permitida: hay otro canal de integración activo."
     3321
     3322msgid "Action skipped: another send/create process is already running for this order."
     3323msgstr "Acción omitida: ya se está ejecutando otro proceso de envío/creación para este pedido."
     3324
    33193325#~ msgid "Scopri come"
    33203326#~ msgstr "Descubre cómo"
  • woopop-electronic-invoice-free/trunk/languages/el-inv-fr_FR.po

    r3460146 r3463326  
    33"Project-Id-Version: POP Electronic Invoice\n"
    44"POT-Creation-Date: 2025-12-11 10:08+0100\n"
    5 "PO-Revision-Date: 2026-02-11 11:00+0100\n"
     5"PO-Revision-Date: 2026-02-16 13:57+0100\n"
    66"Last-Translator: Automatically generated\n"
    77"Language-Team: none\n"
     
    34003400msgid "Invoice delivered successfully"
    34013401msgstr "Facture livrée avec succès"
     3402
     3403msgid "Action not allowed: another integration channel is active."
     3404msgstr "Action non autorisée : un autre canal d’intégration est actif."
     3405
     3406msgid "Action skipped: another send/create process is already running for this order."
     3407msgstr "Action ignorée : un autre processus d’envoi/création est déjà en cours pour cette commande."
  • woopop-electronic-invoice-free/trunk/languages/el-inv-it_IT.po

    r3460146 r3463326  
    33"Project-Id-Version: POP Electronic Invoice\n"
    44"POT-Creation-Date: 2025-12-11 10:09+0100\n"
    5 "PO-Revision-Date: 2026-02-11 11:00+0100\n"
     5"PO-Revision-Date: 2026-02-16 13:57+0100\n"
    66"Last-Translator: Alfio <[email protected]>\n"
    77"Language-Team: \n"
     
    28992899#: src/Xml/BuildXml.php:189
    29002900msgid "State"
    2901 msgstr "Provincia"
     2901msgstr "Stato"
    29022902
    29032903#: src/Utils/Countries.php:1121 src/Utils/Countries.php:1200
     
    34323432msgid "Invoice delivered successfully"
    34333433msgstr "Fattura consegnata con successo"
     3434
     3435msgid "Action not allowed: another integration channel is active."
     3436msgstr "Azione non consentita: un altro canale di integrazione è attivo."
     3437
     3438msgid "Action skipped: another send/create process is already running for this order."
     3439msgstr "Azione saltata: un altro processo di invio/creazione è già in esecuzione per questo ordine."
    34343440
    34353441#~ msgid ""
  • woopop-electronic-invoice-free/trunk/readme.md

    r3461234 r3463326  
    44* **Requires at least:** 4.6
    55* **Tested up to:** 6.9
    6 * **Stable tag:** 6.6.2
     6* **Stable tag:** 6.7.0
    77* **Requires PHP:** 5.6
    88* **License:** GPLv2 or later
     
    179179
    180180## Changelog
     181= 6.7.0 - 17/02/2026 =
     182* Add: DB advisory lock helpers based on MySQL/MariaDB GET_LOCK/RELEASE_LOCK to prevent concurrent duplicate create/send/upload actions.
     183* Fix: Fatture in Cloud send flow now requires explicit docID and wfc-issued_send_document=false; removed implicit docID fallback from stored document meta.
     184* Fix: Fatture in Cloud create flow now uses atomic lock guard to avoid duplicate issued documents under concurrent Ajax/Cron execution.
     185* Fix: Added channel guards to SDI/PEPPOL/FIC status callbacks so native/addon flows run only when their integration channel is effectively active.
     186* Fix: Hardened _invoice_sent writes across channels (SDI native, PEPPOL, Aruba, SDI via PEC, FIC) by requiring minimum success/coherence conditions before setting sent.
     187* Fix: Added recurring cron cleanup for inactive/disabled upload schedules (FIC, Aruba, SDI via PEC) to avoid orphan scheduled events.
     188* Fix: Fatture table invoice number rendering now only shows dismiss icon when both order_number_invoice and formatted number are missing, with recovery from formatted where possible.
     189* Add: Native SDI/PEPPOL timeout-pending flow with explicit admin recovery actions in order list (Confirm UUID / Cancel timeout).
     190* Add: New admin AJAX actions confirmNativeSendWithUuid and cancelNativeTimeoutPending to finalize or clear timeout-pending state safely.
     191* Add: Native timeout helper set (meta keys/channel prefix/timeout detection) and centralized timeout pending metadata management.
     192* Fix: SDI and PEPPOL create/send flows now mark timeout-pending on timeout-like provider responses and clear pending state on successful UUID save.
     193* Fix: SDI and PEPPOL create/send error responses now include readable provider detail extracted from raw payload data when available.
     194* Change: Timeout pending UUID input in native actions UI is now full-width with explicit spacing before recovery buttons.
     195* Fix: getNextInvoiceNumber now aligns stale next numeration values to max(next, last_assigned+1) for the active series, preventing duplicates and backward numbering.
     196* Add: series-aware duplicate check now uses WooCommerce order queries (HPOS compatible), comparing type/prefix/year context against the latest assigned order.
     197* Add: provider-level series hooks for PMPro and Cozmos addons via wc_el_inv-next_number_already_assigned and wc_el_inv-next_number_last_assigned_for_series.
     198* Add: Extended unit test coverage for timeout helpers, native error detail extraction, resolver hardening edge cases, and wrapper/adapter behavior.
     199* Fix: Resolver hardening for invalid filter outputs (channels status/priority), empty priorities, non-string priority entries, and normalized shop country handling.
     200
    181201### 6.6.2 - 13/02/2026
    182202* Fix: WooCommerce Blocks checkout now reliably saves POP additional billing fields into order meta during checkout completion.
  • woopop-electronic-invoice-free/trunk/readme.txt

    r3461234 r3463326  
    44Requires at least: 4.6
    55Tested up to: 6.9
    6 Stable tag: 6.6.2
     6Stable tag: 6.7.0
    77Requires PHP: 5.6
    88License: GPLv2 or later
     
    179179
    180180== Changelog ==
     181= 6.7.0 - 17/02/2026 =
     182* Add: DB advisory lock helpers based on MySQL/MariaDB GET_LOCK/RELEASE_LOCK to prevent concurrent duplicate create/send/upload actions.
     183* Fix: Fatture in Cloud send flow now requires explicit docID and wfc-issued_send_document=false; removed implicit docID fallback from stored document meta.
     184* Fix: Fatture in Cloud create flow now uses atomic lock guard to avoid duplicate issued documents under concurrent Ajax/Cron execution.
     185* Fix: Added channel guards to SDI/PEPPOL/FIC status callbacks so native/addon flows run only when their integration channel is effectively active.
     186* Fix: Hardened _invoice_sent writes across channels (SDI native, PEPPOL, Aruba, SDI via PEC, FIC) by requiring minimum success/coherence conditions before setting sent.
     187* Fix: Added recurring cron cleanup for inactive/disabled upload schedules (FIC, Aruba, SDI via PEC) to avoid orphan scheduled events.
     188* Fix: Fatture table invoice number rendering now only shows dismiss icon when both order_number_invoice and formatted number are missing, with recovery from formatted where possible.
     189* Add: Native SDI/PEPPOL timeout-pending flow with explicit admin recovery actions in order list (Confirm UUID / Cancel timeout).
     190* Add: New admin AJAX actions confirmNativeSendWithUuid and cancelNativeTimeoutPending to finalize or clear timeout-pending state safely.
     191* Add: Native timeout helper set (meta keys/channel prefix/timeout detection) and centralized timeout pending metadata management.
     192* Fix: SDI and PEPPOL create/send flows now mark timeout-pending on timeout-like provider responses and clear pending state on successful UUID save.
     193* Fix: SDI and PEPPOL create/send error responses now include readable provider detail extracted from raw payload data when available.
     194* Change: Timeout pending UUID input in native actions UI is now full-width with explicit spacing before recovery buttons.
     195* Fix: getNextInvoiceNumber now aligns stale next numeration values to max(next, last_assigned+1) for the active series, preventing duplicates and backward numbering.
     196* Add: series-aware duplicate check now uses WooCommerce order queries (HPOS compatible), comparing type/prefix/year context against the latest assigned order.
     197* Add: provider-level series hooks for PMPro and Cozmos addons via wc_el_inv-next_number_already_assigned and wc_el_inv-next_number_last_assigned_for_series.
     198* Add: Extended unit test coverage for timeout helpers, native error detail extraction, resolver hardening edge cases, and wrapper/adapter behavior.
     199* Fix: Resolver hardening for invalid filter outputs (channels status/priority), empty priorities, non-string priority entries, and normalized shop country handling.
     200
    181201= 6.6.2 - 13/02/2026 =
    182202* Fix: WooCommerce Blocks checkout now reliably saves POP additional billing fields into order meta during checkout completion.
  • woopop-electronic-invoice-free/trunk/requires.php

    r3323757 r3463326  
    3131
    3232require_once \WcElectronInvoice\Plugin::getPluginDirPath('/src/Functions/Utils.php');
     33require_once \WcElectronInvoice\Plugin::getPluginDirPath('/src/Functions/Lock.php');
    3334require_once \WcElectronInvoice\Plugin::getPluginDirPath('/src/Functions/Conditionals.php');
    3435require_once \WcElectronInvoice\Plugin::getPluginDirPath('/src/Functions/Invoice.php');
  • woopop-electronic-invoice-free/trunk/src/Admin/XmlOrderListTable.php

    r3445528 r3463326  
    747747
    748748        $invoiceNumber = $order->get_order_number_invoice();
    749         if (empty($invoiceNumber)) {
     749        if (empty($invoiceNumber) && empty($formattedNumber)) {
    750750            // Not invoice number
    751751            return '<span class="color_red dashicons dashicons-dismiss"></span>';
  • woopop-electronic-invoice-free/trunk/src/Functions/CloudApi.php

    r3382415 r3463326  
    186186            $method = \WcElectronInvoice\Functions\filterInput($_POST, 'method') ?? null;
    187187
    188             if (('createAndSendInvoice' === $action || 'createAndSendUblInvoice' === $action) && ('cronjob' === $method || 'ajax' === $method)) {
     188            if (
     189                ('createAndSendInvoice' === $action || 'createAndSendUblInvoice' === $action) &&
     190                \WcElectronInvoice\Functions\isBackgroundExecutionMode($method, ['cronjob', 'ajax'])
     191            ) {
    189192                $response                  = json_decode($body);
    190193                $responseIntegrationViaPop = $response;
     
    327330        $action = \WcElectronInvoice\Functions\filterInput($_POST, 'action') ?? null;
    328331        $method = \WcElectronInvoice\Functions\filterInput($_POST, 'method') ?? null;
    329         if (('createAndSendInvoice' === $action || 'createAndSendUblInvoice' === $action) && ('cronjob' === $method || 'ajax' === $method)) {
     332        if (
     333            ('createAndSendInvoice' === $action || 'createAndSendUblInvoice' === $action) &&
     334            \WcElectronInvoice\Functions\isBackgroundExecutionMode($method, ['cronjob', 'ajax'])
     335        ) {
    330336            return json_encode($responseIntegrationViaPop);
    331337        }
  • woopop-electronic-invoice-free/trunk/src/Functions/Invoice.php

    r3460146 r3463326  
    5151    $maxAttempts = 5; // How many times to retry in case of conflict
    5252    $attempt     = 0;
     53    $scanLimit   = (int)apply_filters('wc_el_inv-next_number_scan_limit', 50, $optionKeyName);
     54    if ($scanLimit < 1) {
     55        $scanLimit = 1;
     56    }
    5357
    5458    while ($attempt < $maxAttempts) {
     
    98102        if ($currentValue < 1) {
    99103            $currentValue = 1;
     104        }
     105
     106        $lastAssigned = getLastAssignedInvoiceNumberForCurrentSeries($optionKeyName);
     107        /**
     108         * Allow providers/integrations to contribute last assigned number for the active series.
     109         *
     110         * @param int    $lastAssigned
     111         * @param string $optionKeyName
     112         */
     113        $lastAssigned = (int)apply_filters('wc_el_inv-next_number_last_assigned_for_series', $lastAssigned, $optionKeyName);
     114        if ($lastAssigned > 0 && $currentValue <= $lastAssigned) {
     115            $currentValue = $lastAssigned + 1;
     116        }
     117
     118        $checked = 0;
     119        while (
     120            $checked < $scanLimit
     121            &&
     122            isInvoiceNumberAlreadyAssigned($currentValue, $optionKeyName)
     123        ) {
     124            $currentValue++;
     125            $checked++;
     126        }
     127
     128        if (
     129            $checked >= $scanLimit
     130            &&
     131            isInvoiceNumberAlreadyAssigned($currentValue, $optionKeyName)
     132        ) {
     133            error_log("[WcElectronInvoice] Unable to align next invoice number for {$optionKeyName}: scan limit reached at {$currentValue}");
     134
     135            return false;
    100136        }
    101137
     
    135171
    136172/**
     173 * Check if candidate invoice number is already assigned.
     174 *
     175 * @param int    $number
     176 * @param string $optionKeyName
     177 *
     178 * @return bool
     179 */
     180function isInvoiceNumberAlreadyAssigned(int $number, string $optionKeyName): bool
     181{
     182    if ($number < 1) {
     183        return false;
     184    }
     185
     186    global $wpdb;
     187
     188    $exists = false;
     189
     190    if ('number_next_invoice' === $optionKeyName || 'number_next_receipt' === $optionKeyName) {
     191        $lastAssigned = getLastAssignedInvoiceNumberForCurrentSeries($optionKeyName);
     192        /**
     193         * Allow providers/integrations to contribute last assigned number for the active series.
     194         *
     195         * @param int    $lastAssigned
     196         * @param string $optionKeyName
     197         */
     198        $lastAssigned = (int)apply_filters('wc_el_inv-next_number_last_assigned_for_series', $lastAssigned, $optionKeyName);
     199        $exists       = ($lastAssigned > 0 && $lastAssigned === $number);
     200
     201        if (! $exists && isset($wpdb) && is_object($wpdb) && isset($wpdb->postmeta, $wpdb->posts) && ! function_exists('wc_get_orders')) {
     202            $typeWhere = '';
     203            if ('number_next_receipt' === $optionKeyName) {
     204                $typeWhere = " AND type_meta.meta_value = 'receipt' ";
     205            } else {
     206                $typeWhere = " AND (type_meta.meta_id IS NULL OR type_meta.meta_value = '' OR type_meta.meta_value = 'invoice') ";
     207            }
     208
     209            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     210            $exists = (bool)$wpdb->get_var(
     211                $wpdb->prepare(
     212                    "SELECT inv_meta.meta_id
     213                     FROM {$wpdb->postmeta} inv_meta
     214                     INNER JOIN {$wpdb->posts} posts ON posts.ID = inv_meta.post_id
     215                     LEFT JOIN {$wpdb->postmeta} type_meta ON type_meta.post_id = posts.ID AND type_meta.meta_key = '_billing_choice_type'
     216                     WHERE inv_meta.meta_key = 'order_number_invoice'
     217                       AND CAST(inv_meta.meta_value AS UNSIGNED) = %d
     218                       AND posts.post_type = 'shop_order'
     219                       AND posts.post_status <> 'trash'
     220                       {$typeWhere}
     221                     LIMIT 1",
     222                    $number
     223                )
     224            );
     225        }
     226    }
     227
     228    /**
     229     * Filter duplicate-number lookup so integrations/providers can extend check.
     230     *
     231     * @param bool   $exists
     232     * @param int    $number
     233     * @param string $optionKeyName
     234     */
     235    return (bool)apply_filters('wc_el_inv-next_number_already_assigned', $exists, $number, $optionKeyName);
     236}
     237
     238/**
     239 * Resolve last assigned invoice number for the current WooCommerce series context.
     240 *
     241 * @param string $optionKeyName
     242 *
     243 * @return int
     244 */
     245function getLastAssignedInvoiceNumberForCurrentSeries(string $optionKeyName): int
     246{
     247    if (! function_exists('wc_get_orders') || ! function_exists('wc_get_order')) {
     248        return 0;
     249    }
     250
     251    $recentOrdersLimit = (int)apply_filters('wc_el_inv-next_number_recent_orders_limit', 200, $optionKeyName, 0);
     252    if ($recentOrdersLimit < 1) {
     253        $recentOrdersLimit = 1;
     254    }
     255
     256    $ordersArgs = [
     257        'type'    => 'shop_order',
     258        'status'  => 'any',
     259        'limit'   => $recentOrdersLimit,
     260        'return'  => 'ids',
     261        'orderby' => 'date',
     262        'order'   => 'DESC',
     263    ];
     264
     265    /**
     266     * Filter recent-orders query used to detect duplicate numbers.
     267     *
     268     * @param array  $ordersArgs
     269     * @param string $optionKeyName
     270     * @param int    $number
     271     */
     272    $ordersArgs = apply_filters('wc_el_inv-next_number_recent_orders_args', $ordersArgs, $optionKeyName, 0);
     273
     274    $seriesContext = getNextNumberSeriesContext($optionKeyName);
     275    $recentOrderIDs = wc_get_orders($ordersArgs);
     276    if (! is_array($recentOrderIDs)) {
     277        return 0;
     278    }
     279
     280    foreach ($recentOrderIDs as $orderID) {
     281        $order = is_object($orderID) ? $orderID : wc_get_order($orderID);
     282        if (! is_object($order) || ! method_exists($order, 'get_meta')) {
     283            continue;
     284        }
     285
     286        $invoiceNumber = (int)$order->get_meta('order_number_invoice');
     287        if ($invoiceNumber < 1) {
     288            continue;
     289        }
     290
     291        $choiceType = (string)$order->get_meta('_billing_choice_type');
     292        $isReceipt  = ('receipt' === $choiceType);
     293        if ('number_next_receipt' === $optionKeyName && ! $isReceipt) {
     294            continue;
     295        }
     296        if ('number_next_invoice' === $optionKeyName && $isReceipt) {
     297            continue;
     298        }
     299
     300        return isOrderInNextNumberSeries($order, $seriesContext) ? $invoiceNumber : 0;
     301    }
     302
     303    return 0;
     304}
     305
     306/**
     307 * Build current series context (type/prefix/year token) for next-number alignment.
     308 *
     309 * @param string $optionKeyName
     310 *
     311 * @return array{numeration_type:string,prefix:string,suffix_year_enabled:bool,suffix_year_format:string,year_token:string}
     312 */
     313function getNextNumberSeriesContext(string $optionKeyName): array
     314{
     315    $numerationType = ('number_next_receipt' === $optionKeyName) ? 'receipt' : 'invoice';
     316    $options        = OptionPage::init();
     317
     318    $prefix = (string)$options->getOptions("prefix_{$numerationType}_number");
     319    $prefix = (string)apply_filters('wc_el_inv-next_number_series_prefix', $prefix, $optionKeyName, $numerationType);
     320
     321    $suffixYearEnabled = ('on' === (string)$options->getOptions('suffix_year_invoice_number'));
     322    $suffixYearFormat  = (string)$options->getOptions('suffix_year_format_invoice_number');
     323    if ('' === $suffixYearFormat) {
     324        $suffixYearFormat = 'Y';
     325    }
     326
     327    $yearToken = $suffixYearEnabled ? date($suffixYearFormat) : '';
     328
     329    return [
     330        'numeration_type'     => $numerationType,
     331        'prefix'              => $prefix,
     332        'suffix_year_enabled' => $suffixYearEnabled,
     333        'suffix_year_format'  => $suffixYearFormat,
     334        'year_token'          => $yearToken,
     335    ];
     336}
     337
     338/**
     339 * Check whether an order belongs to the active next-number series.
     340 *
     341 * @param mixed $order
     342 * @param array $seriesContext
     343 *
     344 * @return bool
     345 */
     346function isOrderInNextNumberSeries($order, array $seriesContext): bool
     347{
     348    if (! is_object($order)) {
     349        return false;
     350    }
     351
     352    $formattedPrefix = '';
     353    if (
     354        method_exists($order, 'get_meta')
     355        &&
     356        method_exists($order, 'get_type')
     357        &&
     358        method_exists($order, 'get_id')
     359    ) {
     360        $formatted = (string)$order->get_meta("{$order->get_type()}_formatted_number_{$order->get_id()}");
     361        if ('' === $formatted) {
     362            $formatted = (string)$order->get_meta("shop_order_formatted_number_{$order->get_id()}");
     363        }
     364        if (preg_match('/^\D*/u', $formatted, $matches)) {
     365            $formattedPrefix = (string)$matches[0];
     366        }
     367    }
     368
     369    if ((string)$seriesContext['prefix'] !== $formattedPrefix) {
     370        return false;
     371    }
     372
     373    if (! empty($seriesContext['suffix_year_enabled'])) {
     374        $seriesYear = '';
     375        $date       = null;
     376        if (method_exists($order, 'get_date_completed')) {
     377            $date = $order->get_date_completed();
     378        }
     379        if (! $date && method_exists($order, 'get_date_created')) {
     380            $date = $order->get_date_created();
     381        }
     382        if ($date instanceof \DateTimeInterface) {
     383            $seriesYear = $date->format((string)$seriesContext['suffix_year_format']);
     384        }
     385        if ($seriesYear !== (string)$seriesContext['year_token']) {
     386            return false;
     387        }
     388    }
     389
     390    return true;
     391}
     392
     393/**
    137394 * removeDuplicateMeta
    138395 *
     
    257514        $orderInvoiceNumber = $order->get_meta('order_number_invoice') ?? null;
    258515        if (! $orderInvoiceNumber) {
     516            $recoveredNumber = recoverOrderInvoiceNumberFromFormatted($order, $numerationType);
     517            if ($recoveredNumber > 0) {
     518                $order->update_meta_data('order_number_invoice', $recoveredNumber);
     519                setFormattedInvoiceNumber($order, $numerationType, true);
     520                $order->save();
     521                $orderInvoiceNumber = $recoveredNumber;
     522            }
     523        }
     524        if (! $orderInvoiceNumber) {
    259525            // Get next invoice number option.
    260526            $invoiceNumber = (int)getNextInvoiceNumber("number_next_{$numerationType}");
     
    272538        $numerationType = apply_filters('wc_el_inv-invoice_number_next_numeration_type', $numerationType, $order);
    273539
    274         if ((! $checkSent || 'no_sent' === $checkSent) && (! $orderInvoiceNumber)) {
     540        if (! $orderInvoiceNumber) {
    275541            // Set invoice number for order.
    276542            $order->update_meta_data('order_number_invoice', $invoiceNumber);
     
    374640        $orderInvoiceNumber = $order->get_meta('order_number_invoice');
    375641        if (! $orderInvoiceNumber) {
     642            $recoveredNumber = recoverOrderInvoiceNumberFromFormatted($order, $numerationType);
     643            if ($recoveredNumber > 0) {
     644                $order->update_meta_data('order_number_invoice', $recoveredNumber);
     645                setFormattedInvoiceNumber($order, $numerationType, true);
     646                $order->save();
     647                $orderInvoiceNumber = $recoveredNumber;
     648            }
     649        }
     650        if (! $orderInvoiceNumber) {
    376651            // Get next invoice number option.
    377652            $invoiceNumber = (int)getNextInvoiceNumber("number_next_{$numerationType}");
     
    388663        $numerationType = apply_filters('wc_el_inv-invoice_number_next_numeration_type', $numerationType, $order);
    389664
    390         if ((! $checkSent || 'no_sent' === $checkSent) && (! $orderInvoiceNumber)) {
     665        if (! $orderInvoiceNumber) {
    391666            // Set invoice number for order.
    392667            $order->update_meta_data('order_number_invoice', $invoiceNumber);
     
    635910
    636911/**
     912 * Try to recover the raw invoice number from an existing formatted number.
     913 *
     914 * @param \WC_Order $order
     915 * @param string    $numerationType
     916 *
     917 * @return int
     918 */
     919function recoverOrderInvoiceNumberFromFormatted($order, $numerationType): int
     920{
     921    $formattedNumber = $order->get_meta("{$order->get_type()}_formatted_number_{$order->get_id()}") ?? null;
     922    if (! $formattedNumber) {
     923        return 0;
     924    }
     925
     926    $normalized = apply_filters('wc_el_inv-normalize_formatted_number', $formattedNumber, $order);
     927    if (is_numeric($normalized) && (int)$normalized > 0) {
     928        return (int)$normalized;
     929    }
     930
     931    $candidate = (string)$formattedNumber;
     932    $options   = OptionPage::init();
     933
     934    $prefix = $options->getOptions("prefix_{$numerationType}_number");
     935    $prefix = apply_filters("wc_el_inv-formatted_number_prefix_{$numerationType}", $prefix, $order);
     936    if (is_string($prefix) && '' !== $prefix && str_starts_with($candidate, $prefix)) {
     937        $candidate = substr($candidate, strlen($prefix));
     938    }
     939
     940    $suffix           = $options->getOptions('suffix_invoice_number');
     941    $suffixYear       = $options->getOptions('suffix_year_invoice_number');
     942    $suffixYearFormat = $options->getOptions('suffix_year_format_invoice_number') ?: 'Y';
     943    if ('on' === $suffixYear) {
     944        $created = $order->has_status('completed') ? $order->get_date_completed() : $order->get_date_created();
     945        if (! $created instanceof \DateTimeInterface) {
     946            $created = new \DateTime();
     947        }
     948        $suffix = "/" . $created->format($suffixYearFormat);
     949    } else {
     950        $suffix = isset($suffix) && '' !== $suffix ? $suffix : '';
     951    }
     952    $suffix = apply_filters('wc_el_inv-formatted_number_suffix_number', $suffix, $order);
     953    if (is_string($suffix) && '' !== $suffix && str_ends_with($candidate, $suffix)) {
     954        $candidate = substr($candidate, 0, -strlen($suffix));
     955    }
     956
     957    $candidate = trim($candidate);
     958
     959    if (ctype_digit($candidate) && (int)$candidate > 0) {
     960        return (int)$candidate;
     961    }
     962
     963    return 0;
     964}
     965
     966/**
    637967 * setCreditNoteFallBackNumeration
    638968 *
     
    6851015
    6861016    return false;
     1017}
     1018
     1019/**
     1020 * Evaluate whether FIC send action can continue.
     1021 *
     1022 * @param int         $docID
     1023 * @param int         $issuedDocumentID
     1024 * @param mixed       $issuedSendDocument
     1025 * @param string|null $invoiceSent
     1026 *
     1027 * @return array{allowed:bool,reason:string}
     1028 */
     1029function evaluateFicSendDecision(
     1030    int $docID,
     1031    int $issuedDocumentID,
     1032    $issuedSendDocument,
     1033    ?string $invoiceSent
     1034): array {
     1035    if ($issuedDocumentID <= 0) {
     1036        return ['allowed' => false, 'reason' => 'missing_issued_document'];
     1037    }
     1038
     1039    if (normalizeBooleanMeta($issuedSendDocument)) {
     1040        return ['allowed' => false, 'reason' => 'already_sent_fic'];
     1041    }
     1042
     1043    if ($docID <= 0) {
     1044        return ['allowed' => false, 'reason' => 'missing_doc_id'];
     1045    }
     1046
     1047    if ($docID !== $issuedDocumentID) {
     1048        return ['allowed' => false, 'reason' => 'doc_id_mismatch'];
     1049    }
     1050
     1051    if ('sent' === (string)$invoiceSent) {
     1052        return ['allowed' => false, 'reason' => 'already_sent_meta'];
     1053    }
     1054
     1055    return ['allowed' => true, 'reason' => 'ok'];
     1056}
     1057
     1058/**
     1059 * Evaluate whether FIC create action can continue.
     1060 *
     1061 * @param bool  $statusAllowed
     1062 * @param bool  $invoiceBlockedOnZero
     1063 * @param mixed $existingDocumentID
     1064 *
     1065 * @return array{allowed:bool,reason:string}
     1066 */
     1067function evaluateFicCreateDecision(
     1068    bool $statusAllowed,
     1069    bool $invoiceBlockedOnZero,
     1070    $existingDocumentID = null
     1071): array {
     1072    if (! $statusAllowed) {
     1073        return ['allowed' => false, 'reason' => 'status_not_allowed'];
     1074    }
     1075
     1076    if ($invoiceBlockedOnZero) {
     1077        return ['allowed' => false, 'reason' => 'total_zero_not_allowed'];
     1078    }
     1079
     1080    if (! empty($existingDocumentID)) {
     1081        return ['allowed' => false, 'reason' => 'already_created'];
     1082    }
     1083
     1084    return ['allowed' => true, 'reason' => 'ok'];
     1085}
     1086
     1087/**
     1088 * Evaluate behavior when a DB lock is required for an action.
     1089 *
     1090 * @param bool $lockAcquired
     1091 * @param bool $isBackgroundJob
     1092 *
     1093 * @return array{continue:bool,respond_error:bool,reason:string}
     1094 */
     1095function evaluateLockBusyDecision(bool $lockAcquired, bool $isBackgroundJob): array
     1096{
     1097    if ($lockAcquired) {
     1098        return [
     1099            'continue'      => true,
     1100            'respond_error' => false,
     1101            'reason'        => 'ok',
     1102        ];
     1103    }
     1104
     1105    if ($isBackgroundJob) {
     1106        return [
     1107            'continue'      => false,
     1108            'respond_error' => false,
     1109            'reason'        => 'lock_busy_background_skip',
     1110        ];
     1111    }
     1112
     1113    return [
     1114        'continue'      => false,
     1115        'respond_error' => true,
     1116        'reason'        => 'lock_busy_foreground_error',
     1117    ];
     1118}
     1119
     1120/**
     1121 * Map FIC send decision reason to normalized error metadata.
     1122 *
     1123 * @param string $reason
     1124 *
     1125 * @return array{code:int,message_key:string}
     1126 */
     1127function mapFicSendDecisionError(string $reason): array
     1128{
     1129    $map = [
     1130        'missing_issued_document' => ['code' => 422, 'message_key' => 'missing_issued_document'],
     1131        'missing_doc_id'          => ['code' => 422, 'message_key' => 'missing_doc_id'],
     1132        'doc_id_mismatch'         => ['code' => 422, 'message_key' => 'doc_id_mismatch'],
     1133        'already_sent_fic'        => ['code' => 422, 'message_key' => 'already_sent'],
     1134        'already_sent_meta'       => ['code' => 422, 'message_key' => 'already_sent'],
     1135    ];
     1136
     1137    if (isset($map[$reason])) {
     1138        return $map[$reason];
     1139    }
     1140
     1141    return ['code' => 422, 'message_key' => 'action_not_allowed'];
     1142}
     1143
     1144/**
     1145 * Map FIC create decision reason to normalized error metadata.
     1146 *
     1147 * @param string $reason
     1148 *
     1149 * @return array{code:int,message_key:string}
     1150 */
     1151function mapFicCreateDecisionError(string $reason): array
     1152{
     1153    $map = [
     1154        'status_not_allowed'     => ['code' => 501, 'message_key' => 'status_not_allowed'],
     1155        'total_zero_not_allowed' => ['code' => 501, 'message_key' => 'total_zero_not_allowed'],
     1156        'already_created'        => ['code' => 422, 'message_key' => 'already_created'],
     1157    ];
     1158
     1159    if (isset($map[$reason])) {
     1160        return $map[$reason];
     1161    }
     1162
     1163    return ['code' => 422, 'message_key' => 'action_not_allowed'];
     1164}
     1165
     1166/**
     1167 * Map lock-busy context to normalized error metadata.
     1168 *
     1169 * @param string $channel
     1170 * @param string $action
     1171 *
     1172 * @return array{code:int,message_key:string}
     1173 */
     1174function mapLockBusyError(string $channel, string $action): array
     1175{
     1176    $channel = strtolower(trim($channel));
     1177    $action  = strtolower(trim($action));
     1178    $key     = "{$channel}:{$action}";
     1179
     1180    $map = [
     1181        'fic:create_invoice'      => ['code' => 409, 'message_key' => 'action_skipped_create'],
     1182        'fic:send_invoice'        => ['code' => 409, 'message_key' => 'action_skipped_send'],
     1183        'sdi:create_and_send'     => ['code' => 409, 'message_key' => 'action_skipped_send_create'],
     1184        'peppol:create_and_send'  => ['code' => 409, 'message_key' => 'action_skipped_send_create'],
     1185        'aruba:upload_invoice'    => ['code' => 409, 'message_key' => 'action_skipped_upload'],
     1186        'sdi_pec:upload_invoice'  => ['code' => 409, 'message_key' => 'action_skipped_upload'],
     1187    ];
     1188
     1189    if (isset($map[$key])) {
     1190        return $map[$key];
     1191    }
     1192
     1193    return ['code' => 409, 'message_key' => 'action_skipped'];
     1194}
     1195
     1196/**
     1197 * Map SDI/PEPPOL native create/send precondition reason to normalized metadata.
     1198 *
     1199 * @param string $channel
     1200 * @param string $reason
     1201 *
     1202 * @return array{code:int,message_key:string}
     1203 */
     1204function mapNativeCreateAndSendPreconditionError(string $channel, string $reason): array
     1205{
     1206    $channel = strtolower(trim($channel));
     1207    $reason  = strtolower(trim($reason));
     1208
     1209    $map = [
     1210        'channel_mismatch'     => ['code' => 422, 'message_key' => 'action_not_allowed_channel'],
     1211        'integration_inactive' => ['code' => 422, 'message_key' => 'action_not_allowed'],
     1212        'action_invalid'       => ['code' => 422, 'message_key' => 'action_not_valid'],
     1213        'uuid_already_exists'  => ['code' => 422, 'message_key' => 'uuid_exists'],
     1214        'response_uuid_missing' => ['code' => 422, 'message_key' => 'response_uuid_missing'],
     1215    ];
     1216
     1217    if ('sdi' !== $channel && 'peppol' !== $channel) {
     1218        return ['code' => 422, 'message_key' => 'action_not_allowed'];
     1219    }
     1220
     1221    if (isset($map[$reason])) {
     1222        return $map[$reason];
     1223    }
     1224
     1225    return ['code' => 422, 'message_key' => 'action_not_allowed'];
     1226}
     1227
     1228/**
     1229 * Map Aruba/SDI-PEC upload precondition reason to normalized metadata.
     1230 *
     1231 * @param string $channel
     1232 * @param string $reason
     1233 *
     1234 * @return array{code:int,message_key:string}
     1235 */
     1236function mapUploadPreconditionError(string $channel, string $reason): array
     1237{
     1238    $channel = strtolower(trim($channel));
     1239    $reason  = strtolower(trim($reason));
     1240
     1241    if ('aruba' !== $channel && 'sdi_pec' !== $channel) {
     1242        return ['code' => 501, 'message_key' => 'upload_error'];
     1243    }
     1244
     1245    $map = [
     1246        'xml_invalid'             => ['code' => 501, 'message_key' => 'xml_invalid'],
     1247        'invalid_email_recipient' => ['code' => 501, 'message_key' => 'invalid_email_recipient'],
     1248        'upload_failed'           => ['code' => 501, 'message_key' => 'upload_failed'],
     1249        'empty_upload_filename'   => ['code' => 501, 'message_key' => 'empty_upload_filename'],
     1250    ];
     1251
     1252    if (isset($map[$reason])) {
     1253        return $map[$reason];
     1254    }
     1255
     1256    return ['code' => 501, 'message_key' => 'upload_error'];
     1257}
     1258
     1259/**
     1260 * Build Aruba upload payload in a deterministic format.
     1261 *
     1262 * @param string $xmlBase64
     1263 *
     1264 * @return array<string,mixed>
     1265 */
     1266function buildArubaUploadPayload(string $xmlBase64): array
     1267{
     1268    return [
     1269        'dataFile'        => $xmlBase64,
     1270        'credential'      => '',
     1271        'domain'          => '',
     1272        'senderPIVA'      => '',
     1273        'skipExtraSchema' => false,
     1274    ];
     1275}
     1276
     1277/**
     1278 * Build integration metadata for native create/send XML generation.
     1279 *
     1280 * @param string $channel
     1281 *
     1282 * @return array<string,mixed>
     1283 */
     1284function buildNativeCreateIntegrationMeta(string $channel): array
     1285{
     1286    $channel = strtolower(trim($channel));
     1287
     1288    if ('sdi' === $channel) {
     1289        return [
     1290            'integration' => [
     1291                'use'    => 'sdi-via-pop',
     1292                'action' => 'create',
     1293            ],
     1294        ];
     1295    }
     1296
     1297    if ('peppol' === $channel) {
     1298        return [
     1299            'integration' => [
     1300                'use'    => 'peppol-via-pop',
     1301                'action' => 'create',
     1302            ],
     1303        ];
     1304    }
     1305
     1306    return [];
     1307}
     1308
     1309/**
     1310 * Build stable FIC document display options.
     1311 *
     1312 * @return array<string,mixed>
     1313 */
     1314function buildFicDocumentDisplayOptions(): array
     1315{
     1316    return [
     1317        'show_totals'              => 'all',
     1318        'show_paypal_button'       => false,
     1319        'show_notification_button' => false,
     1320        'accompanying_invoice'     => false,
     1321        'show_payment_method'      => true,
     1322        'show_payments'            => true,
     1323    ];
     1324}
     1325
     1326/**
     1327 * Build FIC ei_data payload with optional fields.
     1328 *
     1329 * @param string      $odDate
     1330 * @param string      $invoiceDate
     1331 * @param string|null $creditNoteInvoiceNumber
     1332 * @param array|null  $bankData ['iban' => string, 'name' => string, 'beneficiary' => string]
     1333 *
     1334 * @return array<string,mixed>
     1335 */
     1336function buildFicEiDataPayload(
     1337    string $odDate,
     1338    string $invoiceDate,
     1339    ?string $creditNoteInvoiceNumber = null,
     1340    ?array $bankData = null
     1341): array {
     1342    $payload = [
     1343        'od_date'      => $odDate,
     1344        'invoice_date' => $invoiceDate,
     1345    ];
     1346
     1347    if (is_string($creditNoteInvoiceNumber) && '' !== trim($creditNoteInvoiceNumber)) {
     1348        $payload['invoice_number'] = $creditNoteInvoiceNumber;
     1349    }
     1350
     1351    if (is_array($bankData) && ! empty($bankData['iban'])) {
     1352        $payload['bank_iban']        = (string)$bankData['iban'];
     1353        $payload['bank_name']        = (string)($bankData['name'] ?? '');
     1354        $payload['bank_beneficiary'] = (string)($bankData['beneficiary'] ?? '');
     1355    }
     1356
     1357    return $payload;
     1358}
     1359
     1360/**
     1361 * Check if current execution mode is a background one.
     1362 *
     1363 * @param string|null $mode
     1364 * @param array       $backgroundModes
     1365 *
     1366 * @return bool
     1367 */
     1368function isBackgroundExecutionMode(?string $mode, array $backgroundModes = ['cronjob', 'feCronJobUploadRequest']): bool
     1369{
     1370    if (! is_string($mode) || '' === $mode) {
     1371        return false;
     1372    }
     1373
     1374    return in_array($mode, $backgroundModes, true);
     1375}
     1376
     1377/**
     1378 * Normalize meta/option values to boolean consistently.
     1379 *
     1380 * @param mixed $value
     1381 *
     1382 * @return bool
     1383 */
     1384function normalizeBooleanMeta($value): bool
     1385{
     1386    return filter_var($value, FILTER_VALIDATE_BOOLEAN);
     1387}
     1388
     1389/**
     1390 * Resolve integration document type from order type.
     1391 *
     1392 * @param string $orderType
     1393 *
     1394 * @return string
     1395 */
     1396function resolveDocumentTypeFromOrderType(string $orderType): string
     1397{
     1398    return 'shop_order' === $orderType ? 'invoice' : 'credit_note';
     1399}
     1400
     1401/**
     1402 * Check if current execution should return immediate (foreground) errors.
     1403 *
     1404 * @param string|null $mode
     1405 * @param array       $backgroundModes
     1406 *
     1407 * @return bool
     1408 */
     1409function shouldRespondWithImmediateError(
     1410    ?string $mode,
     1411    array $backgroundModes = ['cronjob', 'feCronJobUploadRequest']
     1412): bool {
     1413    return ! isBackgroundExecutionMode($mode, $backgroundModes);
     1414}
     1415
     1416/**
     1417 * Build UUID meta key for native integrations.
     1418 *
     1419 * @param string $channelPrefix
     1420 * @param string $documentType
     1421 *
     1422 * @return string
     1423 */
     1424function buildUuidMetaKey(string $channelPrefix, string $documentType): string
     1425{
     1426    $channelPrefix = trim($channelPrefix);
     1427    $documentType  = trim($documentType);
     1428
     1429    return "{$channelPrefix}-{$documentType}_uuid";
     1430}
     1431
     1432/**
     1433 * Build notifications meta key for native integrations.
     1434 *
     1435 * @param string $channelPrefix
     1436 * @param string $documentType
     1437 *
     1438 * @return string
     1439 */
     1440function buildNotificationsMetaKey(string $channelPrefix, string $documentType): string
     1441{
     1442    $channelPrefix = trim($channelPrefix);
     1443    $documentType  = trim($documentType);
     1444
     1445    return "{$channelPrefix}-{$documentType}_notifications";
     1446}
     1447
     1448/**
     1449 * Build meta key used to mark native timeout pending verification.
     1450 *
     1451 * @return string
     1452 */
     1453function buildNativeTimeoutPendingMetaKey(): string
     1454{
     1455    return '_native_timeout_pending_verification';
     1456}
     1457
     1458/**
     1459 * Build meta key for native timeout pending channel.
     1460 *
     1461 * @return string
     1462 */
     1463function buildNativeTimeoutPendingChannelMetaKey(): string
     1464{
     1465    return '_native_timeout_pending_channel';
     1466}
     1467
     1468/**
     1469 * Build meta key for native timeout pending timestamp.
     1470 *
     1471 * @return string
     1472 */
     1473function buildNativeTimeoutPendingTimestampMetaKey(): string
     1474{
     1475    return '_native_timeout_pending_at';
     1476}
     1477
     1478/**
     1479 * Build meta key for native timeout pending document type.
     1480 *
     1481 * @return string
     1482 */
     1483function buildNativeTimeoutPendingDocumentTypeMetaKey(): string
     1484{
     1485    return '_native_timeout_pending_document_type';
     1486}
     1487
     1488/**
     1489 * Resolve native channel meta prefix from logical channel name.
     1490 *
     1491 * @param string $channel
     1492 *
     1493 * @return string
     1494 */
     1495function resolveNativeChannelPrefix(string $channel): string
     1496{
     1497    $channel = strtolower(trim($channel));
     1498    if ('sdi' === $channel) {
     1499        return 'sdi_pop';
     1500    }
     1501    if ('peppol' === $channel) {
     1502        return 'peppol_pop';
     1503    }
     1504
     1505    return '';
     1506}
     1507
     1508/**
     1509 * Detect timeout-like failures in mixed error payloads (string/array/object).
     1510 *
     1511 * @param mixed $value
     1512 *
     1513 * @return bool
     1514 */
     1515function isLikelyTimeoutError($value): bool
     1516{
     1517    if (is_string($value)) {
     1518        $haystack = strtolower($value);
     1519        $needles  = [
     1520            'timed out',
     1521            'timeout',
     1522            'curl error 28',
     1523            'operation timed out',
     1524            'connection timed out',
     1525            'http request failed',
     1526        ];
     1527
     1528        foreach ($needles as $needle) {
     1529            if (false !== strpos($haystack, $needle)) {
     1530                return true;
     1531            }
     1532        }
     1533
     1534        return false;
     1535    }
     1536
     1537    if (is_array($value) || is_object($value)) {
     1538        $encoded = function_exists('wp_json_encode') ? wp_json_encode($value) : json_encode($value);
     1539        if (is_string($encoded) && '' !== $encoded) {
     1540            return isLikelyTimeoutError($encoded);
     1541        }
     1542    }
     1543
     1544    return false;
     1545}
     1546
     1547/**
     1548 * Extract readable native integration error detail from mixed raw payloads.
     1549 *
     1550 * @param mixed $raw
     1551 *
     1552 * @return string
     1553 */
     1554function extractNativeErrorDetail($raw): string
     1555{
     1556    $status = '';
     1557    $detail = '';
     1558    $title  = '';
     1559    $msg    = '';
     1560
     1561    if (is_object($raw)) {
     1562        $status = isset($raw->code) ? (string)$raw->code : '';
     1563        $msg    = isset($raw->message) && is_string($raw->message) ? trim($raw->message) : '';
     1564
     1565        if (isset($raw->data) && is_object($raw->data)) {
     1566            $status = '' !== $status ? $status : (isset($raw->data->status) ? (string)$raw->data->status : '');
     1567            $detail = isset($raw->data->detail) && is_string($raw->data->detail) ? trim($raw->data->detail) : '';
     1568            $title  = isset($raw->data->title) && is_string($raw->data->title) ? trim($raw->data->title) : '';
     1569        }
     1570    } elseif (is_array($raw)) {
     1571        $status = isset($raw['code']) ? (string)$raw['code'] : '';
     1572        $msg    = isset($raw['message']) && is_string($raw['message']) ? trim($raw['message']) : '';
     1573
     1574        if (isset($raw['data']) && is_array($raw['data'])) {
     1575            $status = '' !== $status ? $status : (isset($raw['data']['status']) ? (string)$raw['data']['status'] : '');
     1576            $detail = isset($raw['data']['detail']) && is_string($raw['data']['detail']) ? trim($raw['data']['detail']) : '';
     1577            $title  = isset($raw['data']['title']) && is_string($raw['data']['title']) ? trim($raw['data']['title']) : '';
     1578        }
     1579    }
     1580
     1581    $genericMessages = [
     1582        'risposta non valida',
     1583        'invalid response',
     1584        'an error occurred',
     1585    ];
     1586
     1587    $msgLower   = strtolower($msg);
     1588    $titleLower = strtolower($title);
     1589    foreach ($genericMessages as $genericMessage) {
     1590        if ($msgLower === $genericMessage) {
     1591            $msg = '';
     1592        }
     1593        if ($titleLower === $genericMessage) {
     1594            $title = '';
     1595        }
     1596    }
     1597
     1598    $bestText = $detail ?: ($msg ?: $title);
     1599    if ('' === $bestText) {
     1600        return '';
     1601    }
     1602
     1603    return '' !== $status ? "[{$status}] {$bestText}" : $bestText;
     1604}
     1605
     1606/**
     1607 * Minimal consistency check for marking invoice as sent using a UUID.
     1608 *
     1609 * @param mixed $uuid
     1610 *
     1611 * @return bool
     1612 */
     1613function canMarkInvoiceSentWithUuid($uuid): bool
     1614{
     1615    return is_string($uuid) && '' !== trim($uuid);
     1616}
     1617
     1618/**
     1619 * Minimal consistency check for marking invoice as sent using an upload filename.
     1620 *
     1621 * @param mixed $uploadFileName
     1622 *
     1623 * @return bool
     1624 */
     1625function canMarkInvoiceSentWithUploadFileName($uploadFileName): bool
     1626{
     1627    return is_string($uploadFileName) && '' !== trim($uploadFileName);
     1628}
     1629
     1630/**
     1631 * Minimal consistency check for marking invoice as sent in FIC flow.
     1632 *
     1633 * @param bool  $responseValid
     1634 * @param int   $docID
     1635 * @param mixed $alreadySentOnFic
     1636 *
     1637 * @return bool
     1638 */
     1639function canMarkInvoiceSentWithFicSend(bool $responseValid, int $docID, $alreadySentOnFic = false): bool
     1640{
     1641    return $responseValid &&
     1642           $docID > 0 &&
     1643           ! normalizeBooleanMeta($alreadySentOnFic);
    6871644}
    6881645
  • woopop-electronic-invoice-free/trunk/src/Functions/Utils.php

    r3409381 r3463326  
    11461146        $action = \WcElectronInvoice\Functions\filterInput($_POST, 'action') ?? null;
    11471147        $method = \WcElectronInvoice\Functions\filterInput($_POST, 'method') ?? null;
    1148         if (('createAndSendInvoice' === $action || 'createAndSendUblInvoice' === $action) && ('cronjob' === $method || 'ajax' === $method)) {
     1148        if (
     1149            ('createAndSendInvoice' === $action || 'createAndSendUblInvoice' === $action) &&
     1150            \WcElectronInvoice\Functions\isBackgroundExecutionMode($method, ['cronjob', 'ajax'])
     1151        ) {
    11491152            return $createXml->create($buildXml->xmlData);
    11501153        }
  • woopop-electronic-invoice-free/trunk/src/Functions/Webhooks.php

    r3341137 r3463326  
    152152    $action          = \WcElectronInvoice\Functions\filterInput($_POST, 'action') ?? null;
    153153    $method          = \WcElectronInvoice\Functions\filterInput($_POST, 'method') ?? null;
     154    $shouldRespond   = \WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob']);
    154155
    155156    if (! \WcElectronInvoice\Wizard::isUserLevelGte($level, 'growth') || ! $usePoPWebhookId) {
    156         if ('cronjob' !== $method) {
     157        if ($shouldRespond) {
    157158            wp_send_json_error([
    158159                'message' => esc_html__('Action not allowed.',
     
    166167    // I check the action parameter if it does not match I get an error
    167168    if ('sendWebhook' !== $action) {
    168         if ('cronjob' !== $method) {
     169        if ($shouldRespond) {
    169170            wp_send_json_error([
    170171                'message' => esc_html__('Action not valid.',
     
    195196    ) {
    196197
    197         if ('cronjob' !== $method) {
     198        if ($shouldRespond) {
    198199            wp_send_json_error([
    199200                'message' => sprintf(esc_html__('Invalid action: Webhook has already been sent, please delete "%s" data to resend',
     
    237238        ]));
    238239
    239         if ('cronjob' !== $method) {
     240        if ($shouldRespond) {
    240241            wp_send_json_error([
    241242                'message' => esc_html__('Webhook Response > Invalid Json.',
     
    264265                true) . "\n");
    265266
    266         if ('cronjob' !== $method) {
     267        if ($shouldRespond) {
    267268            wp_send_json_error([
    268269                'message' => $response->message,
     
    276277    }
    277278
    278     if ('cronjob' !== $method) {
     279    if ($shouldRespond) {
    279280        wp_send_json_success([
    280281            'message'  => $response->message,
     
    337338function webHookScheduledCron(
    338339    int $orderID,
    339     string $from = null,
    340     string $to = null,
     340    ?string $from = null,
     341    ?string $to = null,
    341342    string $provider = 'woocommerce'
    342343): bool {
  • woopop-electronic-invoice-free/trunk/src/Integrations.php

    r3460146 r3463326  
    99use function WcElectronInvoice\Functions\getActiveAddon;
    1010use function WcElectronInvoice\Functions\getListKeyValue;
     11use WcElectronInvoice\WooCommerce\Fields\GeneralFields;
    1112
    1213if (! defined('ABSPATH')) {
     
    1920class Integrations
    2021{
     22    public const CHANNEL_NONE   = 'none';
     23    public const CHANNEL_FIC    = 'fic';
     24    public const CHANNEL_ARUBA  = 'aruba';
     25    public const CHANNEL_SDIPEC = 'sdi_pec';
     26    public const CHANNEL_SDIPOP = 'sdi_pop';
     27    public const CHANNEL_PEPPOL = 'peppol';
     28
    2129    /**
    2230     * $addonPlugins
     
    9098
    9199        return false;
     100    }
     101
     102    /**
     103     * Resolve the active invoicing channel.
     104     *
     105     * The result is deterministic and exclusive:
     106     * one channel or "none".
     107     *
     108     * @return string
     109     */
     110    public static function resolveActiveInvoiceChannel(): string
     111    {
     112        $channels = [
     113            self::CHANNEL_FIC    => (bool)get_option('wc_el_inv-addon-fattureincloud'),
     114            self::CHANNEL_ARUBA  => (bool)get_option('wc_el_inv-addon-aruba'),
     115            self::CHANNEL_SDIPEC => (bool)get_option('wc_el_inv-addon-sdi-pec'),
     116            self::CHANNEL_SDIPOP => (bool)get_option('wc_el_inv-addon-sdi-via-pop', false) &&
     117                                    'active' === get_option('wc_el_inv-active_sdipop_integration'),
     118            self::CHANNEL_PEPPOL => (bool)get_option('wc_el_inv-addon-peppol-via-pop', false) &&
     119                                    'active' === get_option('wc_el_inv-active_peppol_integration'),
     120        ];
     121
     122        $defaultChannels = $channels;
     123        $channels        = apply_filters('wc_el_inv-invoice_channels_status', $channels);
     124        if (! is_array($channels)) {
     125            $channels = $defaultChannels;
     126        }
     127
     128        if (! empty($channels[self::CHANNEL_SDIPOP]) && ! empty($channels[self::CHANNEL_PEPPOL])) {
     129            $shopCountry = strtoupper(trim((string)GeneralFields::getGeneralInvoiceOptionCountryState()));
     130
     131            if (in_array($shopCountry, ['IT', 'SM'], true)) {
     132                $channels[self::CHANNEL_PEPPOL] = false;
     133            } elseif (defined('WC_EL_INV_UBL_COUNTRIES') &&
     134                      is_array(WC_EL_INV_UBL_COUNTRIES) &&
     135                      in_array($shopCountry, WC_EL_INV_UBL_COUNTRIES, true)
     136            ) {
     137                $channels[self::CHANNEL_SDIPOP] = false;
     138            } else {
     139                $channels[self::CHANNEL_PEPPOL] = false;
     140            }
     141        }
     142
     143        $defaultPriority = [
     144            self::CHANNEL_FIC,
     145            self::CHANNEL_ARUBA,
     146            self::CHANNEL_SDIPEC,
     147            self::CHANNEL_SDIPOP,
     148            self::CHANNEL_PEPPOL,
     149        ];
     150
     151        $priority = apply_filters('wc_el_inv-invoice_channel_priority', $defaultPriority);
     152
     153        if (! is_array($priority) || empty($priority)) {
     154            $priority = $defaultPriority;
     155        }
     156
     157        foreach ($priority as $channel) {
     158            if (! is_string($channel) || '' === $channel) {
     159                continue;
     160            }
     161            if (isset($channels[$channel]) && (bool)$channels[$channel]) {
     162                return (string)$channel;
     163            }
     164        }
     165
     166        return self::CHANNEL_NONE;
     167    }
     168
     169    /**
     170     * Check if the provided channel is the resolved active one.
     171     *
     172     * @param string $channel
     173     *
     174     * @return bool
     175     */
     176    public static function isInvoiceChannelActive(string $channel): bool
     177    {
     178        return self::resolveActiveInvoiceChannel() === $channel;
     179    }
     180
     181    /**
     182     * Check if at least one invoicing channel is active.
     183     *
     184     * @return bool
     185     */
     186    public static function hasAnyInvoiceChannelActive(): bool
     187    {
     188        return self::CHANNEL_NONE !== self::resolveActiveInvoiceChannel();
    92189    }
    93190
  • woopop-electronic-invoice-free/trunk/src/InvoiceApi.php

    r3409381 r3463326  
    3737
    3838        return (object)[
    39             'active_plugin_integration' => $integrationPluginActive,
     39            'active_plugin_integration' => (bool) $integrationPluginActive,
    4040            'active_integration'        => 'active' === $sdiViaPOP,
    4141            'active_signature'          => 'active' === $sdiViaPOPSignature,
     
    167167
    168168        // Check if uuid exist
    169         $uuid = $order->get_meta('sdi_pop-invoice_uuid');
     169        $uuid = $order->get_meta(\WcElectronInvoice\Functions\buildUuidMetaKey('sdi_pop', 'invoice'));
    170170        if ($uuid) {
    171171            // Log
     
    251251        $checkSent = $order->get_meta("_invoice_sent", true);
    252252        // Check if uuid exist
    253         $uuid = $order->get_meta('sdi_pop-invoice_uuid');
     253        $uuid = $order->get_meta(\WcElectronInvoice\Functions\buildUuidMetaKey('sdi_pop', 'invoice'));
    254254        // Check WC_Order instance
    255255        $wcOrderClass = \WcElectronInvoice\Functions\wcOrderClassName($order, '\WC_Order');
     
    331331        }
    332332
    333         $uuid = \WcElectronInvoice\Functions\getPostMeta("sdi_pop-invoice_uuid", null, $id, true, 'order',
     333        $uuid = \WcElectronInvoice\Functions\getPostMeta(\WcElectronInvoice\Functions\buildUuidMetaKey('sdi_pop',
     334                'invoice'), null, $id, true, 'order',
    334335            $provider) ?: null;
    335336
     
    337338            $provider) ?: null;
    338339
    339         $notificationsData = \WcElectronInvoice\Functions\getPostMeta("sdi_pop-invoice_notifications", null, $id, true,
     340        $notificationsData = \WcElectronInvoice\Functions\getPostMeta(\WcElectronInvoice\Functions\buildNotificationsMetaKey('sdi_pop',
     341                'invoice'), null, $id, true,
    340342            'order',
    341343            $provider) ?: null;
     
    344346            'order',
    345347            $provider) ?: null;
     348        $timeoutPending = \WcElectronInvoice\Functions\normalizeBooleanMeta(
     349            \WcElectronInvoice\Functions\getPostMeta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingMetaKey(),
     350                null,
     351                $id,
     352                true,
     353                'order',
     354                $provider)
     355        );
     356        $timeoutChannel = (string)(\WcElectronInvoice\Functions\getPostMeta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingChannelMetaKey(),
     357                null,
     358                $id,
     359                true,
     360                'order',
     361                $provider) ?: '');
     362        $timeoutDocType = (string)(\WcElectronInvoice\Functions\getPostMeta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingDocumentTypeMetaKey(),
     363                null,
     364                $id,
     365                true,
     366                'order',
     367                $provider) ?: 'invoice');
     368        $orderNumberInvoice = (string)(\WcElectronInvoice\Functions\getPostMeta('order_number_invoice',
     369                null,
     370                $id,
     371                true,
     372                'order',
     373                $provider) ?: '');
    346374
    347375        $send          = __('Send invoice', WC_EL_INV_TEXTDOMAIN);
     
    352380        $output .= '<div class="sdi_pop_actions">';
    353381        $output .= sprintf('<hr><h4>%s:</h4>', __('SdI native actions', WC_EL_INV_TEXTDOMAIN));
     382        if ($timeoutPending && 'sdi' === $timeoutChannel) {
     383            $output .= sprintf(
     384                '<div class="pop-notice notice-warning timeout-native-pending"><p>%s</p><p>%s%s</p></div>',
     385                esc_html__('Send not confirmed due to provider timeout. Do not resend: verify with POP support and enter document UUID.',
     386                    WC_EL_INV_TEXTDOMAIN),
     387                esc_html__('Invoice number: ', WC_EL_INV_TEXTDOMAIN),
     388                esc_html('' !== $orderNumberInvoice ? $orderNumberInvoice : 'N/A')
     389            );
     390            $output .= sprintf(
     391                '<input type="text" class="native-timeout-uuid-input" style="display:block;width:100%%;min-width:100%%;margin:0 0 8px 0;" id="native_timeout_uuid_%1$s" placeholder="%2$s" />',
     392                esc_attr($id),
     393                esc_attr__('Enter verified UUID', WC_EL_INV_TEXTDOMAIN)
     394            );
     395            $output .= sprintf(
     396                '<a href="javascript:;" data-provider="%1$s" data-id="%2$s" data-channel="sdi" data-document-type="%3$s" class="api-action confirm-timeout-uuid button button-primary" id="api_confirm_timeout_uuid_%2$s">%4$s</a>',
     397                esc_attr($provider),
     398                esc_attr($id),
     399                esc_attr($timeoutDocType),
     400                esc_html__('Confirm UUID', WC_EL_INV_TEXTDOMAIN)
     401            );
     402            $output .= sprintf(
     403                '<a href="javascript:;" data-provider="%1$s" data-id="%2$s" data-channel="sdi" class="api-action cancel-timeout-pending button button-secondary" id="api_cancel_timeout_%2$s">%3$s</a>',
     404                esc_attr($provider),
     405                esc_attr($id),
     406                esc_html__('Cancel timeout', WC_EL_INV_TEXTDOMAIN)
     407            );
     408            $output .= '</div>';
     409
     410            return $output;
     411        }
     412
    354413        if ($uuid) {
    355414            // Created
     
    440499        $method                   = \WcElectronInvoice\Functions\filterInput($_POST, 'method') ?? null;
    441500
     501        if (! \WcElectronInvoice\Integrations::isInvoiceChannelActive(\WcElectronInvoice\Integrations::CHANNEL_SDIPOP)) {
     502            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     503                $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('sdi',
     504                    'channel_mismatch');
     505                wp_send_json_error([
     506                    'message' => esc_html__('Action not allowed: another integration channel is active.',
     507                        WC_EL_INV_TEXTDOMAIN),
     508                    'code'    => $preconditionError['code'],
     509                ]);
     510            }
     511
     512            return;
     513        }
     514
    442515        if (! \WcElectronInvoice\Wizard::isUserLevelGte($level, 'growth') ||
    443516            (! $getSdiPOPIntegrationData->active_plugin_integration ||
    444517             ! $getSdiPOPIntegrationData->active_integration)
    445518        ) {
    446             if ('cronjob' !== $method) {
     519            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     520                $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('sdi',
     521                    'integration_inactive');
    447522                wp_send_json_error([
    448523                    'message' => esc_html__('Action not allowed.',
    449524                        WC_EL_INV_TEXTDOMAIN),
     525                    'code'    => $preconditionError['code'],
    450526                ]);
    451527            }
     
    456532        // I check the action parameter if it does not match I get an error
    457533        if ('createAndSendInvoice' !== $action) {
    458             if ('cronjob' !== $method) {
     534            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     535                $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('sdi',
     536                    'action_invalid');
    459537                wp_send_json_error([
    460538                    'message' => esc_html__('Action not valid.',
    461539                        WC_EL_INV_TEXTDOMAIN),
     540                    'code'    => $preconditionError['code'],
    462541                ]);
    463542            }
     
    467546
    468547        $order   = OrderQuery::instance()->getProviderOrder((int)$id, $provider);
    469         $type    = $order->get_type() === 'shop_order' ? 'invoice' : 'credit_note';
    470         $metaKey = "sdi_pop-{$type}_uuid";
     548        $type    = \WcElectronInvoice\Functions\resolveDocumentTypeFromOrderType((string)$order->get_type());
     549        $metaKey = \WcElectronInvoice\Functions\buildUuidMetaKey('sdi_pop', $type);
    471550
    472551        $uuid = $order->get_meta($metaKey);
     
    476555            \WcElectronInvoice\Functions\log("createAndSendInvoice > UUID exist: {$uuid}" . "\n");
    477556
    478             if ('cronjob' !== $method) {
     557            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     558                $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('sdi',
     559                    'uuid_already_exists');
    479560                wp_send_json_error([
    480561                    'message' => esc_html__("createAndSendInvoice - UUID exist: {$uuid}",
    481562                        WC_EL_INV_TEXTDOMAIN),
     563                    'code'    => $preconditionError['code'],
    482564                ]);
    483565            }
     
    486568        }
    487569
    488         add_filter('wc_el_inv-params_filter_before_api_request', function ($params) {
    489             $params['integration'] = [
    490                 'use'    => 'sdi-via-pop',
    491                 'action' => 'create',
    492             ];
    493 
    494             return $params;
    495         });
    496 
    497         $data     = getXmlFromOrder($order);
    498         $response = json_decode($data);
    499 
    500         if (json_last_error() !== JSON_ERROR_NONE || ! is_object($response)) {
    501             \WcElectronInvoice\Functions\log("createAndSendInvoice > check json response: Invalid response from the XML generator:" . "\n" . print_r($data,
    502                     true) . "\n");
    503 
    504             if ('cronjob' !== $method) {
     570        $lockKey = \WcElectronInvoice\Functions\buildDbLockKey('sdi', 'create_and_send', (int)$order->get_id(),
     571            (string)$provider, $metaKey);
     572        $lockDecision = \WcElectronInvoice\Functions\evaluateLockBusyDecision(
     573            \WcElectronInvoice\Functions\acquireDbLock($lockKey, 0),
     574            \WcElectronInvoice\Functions\isBackgroundExecutionMode($method, ['cronjob'])
     575        );
     576        if (! $lockDecision['continue']) {
     577            if ($lockDecision['respond_error']) {
     578                $lockError = \WcElectronInvoice\Functions\mapLockBusyError('sdi', 'create_and_send');
    505579                wp_send_json_error([
    506                     'message' => esc_html__('createAndSendInvoice > check json response: Invalid response from the XML generator.',
     580                    'message' => esc_html__('Action skipped: another send/create process is already running for this order.',
    507581                        WC_EL_INV_TEXTDOMAIN),
    508                     'raw'     => $data,
     582                    'code'    => $lockError['code'],
    509583                ]);
    510584            }
     
    513587        }
    514588
    515         if (
    516             ! property_exists($response, 'message') ||
    517             ! property_exists($response, 'data') ||
    518             ! property_exists($response, 'xml_string')
    519         ) {
    520             \WcElectronInvoice\Functions\log("createAndSendInvoice > check json property: Invalid response from the XML generator:" . "\n" . print_r($response,
    521                     true) . "\n");
    522 
    523             if ('cronjob' !== $method) {
    524                 wp_send_json_error([
    525                     'message' => esc_html__('createAndSendInvoice > check json property: Invalid response from the XML generator.',
    526                         WC_EL_INV_TEXTDOMAIN),
    527                     'check'   => 'json property',
    528                     'raw'     => $response,
    529                 ]);
    530             }
    531 
    532             return;
    533         }
    534 
    535         if (! $response->success) {
    536             \WcElectronInvoice\Functions\log("createAndSendInvoice > check success: Invalid response from the XML generator" . "\n" . print_r($response,
    537                     true) . "\n");
    538 
    539             if ('cronjob' !== $method) {
    540                 wp_send_json_error([
    541                     'message' => esc_html__('createAndSendInvoice > check success: Invalid response from the XML generator.',
    542                         WC_EL_INV_TEXTDOMAIN),
    543                     'check'   => 'success',
    544                     'raw'     => $response,
    545                 ]);
    546             }
    547 
    548             return;
    549         }
    550 
    551         if ('credit_note' === $type && $order->get_parent_id()) {
    552             $parent = OrderQuery::instance()->getProviderOrder((int)$order->get_parent_id(), $provider);
    553             if ($parent) {
    554                 $parent->update_meta($metaKey, $response->data->uuid);
    555             }
    556         } else {
    557             $order->update_meta($metaKey, $response->data->uuid);
    558         }
    559 
    560         $order->update_meta('_invoice_sent', 'sent');
    561 
    562         if ('cronjob' !== $method) {
     589        try {
     590            add_filter('wc_el_inv-params_filter_before_api_request', function ($params) {
     591                $integrationMeta = \WcElectronInvoice\Functions\buildNativeCreateIntegrationMeta('sdi');
     592                if (! empty($integrationMeta['integration'])) {
     593                    $params['integration'] = $integrationMeta['integration'];
     594                }
     595
     596                return $params;
     597            });
     598
     599            $data     = getXmlFromOrder($order);
     600            $response = json_decode($data);
     601            if (\WcElectronInvoice\Functions\isLikelyTimeoutError($data)) {
     602                self::markNativeTimeoutPending($order, 'sdi', $type);
     603            }
     604
     605            if (json_last_error() !== JSON_ERROR_NONE || ! is_object($response)) {
     606                if (\WcElectronInvoice\Functions\isLikelyTimeoutError($data)) {
     607                    self::markNativeTimeoutPending($order, 'sdi', $type);
     608                }
     609                \WcElectronInvoice\Functions\log("createAndSendInvoice > check json response: Invalid response from the XML generator:" . "\n" . print_r($data,
     610                        true) . "\n");
     611
     612                if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     613                    wp_send_json_error([
     614                        'message' => esc_html__('createAndSendInvoice > check json response: Invalid response from the XML generator.',
     615                            WC_EL_INV_TEXTDOMAIN),
     616                        'raw'     => $data,
     617                    ]);
     618                }
     619
     620                return;
     621            }
     622
     623            if (
     624                ! property_exists($response, 'message') ||
     625                ! property_exists($response, 'data') ||
     626                ! property_exists($response, 'xml_string')
     627            ) {
     628                if (\WcElectronInvoice\Functions\isLikelyTimeoutError($response)) {
     629                    self::markNativeTimeoutPending($order, 'sdi', $type);
     630                }
     631                \WcElectronInvoice\Functions\log("createAndSendInvoice > check json property: Invalid response from the XML generator:" . "\n" . print_r($response,
     632                        true) . "\n");
     633
     634                if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     635                    wp_send_json_error([
     636                        'message' => esc_html__('createAndSendInvoice > check json property: Invalid response from the XML generator.',
     637                            WC_EL_INV_TEXTDOMAIN),
     638                        'check'   => 'json property',
     639                        'raw'     => $response,
     640                    ]);
     641                }
     642
     643                return;
     644            }
     645
     646            if (! $response->success) {
     647                if (\WcElectronInvoice\Functions\isLikelyTimeoutError($response)) {
     648                    self::markNativeTimeoutPending($order, 'sdi', $type);
     649                }
     650                \WcElectronInvoice\Functions\log("createAndSendInvoice > check success: Invalid response from the XML generator" . "\n" . print_r($response,
     651                        true) . "\n");
     652
     653                if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     654                    $message = esc_html__('createAndSendInvoice > check success: Invalid response from the XML generator.',
     655                        WC_EL_INV_TEXTDOMAIN);
     656                    $detail  = \WcElectronInvoice\Functions\extractNativeErrorDetail($response);
     657                    if ('' !== $detail) {
     658                        $message .= ' ' . $detail;
     659                    }
     660                    wp_send_json_error([
     661                        'message' => $message,
     662                        'check'   => 'success',
     663                        'raw'     => $response,
     664                    ]);
     665                }
     666
     667                return;
     668            }
     669
     670            $responseUuid = property_exists($response->data, 'uuid') ? (string)$response->data->uuid : '';
     671            if (! \WcElectronInvoice\Functions\canMarkInvoiceSentWithUuid($responseUuid)) {
     672                \WcElectronInvoice\Functions\log("createAndSendInvoice > check uuid: Invalid response from the XML generator" . "\n" . print_r($response,
     673                        true) . "\n");
     674
     675                if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     676                    $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('sdi',
     677                        'response_uuid_missing');
     678                    $message           = esc_html__('createAndSendInvoice > check uuid: Invalid response from the XML generator.',
     679                        WC_EL_INV_TEXTDOMAIN);
     680                    $detail            = \WcElectronInvoice\Functions\extractNativeErrorDetail($response);
     681                    if ('' !== $detail) {
     682                        $message .= ' ' . $detail;
     683                    }
     684                    wp_send_json_error([
     685                        'message' => $message,
     686                        'check'   => 'uuid',
     687                        'raw'     => $response,
     688                        'code'    => $preconditionError['code'],
     689                    ]);
     690                }
     691
     692                return;
     693            }
     694
     695            if ('credit_note' === $type && $order->get_parent_id()) {
     696                $parent = OrderQuery::instance()->getProviderOrder((int)$order->get_parent_id(), $provider);
     697                if ($parent) {
     698                    $parent->update_meta($metaKey, $responseUuid);
     699                }
     700            } else {
     701                $order->update_meta($metaKey, $responseUuid);
     702            }
     703
     704            if (\WcElectronInvoice\Functions\canMarkInvoiceSentWithUuid($responseUuid)) {
     705                $order->update_meta('_invoice_sent', 'sent');
     706                self::clearNativeTimeoutPending($order);
     707            }
     708        } finally {
     709            \WcElectronInvoice\Functions\releaseDbLock($lockKey);
     710        }
     711
     712        if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
    563713            wp_send_json_success([
    564714                'message'  => $response->message,
    565715                'provider' => $provider,
    566716                'order_id' => $id,
    567                 'uuid'     => property_exists($response->data, 'uuid') ? $response->data->uuid : null,
     717                'uuid'     => $responseUuid,
    568718                'xml'      => $response->xml_string,
    569719            ]);
     
    573723
    574724        // Notify
    575         if ($uuid = property_exists($response->data, 'uuid') ? $response->data->uuid : null) {
     725        if ($uuid = $responseUuid) {
    576726            $time          = self::jobTiming('notify');
    577727            $nextScheduled = wp_next_scheduled(
     
    628778             ! $getSdiPOPIntegrationData->active_integration)
    629779        ) {
    630             if ('cronjob' !== $method) {
     780            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
    631781                wp_send_json_error([
    632782                    'message' => esc_html__('Action not allowed.',
     
    640790        // I check the action parameter if it does not match I get an error
    641791        if ('notificationsInvoice' !== $action) {
    642             if ('cronjob' !== $method) {
     792            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
    643793                wp_send_json_error([
    644794                    'message' => esc_html__('Action not valid.',
     
    651801
    652802        $order             = OrderQuery::instance()->getProviderOrder($id, $provider);
    653         $notificationsMeta = $order->get_meta('sdi_pop-invoice_notifications');
     803        $notificationsMeta = $order->get_meta(\WcElectronInvoice\Functions\buildNotificationsMetaKey('sdi_pop',
     804                'invoice'));
    654805
    655806        if ('' === $uuid) {
    656807            \WcElectronInvoice\Functions\log("notificationsInvoice - check uuid: Missing uuid parameter" . "\n");
    657808
    658             if ('cronjob' !== $method) {
     809            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
    659810                wp_send_json_error([
    660811                    'message' => esc_html__('notificationsInvoice - check uuid: Missing uuid parameter',
     
    670821            \WcElectronInvoice\Functions\log("notificationsInvoice - check notifications for uuid: Exist notifications for uuid" . "\n");
    671822
    672             if ('cronjob' !== $method) {
     823            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
    673824                wp_send_json_error([
    674825                    'message'            => esc_html__('notificationsInvoice - check notifications for uuid: Exist notifications for uuid',
     
    8921043                ];
    8931044
    894                 if ('cronjob' === $method) {
     1045                if (\WcElectronInvoice\Functions\isBackgroundExecutionMode($method, ['cronjob'])) {
    8951046                    // If no notification is present, I reschedule the cron for the request
    8961047                    $time          = self::jobTiming('notify');
     
    9111062
    9121063        if (! empty($notificationsMeta)) {
    913             $order->update_meta('sdi_pop-invoice_notifications', $notificationsMeta);
    914         }
    915 
    916         if ('cronjob' !== $method) {
     1064            $order->update_meta(\WcElectronInvoice\Functions\buildNotificationsMetaKey('sdi_pop', 'invoice'),
     1065                $notificationsMeta);
     1066        }
     1067
     1068        if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
    9171069            wp_send_json_success([
    9181070                'message'           => $message,
     
    9621114             ! $getSdiPOPIntegrationData->active_integration)
    9631115        ) {
    964             if ('cronjob' !== $method) {
     1116            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
    9651117                wp_send_json_error([
    9661118                    'message' => esc_html__('Action not allowed.',
     
    9741126        // I check the action parameter if it does not match I get an error
    9751127        if ('preserveInvoice' !== $action) {
    976             if ('cronjob' !== $method) {
     1128            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
    9771129                wp_send_json_error([
    9781130                    'message' => esc_html__('Action not valid.',
     
    9891141            \WcElectronInvoice\Functions\log("check uuid > Missing uuid parameter" . "\n");
    9901142
    991             if ('cronjob' !== $method) {
     1143            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
    9921144                wp_send_json_error([
    9931145                    'message' => esc_html__('Missing uuid parameter', WC_EL_INV_TEXTDOMAIN),
     
    10111163         */
    10121164        if ($getSdiPOPIntegrationData->active_storage) {
    1013             $notificationsData = \WcElectronInvoice\Functions\getPostMeta("sdi_pop-invoice_notifications", null, $id,
     1165            $notificationsData = \WcElectronInvoice\Functions\getPostMeta(\WcElectronInvoice\Functions\buildNotificationsMetaKey('sdi_pop',
     1166                    'invoice'), null, $id,
    10141167                true,
    10151168                'order',
     
    10841237
    10851238        // Update notifications meta data
    1086         $notificationsStored = $order->get_meta('sdi_pop-invoice_notifications');
     1239        $notificationsStored = $order->get_meta(\WcElectronInvoice\Functions\buildNotificationsMetaKey('sdi_pop',
     1240                'invoice'));
    10871241        if (! empty($notificationsMeta)) {
    10881242            $notificationsMeta = array_merge($notificationsStored, $notificationsMeta);
    1089             $order->update_meta('sdi_pop-invoice_notifications', $notificationsMeta);
    1090         }
    1091 
    1092         if ('cronjob' !== $method) {
     1243            $order->update_meta(\WcElectronInvoice\Functions\buildNotificationsMetaKey('sdi_pop', 'invoice'),
     1244                $notificationsMeta);
     1245        }
     1246
     1247        if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
    10931248            wp_send_json_success([
    10941249                'message'           => $message,
     
    11151270        return $preserve;
    11161271    }
     1272
     1273    /**
     1274     * Confirm native (SDI/PEPPOL) timeout-pending send by manual UUID input.
     1275     *
     1276     * @return void
     1277     */
     1278    public static function confirmNativeSendWithUuid()
     1279    {
     1280        $provider = sanitize_text_field(\WcElectronInvoice\Functions\filterInput($_POST, 'provider') ?? '');
     1281        $id       = sanitize_text_field(\WcElectronInvoice\Functions\filterInput($_POST, 'id') ?? '');
     1282        $channel  = sanitize_text_field(\WcElectronInvoice\Functions\filterInput($_POST, 'channel') ?? '');
     1283        $uuid     = sanitize_text_field(\WcElectronInvoice\Functions\filterInput($_POST, 'uuid') ?? '');
     1284        $action   = \WcElectronInvoice\Functions\filterInput($_POST, 'action') ?? null;
     1285
     1286        if ('confirmNativeSendWithUuid' !== $action) {
     1287            wp_send_json_error([
     1288                'message' => esc_html__('Action not valid.', WC_EL_INV_TEXTDOMAIN),
     1289            ]);
     1290
     1291            return;
     1292        }
     1293
     1294        if (! in_array($channel, ['sdi', 'peppol'], true)) {
     1295            wp_send_json_error([
     1296                'message' => esc_html__('Action not valid.', WC_EL_INV_TEXTDOMAIN),
     1297            ]);
     1298
     1299            return;
     1300        }
     1301
     1302        if (! \WcElectronInvoice\Functions\canMarkInvoiceSentWithUuid($uuid)) {
     1303            wp_send_json_error([
     1304                'message' => esc_html__('UUID is required.', WC_EL_INV_TEXTDOMAIN),
     1305            ]);
     1306
     1307            return;
     1308        }
     1309
     1310        $order = OrderQuery::instance()->getProviderOrder((int)$id, $provider);
     1311        if (! $order) {
     1312            wp_send_json_error([
     1313                'message' => esc_html__('Order not found.', WC_EL_INV_TEXTDOMAIN),
     1314            ]);
     1315
     1316            return;
     1317        }
     1318
     1319        $pending = \WcElectronInvoice\Functions\normalizeBooleanMeta(
     1320            $order->get_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingMetaKey())
     1321        );
     1322        $pendingChannel = (string)$order->get_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingChannelMetaKey());
     1323        if (! $pending || $pendingChannel !== $channel) {
     1324            wp_send_json_error([
     1325                'message' => esc_html__('Timeout verification is not pending for this channel.', WC_EL_INV_TEXTDOMAIN),
     1326            ]);
     1327
     1328            return;
     1329        }
     1330
     1331        $documentType  = \WcElectronInvoice\Functions\resolveDocumentTypeFromOrderType((string)$order->get_type());
     1332        $channelPrefix = \WcElectronInvoice\Functions\resolveNativeChannelPrefix($channel);
     1333        if ('' === $channelPrefix) {
     1334            wp_send_json_error([
     1335                'message' => esc_html__('Action not valid.', WC_EL_INV_TEXTDOMAIN),
     1336            ]);
     1337
     1338            return;
     1339        }
     1340
     1341        $metaKey = \WcElectronInvoice\Functions\buildUuidMetaKey($channelPrefix, $documentType);
     1342        if ('credit_note' === $documentType && $order->get_parent_id()) {
     1343            $parent = OrderQuery::instance()->getProviderOrder((int)$order->get_parent_id(), $provider);
     1344            if ($parent) {
     1345                $parent->update_meta($metaKey, $uuid);
     1346                $parent->save();
     1347            }
     1348        } else {
     1349            $order->update_meta($metaKey, $uuid);
     1350        }
     1351
     1352        $order->update_meta('_invoice_sent', 'sent');
     1353        $order->update_meta('_invoice_sent_timestamp', time());
     1354        self::clearNativeTimeoutPending($order);
     1355        $order->save();
     1356
     1357        wp_send_json_success([
     1358            'message' => esc_html__('UUID saved and invoice marked as sent.', WC_EL_INV_TEXTDOMAIN),
     1359            'uuid'    => $uuid,
     1360            'channel' => $channel,
     1361        ]);
     1362    }
     1363
     1364    /**
     1365     * Cancel native timeout pending state and re-enable send action.
     1366     *
     1367     * @return void
     1368     */
     1369    public static function cancelNativeTimeoutPending()
     1370    {
     1371        $provider = sanitize_text_field(\WcElectronInvoice\Functions\filterInput($_POST, 'provider') ?? '');
     1372        $id       = sanitize_text_field(\WcElectronInvoice\Functions\filterInput($_POST, 'id') ?? '');
     1373        $channel  = sanitize_text_field(\WcElectronInvoice\Functions\filterInput($_POST, 'channel') ?? '');
     1374        $action   = \WcElectronInvoice\Functions\filterInput($_POST, 'action') ?? null;
     1375
     1376        if ('cancelNativeTimeoutPending' !== $action) {
     1377            wp_send_json_error([
     1378                'message' => esc_html__('Action not valid.', WC_EL_INV_TEXTDOMAIN),
     1379            ]);
     1380
     1381            return;
     1382        }
     1383
     1384        if (! in_array($channel, ['sdi', 'peppol'], true)) {
     1385            wp_send_json_error([
     1386                'message' => esc_html__('Action not valid.', WC_EL_INV_TEXTDOMAIN),
     1387            ]);
     1388
     1389            return;
     1390        }
     1391
     1392        $order = OrderQuery::instance()->getProviderOrder((int)$id, $provider);
     1393        if (! $order) {
     1394            wp_send_json_error([
     1395                'message' => esc_html__('Order not found.', WC_EL_INV_TEXTDOMAIN),
     1396            ]);
     1397
     1398            return;
     1399        }
     1400
     1401        $pending = \WcElectronInvoice\Functions\normalizeBooleanMeta(
     1402            $order->get_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingMetaKey())
     1403        );
     1404        $pendingChannel = (string)$order->get_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingChannelMetaKey());
     1405        if (! $pending || $pendingChannel !== $channel) {
     1406            wp_send_json_error([
     1407                'message' => esc_html__('Timeout verification is not pending for this channel.', WC_EL_INV_TEXTDOMAIN),
     1408            ]);
     1409
     1410            return;
     1411        }
     1412
     1413        self::clearNativeTimeoutPending($order);
     1414        $order->save();
     1415
     1416        wp_send_json_success([
     1417            'message' => esc_html__('Timeout status removed. Send action is enabled again.', WC_EL_INV_TEXTDOMAIN),
     1418            'channel' => $channel,
     1419        ]);
     1420    }
     1421
     1422    /**
     1423     * Store timeout pending verification metadata on order.
     1424     *
     1425     * @param mixed  $order
     1426     * @param string $channel
     1427     * @param string $documentType
     1428     *
     1429     * @return void
     1430     */
     1431    public static function markNativeTimeoutPending($order, string $channel, string $documentType = 'invoice'): void
     1432    {
     1433        if (! is_object($order) || ! method_exists($order, 'update_meta')) {
     1434            return;
     1435        }
     1436
     1437        $channel = strtolower(trim($channel));
     1438        if (! in_array($channel, ['sdi', 'peppol'], true)) {
     1439            return;
     1440        }
     1441
     1442        $order->update_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingMetaKey(), 'yes');
     1443        $order->update_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingChannelMetaKey(), $channel);
     1444        $order->update_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingDocumentTypeMetaKey(), $documentType);
     1445        $order->update_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingTimestampMetaKey(), time());
     1446        $order->save();
     1447    }
     1448
     1449    /**
     1450     * Clear timeout pending verification metadata on order.
     1451     *
     1452     * @param mixed $order
     1453     *
     1454     * @return void
     1455     */
     1456    public static function clearNativeTimeoutPending($order): void
     1457    {
     1458        if (! is_object($order) || ! method_exists($order, 'delete_meta')) {
     1459            return;
     1460        }
     1461
     1462        $order->delete_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingMetaKey());
     1463        $order->delete_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingChannelMetaKey());
     1464        $order->delete_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingDocumentTypeMetaKey());
     1465        $order->delete_meta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingTimestampMetaKey());
     1466        $order->save();
     1467    }
    11171468}
  • woopop-electronic-invoice-free/trunk/src/PeppolApi.php

    r3444920 r3463326  
    3737
    3838        return (object)[
    39             'active_plugin_integration' => $integrationPluginActive ?? null,
    40             'peppol_integration'        => $peppolIntegration ?? null,
     39            'active_plugin_integration' => (bool) $integrationPluginActive,
     40            'peppol_integration'        => 'active' === $peppolIntegration,
    4141            'identifier_scheme'         => $idScheme ?? null,
    4242            'identifier_value'          => $idValue ?? null,
     
    9090
    9191        // Check if uuid exist
    92         $uuid = $order->get_meta('peppol_pop-invoice_uuid');
     92        $uuid = $order->get_meta(\WcElectronInvoice\Functions\buildUuidMetaKey('peppol_pop', 'invoice'));
    9393        if ($uuid) {
    9494            // Log
     
    173173        $checkSent = $order->get_meta("_invoice_sent", true);
    174174        // Check if uuid exist
    175         $uuid = $order->get_meta('peppol_pop-invoice_uuid');
     175        $uuid = $order->get_meta(\WcElectronInvoice\Functions\buildUuidMetaKey('peppol_pop', 'invoice'));
    176176        // Check WC_Order instance
    177177        $wcOrderClass = \WcElectronInvoice\Functions\wcOrderClassName($order, '\WC_Order');
     
    279279        $checkSent = \WcElectronInvoice\Functions\getPostMeta("_invoice_sent", null, $id, true, 'order',
    280280            $provider) ?: null;
    281 
    282         $uuid = \WcElectronInvoice\Functions\getPostMeta("peppol_pop-invoice_uuid", null, $id, true, 'order',
     281        $timeoutPending = \WcElectronInvoice\Functions\normalizeBooleanMeta(
     282            \WcElectronInvoice\Functions\getPostMeta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingMetaKey(),
     283                null,
     284                $id,
     285                true,
     286                'order',
     287                $provider)
     288        );
     289        $timeoutChannel = (string)(\WcElectronInvoice\Functions\getPostMeta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingChannelMetaKey(),
     290                null,
     291                $id,
     292                true,
     293                'order',
     294                $provider) ?: '');
     295        $timeoutDocType = (string)(\WcElectronInvoice\Functions\getPostMeta(\WcElectronInvoice\Functions\buildNativeTimeoutPendingDocumentTypeMetaKey(),
     296                null,
     297                $id,
     298                true,
     299                'order',
     300                $provider) ?: 'invoice');
     301        $orderNumberInvoice = (string)(\WcElectronInvoice\Functions\getPostMeta('order_number_invoice',
     302                null,
     303                $id,
     304                true,
     305                'order',
     306                $provider) ?: '');
     307
     308        $uuid = \WcElectronInvoice\Functions\getPostMeta(\WcElectronInvoice\Functions\buildUuidMetaKey('peppol_pop',
     309                'invoice'), null, $id, true, 'order',
    283310            $provider) ?: null;
    284311
    285312        $output .= '<div class="peppol_pop_actions">';
    286313        $output .= sprintf('<hr><h4>%s:</h4>', __('PEPPOL native actions', WC_EL_INV_TEXTDOMAIN));
     314        if ($timeoutPending && 'peppol' === $timeoutChannel) {
     315            $output .= sprintf(
     316                '<div class="pop-notice notice-warning timeout-native-pending"><p>%s</p><p>%s%s</p></div>',
     317                esc_html__('Send not confirmed due to provider timeout. Do not resend: verify with POP support and enter document UUID.',
     318                    WC_EL_INV_TEXTDOMAIN),
     319                esc_html__('Invoice number: ', WC_EL_INV_TEXTDOMAIN),
     320                esc_html('' !== $orderNumberInvoice ? $orderNumberInvoice : 'N/A')
     321            );
     322            $output .= sprintf(
     323                '<input type="text" class="native-timeout-uuid-input" style="display:block;width:100%%;min-width:100%%;margin:0 0 8px 0;" id="native_timeout_uuid_%1$s" placeholder="%2$s" />',
     324                esc_attr($id),
     325                esc_attr__('Enter verified UUID', WC_EL_INV_TEXTDOMAIN)
     326            );
     327            $output .= sprintf(
     328                '<a href="javascript:;" data-provider="%1$s" data-id="%2$s" data-channel="peppol" data-document-type="%3$s" class="api-action confirm-timeout-uuid button button-primary" id="api_confirm_timeout_uuid_%2$s">%4$s</a>',
     329                esc_attr($provider),
     330                esc_attr($id),
     331                esc_attr($timeoutDocType),
     332                esc_html__('Confirm UUID', WC_EL_INV_TEXTDOMAIN)
     333            );
     334            $output .= sprintf(
     335                '<a href="javascript:;" data-provider="%1$s" data-id="%2$s" data-channel="peppol" class="api-action cancel-timeout-pending button button-secondary" id="api_cancel_timeout_%2$s">%3$s</a>',
     336                esc_attr($provider),
     337                esc_attr($id),
     338                esc_html__('Cancel timeout', WC_EL_INV_TEXTDOMAIN)
     339            );
     340            $output .= '</div>';
     341
     342            return $output;
     343        }
     344
    287345        if ($uuid || 'sent' === $checkSent) {
    288346            // Created and notify
     
    321379        $method                      = \WcElectronInvoice\Functions\filterInput($_POST, 'method') ?? null;
    322380
     381        if (! \WcElectronInvoice\Integrations::isInvoiceChannelActive(\WcElectronInvoice\Integrations::CHANNEL_PEPPOL)) {
     382            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     383                $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('peppol',
     384                    'channel_mismatch');
     385                wp_send_json_error([
     386                    'message' => esc_html__('Action not allowed: another integration channel is active.',
     387                        WC_EL_INV_TEXTDOMAIN),
     388                    'code'    => $preconditionError['code'],
     389                ]);
     390            }
     391
     392            return;
     393        }
     394
    323395        if (! \WcElectronInvoice\Wizard::isUserLevelGte($level, 'growth') ||
    324396            (! $getPEPPOLPOPIntegrationData->active_plugin_integration ||
    325397             ! $getPEPPOLPOPIntegrationData->peppol_integration)
    326398        ) {
    327             if ('cronjob' !== $method) {
     399            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     400                $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('peppol',
     401                    'integration_inactive');
    328402                wp_send_json_error([
    329403                    'message' => esc_html__('Action not allowed.',
    330404                        WC_EL_INV_TEXTDOMAIN),
     405                    'code'    => $preconditionError['code'],
    331406                ]);
    332407            }
     
    337412        // I check the action parameter if it does not match I get an error
    338413        if ('createAndSendUblInvoice' !== $action) {
    339             if ('cronjob' !== $method) {
     414            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     415                $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('peppol',
     416                    'action_invalid');
    340417                wp_send_json_error([
    341418                    'message' => esc_html__('Action not valid.',
    342419                        WC_EL_INV_TEXTDOMAIN),
     420                    'code'    => $preconditionError['code'],
    343421                ]);
    344422            }
     
    348426
    349427        $order = OrderQuery::instance()->getProviderOrder((int)$id, $provider);
    350         $type  = $order->get_type() === 'shop_order' ? 'invoice' : 'credit_note';
    351 
    352         $metaKey = "peppol_pop-{$type}_uuid";
     428        $type  = \WcElectronInvoice\Functions\resolveDocumentTypeFromOrderType((string)$order->get_type());
     429
     430        $metaKey = \WcElectronInvoice\Functions\buildUuidMetaKey('peppol_pop', $type);
    353431
    354432        $uuid = $order->get_meta($metaKey);
     
    358436            \WcElectronInvoice\Functions\log("createAndSendUblInvoice - UUID exist: {$uuid}" . "\n");
    359437
    360             if ('cronjob' !== $method) {
     438            if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     439                $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('peppol',
     440                    'uuid_already_exists');
    361441                wp_send_json_error([
    362442                    'message' => esc_html__("createAndSendUblInvoice - UUID exist: {$uuid}",
    363443                        WC_EL_INV_TEXTDOMAIN),
     444                    'code'    => $preconditionError['code'],
    364445                ]);
    365446            }
     
    368449        }
    369450
    370         add_filter('wc_el_inv-params_filter_before_api_request', function ($params) {
    371             $params['integration'] = [
    372                 'use'    => 'peppol-via-pop',
    373                 'action' => 'create',
    374             ];
    375 
    376             return $params;
    377         });
    378 
    379         $data     = getXmlFromOrder($order);
    380         $response = json_decode($data);
    381 
    382         if (json_last_error() !== JSON_ERROR_NONE || ! is_object($response)) {
    383             \WcElectronInvoice\Functions\log("createAndSendUblInvoice - check json response: Invalid response from the XML generator:" . "\n" . print_r($data,
    384                     true) . "\n");
    385 
    386             if ('cronjob' !== $method) {
     451        $lockKey = \WcElectronInvoice\Functions\buildDbLockKey('peppol', 'create_and_send', (int)$order->get_id(),
     452            (string)$provider, $metaKey);
     453        $lockDecision = \WcElectronInvoice\Functions\evaluateLockBusyDecision(
     454            \WcElectronInvoice\Functions\acquireDbLock($lockKey, 0),
     455            \WcElectronInvoice\Functions\isBackgroundExecutionMode($method, ['cronjob'])
     456        );
     457        if (! $lockDecision['continue']) {
     458            if ($lockDecision['respond_error']) {
     459                $lockError = \WcElectronInvoice\Functions\mapLockBusyError('peppol', 'create_and_send');
    387460                wp_send_json_error([
    388                     'message' => esc_html__('createAndSendUblInvoice - check json response: Invalid response from the XML generator.',
     461                    'message' => esc_html__('Action skipped: another send/create process is already running for this order.',
    389462                        WC_EL_INV_TEXTDOMAIN),
    390                     'raw'     => $data,
     463                    'code'    => $lockError['code'],
    391464                ]);
    392465            }
     
    395468        }
    396469
    397         if (
    398             ! property_exists($response, 'message') ||
    399             ! property_exists($response, 'data') ||
    400             ! property_exists($response, 'xml_string')
    401         ) {
    402             \WcElectronInvoice\Functions\log("createAndSendUblInvoice - check json property: Invalid response from the XML generator:" . "\n" . print_r($response,
    403                     true) . "\n");
    404 
    405             if ('cronjob' !== $method) {
    406                 wp_send_json_error([
    407                     'message' => esc_html__('createAndSendUblInvoice - check json property: Invalid response from the XML generator.',
    408                         WC_EL_INV_TEXTDOMAIN),
    409                     'check'   => 'json property',
    410                     'raw'     => $response,
    411                 ]);
    412             }
    413 
    414             return;
    415         }
    416 
    417         $success       = filter_var($response->success ?? false, FILTER_VALIDATE_BOOLEAN);
    418         $code          = $response->code ?? 0;
    419         $responseError = false;
    420         $uuid          = ! empty($response->data->uuid) ? $response->data->uuid : null;
    421         if (! $success && $code !== 202) {
    422             $message = esc_html__('createAndSendUblInvoice - check success: Invalid response from the XML generator.',
    423                 WC_EL_INV_TEXTDOMAIN);
    424 
    425             // not uuid
    426             if (! $uuid) {
    427                 $responseError = true;
    428             }
    429 
    430             // Violations
    431             if (! empty($response->data->violations)) {
    432                 $messageViolations = '';
    433                 $responseError     = true;
    434                 $violations        = (array)$response->data->violations;
    435                 if (! empty($violations)) {
    436                     $messages = array_map(function ($violation) {
    437                         return sprintf(
    438                             "Message: %s\nPropertyPath: %s\nCode: [%s]\n",
    439                             $violation->message ?? '',
    440                             $violation->propertyPath ?? '',
    441                             $violation->code ?? '',
    442                         );
    443                     }, $violations);
    444 
    445                     $messageViolations = "\n\n" . implode("\n", $messages);
    446                 }
    447 
    448                 $message = $message . $messageViolations;
    449             }
    450 
    451             if ($responseError) {
     470        try {
     471            add_filter('wc_el_inv-params_filter_before_api_request', function ($params) {
     472                $integrationMeta = \WcElectronInvoice\Functions\buildNativeCreateIntegrationMeta('peppol');
     473                if (! empty($integrationMeta['integration'])) {
     474                    $params['integration'] = $integrationMeta['integration'];
     475                }
     476
     477                return $params;
     478            });
     479
     480            $data     = getXmlFromOrder($order);
     481            $response = json_decode($data);
     482            if (\WcElectronInvoice\Functions\isLikelyTimeoutError($data)) {
     483                \WcElectronInvoice\InvoiceApi::markNativeTimeoutPending($order, 'peppol', $type);
     484            }
     485
     486            if (json_last_error() !== JSON_ERROR_NONE || ! is_object($response)) {
     487                if (\WcElectronInvoice\Functions\isLikelyTimeoutError($data)) {
     488                    \WcElectronInvoice\InvoiceApi::markNativeTimeoutPending($order, 'peppol', $type);
     489                }
     490                \WcElectronInvoice\Functions\log("createAndSendUblInvoice - check json response: Invalid response from the XML generator:" . "\n" . print_r($data,
     491                        true) . "\n");
     492
     493                if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     494                    wp_send_json_error([
     495                        'message' => esc_html__('createAndSendUblInvoice - check json response: Invalid response from the XML generator.',
     496                            WC_EL_INV_TEXTDOMAIN),
     497                        'raw'     => $data,
     498                    ]);
     499                }
     500
     501                return;
     502            }
     503
     504            if (
     505                ! property_exists($response, 'message') ||
     506                ! property_exists($response, 'data') ||
     507                ! property_exists($response, 'xml_string')
     508            ) {
     509                if (\WcElectronInvoice\Functions\isLikelyTimeoutError($response)) {
     510                    \WcElectronInvoice\InvoiceApi::markNativeTimeoutPending($order, 'peppol', $type);
     511                }
     512                \WcElectronInvoice\Functions\log("createAndSendUblInvoice - check json property: Invalid response from the XML generator:" . "\n" . print_r($response,
     513                        true) . "\n");
     514
     515                if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     516                    wp_send_json_error([
     517                        'message' => esc_html__('createAndSendUblInvoice - check json property: Invalid response from the XML generator.',
     518                            WC_EL_INV_TEXTDOMAIN),
     519                        'check'   => 'json property',
     520                        'raw'     => $response,
     521                    ]);
     522                }
     523
     524                return;
     525            }
     526
     527            $success       = filter_var($response->success ?? false, FILTER_VALIDATE_BOOLEAN);
     528            $code          = $response->code ?? 0;
     529            $responseError = false;
     530            $uuid          = ! empty($response->data->uuid) ? $response->data->uuid : null;
     531            if (! $success && $code !== 202) {
     532                $message = esc_html__('createAndSendUblInvoice - check success: Invalid response from the XML generator.',
     533                    WC_EL_INV_TEXTDOMAIN);
     534                $detail  = \WcElectronInvoice\Functions\extractNativeErrorDetail($response);
     535                if ('' !== $detail) {
     536                    $message .= ' ' . $detail;
     537                }
     538
     539                // not uuid
     540                if (! $uuid) {
     541                    $responseError = true;
     542                }
     543
     544                // Violations
     545                if (! empty($response->data->violations)) {
     546                    $messageViolations = '';
     547                    $responseError     = true;
     548                    $violations        = (array)$response->data->violations;
     549                    if (! empty($violations)) {
     550                        $messages = array_map(function ($violation) {
     551                            return sprintf(
     552                                "Message: %s\nPropertyPath: %s\nCode: [%s]\n",
     553                                $violation->message ?? '',
     554                                $violation->propertyPath ?? '',
     555                                $violation->code ?? '',
     556                            );
     557                        }, $violations);
     558
     559                        $messageViolations = "\n\n" . implode("\n", $messages);
     560                    }
     561
     562                    $message = $message . $messageViolations;
     563                }
     564
     565                if ($responseError) {
     566                    if (\WcElectronInvoice\Functions\isLikelyTimeoutError($response)) {
     567                        \WcElectronInvoice\InvoiceApi::markNativeTimeoutPending($order, 'peppol', $type);
     568                    }
     569                    \WcElectronInvoice\Functions\log(
     570                        "createAndSendUblInvoice - check success: Invalid response from the XML generator" . "\n" .
     571                        wp_json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"
     572                    );
     573
     574                    if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     575                        wp_send_json_error([
     576                            'message' => $message,
     577                            'check'   => 'success',
     578                            'raw'     => $response,
     579                        ]);
     580                    }
     581
     582                    return;
     583                }
     584            }
     585
     586            if (! \WcElectronInvoice\Functions\canMarkInvoiceSentWithUuid($uuid)) {
    452587                \WcElectronInvoice\Functions\log(
    453                     "createAndSendUblInvoice - check success: Invalid response from the XML generator" . "\n" .
     588                    "createAndSendUblInvoice - check uuid: Invalid response from the XML generator" . "\n" .
    454589                    wp_json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"
    455590                );
    456591
    457                 if ('cronjob' !== $method) {
     592                if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
     593                    $preconditionError = \WcElectronInvoice\Functions\mapNativeCreateAndSendPreconditionError('peppol',
     594                        'response_uuid_missing');
    458595                    wp_send_json_error([
    459                         'message' => $message,
    460                         'check'   => 'success',
     596                        'message' => esc_html__('createAndSendUblInvoice - check uuid: Invalid response from the XML generator.',
     597                            WC_EL_INV_TEXTDOMAIN),
     598                        'check'   => 'uuid',
    461599                        'raw'     => $response,
     600                        'code'    => $preconditionError['code'],
    462601                    ]);
    463602                }
     
    465604                return;
    466605            }
    467         }
    468 
    469         if ('credit_note' === $type && $order->get_parent_id()) {
    470             $parent = OrderQuery::instance()->getProviderOrder((int)$order->get_parent_id(), $provider);
    471             if ($parent) {
    472                 $parent->update_meta($metaKey, $response->data->uuid);
    473             }
    474         } else {
    475             $order->update_meta($metaKey, $response->data->uuid);
    476         }
    477 
    478         $order->update_meta('_invoice_sent', 'sent');
    479 
    480         if ('cronjob' !== $method) {
     606
     607            if ('credit_note' === $type && $order->get_parent_id()) {
     608                $parent = OrderQuery::instance()->getProviderOrder((int)$order->get_parent_id(), $provider);
     609                if ($parent) {
     610                    $parent->update_meta($metaKey, $uuid);
     611                }
     612            } else {
     613                $order->update_meta($metaKey, $uuid);
     614            }
     615
     616            if (\WcElectronInvoice\Functions\canMarkInvoiceSentWithUuid($uuid)) {
     617                $order->update_meta('_invoice_sent', 'sent');
     618                \WcElectronInvoice\InvoiceApi::clearNativeTimeoutPending($order);
     619            }
     620        } finally {
     621            \WcElectronInvoice\Functions\releaseDbLock($lockKey);
     622        }
     623
     624        if (\WcElectronInvoice\Functions\shouldRespondWithImmediateError($method, ['cronjob'])) {
    481625            wp_send_json_success([
    482626                'message'  => $response->message,
  • woopop-electronic-invoice-free/trunk/src/WooCommerce/Fields/InvoiceFields.php

    r3460902 r3463326  
    933933                    if ($statusSdiViaPop->active_integration) {
    934934                        // remove uuid and old notifications
    935                         $order->delete_meta('sdi_pop-invoice_uuid');
    936                         $order->delete_meta('sdi_pop-invoice_notifications');
     935                        $order->delete_meta(\WcElectronInvoice\Functions\buildUuidMetaKey('sdi_pop', 'invoice'));
     936                        $order->delete_meta(\WcElectronInvoice\Functions\buildNotificationsMetaKey('sdi_pop', 'invoice'));
    937937                    }
    938938
     
    940940                    if ($statusSdiViaPop->peppol_integration) {
    941941                        // remove uuid
    942                         $order->delete_meta('peppol_pop-invoice_uuid');
     942                        $order->delete_meta(\WcElectronInvoice\Functions\buildUuidMetaKey('peppol_pop', 'invoice'));
    943943                    }
    944944                }
     
    11971197            $invoiceNumberView = $number;
    11981198        }
     1199        $invoiceNumberDisplay = $invoiceNumberView ?? ($number ?: $invoiceNumber);
    11991200
    12001201        echo '<div class="wc_el_inv__general-order invoice-date">';
     
    12041205            esc_html__('Number', WC_EL_INV_TEXTDOMAIN),
    12051206            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    1206             $invoiceNumberView ?? $invoiceNumber,
     1207            $invoiceNumberDisplay,
    12071208            esc_html__('Date', WC_EL_INV_TEXTDOMAIN),
    12081209            esc_html__($dateCompleted->format('Y-m-d - H:i'))
  • woopop-electronic-invoice-free/trunk/vendor/composer/installed.php

    r3460902 r3463326  
    44        'pretty_version' => 'dev-main',
    55        'version' => 'dev-main',
    6         'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     6        'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    1414            'pretty_version' => 'dev-main',
    1515            'version' => 'dev-main',
    16             'reference' => '09194be380794c1eca19b4ba745800898f9d0391',
     16            'reference' => 'cfce0241f8a719c93e1d6926bfcd08373376efc0',
    1717            'type' => 'library',
    1818            'install_path' => __DIR__ . '/../../',
Note: See TracChangeset for help on using the changeset viewer.