Skip to main content

Inbound EPCIS

A supplier's EPCIS file is imported on the receipt it belongs to (Import EPCIS, or the external API recipe). The modules keep the original file, announce its units as Expected packages, and post the inbound DSCSA document when the receipt is done. This page describes what the file has to be, what is refused, and the pure-Python reader behind the import, aglow_rx_tracking.tools.epcis_reader.parse_epcis(data). How users import and review files: Receive a sealed case with the supplier's EPCIS file and Review supplier EPCIS files.

What the modules accept​

  • EPCIS 1.2 XML: an EPCISDocument, an EPCISQueryDocument (query results) or an EPCISMasterDataDocument, optionally wrapped in an SBDH StandardBusinessDocument. EPCIS 1.0 and 1.1 are read as 1.2 with a warning.
  • Namespaces are matched by URI, never by prefix. The EPCIS elements may be unqualified (as the EPCIS 1.2 schema defines them) or in the EPCIS namespace. The GS1 US healthcare extension (http://epcis.gs1us.org/hc/ns: the Transaction Statement and direct-purchase elements) is recognised by its namespace, and read with a warning when a file puts it in another one.
  • Parties are compared by GLN. A party's SGLN is reduced to its 13-digit GLN, because the same GLN can be written with different company prefix lengths.
  • Serialized units. The shipping event (business step shipping) lists SGTINs or SSCCs. An SSCC is resolved into the units the file's aggregation events packed into it (a sealed case); lot and expiry come from the commissioning events' ILMD, or else from the file's lot master data.

What is refused​

The reader refuses the file with an error (nothing is imported) when it is:

CaseMessage starts with
empty"The EPCIS file is empty."
JSON (EPCIS 2.0 JSON-LD)"This is a JSON file (probably EPCIS 2.0 JSON-LD). Only EPCIS 1.2 XML files can be imported; …"
EPCIS 2.0 XML"This is an EPCIS 2.0 file. Only EPCIS 1.2 XML files can be imported …"
not well-formed XML"The file is not well-formed XML and cannot be read: …"
XML with a DOCTYPE"The file contains a DOCTYPE declaration. EPCIS files never need one, and DTDs and entities are refused for security reasons."
another XML document"This is not an EPCIS file: its root element is …"
an SBDH without an EPCIS document inside"The file is a GS1 Standard Business Document but contains no EPCIS document."

The XML is parsed without network access, DTD loading or entity expansion, and with lxml's size limits on. On the receipt, the import shows such a message after "FILE can't be read as an EPCIS 1.2 file:". The import then checks the file against the receipt and refuses it, listing every problem at once, when the seller or buyer has another GLN than the receipt's vendor or the company, the file announces no serialized unit, a GTIN is unknown or belongs to a product that isn't on the receipt, a unit has no lot, or a serial is already expected on another open receipt. The messages and what to do: Troubleshooting: receiving.

Warnings​

Everything else the reader or the import finds wrong is a warning: the file is imported, and each warning is one line of the import's warning_text (the Warnings tab). Examples: a missing or unaffirmed Transaction Statement, a count or purchase order number that differs from the receipt, a missing expiry date, a party without a GLN, a missing namespace or schemaVersion. The reader lists at most 200 warnings and counts the rest. The full list: Warnings on the Warnings tab.

A corrected file imported on the same receipt before the receipt is done replaces the earlier import, which becomes Superseded; both files stay stored.

What the reader returns​

parse_epcis(data) takes the file as bytes (a str is encoded as UTF-8 first; pass bytes when the file declares another encoding) and returns a dict. It raises EpcisError (a ValueError with a message meant for users) for the refusals above. Dates are datetime.date, times naive UTC datetime.datetime, GLNs 13-digit strings, GTINs 14-digit strings, SSCCs 18-digit strings, and a missing value is None.

KeyContent
document_type, schema_version, creation_date, creation_date_rawthe root element's name, schemaVersion and creationDate
sbdhpresent, sender_gln, receiver_gln, senders, receivers, document_id (the InstanceIdentifier), document_type, standard, type_version, header_version, creation_date
master_dataproducts (per GTIN: NDC, NDC-11, name, manufacturer, dosage form, strength, container size, package type and every raw attribute; placeholder values such as NA become None), lots (per lot class: lot, expiry), locations (per SGLN: GLN, extension, name and address)
transaction_statementthe header's GS1 US extension: present, affirmed, legal_notice, direct_purchase
eventsevery event in document order: type, id, time and time zone, action, business step and disposition (short CBV codes and URIs), read point and business location, EPCs, children, quantities, business transactions, sources, destinations, ILMD, the GS1 US extension, error declaration
shipmentderived from the shipping events: shipped_at, ship_date, seller_gln and buyer_gln (owning parties), ship_from_gln and ship_to_gln (locations), ship_from_possessor_gln and ship_to_possessor_gln, the business transactions with po_refs, invoice_refs, desadv_refs and supplier_document_ref, the Transaction Statement flags, and packages, containers, unresolved, lines and quantities
historythe transaction history the file gives: at most one hop (this seller to this buyer), since EPCIS 1.2 carries only the current transaction
warningslist of readable strings

Each entry of shipment["packages"] is one shipped unit: gtin14, serial, sscc (the innermost case), containers, inferred (true when only its case was shipped and the unit comes from the aggregation), lot, lot_source, expiry, expiry_source and its issues. ship_from_possessor_gln and ship_to_possessor_gln are a documented extension point; the complete description of every key is the function's docstring.

from odoo.addons.aglow_rx_tracking.tools.epcis_reader import EpcisError, parse_epcis

with open("supplier-epcis.xml", "rb") as handle:
try:
data = parse_epcis(handle.read())
except EpcisError as error:
print("refused:", error)
else:
shipment = data["shipment"]
print(shipment["seller_gln"], shipment["buyer_gln"], len(shipment["packages"]), data["warnings"])