# FiloBlu_Rma (`filoblu/mod2-extrma`)

Estensione del modulo RMA di Magento Commerce (`Magento_Rma`). Aggiunge il flusso di **cambio
taglia**, il flusso **easy return**, la mappatura dei motivi di reso, le policy di reso e una serie
di endpoint REST usati dagli storefront headless.

```
modulo Magento    FiloBlu_Rma          setup_version 2.2.12
namespace         FiloBlu\Rma\         (psr-4 sulla radice del repo)
sequence          Magento_Rma, Magento_Sales, FiloBlu_Refilo
autore originale  Jordy De Rosa
```

> **Stato di questo documento**: parziale. È stato scritto il 04.08.2026 a valle di un'analisi
> concentrata sul **flusso di cambio taglia** (ticket MNTFOS-1337) e documenta bene quella parte;
> easy return, policy di reso, etichette di spedizione, griglie admin e blocchi frontend sono
> nominati ma non approfonditi. Quello che c'è è stato verificato sul codice e sul DB di
> antonymorato; quello che manca è segnato in fondo. Integrare liberamente. Esteso il 10.08.2026 con
> la trappola 10 (mail di credit memo dell'ordine padre, EFFCNT-1646).

Dimensioni indicative: ~206 file PHP, di cui Model 55, Block 42, Plugin 32, Helper 14, Handlers 9,
Controller 7, Setup 6, Console 4, Observer 3. Nessuna suite di test: sotto `Test/` c'è solo
materiale `Draft/`.

---

## Flusso cambio taglia

L'idea: il cliente apre un reso scegliendo come risoluzione *Change Size* e indicando la taglia
che vuole al posto di quella resa. Il modulo crea **un nuovo ordine Magento** con metodo di
pagamento `changesizepay` e stato iniziale `waiting_for_rma_receiving`; da lì l'ordine segue il
normale flusso di evasione.

### Punto di ingresso

`Plugin/Model/RmaModelRmaProcessor::afterSaveRma` (plugin su `Magento\Rma\Model\Rma::saveRma`)
orchestra tutto il post-salvataggio del reso, in quest'ordine:

1. `EasyReturnHandler::saveEasyReturnFields`
2. `RmaItemResolutionHistoryHelper::saveRmaItemsData`
3. `setChangeSizeAttributeValuesBeforeCreateChangeSizeOrder` (vedi trappola 2)
4. `ChangeSizeItemsHelper::saveData`
5. **`CreateChangeSizeOrderHandler::checkIfCreateChangeSizeOrder`** ← creazione ordine
6. `RmaEmailHandler::sendWrongOrDamagedRmaEmail`
7. `RmaItemStatusUpdateHandler::shouldUpdateStatus`
8. `RmaEmailHandler::sendRmaEmail`
9. `RmaPoliciesHandler::checkPolicies`

### Condizioni perché l'ordine venga creato

Tutte in `Handlers/CreateChangeSizeOrderHandler::checkIfCreateChangeSizeOrder`:

1. `isChangeSizeEnabled($websiteId)` — flag di configurazione a livello **website**
2. item del reso in stato `pending`
3. `resolution` dell'item = opzione *Change Size*
4. nessun ordine di cambio taglia già collegato all'item
5. **valore `change_size` presente sull'item**, cioè il `product_id` della taglia di destinazione

Se manca la 1 il metodo esce lasciando un warning (`ChangeSize disabilitato per il website N:
nessun ordine generato per RMA X`, vedi §Log per il file giusto dove cercarlo); se manca una delle
altre esce e **non produce nulla di visibile**. La 1 e la 5 sono quelle che in produzione hanno
causato resi senza ordine.

### Strategia di creazione

`filoblu_disablerma_section/rma_group/rma_change_size_order_creation_strategy`:

- `each` — un ordine per ogni item richiesto (valore usato su antonymorato)
- `total` — un solo ordine con tutti gli item

### Recupero manuale

```bash
bin/magento filoblu:rma:generate:missing -i <rma_increment_id> -s <sku_nuova_taglia>
```

Genera l'ordine per un reso che non l'ha prodotto. **Non passa dal controllo
`isChangeSizeEnabled`**, quindi funziona anche su website dove la feature è disabilitata, e accetta
la SKU esplicitamente (`$rmaItem->getChangeSize() ?: $newProduct->getId()`), quindi funziona anche
quando il valore `change_size` non è mai stato salvato. Vedi le trappole 3, 4 e 5 prima di usarlo.

Altri comandi:

| Comando | Scopo |
|---|---|
| `filoblu:rma:generate:missing` | genera ordini di cambio taglia mancanti |
| `filoblu:easyrma:generate:missing` | genera dati easy return mancanti |
| `filoblu:sync-changesize-order-and-rma` | riallinea ordine di cambio taglia e reso |
| `filoblu:rma:restore` | ripristina un reso su un ordine |

---

## Configurazione

Sezione admin: **Stores → Configuration → FiloBlu → RMA Configuration**
(`filoblu_disablerma_section/rma_group/...`).

| Path | Note |
|---|---|
| `rma_change_size` | abilita il cambio taglia. **Scope website**, default `0`. Un website senza riga esplicita eredita il default, cioè disabilitato |
| `changesize_option_mapping` | `option_id` dell'opzione *Change Size* dell'attributo `resolution` |
| `rma_change_sizeorder_can_use_changesize` | se un ordine di cambio taglia possa a sua volta essere cambiato |
| `rma_change_size_order_creation_strategy` | `each` \| `total` |
| `add_shipping_costs` | se addebitare la spedizione sull'ordine di cambio taglia |
| `allowed_change_size_order_status` | stati verso cui un ordine di cambio taglia può transitare liberamente (vedi trappola 6) |
| `rma_auto_authorized` | autorizzazione automatica del reso |
| `sales/rma_reason/reason` | mappatura `brand_reason` (option_id del motivo) → `standard_reason` (`wrong_size`, `damaged_product`, `changed_my_mind`, …) |
| `payment/changesizepay/*` | metodo di pagamento offline dell'ordine di cambio taglia. Su antonymorato `active=1` a scope default e `allowspecific=0` |
| `sales_email/creditmemo/force_sending` | **solo nelle versioni < 2.9.176**: forza `send_email = 1` a ogni salvataggio di credit memo da backend. Vedi trappola 10 |

Il cambio taglia viene offerto **solo** se il motivo selezionato mappa su `wrong_size`
(`RmaHelper::STANDARD_REASON_ID_FOR_CHANGESIZE`) — vedi `ChangeSize::isSelectedReasonWrongSize`.

---

## Dati

### Tabelle del modulo

| Tabella | Contenuto |
|---|---|
| `filoblu_rma_items` | `rma_item_id → order_id`: è l'extension attribute `change_size_order_id`, cioè il collegamento fra item del reso e ordine di cambio taglia. Gestita da `Model/Rma/RmaExtensionAttributesRepository` |
| `filoblu_change_size_items` | dettaglio della richiesta: ordine e item padre, reso, SKU vecchia e nuova, ordine di cambio taglia generato |
| `filoblu_easy_rma_items` | dati indirizzo/telefono della richiesta easy return (chiave `rma_entity_id`) |
| `filoblu_rma_items_resolution_history` | storico dei cambi di risoluzione per item |
| `filoblu_change_size_matrixrate` | tariffe di spedizione del cambio taglia. **Su antonymorato è vuota per tutti i website** |

> In `filoblu_change_size_items` i due campi `parent_order_id` e `parent_order_item` hanno nomi
> simili ma appartengono a spazi di ID diversi: `parent_order_id` è un `sales_order.entity_id`
> (scritto con `$rma->getOrderId()`), `parent_order_item` è un `sales_order_item.item_id` (scritto
> con `$item->getOrderItemId()`), entrambi in `ChangeSizeItemsHelper::saveData`. Confonderli non
> produce nessun errore, solo una query che non torna niente: è esattamente la trappola 10.

### Tabelle core coinvolte

`magento_rma`, `magento_rma_item_entity` e i suoi EAV
`magento_rma_item_entity_{int,varchar,text,decimal,datetime}`.

> Attenzione: `resolution`, `condition`, `reason` e `change_size` sono **attributi EAV**, non
> colonne di `magento_rma_item_entity`. Una query sull'item deve fare join sulle tabelle EAV: i
> primi tre sono `int` e stanno in `magento_rma_item_entity_int`, `change_size` è `varchar` e sta
> in `magento_rma_item_entity_varchar`.

Gli `attribute_id` e gli `option_id` dipendono invece dall'ordine di installazione, quindi non hanno
alcun valore fuori dall'installazione da cui li si legge: vanno sempre ricavati dal DB su cui si sta
lavorando. Nemmeno gli ambienti di uno stesso progetto coincidono — su morato l'opzione
*Change Size* dell'attributo `resolution` è `460` in produzione e `459` in stage. Nel codice
l'`option_id` di *Change Size* si legge da `changesize_option_mapping`, mai hardcodato.

---

## Endpoint REST

`etc/webapi.xml` — 7 route, **tutte dichiarate `anonymous`**.

| Metodo | URL | Servizio |
|---|---|---|
| POST | `/V1/filoblu-rma/changesize` | `getSizes` — taglie alternative disponibili |
| POST | `/V1/filoblu-rma/getChangeSizeEnabledProducts` | `getChangeSizeEnabledProducts` |
| GET | `/V1/filoblu-rma/returnsAttributeWithAlternativesMetadata/itemId/:itemId/qty/:qty` | `getAllAttributesWithAlternativesMetadata` — motivi e risoluzioni ammessi |
| POST | `/V1/filoblu-rma/getlabel` | `getLabel` |
| POST | `/V1/filoblu-rma/estimatereturnshippingcost` | `estimateReturnShippingCost` |
| GET | `/V1/filoblu-rma/getReturnableItems/:orderId` | `getReturnableItems` |
| POST | `/V1/filoblu-rma/getreturntypes` | `getReturnTypes` |

Essendo route anonime, **i parametri in ingresso non sono attendibili**: vedi trappola 1.

### Chi le consuma (verificato su antonymorato, 04.08.2026)

Lo storefront headless non chiama Magento direttamente: passa da **honey**
(`beehive-api/honeyv2`), che nei suoi datasource usa **solo 4** delle 8 route —
`returnsAttributeWithAlternativesMetadata`, `getReturnableItems`, `estimatereturnshippingcost`,
`getreturntypes`. `getSizes` e `getChangeSizeEnabledProducts` **non sono chiamate da honey**: le
taglie arrivano già annidate nelle opzioni della risoluzione (`ChangeSize.php`,
`$option->setOptions($sizes)` dentro `getAllowedResolutions`), quindi una sola chiamata restituisce
risoluzioni e taglie insieme.

Lo store code è propagato correttamente su tutta la catena: lo storefront imposta l'header `store`
(`refilo-storefront/src/client/system/api.js:55-58`, da `window.INITIAL_STATE.store.store`), honey lo riceve come
`@param.header.string('store')` e lo mette nell'URL `/rest/{store}/V1/filoblu-rma/...`. Sul percorso
del cliente **non esiste** quindi una chiamata "senza store code" che cada sul website di default:
la divergenza della trappola 1 nasce dallo store view di navigazione, non da un contesto mancante.

Lo storefront però non è l'unico consumer. Il JS della creazione RMA di admin
(`view/adminhtml/web/js/form/changesize.js`) chiama `/rest/V1/filoblu-rma/changesize` e
`/rest/V1/filoblu-rma/returnsAttributeWithAlternativesMetadata/...` **senza store code**, quindi
quelle richieste si risolvono sullo store di default: è un percorso in cui il contesto è davvero
assente, e il motivo per cui il website va derivato dall'ordine anche negli endpoint che sembrano
serviti solo allo storefront.

## Observer

| Evento | Observer |
|---|---|
| `sales_order_save_before` | `Observer/UpdateStatusVerify` |
| `checkout_submit_all_after` | `Observer/Sales/Order/FreeReturnShipping` |
| `filoblu_override_theoric_shipping_amount` | `Observer/OverrideTheoricShippingAmount` |

## Log

Logger dedicato `FiloBlu\Rma\Logger\Logger` (estende Monolog) →
**`var/log/filoblu_extrma.log`**, canale `filoblu_extrma_logger`. È il primo posto dove guardare.

Non tutto il modulo lo usa, però: le classi che si fanno iniettare `Psr\Log\LoggerInterface` invece
di `FiloBlu\Rma\Logger\Logger` scrivono nei log standard di Magento. `CreateChangeSizeOrderHandler`
è una di queste, quindi il warning sul cambio taglia disabilitato finisce in **`var/log/system.log`**
sul canale `report`, non in `filoblu_extrma.log`.

Su `system.log` serve **`grep -a`**: il file contiene byte che grep considera binari e senza `-a`
risponde solo `binary file matches`, senza stampare la riga trovata.

---

## Trappole note

### 1. Il website va risolto dall'ordine, non dal contesto

`rma_change_size` è a scope **website**. Storicamente lettura e scrittura lo risolvevano in modo
diverso: le opzioni mostrate al cliente lo prendevano dal contesto della richiesta
(`storeManager->getWebsite()`), la creazione dell'ordine dallo store dell'ordine.

Il contesto della richiesta è lo store view che il cliente sta **navigando**, che non è
necessariamente quello in cui ha ordinato. Un reso aperto navigando uno store view diverso da quello
dell'ordine — ad esempio ordine su `gb`, dove il cambio taglia è spento, e navigazione su `eu`,
`int` o `roe`, dove è attivo — otteneva la lista delle risoluzioni dal website di navigazione:
*Change Size* veniva offerto, il reso salvato con quella risoluzione, e nessun ordine creato perché
la creazione valutava il website dell'ordine. Tutto in silenzio.

Corretto in **MNTFOS-1337** su `ChangeSize::getAllowedResolutions`, `ChangeSize::getSizes`,
`ChangeSize::getChangeSizeEnabledProducts` e `Plugin/Block/Form/Renderer/Select`. Il website si
deriva con `RmaHelper::getStoreByOrderItemId($orderItemId)`. **Non reintrodurre
`storeManager->getWebsite()` in codice di frontend o API.**

Due corollari sui parametri in ingresso:

- `getSizes` e `getChangeSizeEnabledProducts` ricevono ancora un `$websiteId` in firma (mantenuto
  per compatibilità dell'interfaccia) che **non viene usato**. Non fidarsene.
- `getChangeSizeEnabledProducts` risolve il website dal **primo** `order_item_id` della lista e
  applica l'esito a tutta la lista. Finché gli item arrivano da un solo ordine è corretto; con item
  di ordini diversi decide il primo, quindi o passa anche un item di un website disabilitato, o —
  se il primo item è di un website disabilitato — la risposta torna vuota scartando anche gli item
  legittimi.

Lo stesso vale per il **form RMA di admin**, che ha una sorgente di opzioni tutta sua. Le tre select
`resolution` del backend (`NewRma\Tab\Items`, `NewRma\Tab\Items\Grid`, `Edit\Tab\Items\Grid`)
prendono le opzioni da `Magento\Rma\Helper\Eav::getAttributeOptionValues('resolution')`, che
restituisce tutte le opzioni dell'attributo senza guardare l'ordine: senza filtro il backend espone
*Change Size* anche su un website dove è spenta, con lo stesso esito silenzioso del frontend — reso
salvato con quella risoluzione e nessun ordine creato.

Il filtro è `Plugin/Rma/Helper/Eav`, registrato in `etc/adminhtml/di.xml` e quindi attivo solo in
area admin: risolve il website dall'ordine in request (`order_id`, che sopravvive anche alla
chiamata ajax `addProductGrid` perché usa `_current => true`) e toglie l'opzione quando la feature è
disabilitata. Vale **solo in creazione**: sulla edit di un reso già salvato rimuovere l'opzione
svuoterebbe la select del valore corrente, quindi lì non interviene.

Attenzione al perimetro: il filtro nasconde l'opzione, non la rifiuta. Una POST costruita a mano può
ancora salvare quella risoluzione, e in quel caso l'unica traccia è il warning di
`CreateChangeSizeOrderHandler`.

### 2. `change_size` viene rimosso e reinserito in memoria

`RmaModelRmaProcessor::beforeSaveRma` **rimuove** `change_size` dai dati degli item prima del
salvataggio (M2.4.7 rifiuta attributi select senza opzioni), tenendolo in una proprietà
dell'istanza, e `setChangeSizeAttributeValuesBeforeCreateChangeSizeOrder` lo reinserisce sulle
extension attributes prima della creazione ordine. È un passaggio di stato in memoria fra due
metodi dello stesso plugin: se l'abbinamento per `order_item_id` non torna, il valore si perde e
l'ordine non viene creato.

### 3. `filoblu:rma:generate:missing` fallisce in silenzio

`Console/GenerateMissingChangeSizeOrders` incapsula la generazione in un
`catch (\Exception $e) { $x = null; }`: se qualcosa va storto il comando **esce con codice 0, senza
output e senza log**. Con questo comando *nessun output non significa riuscito*: verificare sempre
sui dati (`filoblu_rma_items`, `change_size_order_id`, esistenza dell'ordine).

Inoltre l'abbinamento item ↔ SKU è **posizionale** (`$newSku = $skus[$count]`): su resi con più
item l'ordine dei valori passati a `-s` conta.

### 4. `generateMissingOrder` scrive l'ordine sbagliato, poi si corregge

`CreateChangeSizeOrderHandler::generateMissingOrder` scrive in `filoblu_rma_items` l'id
dell'**ordine padre** invece di quello dell'ordine di cambio taglia. Il valore viene corretto
subito dopo da `ChangeSizeItemsHelper::generateOrder` → `updateFiloBluRmaItem`, quindi il risultato
netto è giusto — ma se quel secondo write fallisse il collegamento resterebbe sbagliato. Da
verificare dopo ogni recupero manuale.

### 5. Rilanciare il comando con una SKU diversa crea duplicati

`findOrCreateChangeSizeItem` cerca la riga per la terna `rma_item_id` + `parent_product_sku` +
`new_product_sku`. Se il primo tentativo è andato con una SKU sbagliata, un secondo tentativo con
la SKU corretta **non riusa** quella riga: ne crea una seconda e lascia un orfano con
`change_size_order_id = 0`. Correggere o cancellare la riga prima di rilanciare.

### 6. Gli ordini di cambio taglia non cambiano stato liberamente

`Observer/UpdateStatusVerify` solleva `LocalizedException` se si prova a cambiare lo stato di un
ordine di cambio taglia prima che il reso collegato sia processato, a meno che lo stato di
destinazione sia in `allowed_change_size_order_status` o `waiting_for_rma_receiving`. Un ordine
fermo in `waiting_for_rma_receiving` sta aspettando la lavorazione del reso: è di processo, non un
bug.

### 7. La generazione manda la mail al cliente

Sia il flusso automatico sia `generateMissingOrder` chiamano `orderSender->send($order)`. Non
esiste modalità di prova: appena il comando riesce, il cliente riceve la conferma d'ordine.

### 8. Esistono due `isChangeSizeEnabled()`, con scope diversi

| Metodo | Argomento | Scope letto |
|---|---|---|
| `ChangeSizeAttributeHelper::isChangeSizeEnabled($websiteId)` | website id | `SCOPE_WEBSITE` |
| `ChangeSizeHelper::isChangeSizeEnabled($storeId)` | store id | `SCOPE_STORE` |

Stesso nome, stessa configurazione letta, argomento diverso. Entrambi funzionano — uno store
risolve comunque sul suo website — ma passare un **website id** al secondo, o uno **store id** al
primo, legge lo scope sbagliato e **non dà nessun errore**: restituisce semplicemente il valore di
un altro website, o il default. Prima di chiamarlo, guardare quale dei due helper è iniettato.

### 9. `changesize.js`: il blocco `ready()` deve stare in fondo

In `view/adminhtml/web/js/form/changesize.js` tutte le funzioni sono assegnate a `var`
(`var f = function() {...}`), quindi esistono solo dopo che il corpo del modulo è stato eseguito.
Magento 2.4 usa **jQuery 1.12**, dove un callback registrato con `$(document).ready()` a DOM già
pronto — sempre il caso sotto RequireJS, che carica dopo `DOMContentLoaded` — viene eseguito in modo
**sincrono**. Un `ready()` in testa al file gira quindi con le `var` ancora `undefined`.

Il danno non si ferma al modulo: la `TypeError` interrompe l'esecuzione del resto del file (niente
`ajaxComplete`, quindi niente `addFormCell`) **e** abortisce il ciclo RequireJS in corso, per cui
`window.rma = new AdminRma()` — dichiarata in `Magento_Rma/view/adminhtml/templates/new/general.phtml`
— non viene mai eseguita. I sintomi visibili sono la griglia degli item dell'ordine che resta vuota
in creazione RMA e `rma is not defined` al click su *Add Products*: entrambi puntano altrove, ma la
causa è qui.

Chi tocca il file: le invocazioni vanno in fondo, dopo tutte le assegnazioni, oppure le funzioni
vanno dichiarate come `function f() {}`.

### 10. La mail di credit memo dell'ordine padre è soppressa da un plugin, non da una configurazione

Quando la merce di un cambio taglia rientra e si emette la credit memo sull'**ordine padre**, la mail
al cliente non deve partire. In admin non c'è nessun interruttore per questo — chi lo cerca sotto
*RMA Configuration* o fra le Sales Emails non lo trova, perché il blocco è hardcoded:
`Plugin/Sales/Model/CreditMemoSenderManager::aroundSend`, registrato in `etc/di.xml` su
`Magento\Sales\Model\Order\Email\Sender\CreditmemoSender::send` (MNTFOS-687). Se l'ordine ha righe in
`filoblu_change_size_items` il plugin ritorna senza chiamare `$proceed` e la mail non esce.

Quel metodo è il punto di strozzatura giusto: copre sia l'invio sincrono dal controller admin sia
quello asincrono, perché con `sales_email/general/async_sending = 1` (il caso di antonymorato) le mail
vengono spedite dal cron via `Magento\Sales\Model\EmailSenderHandler`, che chiama comunque
`CreditmemoSender::send`.

Fino a **EFFCNT-1646** il filtro era però `parent_order_item = $order->getEntityId()`, cioè un
`sales_order_item.item_id` confrontato con un `sales_order.entity_id`. Su antonymorato non ha mai
soppresso niente: 1513 righe in tabella, 0 con `parent_order_id = parent_order_item`, e nemmeno una
collisione possibile dato che `MAX(sales_order.entity_id)` era 122.350 contro `parent_order_item` fra
305.074 e 528.657. Risultato: 1497 credit memo emesse su ordini padre di cambio taglia, **1248 con
`email_sent = 1`**, cioè mail effettivamente partite per anni. Il filtro corretto è `parent_order_id`.

Due cose da sapere ora che il blocco funziona:

- il controllo è **per ordine, non per credit memo**. Un ordine che ha avuto anche un solo reso con
  cambio taglia non manderà più nessuna mail di credit memo, comprese quelle di rimborsi successivi
  che col cambio taglia non c'entrano nulla.
- la credit memo dell'**ordine di cambio taglia** non è toccata: il suo `entity_id` non compare come
  `parent_order_id`, quindi lì la mail parte regolarmente.

Se serve fermare le mail su un'installazione che non ha ancora il fix, le uniche leve sono di
configurazione core: `sales_email/creditmemo/enabled = No` (che però spegne **tutte** le mail di
credit memo dello store view) e, nelle versioni **< 2.9.176**,
`sales_email/creditmemo/force_sending = No`. Quest'ultima viene da MNTFOS-801 (`a8aa5af6`), che
aggiungeva un plugin `before` su `Sales\Controller\Adminhtml\Order\Creditmemo\Save` per forzare
`send_email = 1`: è stata **revertata** in `ee9b8a30` (15.05.2026) e non esiste più da 2.9.176, ma su
antonymorato — ancora a 2.9.170 — risulta a `1` a scope default, e amplificava il problema forzando
la spunta a ogni salvataggio.

---

## Cosa questo documento non copre

- **Easy return**: `Model/EasyReturn`, `Handlers/EasyReturnHandler`, `filoblu_easy_rma_items`,
  comando `filoblu:easyrma:generate:missing`
- **Policy di reso**: `Handlers/RmaPoliciesHandler`, `Helper/RmaPoliciesHandler`
- **Etichette di spedizione e corrieri**: `magento_rma_shipping_label`,
  `Handlers/CustomCarrierHandler`, le griglie matrixrate
- **Email**: `Plugin/EmailTemplate`, `Model/RmaEmailManagment`, i template sotto
  `view/frontend/email/`
- **Blocchi e griglie admin** (42 file in `Block/`) e i 32 plugin in `Plugin/`, di cui qui sono
  citati solo quelli sul percorso del cambio taglia
- **Rimborsi e credit memo**: `Helper/RefundParentOrderHelper`,
  `Plugin/Helper/RmaHelperCreditmemoProcessor` e il rimborso online automatico sull'ordine padre
  (`filoblu_disablerma_section/online_payment_refund_for_parent_order/*`, integrazione Adyen).
  Della catena credit memo qui è documentata **solo** la soppressione della mail (trappola 10)
- **Integrazione pagamenti**: `Plugin/Model/Payment/Kp.php`, `Helper/OfflinePaymentMethodsHelper`
- **La coda storica**: su antonymorato risultano ~130 item con risoluzione *Change Size* e nessun
  ordine collegato, distribuiti su più store negli anni. Mai passati in rassegna; è probabilmente
  il motivo per cui `GenerateMissingChangeSizeOrders` esiste
