Sistema B2B professionale per Magento 2 Open Source
- SKU
- M2-B2B
Descrizione / Sistema B2B professionale per Magento 2 Open Source
MAGENTO 2 · MAGENTO OPEN SOURCE · AZIENDE · COMMERCIO B2B
Unisci le vendite B2B e B2C in un’unica installazione Magento.
Kowal B2B Suite
Sviluppa un portale per clienti business con aziende, ruoli, prezzi, cataloghi, RFQ, limiti, documenti e integrazioni. Attiva la logica B2B per website selezionati e adatta il processo alle tue regole commerciali.
16 moduli e tema · Website scope · REST e GraphQL
COME FUNZIONA IN PRATICA
01 · Definisci il modello commerciale
Definisci aziende, canali, prezzi e fonti dati.
02 · Configuri il processo B2B
Assegni ruoli, catalogo, condizioni e approvazioni.
03 · Colleghi i sistemi e implementi
Scegli gli adapter e verifichi gli scenari completi.
Cosa ottiene il tuo shop?
Scopri le funzionalità, gli utilizzi e le impostazioni del modulo.
Scopri il modulo
KOWAL B2B SUITE · MAGENTO 2 OPEN SOURCE
Un sistema B2B professionale senza passare ad Adobe Commerce
Kowal B2B Suite estende Magento 2 Open Source con le funzionalità necessarie per la vendita all’ingrosso e contrattuale.
Gestisci aziende, utenti, prezzi individuali, ordini rapidi, limiti di credito commerciale, approvazioni, offerte, documenti e integrazioni — in un unico ambiente Magento.
Aziende e ruoli · Prezzi e cataloghi · RFQ · Credito commerciale · Documenti · Integrazioni
Vendita B2B adattata alle tue regole commerciali
Ogni cliente business può ricevere prezzi, catalogo, condizioni di pagamento, limiti e utenti con autorizzazioni diverse.
Il sistema supporta sia un modello wholesale semplice sia processi di acquisto avanzati con approvazione a più livelli e integrazione con ERP, PIM, WMS o CRM.
Le aree principali della piattaforma B2B
Sposta la gestione quotidiana dei clienti business da e-mail, fogli di calcolo e accordi manuali direttamente in Magento.
Aziende, utenti e ruoli di acquisto
Una singola azienda può avere più utenti, indirizzi e persone responsabili degli acquisti. I ruoli definiscono chi può ordinare, approvare o consultare i dati.
Prezzi e cataloghi individuali
Ogni cliente business può ricevere il proprio catalogo, prezzi contrattuali, soglie quantitative e regole di acquisto. Il cliente vede l’offerta destinata alla sua azienda.
Ordini rapidi e liste di acquisto
I clienti possono inserire in modo efficiente ordini di grandi dimensioni tramite SKU, utilizzare liste di acquisto e importare righe invece di cercare ogni prodotto separatamente.
RFQ, offerte e approvazioni
Il processo di offerta può includere prezzi negoziati, data di validità e condizioni individuali. Gli ordini possono passare attraverso un’approvazione dipendente dal valore o dal ruolo dell’utente.
Controllo finanziario senza lavorare nei fogli di calcolo
Limiti di credito commerciale, utilizzo del credito e scadenze di pagamento aiutano a controllare la vendita con pagamento differito.
Il cliente ha accesso ai documenti e il team vede condizioni commerciali e stato del processo in un unico punto.
Documenti nel portale cliente
- fatture e note di credito,
- documenti di consegna e conferme,
- offerte PDF,
- documenti da Magento o dal sistema ERP.
Come si svolge l’implementazione?
La piattaforma può essere implementata per fasi. Prima mettiamo in ordine le regole commerciali, poi le riproduciamo in Magento e le integriamo con i sistemi aziendali.
1. Modello di vendita
Definiamo clienti, canali, listini, pagamenti, restrizioni e fonti dati.
2. Regole per le aziende
Configuriamo utenti, prezzi, catalogo, indirizzi, limiti e ruoli di acquisto.
3. Processo di acquisto
Implementiamo ordini rapidi, offerte, approvazioni, documenti e regole di checkout.
4. Integrazioni e test
Colleghiamo ERP, PIM, WMS o CRM, testiamo gli scenari e prepariamo il team al lavoro.
Integrazioni con i sistemi aziendali
I dati su clienti, prezzi, prodotti, ordini e documenti possono essere scambiati con ERP, PIM, WMS, CRM, EDI e sistemi di acquisto dei clienti business.
La sincronizzazione può includere limiti di credito commerciale, condizioni di pagamento, stati degli ordini e documenti. Lo storico di attività ed errori facilita il controllo del processo.
Un B2B che cresce insieme al business
Kowal B2B Suite è una soluzione per grossisti, distributori, produttori e aziende che gestiscono vendite contrattuali.
Puoi iniziare con aziende, prezzi e catalogo, quindi estendere la piattaforma con ordini rapidi, RFQ, limiti, approvazioni, documenti e integrazioni.
Un solo Magento. Un solo catalogo. Un unico processo di vendita B2C e B2B.
Un pacchetto, architettura modulare
kowal/metapackage-b2b-suite fornisce i file di 16 moduli B2B e del tema frontend/Kowal/b2b. Il pacchetto principale li registra in autoload e sostituisce i nomi dei sottopacchetti, quindi l’installazione della suite non richiede un repository separato per ogni modulo.
L’implementazione è progettata per Magento Open Source 2.4.9 e PHP 8.3. L’ambito dei moduli attivati, la compatibilità del tema e gli scenari di processo devono essere verificati nell’installazione di destinazione.
Website e modello di accesso dell’azienda
Le funzionalità B2B operano nel contesto del website_id selezionato. L’azienda deve avere una relazione attiva con questo canale e i ruoli definiscono l’accesso ad acquisti, utenti, offerte e documenti.
Il primo modello aziendale prevede un solo website assegnato. La vendita B2C può utilizzare la stessa installazione, ma l’attivazione B2B e l’accesso alle aziende restano separati tramite la configurazione dei canali.
Quick Order, approvazioni e implementazione graduale
L’ordine rapido verifica SKU, quantità, assegnazione al website, visibilità per l’azienda e prezzo B2B. Le liste di acquisto e i log di import aiutano a gestire ordini ricorrenti.
Le regole approval considerano azienda, website, valuta e soglia. Il request pending corrente viene chiuso con una decisione approve, reject o cancel. Il flusso a più livelli deve essere concordato e verificato come ambito specifico dell’implementazione.
Documenti e accesso sicuro
I documenti sono assegnati all’azienda e al website. I file PDF si trovano in var/kowal_b2b_documents e vengono scaricati tramite un controller che verifica l’accesso.
La fonte può essere Magento Sales o una modalità ibrida Magento + API. Il pacchetto include documenti di ordini, fatture, spedizioni e note di credito, oltre a un renderer PDF condiviso; la sincronizzazione dei documenti da un sistema esterno richiede un adapter adeguato.
Le integrazioni richiedono adapter e fonti dati
Il layer Integration fornisce profili, mappatura degli identificatori, job, retry, log e dead letter queue. Un ERP, PIM, WMS o CRM specifico viene collegato tramite un adapter che implementa SyncAdapterInterface.
Prima dell’avvio, definisci la fonte di verità per aziende, prezzi, stock, ordini e documenti. REST e GraphQL costituiscono punti di integrazione; l’installazione del pacchetto non collega automaticamente tutti i sistemi aziendali.
Installazione tramite Composer
Pacchetto kowal/metapackage-b2b-suite, modulo Kowal_B2BBase. Dopo aver configurato l’accesso al repository Kowal, installa il pacchetto, abilita il modulo, aggiorna Magento e pulisci la cache. In produzione considera la compilazione e il deploy dei contenuti statici secondo il processo dello shop.
Dalla configurazione al risultato
1. Definisci il modello commerciale
Definisci aziende, canali, prezzi e fonti dati.
2. Configuri il processo B2B
Assegni ruoli, catalogo, condizioni e approvazioni.
3. Colleghi i sistemi e implementi
Scegli gli adapter e verifichi gli scenari completi.
Portale ordini per il cliente business
L’azienda è assegnata a un website B2B attivo. Il suo utente utilizza il catalogo e il prezzo conformi alle condizioni concordate, aggiunge righe tramite SKU e invia l’ordine.
A seconda della configurazione, il processo include limite e approvazione. Documenti e integrazioni restano collegati all’azienda e al canale di vendita corretto.
Pianifica un’implementazione B2B su misura per la tua azienda
Vuoi adattare il modulo al tuo shop? Richiedi l’implementazione di Kowal B2B Suite e discuti la configurazione o le estensioni necessarie.
Maggiori Informazioni
| Conformità al modello | Luma / Vuoto, Fabbro |
|---|
Configurazione dell'integrazione
Kowal B2B API — documentazione REST completa
Versione del contratto:
V1· Fonte di verità:Kowal_B2BApi/etc/webapi.xml· Destinatari: amministratori di shop, partner di implementazione e team che integrano ERP, PIM, WMS o sistemi di acquisto.
1. Finalità e limiti dell’API
Kowal B2B API espone dati e processi B2B operanti in Magento 2: aziende, catalogo e prezzi individuali, acquisti rapidi, documenti, limite di credito commerciale, approvazioni, RFQ e code di integrazione. È una REST API per integrazioni di sistema; non sostituisce gli endpoint Magento standard per catalogo, carrello, checkout e account cliente.
Il documento descrive solo gli endpoint attualmente esposti dal modulo. Non contiene promesse di operazioni non rese disponibili dal contratto V1, ad esempio creazione di ordini tramite REST, CRUD dei listini o modifica della configurazione website.
2. Ottenimento dell’accesso e regole di collaborazione
2.1. Processo di avvio dell’integrazione
- L’integratore comunica all’amministratore dello shop obiettivo dell’integrazione, ambiente (test/produzione), sistema sorgente, ambito dei dati ed elenco delle aree API richieste.
- L’amministratore crea o configura l’integrazione Magento, le assegna i permessi ACL minimi e trasmette tramite canale sicuro:
baseUrl, token,websiteId,companyId(se applicabile), valuta e dati di test. - L’integratore esegue il test
GET /V1/kowal-b2b/websites/:websiteId/config, quindi i test funzionali nell’ambiente di test. - Prima della produzione, entrambe le parti concordano calendario di sincronizzazione, dimensione massima dei dati, retry, proprietario dei dati e modalità di segnalazione degli errori.
- I token sono conservati esclusivamente in un secret vault; l’amministratore li ruota o revoca in caso di cambio fornitore, utente o ambito dell’integrazione.
2.2. Responsabilità
| Parte | Responsabilità |
|---|---|
| Amministratore dello shop | Configurazione del B2B website, dell’azienda e della relazione azienda-website, del token e delle ACL; trasmissione dei dati di test; decisione sull’accesso ai dati. |
| Integratore | Conservazione sicura del token, uso corretto del contesto websiteId/companyId, validazione dei dati, gestione degli errori e dei retry, non divulgazione dei dati di altre aziende. |
| Owner del processo business | Accordo sulla fonte di verità per prezzi, documenti, RFQ, limiti e stati di sincronizzazione. |
Non usare il token amministratore in un’applicazione client né condividere un unico token tra sistemi indipendenti. Ogni integrazione dovrebbe avere il proprio token e solo le ACL richieste.
2.3. Permessi
Magento verifica il token Bearer e le ACL assegnate all’utente amministrativo o all’integrazione. Gli endpoint B2B richiedono uno dei seguenti gruppi di permessi:
| ACL | Ambito |
|---|---|
config | Websites e feature flags |
companies, company_save, company_users, batch | Aziende, loro utenti e import batch di aziende |
products, prices, catalog_permissions | Catalogo, disponibilità, prezzi e visibilità |
import_export, quick_order, documents | Profili import/export, liste di acquisto e documenti |
credit_limits, approvals, orders, quotes | Limite, workflow di approvazione, contesto ordini e RFQ |
system_integrations | Profili, mappature e job di integrazione |
L’ambito dovrebbe derivare dallo scopo dell’integrazione. Esempio: un sistema di acquisto di solito ha bisogno di products, prices, catalog_permissions, quick_order e quotes; un ERP che gestisce documenti — documents e, se è proprietario della sincronizzazione, system_integrations.
3. Convenzioni tecniche
Indirizzo, header e parametri
L’indirizzo base ha la forma https://b2b.example.com/rest/V1. Tutti i percorsi nel resto del documento sono indicati a partire da /V1; l’indirizzo completo dell’endpoint è BASE_URL + percorso.
Authorization: Bearer Accept: application/jsonContent-Type: application/json L’API usa JSON. I nomi nell’URL sono in camelCase (websiteId, companyId) e i campi JSON corrispondono ai nomi dei parametri dei contratti Magento. :companyId, :quoteId, :sku ecc. sono parametri di percorso. Negli esempi ? indica un parametro opzionale.
websiteId è richiesto per le operazioni dipendenti dal canale di vendita. Deve indicare un website esistente con B2B abilitato. companyId indica l’azienda che deve avere una relazione attiva con quel website quando l’operazione opera nel suo contesto. L’API non seleziona un website né un’azienda predefiniti.
Request body e idempotenza
Magento serializza l’oggetto DTO sotto il nome del parametro del metodo: la maggior parte delle scritture accetta {'request': {...}}; la creazione di un’azienda usa {'company': {...}}; il batch di aziende — {'companies': [...]}. Gli endpoint con argomenti semplici accettano campi senza wrapper, ad es. {'websiteId': 1, 'currency': 'PLN', 'items': [...]}.
L’header Idempotency-Key (max 128 caratteri) è attualmente supportato da POST /companies: un retry con la stessa chiave restituirà l’azienda creata in precedenza. Per un job di integrazione usa il campo request.idempotencyKey. Non assumere idempotenza automatica per altri endpoint di scrittura; ripetili solo dopo aver determinato lo stato dell’operazione.
Risposte ed errori
Le risposte di successo sono oggetti o array dei contratti Magento. I campi restituiti dagli oggetti possono essere estesi in modo compatibile; l’integrazione dovrebbe ignorare i campi sconosciuti. Un errore business può contenere:
{ 'code': 'b2b.company.not_assigned_to_website', 'message': 'B2B company is not assigned to website.', 'website_id': 1, 'trace_id': 'request-correlation-id', 'details': {'company_id': 10}, 'field_errors': []}| HTTP | Significato | Reazione dell’integratore |
|---|---|---|
400 / 422 | Dati non corretti o validazione di dominio | Correggi i dati; non ripetere senza modificare il payload. |
401 / 403 | Token/ACL assente o insufficiente | Non ripetere; segnala all’amministratore l’ambito del token. |
404 | Risorsa o relazione inesistente nel contesto dato | Verifica identificatori e contesto website. |
409 | Conflitto di stato o duplicato | Leggi lo stato corrente prima di decidere se ripetere. |
5xx / timeout | Errore tecnico | Applica retry limitati con backoff e conserva trace_id. |
Non registrare token, dati personali completi o segreti di configurazione. La segnalazione al team dello shop dovrebbe includere ora, metodo, percorso senza segreti, stato HTTP, trace_id e payload anonimizzato.
4. Schemi dei dati di input
Gli schemi seguenti sono comuni agli endpoint di riferimento. Un campo senza ? è richiesto dal contratto; ? indica un valore opzionale. I campi metadata, config, configuration e payload sono oggetti JSON.
| DTO / wrapper | Campi |
|---|---|
company | websiteId, name, taxId, externalId?, status?, salesRepresentativeId?, customerGroupId?, websiteActive? |
companyUser | websiteId, roleId, active |
request — RFQ | websiteId, companyId, customerId?, externalId?, title, currency, validUntil?, customerNote?, salesNote?, metadata? |
request — riga RFQ | quoteId, sku, productId?, name?, qty, requestedPrice?, offeredPrice?, comment?, metadata? |
request — decisione RFQ | customerId?, adminUserId?, message? |
request — commento RFQ | quoteId, customerId?, adminUserId?, authorType, message, visibleForCustomer |
request — documento | websiteId, companyId, orderId?, orderIncrementId?, invoiceId?, invoiceIncrementId?, creditmemoId?, creditmemoIncrementId?, externalId?, documentNumber, documentType, status, issueDate?, dueDate?, grandTotal?, currency?, metadata? |
request — file documento | documentId, fileName, filePath, mimeType, fileSize, checksum?, primary |
request — limite | websiteId, companyId, termsId?, creditLimit, currency, active, status, metadata? |
request — esposizione limite | websiteId, companyId, sourceType, sourceId, sourceIncrementId?, amount, currency, dueDate?, metadata? |
request — condizioni di pagamento | websiteId, code, name, daysDue, active, description? |
request — regola di approvazione | websiteId, companyId, name, thresholdAmount, currency?, priority, active, metadata? |
request — approvatore | ruleId, customerId, sortOrder, active |
request — decisione di approvazione | approverCustomerId?, comment? |
request — richiesta di approvazione | websiteId, companyId, orderId?, orderIncrementId?, requesterCustomerId?, grandTotal, currency, comment?, metadata? |
request — profilo import/export | websiteId, code, name, direction, entityType, format, behavior, active, configuration? |
request — profilo di integrazione | websiteId, code, name, systemType, adapterCode, direction, active, maxAttempts, config? |
request — mappatura | websiteId, systemType, entityType, localId, externalId, metadata? |
request — job di integrazione | profileId, direction?, entityType, operation, idempotencyKey?, maxAttempts?, payload?, scheduledAt? |
5. Riferimento degli endpoint
Nelle tabelle ACL indica la risorsa di permesso Magento minima. Risultato definisce il tipo di risposta di successo.
5.1. Websites e configurazione
| Metodo ed endpoint | ACL | Input | Risultato e utilizzo |
|---|---|---|---|
GET /V1/kowal-b2b/websites | config | — | Array di website (websiteId, codice, nome, flag B2B). Scarica prima della configurazione dell’integrazione. |
GET /V1/kowal-b2b/websites/:websiteId/config | config | path: websiteId | Configurazione del B2B website (tra cui enabled/debug/retention log). Esegui come test di accesso. |
GET /V1/kowal-b2b/websites/:websiteId/features | config | path: websiteId | Feature flags attive per il website; usa per abilitare condizionalmente le funzionalità del cliente. |
5.2. Aziende e utenti aziendali
| Metodo ed endpoint | ACL | Input | Risultato e utilizzo |
|---|---|---|---|
GET /companies?websiteId= | companies | query: websiteId | Array di aziende assegnate al website. |
POST /companies | company_save | body: company | Crea l’azienda e la relazione con il website; usa Idempotency-Key in caso di retry. Restituisce l’azienda. |
POST /companies/batch | batch | body: companies — array di company | Crea più aziende e restituisce il risultato per elemento, inclusi gli errori. |
GET /companies/:companyId?websiteId= | companies | path: companyId; query: websiteId | Azienda verificata nel contesto website. |
PUT /companies/:companyId | company_save | path: companyId; body: company | Aggiorna dati e relazione dell’azienda per company.websiteId. |
POST /companies/:companyId/activate?websiteId= | company_save | path: companyId; query: websiteId | Attiva l’azienda nel website indicato e restituisce l’azienda. |
POST /companies/:companyId/block?websiteId= | company_save | path: companyId; query: websiteId | Blocca l’azienda nel website indicato e restituisce l’azienda. |
GET /companies/:companyId/users?websiteId= | company_users | path: companyId; query: websiteId | Array di assegnazioni clienti all’azienda. |
PUT /companies/:companyId/users/:customerId | company_users | path: companyId, customerId; body: companyUser | Assegna/aggiorna ruolo e attività del cliente nell’azienda; restituisce l’assegnazione. |
5.3. Prodotti, prezzo e visibilità del catalogo
| Metodo ed endpoint | ACL | Input | Risultato e utilizzo |
|---|---|---|---|
GET /products?websiteId= | products | query: websiteId | Array dei prodotti Magento di base per il website. |
GET /products/:sku?websiteId= | products | path: sku codificato; query: websiteId | Prodotto (id, sku, nome, tipo, stato, websites). |
GET /products/:sku/availability?websiteId= | products | path: sku; query: websiteId | Disponibilità e quantità vendibile MSI per SKU. |
GET /products/:sku/b2b-status?websiteId= | products | path: sku; query: websiteId | Stato B2B: assegnazione al website, traduzioni e dati stock. |
GET /reports/products/missing-translations?websiteId= | products | query: websiteId | Report degli SKU senza traduzioni richieste, con reason/details. |
GET /reports/products/missing-stock?websiteId= | products | query: websiteId | Report degli SKU senza dati stock richiesti. |
GET /companies/:companyId/products/:sku/price?websiteId=¤cy=&qty= | prices | path: companyId, sku; query: websiteId, currency, opz. qty (predefinito 1) | Spiegazione del prezzo corretto per azienda, valuta e quantità; consulta prima dell’acquisto. |
GET /companies/:companyId/products/:sku/visibility?websiteId= | catalog_permissions | path: companyId, sku; query: websiteId | Risultato di visibilità/acquisto dello SKU per l’azienda. |
GET /companies/:companyId/catalog-visibility?websiteId= | catalog_permissions | path: companyId; query: websiteId | Array di voci dell’indice del catalogo visibile dell’azienda. |
POST /companies/:companyId/catalog-visibility/reindex?websiteId= | catalog_permissions | path: companyId; query: websiteId | Ricostruisce l’indice e restituisce il numero di voci indicizzate. Operazione amministrativa. |
5.4. Import ed export file-based
| Metodo ed endpoint | ACL | Input | Risultato e utilizzo |
|---|---|---|---|
GET /import-export/profiles?websiteId= | import_export | query: websiteId | Array di profili import/export del website. |
POST /import-export/profiles | import_export | body: request — profilo import/export | Salva il profilo e restituisce i suoi dati. |
POST /import-export/import/:profileId?sourceFile=&dryRun= | import_export | path: profileId; query: sourceFile, opz. dryRun=false | Avvia l’import del file indicato lato ambiente Magento; restituisce il job. Non invia multipart. |
POST /import-export/export/:profileId?resultFile= | import_export | path: profileId; query opz.: resultFile | Crea un job di export, opzionalmente con percorso file di destinazione. |
GET /import-export/jobs?websiteId= | import_export | query: websiteId | Array di job import/export per il website. |
GET /import-export/jobs/:jobId | import_export | path: jobId | Stato, risultato e dati di un singolo job. |
GET /import-export/jobs/:jobId/logs | import_export | path: jobId | Log diagnostici del job. |
5.5. Ordine rapido e liste di acquisto
| Metodo ed endpoint | ACL | Input | Risultato e utilizzo |
|---|---|---|---|
POST /companies/:companyId/quick-order/validate | quick_order | path: companyId; body: websiteId, currency, items (sku, qty) | Valida più SKU nel contesto dell’azienda: disponibilità, visibilità e prezzo; restituisce un risultato aggregato. |
GET /companies/:companyId/shopping-lists?websiteId= | quick_order | path: companyId; query: websiteId | Array di liste di acquisto dell’azienda. |
POST /companies/:companyId/shopping-lists | quick_order | path: companyId; body: websiteId, name, opz. customerId, isDefault=false | Crea una lista di acquisto e la restituisce. |
GET /shopping-lists/:listId/items | quick_order | path: listId | Array delle righe della lista indicata. |
POST /shopping-lists/:listId/items | quick_order | path: listId; body: currency, items (sku, qty) | Aggiunge righe alla lista e restituisce le righe salvate. |
5.6. Documenti e file
| Metodo ed endpoint | ACL | Input | Risultato e utilizzo |
|---|---|---|---|
GET /companies/:companyId/documents?websiteId=&documentType= | documents | path: companyId; query: websiteId, opz. documentType | Documenti dell’azienda, opzionalmente filtrati per tipo. |
GET /documents/:documentId?companyId=&websiteId= | documents | path: documentId; query: companyId, websiteId | Dettagli del documento dopo il controllo di appartenenza all’azienda. |
POST /documents | documents | body: request — documento | Crea o salva i metadati del documento e restituisce il documento. |
POST /documents/files | documents | body: request — file documento | Registra un file esistente nello storage Magento (nome, percorso, MIME, dimensione), non invia binari. |
GET /documents/:documentId/files?companyId=&websiteId= | documents | path: documentId; query: companyId, websiteId | Array dei metadati dei file del documento. |
GET /documents/:documentId/sync-logs | documents | path: documentId | Log di sincronizzazione del documento; destinati all’integrazione amministrativa. |
5.7. Limite di credito commerciale e condizioni di pagamento
| Metodo ed endpoint | ACL | Input | Risultato e utilizzo |
|---|---|---|---|
GET /companies/:companyId/credit/status?websiteId=¤cy= | credit_limits | path: companyId; query: websiteId, currency | Stato del limite, utilizzo e importo disponibile dell’azienda. |
POST /credit-limits | credit_limits | body: request — limite | Crea/aggiorna il limite dell’azienda e restituisce il limite. |
POST /credit/exposures | credit_limits | body: request — esposizione | Riserva l’esposizione del limite per la fonte (es. ordine) e la restituisce. |
POST /credit/exposures/:exposureId/release | credit_limits | path: exposureId; body opz.: message | Rilascia l’esposizione; restituisce il suo stato. |
GET /payment-terms?websiteId= | credit_limits | query: websiteId | Array delle condizioni di pagamento attive/conosciute del website. |
POST /payment-terms | credit_limits | body: request — condizioni di pagamento | Salva le condizioni di pagamento e le restituisce. |
5.8. Workflow di approvazione
| Metodo ed endpoint | ACL | Input | Risultato e utilizzo |
|---|---|---|---|
GET /companies/:companyId/approval/rules?websiteId=¤cy= | approvals | path: companyId; query: websiteId, opz. currency | Regole di approvazione dell’azienda per canale/valuta. |
POST /approval/rules | approvals | body: request — regola | Crea/aggiorna la regola di soglia e la restituisce. |
GET /approval/rules/:ruleId/approvers | approvals | path: ruleId | Array degli approvatori assegnati, in ordine sortOrder. |
POST /approval/approvers | approvals | body: request — approvatore | Salva l’assegnazione del cliente come approvatore. |
GET /companies/:companyId/approval/required?websiteId=&grandTotal=¤cy= | approvals | path: companyId; query: websiteId, grandTotal, currency | Boolean: se l’importo richiede approvazione. |
POST /approval/requests | approvals | body: request — richiesta di approvazione | Crea un request per l’ordine e lo restituisce. |
GET /companies/:companyId/approval/requests?websiteId=&status= | approvals | path: companyId; query: websiteId, opz. status | Array di request di approvazione dell’azienda. |
POST /approval/requests/:requestId/approve | approvals | path: requestId; body: request — decisione | Approva il request; restituisce il suo stato aggiornato. |
POST /approval/requests/:requestId/reject | approvals | path: requestId; body: request — decisione | Respinge il request; restituisce il suo stato aggiornato. |
POST /approval/requests/:requestId/cancel | approvals | path: requestId; body: request — decisione | Annulla il request; restituisce il suo stato aggiornato. |
GET /approval/requests/:requestId/decisions | approvals | path: requestId | Storico delle decisioni per il request. |
5.9. Contesto B2B degli ordini
| Metodo ed endpoint | ACL | Input | Risultato e utilizzo |
|---|---|---|---|
GET /orders/:orderId/b2b-context | orders | path: orderId | Contesto B2B di un singolo ordine Magento nativo (es. azienda, stato approvazione, limite, export). |
GET /companies/:companyId/orders/b2b-context?websiteId=&approvalStatus= | orders | path: companyId; query: websiteId, opz. approvalStatus | Array di contesti degli ordini dell’azienda, opzionalmente filtrato per stato di approvazione. |
5.10. RFQ e offerte commerciali
| Metodo ed endpoint | ACL | Input | Risultato e utilizzo |
|---|---|---|---|
GET /companies/:companyId/quotes?websiteId=&status= | quotes | path: companyId; query: websiteId, opz. status | RFQ e offerte dell’azienda, opzionalmente per stato. |
POST /quotes | quotes | body: request — RFQ | Crea un RFQ bozza e lo restituisce. Aggiungi le righe con un endpoint separato. |
GET /quotes/:quoteId | quotes | path: quoteId | Dettagli RFQ/offerta. |
GET /quotes/:quoteId/items | quotes | path: quoteId | Array di righe RFQ. |
POST /quotes/items | quotes | body: request — riga RFQ | Aggiunge SKU e quantità, prezzo richiesto/offerto opzionale e commento. |
GET /quotes/:quoteId/comments | quotes | path: quoteId | Commenti RFQ. Il cliente vede solo i commenti contrassegnati come visibili. |
POST /quotes/comments | quotes | body: request — commento RFQ | Aggiunge un messaggio; authorType e visibleForCustomer definiscono autore e visibilità. |
GET /quotes/:quoteId/history | quotes | path: quoteId | Storico dei cambi di stato e degli eventi RFQ. |
POST /quotes/:quoteId/submit | quotes | path: quoteId; body: request — decisione RFQ | Invia il RFQ bozza alla quotazione. |
POST /quotes/:quoteId/make-offer | quotes | path: quoteId; body: request — decisione RFQ | Il commerciale crea/trasmette l’offerta; prima della chiamata definisci i prezzi delle righe. |
POST /quotes/:quoteId/accept | quotes | path: quoteId; body: request — decisione RFQ | Accetta l’offerta corrente. In questo contratto non crea un ordine REST. |
POST /quotes/:quoteId/reject | quotes | path: quoteId; body: request — decisione RFQ | Respinge l’offerta/RFQ. |
POST /quotes/:quoteId/cancel | quotes | path: quoteId; body: request — decisione RFQ | Annulla il RFQ, se lo stato corrente lo consente. |
5.11. Profili e job di integrazione
| Metodo ed endpoint | ACL | Input | Risultato e utilizzo |
|---|---|---|---|
GET /integrations/profiles?websiteId=&systemType= | system_integrations | query: websiteId, opz. systemType | Profili di integrazione per il website. |
POST /integrations/profiles | system_integrations | body: request — profilo di integrazione | Salva il profilo ERP/PIM/WMS ecc. e lo restituisce. |
POST /integrations/mappings | system_integrations | body: request — mappatura | Salva la coppia ID locale ↔ ID esterno. |
GET /integrations/mappings/local?websiteId=&systemType=&entityType=&localId= | system_integrations | query: tutti i parametri richiesti | Cerca la mappatura tramite identificatore Magento. |
GET /integrations/mappings/external?websiteId=&systemType=&entityType=&externalId= | system_integrations | query: tutti i parametri richiesti | Cerca la mappatura tramite identificatore del sistema esterno. |
POST /integrations/jobs | system_integrations | body: request — job | Pubblica un job da eseguire; idempotencyKey identifica l’operazione business. |
GET /integrations/jobs?websiteId=&status= | system_integrations | query: websiteId, opz. status | Array di job di integrazione. |
GET /integrations/jobs/:jobId | system_integrations | path: jobId | Stato e dettagli di un singolo job. |
POST /integrations/jobs/:jobId/process | system_integrations | path: jobId | Elabora il job e restituisce il suo stato aggiornato. |
POST /integrations/jobs/:jobId/retry | system_integrations | path: jobId | Ripete il job dopo un errore, secondo il suo limite di tentativi. |
GET /integrations/jobs/:jobId/logs | system_integrations | path: jobId | Log di esecuzione del job. |
GET /integrations/jobs/:jobId/errors | system_integrations | path: jobId | Errori di dominio/tecnici del job. |
6. Esempi di chiamate
export BASE_URL='https://b2b.example.com/rest/V1'export TOKEN='token-przekazany-przez-administratora'export WEBSITE_ID=1 COMPANY_ID=10 SKU='B2B-SKU-001' CURRENCY='PLN'# Test dostępu i konfiguracji kanałucurl -sS '$BASE_URL/kowal-b2b/websites/$WEBSITE_ID/config' \ -H 'Authorization: Bearer $TOKEN' -H 'Accept: application/json'# Cena kontraktowa firmy dla konkretnej ilościcurl -sS '$BASE_URL/kowal-b2b/companies/$COMPANY_ID/products/$SKU/price?websiteId=$WEBSITE_ID¤cy=$CURRENCY&qty=5' \ -H 'Authorization: Bearer $TOKEN' -H 'Accept: application/json'# Walidacja koszyka po SKUcurl -sS -X POST '$BASE_URL/kowal-b2b/companies/$COMPANY_ID/quick-order/validate' \ -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' \ --data '{'websiteId':1,'currency':'PLN','items':[{'sku':'B2B-SKU-001','qty':5}]}'# Utworzenie RFQcurl -sS -X POST '$BASE_URL/kowal-b2b/quotes' \ -H 'Authorization: Bearer $TOKEN' -H 'Content-Type: application/json' \ --data '{'request':{'websiteId':1,'companyId':10,'customerId':25,'title':'Dostawa kwartalna','currency':'PLN','externalId':'ERP-RFQ-2026-001'}}'7. Scenari di integrazione consigliati
Acquisto tramite SKU
- Leggi la configurazione del website e salva
websiteId,companyIde valuta trasmessi. - Usa quick order per la validazione aggregata di SKU, quantità, visibilità e disponibilità.
- Per le righe accettate, recupera il prezzo da
pricecon ilqtycorretto. - Crea un RFQ, aggiungi righe e inviale tramite
submitse l’acquisto richiede un’offerta. - Monitora stato e storico del RFQ; dopo l’accettazione procedi secondo il processo di ordine Magento concordato, perché
V1non espone un endpoint di creazione order.
Documenti e rendicontazione
- Scarica l’elenco dei documenti dell’azienda, opzionalmente filtrando per
documentType. - Per il documento selezionato, scarica metadati ed elenco dei file; il percorso file non è automaticamente un URL pubblico — concorda con l’amministratore dello shop la modalità di download del binario.
- Leggi lo stato del limite prima del processo di acquisto; solo un’integrazione con ACL
credit_limitspuò gestirlo.
Operazioni asincrone
- Salva profilo di integrazione e mappature degli identificatori.
- Pubblica un job con
idempotencyKeybusiness e conservajobId. - Leggi job e log/errori; richiama
retrysolo dopo aver analizzato l’errore e rimosso la causa.
8. Checklist di accettazione prima della produzione
- Il token ha solo le ACL richieste e non è un token amministratore usato dalla UI.
- L’integratore gestisce correttamente
401,403,404, validazione422, conflitto409e timeout. - Ogni request nel contesto B2B trasmette il
websiteIdcorretto e i dati aziendali usano ilcompanyIdcorretto. - Prezzi, visibilità e disponibilità vengono verificati prima di creare il processo di acquisto.
- La creazione dell’azienda usa
Idempotency-Key; i job di integrazione hanno unidempotencyKeystabile nel body. - I log non contengono token né dati sensibili e la procedura di supporto trasmette
trace_id. - I test sono stati eseguiti nell’ambiente di test su un’azienda che ha una relazione attiva con il B2B website.
9. Compatibilità
Il contratto è versionato tramite /V1. L’estensione della risposta con nuovi campi è compatibile; l’integratore dovrebbe tollerare campi sconosciuti. Una modifica che rimuove un campo, il significato di un campo o un endpoint richiede una nuova versione dell’API. In caso di discrepanza tra il documento e l’installazione attiva, fa fede il contratto attivo webapi.xml della versione del modulo.
Istruzioni per l'installazione del modulo
Installazione e configurazione di Kowal B2B Suite
Questo manuale è destinato alla persona che ha acquistato Kowal B2B Suite e deve avviarlo in un’installazione Magento Open Source esistente. Guida attraverso due fasi:
- installazione tecnica — eseguita da uno sviluppatore o da un amministratore server;
- configurazione business — eseguita nel pannello Magento dall’amministratore dello shop.
Prima di iniziare, esegui una copia del database e dei file Magento. Consigliamo di effettuare il primo avvio in un ambiente di test e solo successivamente di implementarlo in produzione.
1. Cosa ricevi e di cosa hai bisogno
Kowal B2B Suite viene fornito come pacchetto Composer kowal/metapackage-b2b-suite. Contiene i moduli B2B e il theme frontend/Kowal/b2b.
Requisiti
| Elemento | Requisito |
|---|---|
| Magento | Magento Open Source 2.4.9 |
| PHP | 8.3 |
| Composer | Composer 2 |
| Accesso server | SSH e possibilità di eseguire bin/magento |
| Magento | catalogo, clienti, checkout, Sales, MSI e cron funzionanti |
possibilità di scrittura in var/ da parte del processo PHP |
Da Kowal riceverai:
- indirizzo del repository Composer privato — di seguito come
REPOSITORY_URL; - nome utente o identificatore di accesso —
USERNAME; - token di accesso —
ACCESS_TOKEN; - intervallo di versioni consentito del pacchetto.
Non salvare il token nel repository Git, nei ticket o nella cronologia della shell condivisa con altre persone.
2. Installazione tecnica
Esegui tutti i comandi nella directory principale dell’installazione Magento esistente, come utente con accesso ai file e al comando bin/magento.
2.1. Preparazione
- Assicurati che Magento funzioni correttamente prima della modifica.
- Registra lo stato del deployment ed esegui un backup del database e delle directory
app/etc,pub/mediaevar. - In produzione, abilita la modalità maintenance durante l’aggiornamento:
bin/magento maintenance:enable2.2. Aggiunta del repository privato
Aggiungi il repository Composer Kowal e configura le credenziali ricevute.
Riceverai via e-mail dopo l’acquisto i dati di accesso al repository Composer (indirizzo e-mail del cliente e token di licenza). Sono disponibili anche nel pannello cliente dopo l’accesso a kowal.store. Sostituisci TWOJ_EMAIL_KLIENTA con l’indirizzo e-mail del tuo account e TWOJ_TOKEN con il token ricevuto. Esegui i comandi nella directory principale di Magento.
composer config repositories.kowal composer https://repo.kowal.storecomposer config http-basic.repo.kowal.store 'TWOJ_EMAIL_KLIENTA' 'TWOJ_TOKEN'Composer di solito salva le credenziali nel file locale auth.json; non aggiungere questo file a Git.
2.3. Installazione del pacchetto
Installa la versione fornita insieme alla licenza. Per la linea corrente del pacchetto, un comando di esempio è:
composer require kowal/metapackage-b2b-suite:^0.2 --with-all-dependenciesDopo aver scaricato le dipendenze, esegui l’aggiornamento dello schema e della configurazione Magento:
bin/magento setup:upgradebin/magento cache:cleanIn modalità produzione esegui inoltre:
bin/magento setup:di:compilebin/magento setup:static-content:deploy -f pl_PL en_USbin/magento cache:flushUsa solo i locale attivi nello shop. Se l’installazione utilizza un processo di deployment diverso, includi i comandi sopra nella sua procedura standard.
2.4. Verifica dell’installazione
bin/magento module:status | grep Kowalbin/magento indexer:statusNel pannello di amministrazione dovrebbe comparire il menu B2B e la scheda Stores > Configuration > Kowal > B2B. Se non sono presenti, controlla il risultato di setup:upgrade, la cache e i ruoli ACL dell’utente amministratore.
2.5. Permessi dei file PDF
I PDF generati dal B2B usano mPDF e directory sotto var/, tra cui var/tmp/kowal_b2b_mpdf e var/kowal_b2b_documents. Il processo PHP deve poterle creare e scrivere. La mancanza di questi permessi si manifesta con un errore di generazione del documento o del PDF.
Al termine del deployment in produzione, disabilita la maintenance:
bin/magento maintenance:disable3. Configurazione della struttura dello shop B2B
3.1. Website e store view
La suite opera nell’ambito website, non globalmente. Puoi gestire B2C e B2B nella stessa installazione Magento, ma il B2B dovrebbe funzionare in un website separato o in un website di vendita aziendale scelto consapevolmente.
- In Stores > Settings > All Stores crea o seleziona il website B2B, il relativo store e store view.
- Ricorda il
website_id— viene utilizzato da aziende, prezzi, documenti, API e integrazioni. - In Content > Design > Configuration assegna il theme
frontend/Kowal/b2bsolo alla B2B store view. - Verifica che valute attive, imposte, metodi di spedizione e metodi di pagamento siano configurati per lo stesso scope.
Non assegnare il theme B2B alla store view B2C se non vuoi modificarne il livello di presentazione. La semplice installazione dei moduli non abilita la logica B2B per tutti i website.
3.2. Attivazione del B2B
- Apri Stores > Configuration > Kowal > B2B.
- Nel selettore scope scegli il Website corretto.
- Nella sezione General imposta Enable B2B su
Yes. - Durante l’implementazione puoi abilitare Debug Mode; disabilitalo dopo i test.
- Nello scope Default Config imposta il periodo di retention dei log.
- Salva la configurazione e pulisci la cache.
Dopo l’attivazione, tutte le store view appartenenti a questo website vengono trattate come B2B. Un’azienda senza una relazione attiva con questo website non potrà accedervi.
3.3. Dati del venditore
Completa i dati dell’azienda venditrice in Stores > Configuration > General > Store Information: nome, indirizzo, telefono e partita IVA/codice fiscale. Sono usati nei documenti e nei PDF RFQ. Completa anche l’indirizzo e-mail generale del mittente nella configurazione Sales Emails e il logo e-mail, se deve comparire nei PDF.
4. Configurazione del primo cliente B2B
Applica la seguente sequenza per ogni azienda. Permette di evitare una situazione in cui il cliente ha un account ma non vede l’offerta o non può procedere al checkout.
4.1. Azienda, persone e indirizzi
- Apri B2B > Companies e crea un’azienda.
- Inserisci nome, partita IVA/codice fiscale, identificatore esterno (se l’azienda è sincronizzata con ERP), stato
Activee assegnazione al B2B website. - Aggiungi un utente dell’azienda o assegna un cliente Magento esistente. Assegnagli il ruolo corretto e attiva l’assegnazione.
- In B2B > Company Addresses aggiungi gli indirizzi dell’azienda; contrassegna l’indirizzo di fatturazione e di spedizione predefinito per il website B2B.
- In B2B > Company Contacts aggiungi un contatto principale attivo con e-mail e telefono.
L’indirizzo di fatturazione e il contatto principale sono usati anche nei PDF RFQ. La mancanza di dati non blocca il PDF, ma il documento sarà meno completo.
4.2. Ruoli aziendali
Assegna agli utenti solo le autorizzazioni necessarie. Una suddivisione tipica è:
| Ruolo | Attività di esempio |
|---|---|
| Amministratore azienda | utenti, ruoli, indirizzi, documenti e storico aziendale |
| Buyer | catalogo, ordine rapido, carrello, RFQ e ordini |
| Approvatore | decisioni nel workflow di approvazione |
| Contabilità | documenti e informazioni di fatturazione |
Testa l’account di ogni tipo, soprattutto il permesso di inviare ordini e approvazioni. Non usare un unico account condiviso per tutta l’azienda.
5. Offerta, prezzi e checkout
5.1. Catalogo e prezzi
- Assicurati che i prodotti Magento siano assegnati al B2B website, attivi e con dati MSI corretti.
- Configura listini o prezzi contrattuali per l’azienda nell’area B2B > Pricing (Price Lists, Price List Assignments o Contract Prices).
- Configura le regole di visibilità del catalogo per l’azienda secondo il modello di autorizzazioni implementato, quindi verifica la visibilità sull’account cliente.
- Testa sull’account aziendale: ricerca SKU, visibilità del prodotto e prezzo per quantità
1e per soglia quantitativa.
Il prezzo B2B dipende da azienda, website, valuta e quantità. Un prodotto visibile in B2C non deve necessariamente essere disponibile per il cliente B2B.
5.2. Spedizione e pagamento
- Abilita i metodi di spedizione e pagamento richiesti nella configurazione standard Magento per la B2B store view.
- In B2B > Checkout aggiungi regole dei metodi disponibili per azienda/website, se vuoi limitarne la scelta.
- Esegui un test del carrello sugli indirizzi predefiniti dell’azienda.
Se la creazione dell’ordine da RFQ deve funzionare automaticamente lato amministratore, l’azienda deve avere indirizzi predefiniti univoci, un metodo di pagamento e di spedizione attivo. In caso contrario la suite caricherà i dati nel Backend Order Create nativo, dove l’amministratore completa gli elementi mancanti.
5.3. Limiti e approvazioni
Queste funzionalità sono opzionali, ma dovrebbero essere configurate prima di abilitarle per i clienti.
- In B2B > Credit > Payment Terms aggiungi le condizioni di pagamento.
- In B2B > Credit > Credit Limits assegna all’azienda limite, valuta, stato e termine di pagamento.
- In B2B > Approvals > Rules crea una regola di soglia importo per azienda e website.
- In B2B > Approvals > Approvers assegna le persone approvatrici.
- Testa il carrello sotto e sopra la soglia e verifica se la decisione dell’approvatore modifica il processo successivo.
6. Documenti, PDF e RFQ
6.1. Documenti Magento
In Stores > Configuration > Kowal > B2B > Documents scegli la fonte dei documenti e decidi quali documenti devono essere generati automaticamente: conferme d’ordine, fatture, documenti di consegna e note di credito. Imposta anche lo stato di destinazione del documento.
In B2B > Documents > PDF Templates:
- verifica il template attivo per ogni tipo e website;
- esegui l’anteprima;
- adatta HTML/CSS solo se hai una persona che conosce i template Magento e CSS mPDF;
- genera una fattura o una conferma di test e verifica il download dall’account aziendale.
6.2. RFQ e offerta PDF
- In B2B > Quotes > RFQ crea un RFQ di test per un’azienda e un utente attivi.
- Aggiungi SKU visibili per l’azienda, quantità e prezzo offerto.
- Imposta la data di validità, invia l’offerta al cliente e genera il PDF.
- Verifica la variante cliente: dati venditore/acquirente, righe, prezzo, totale e data di validità.
- Verifica la variante amministratore: inoltre note commerciali, commenti, storico e informazioni interne sulle righe.
Il PDF cliente non può contenere note del commerciale né storico interno degli stati.
7. Ordini rapidi, import e integrazioni
Ordini rapidi
In B2B > Quick Order > Debug SKU verifica la lista SKU,qty per azienda, website e valuta. Quindi crea una lista di acquisto e testane l’uso da parte del cliente sullo storefront.
Import ed export
In B2B > Import/Export definisci un profilo solo dopo aver stabilito la fonte dati. La configurazione del profilo è un oggetto JSON e deve contenere percorsi file accessibili sul server Magento. Esegui prima l’import in modalità test e analizza job e log.
Integrazioni API
Prima di creare un token di integrazione, definisci l’ambito dei dati e il proprietario della sincronizzazione. Usa un’integrazione Magento separata per ogni ERP, PIM, WMS o middleware e assegnale solo le ACL B2B necessarie. L’elenco completo di endpoint, token e regole di sicurezza si trova nella documentazione API per integratori.
8. Checklist di accettazione
Prima dell’avvio in produzione conferma che:
- I moduli Kowal sono attivi, cron funziona e cache e indici sono corretti.
- Il B2B è abilitato esclusivamente per il website corretto.
- Il theme B2B è assegnato esclusivamente alla B2B store view.
- L’azienda di test ha stato Active, utente, ruolo, contatto e indirizzi predefiniti.
- Il prodotto di test è attivo, assegnato al website, visibile per l’azienda e ha un prezzo B2B corretto.
- Il cliente può accedere, cercare SKU, usare quick order e aggiungere il prodotto al carrello.
- Il checkout mostra solo i metodi di spedizione e pagamento consentiti.
- Il limite e la regola di approvazione funzionano secondo la policy aziendale, se queste funzionalità sono usate.
- Il documento e il PDF RFQ vengono generati e sono disponibili solo per l’azienda corretta.
- Il RFQ può essere inviato, accettato e trasferito al processo di creazione dell’ordine.
- Le integrazioni hanno token separati e ACL limitate, se usate.
9. Problemi più comuni
| Sintomo | Cosa verificare |
|---|---|
| Non c’è il menu B2B | setup:upgrade, stato dei moduli, cache e ACL dell’amministratore. |
| L’azienda o il cliente non vede il B2B | Se il B2B è attivo per il website corretto e se l’azienda ha una relazione attiva con quel website. |
| Il prodotto non è visibile o non ha prezzo | Assegnazione del prodotto al website, attività dello SKU, dati MSI, regole di catalogo, prezzo aziendale, valuta e quantità. |
| Manca il metodo di spedizione o pagamento | Configurazione standard Magento, scope della B2B store view e regole B2B Checkout. |
| Il PDF non viene creato | Template PDF attivo, pacchetto mPDF, permessi su var/, dati del venditore e log Magento. |
| Il RFQ non crea automaticamente l’ordine | Indirizzi predefiniti dell’azienda, pagamento e spedizione univoci; negli altri casi usa Backend Order Create. |
L’API restituisce 403 | Il token di integrazione non ha la ACL richiesta; non usare il token amministratore in un’applicazione esterna. |
Quando contatti il supporto, indica versione Magento, versione del pacchetto, passaggi per riprodurre il problema, ora dell’evento, log anonimizzati in modo sicuro ed eventuale trace_id. Non inviare token né password.
10. Aggiornamento del pacchetto
- Consulta le informazioni di versione fornite da Kowal.
- Esegui prima l’aggiornamento in ambiente di test.
- Esegui un backup,
composer update kowal/metapackage-b2b-suite --with-all-dependencies,bin/magento setup:upgradee in produzione anche compilazione DI e deployment degli asset statici. - Ripeti la checklist di accettazione, con particolare attenzione a prezzi, checkout, documenti e integrazioni.
Non aggiornare il pacchetto copiando manualmente i file in app/code; questo causa problemi con Composer e rende più difficile il supporto successivo.