EDI SPS Commerce
EDI integration with SPS Commerce via their canonical RSX XML format (namespace http://www.spscommerce.com/RSX, schema v7.7.0), on top of the OCA EDI framework (edi_core_oca), targeting Odoo 19.0.
RSX is SPS’s proprietary normalized schema: one document type covers every trading partner — SPS maps each partner’s native X12/EDIFACT/API behind it, so we integrate once instead of per-partner segment maps.
Documents covered:
Orders inbound — RSX Orders → draft sale.order
Shipments outbound (ASN) — stock.picking → RSX Shipments
Invoices outbound — account.move → RSX Invoices
All three share one group vocabulary (Address, Dates, References, Notes, PaymentTerms, Taxes, ChargesAllowances, CarrierInformation, part-number id block). Inbound payloads are parsed by services/rsx.py (xmltodict) into schema-shaped dicts; outbound payloads are rendered by QWeb edi.exchange.template.output templates that consume the business records directly.
Transport is pluggable per exchange record:
SPS-hosted SFTP (sftp.spscommerce.com) via edi_storage_oca — inbound files polled from out/testout, outbound uploaded to in/testin;
Manual drop (fallback) — import wizard for inbound, local directory for outbound.
This module is a POC in active development: the mappings cover the core fields and defer the long schema tail (carrier detail, pack levels, charges/allowances, taxes detail) to the partner implementation guide.
Not affiliated with or endorsed by SPS Commerce.
Table of contents
Configuration
Dependencies
Odoo modules:
edi_core_oca — exchange framework (backends, exchange types, records).
edi_storage_oca — storage-backed transport (pulls fs_storage).
edi_exchange_template_oca — QWeb output templates.
sale_stock, account — sales/delivery orders and invoices.
Python packages:
xmltodict — inbound parser (manifest external_dependencies).
fsspec + paramiko — for the SFTP transport (via fs_storage; paramiko must be installed manually).
Shipped configuration
Backend type sps.
Backend SPS Commerce Backend with the SPS directory layout:
::
input_dir_pending = out # SPS → you (live) input_dir_done = archive # you clean “out” yourself input_dir_error = archive output_dir_pending = in # you → SPS (live) output_dir_done = (empty) # SPS deletes “in” files; unused output_dir_error = (empty)
Exchange types (all handler roles → edi.sps.handler):
Code |
Direction |
Document |
Filename |
|---|---|---|---|
sps_orders_in |
input |
rsx.document: orders |
^PO |
sps_shipments_out |
output |
rsx.document: shipments |
SH-{dt}.xml |
sps_invoices_out |
output |
rsx.document: invoices |
IN-{dt}.xml |
Output templates sps_shipments / sps_invoices (edi.exchange.template.output records + QWeb views, incl. shared fragments sps_meta/sps_address/sps_item_ids), bound via output_template_id on the outbound types.
Exchange type rules: Send SPS ASN button on done pickings, Send SPS Invoice button on posted customer invoices.
Exchange-type advanced settings (rsx: block):
document — expected RSX root (orders/shipments/invoices).
sender_id / receiver_id — RSX Meta envelope ids.
outbound_dir — local dir for the no-storage fallback.
xsd_validate — (planned) XSD validation toggle; schemas are user-supplied, not shipped.
SPS SFTP map
SPS hosts the endpoint at sftp.spscommerce.com with fixed directory pairs — out/testout inbound, in/testin outbound. Create archive on the SPS account first (rename fails otherwise); we must clean out ourselves. SPS deletes in files during processing, so outbound types have no check handler: output_sent is terminal until an inbound ack (997/855) arrives.
Test mode
The directory names carry the environment — same SFTP account, live files in out/in, test files in testout/testin — set on the backend’s Storage page (input_dir_pending/output_dir_pending
their test_* counterparts). Test mode is supported because the sps backend type ships with test_mode_supported; enable the Test mode checkbox (edi.backend.test_mode from edi_core_oca) and get_conn_parameter() returns the live or test folder accordingly — configured values are never rewritten. data/neutralize.sql in edi_core_oca sets test mode on all backends for copied databases. To point test mode at a different SFTP account entirely, set test_storage_id instead.
Partner and product matching
Partner: res.partner.ref = the SPS trading-partner id (OrderHeader.TradingPartnerId, falling back to the Meta sender ids). The id is also what outbound templates emit in TradingPartnerId.
Product: OrderLine.VendorPartNumber/ApplicationId → default_code; BuyerPartNumber → product.customer.code when installed; GTIN/EAN/ConsumerPackageCode → barcode.
UOM: RSX carries X12 355 codes; a mapping table to uom.uom is planned — today quantities import in the product’s UoM.
Customizing a partner
Each SPS relationship still has a mandatory-subset profile. Point the partner’s own exchange type at a copy of the shipped edi.exchange.template.output (or extend it via code_snippet) — no code changes needed.
Usage
Inbound Orders — manual drop (no storage)
Open EDI → Exchanges → Import SPS RSX.
Pick the exchange type, upload the RSX Orders file.
The wizard creates an edi.exchange.record in input_pending and runs receive → validate → process immediately.
Expected result: one draft sale.order per <Order> — PurchaseOrderNumber → client_order_ref, Dates[002] → commitment_date, one line per LineItem. The exchange record is linked as the order’s EDI origin. TsetPurposeCode other than 00 (original) raises — cancel/change documents are handled manually for now.
Inbound Orders — via SPS SFTP
SPS drops a PO* file into out/testout.
The storage poller creates an input_pending record; the input sync cron receives, validates, and processes it; processed files move to archive.
Outbound Shipments (ASN)
Manual: open a done delivery order → Send SPS ASN.
Automatic: stock.picking.button_validate queues a Shipments exchange for done pickings when the partner is enabled on the type.
Retrigger: call picking._edi_sps_queue_shipments(force=True) (server action / shell) — skips pickings with an existing record unless force.
The picking is rendered by the sps_shipments template and uploaded to output_dir_pending (SPS: in/testin) as SH-<dt>.xml, or written to rsx.outbound_dir without storage.
Outbound Invoices
Open a posted customer invoice (or credit note) → Send SPS Invoice. The move is rendered by sps_invoices (DI for invoices, CR for credit notes) and delivered the same way.
Extension points
services/rsx.py — inbound codec: parse/parse_orders + FORCE_LIST for repeatable groups.
edi.exchange.template.output — outbound documents; copy a shipped template per partner profile, or use code_snippet for extra render values.
edi.sps.handler — handler roles; partner/product matching in _find_partner/_find_product.
edi.exchange.type.advanced_settings_edit — rsx.* config.
Known issues / Roadmap
Status: in progress
Decisions
SPS RSX XML (schema v7.7.0) is the payload format — normalized across partners by SPS; proprietary to SPS but standard connector practice (cf. account_avatax, delivery_sendcloud).
Rejected: extending the raw-X12 codec in edi_x12_poc — per-partner segment maps duplicate work SPS already did.
Rejected: base_business_document_import/UBL — the wire format is fixed by SPS.
Module name edi_spscommerce.
Rejected: edi_rsx — RSX is SPS’s proprietary namespace; a generic name would overstate portability.
Transport: SPS-hosted SFTP (sftp.spscommerce.com) via edi_storage_oca — out/testout in, in/testin out, archive for processed; no outbound check (SPS deletes in files, output_sent terminal until an inbound ack).
Rejected: SPS REST API/AS2 — would need edi_webservice_oca and loses the file-based audit trail.
Inbound codec services/rsx.py via xmltodict — schema-shaped dicts (RSX element names verbatim), FORCE_LIST for [0..*] groups; the handler does RSX→Odoo translation.
Outbound generation via QWeb edi.exchange.template.output — templates consume record directly (mapping lives in data; per-partner overrides need no code). Handler injects only meta.
2026-11 — supersedes programmatic ``etree`` builders.
2026-11 — record-direct templates supersede ``_edi_sps_*_values`` dict builders — one mapping surface.
Batching: <Order>/<Shipment>/<Invoice>[1..*] — plan to split one file into one exchange record per document (error isolation).
Rejected: one record per file — a bad document would block the batch.
Inbound Orders create draft sale.order; TsetPurposeCode drives create/cancel/change (only 00 implemented).
XSD validation behind rsx.xsd_validate for the test phase; schemas are user-supplied, not shipped.
OCA publication: license LGPL-3; no vendored SPS materials (tests use hand-written fixtures); nominative use of the name; confirm the SPS agreement doesn’t mark RSX confidential before publishing.
Open questions
Which address types does the partner send (ST+BY only, or separate BT)? Drives partner-matching fallback order.
Do we get TsetPurposeCode 01/04/05 (cancel/change) or originals only?
ASN depth: PackLevel/SSCC required, or OrderLevel→ItemLevel enough?
Invoice trigger: post of account.move, or batch end-of-day?
RSX filename convention: same PO/SH/IN prefixes + .xml?
Plan
Iteration 1 (done): module scaffold, inbound codec, outbound QWeb templates + skeleton mappings, SFTP/manual transports, tests.
Iteration 2: Orders full mapping (ship-to/GLN matching, payment terms, UOM table, currency, notes, dropship refs); split-per-document receive.
Iteration 3: Shipments full mapping (carrier/SCAC, tracking, PackLevel/SSCC); Invoices (terms, taxes, charges/allowances).
Iteration 4: partner profile config; verify vs SPS testin/testout.
Iteration 5: inbound acks (997/855), cancel/change order handling.
Tasks
☒ Scaffold edi_spscommerce (manifest, handler, backend type, three exchange types, access rules, import wizard, tests)
☒ Codec services/rsx.py — xmltodict parse (verified against the real Orders.xml sample)
☒ Outbound generation via edi_exchange_template_oca — QWeb views consuming record + template records on the outbound types (skeleton mappings)
☒ Split design doc into readme/ fragments (this doc)
☐ Split-per-document receive (one exchange record per <Order>)
☐ x_sps_* fields — x_sps_location (partner GLN/location id), x_sps_customer_ref / x_sps_line_ref on sale orders
☐ Orders → sale.order full mapping (ship-to match/create, UOM table, payment terms, currency, notes, dropship refs)
☐ UOM map: X12 355 codes → uom.uom (module data + lookup)
☐ TsetPurposeCode 01/04/05 — cancel/change order handling
☐ Shipments full mapping (carrier/SCAC, tracking, PackLevel/SSCC, Dates, References)
☐ Invoices full mapping (payment terms, taxes, charges/allowances, BOL/PRO)
☐ Partner profile config (trading-partner id, code maps, per-partner mandatory fields)
☐ Implement rsx.xsd_validate (user-supplied schema path)
☐ Inbound acks — 997/855 exchange types
☐ Confirm with SPS: address types sent, purpose codes used, PackLevel/SSCC requirement, invoice trigger, RSX filename convention
☐ Verify against SPS testin/testout
☐ Confirm SPS agreement permits OCA publication
☐ Regenerate README.rst/static/description with oca-gen-addon-readme before publishing
Bug Tracker
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed feedback.
Do not contact contributors directly about support or help with technical issues.
Credits
Maintainers
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.
This module is part of the OCA/edi project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
Metadata
Release files for odoo-addon-edi-spscommerce 19.0.1.0.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| odoo_addon_edi_spscommerce-19.0.1.0.0.4-py3-none-any.whl | Python 3 | none | any | Details for odoo_addon_edi_spscommerce-19.0.1.0.0.4-py3-none-any.whl |
Release files / odoo_addon_edi_spscommerce-19.0.1.0.0.4-py3-none-any.whl
| Download URL | odoo_addon_edi_spscommerce-19.0.1.0.0.4-py3-none-any.whl |
|---|---|
| Size | 65.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5185da01f2f66a8bf27b16ac89b9b241c505b8c91dcce0f1635502481f42f71d
|
|
BLAKE2b-256 checksum How to use checksums |
e8046b75bfdd3b008c0eefacee177eb63460db61f9d274dae67aac32bf3d23e7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Security for odoo-addon-edi-spscommerce 19.0.1.0.0.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.