Metadata-Version: 2.1
Name: odoo-addon-edi_spscommerce
Version: 19.0.1.0.0.4
Requires-Dist: odoo-addon-edi_core_oca==19.0.*
Requires-Dist: odoo-addon-edi_exchange_template_oca==19.0.*
Requires-Dist: odoo-addon-edi_storage_oca==19.0.*
Requires-Dist: odoo==19.0.*
Requires-Dist: xmltodict
Summary: SPS Commerce RSX XML: inbound Orders, outbound Shipments and Invoices on OCA EDI framework
Home-page: https://github.com/OCA/edi-framework
License: LGPL-3
Author: Gray Matter Logic
Classifier: Programming Language :: Python
Classifier: Framework :: Odoo
Classifier: Framework :: Odoo :: 19.0
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
Classifier: Development Status :: 3 - Alpha
Description-Content-Type: text/x-rst

.. image:: https://odoo-community.org/readme-banner-image
   :target: https://odoo-community.org/get-involved?utm_source=readme
   :alt: Odoo Community Association

================
EDI SPS Commerce
================

.. 
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
   !! This file is generated by oca-gen-addon-readme !!
   !! changes will be overwritten.                   !!
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
   !! source digest: sha256:a56ce8d5477a4bb7613c836802ecc529ac27ce2d8d06dca4f8a84e119730119a
   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
    :target: https://odoo-community.org/page/development-status
    :alt: Alpha
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
    :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
    :alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fedi-lightgray.png?logo=github
    :target: https://github.com/OCA/edi/tree/19.0/edi_spscommerce
    :alt: OCA/edi
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
    :target: https://translation.odoo-community.org/projects/edi-19-0/edi-19-0-edi_spscommerce
    :alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
    :target: https://runboat.odoo-community.org/builds?repo=OCA/edi&target_branch=19.0
    :alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

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.*

.. IMPORTANT::
   This is an alpha version, the data model and design can change at any time without warning.
   Only for development or testing purpose, do not use in production.
   `More details on development status <https://odoo-community.org/page/development-status>`_

**Table of contents**

.. contents::
   :local:

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)
-----------------------------------------

1. Open **EDI → Exchanges → Import SPS RSX**.
2. Pick the exchange type, upload the RSX ``Orders`` file.
3. 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
-----------------------------

1. SPS drops a ``PO*`` file into ``out``/``testout``.
2. 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 <https://github.com/OCA/edi/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 <https://github.com/OCA/edi/issues/new?body=module:%20edi_spscommerce%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* Gray Matter Logic

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
   :alt: Odoo Community Association
   :target: https://odoo-community.org

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 <https://github.com/OCA/edi/tree/19.0/edi_spscommerce>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
