---
title: "openepcis_connector_events: visibility events"
description: "Every validated transfer becomes an EPCIS 2.0 ObjectEvent, packing an AggregationEvent under an SSCC, a return a TransactionEvent, and a wrong report an error declaration. Three settings decide what is reported — the company switch, the operation type, the location — and an outbox keeps the warehouse from ever waiting on the repository. Partners' events come back into an inbox that shows and, where two settings agree, posts."
canonical_url: "https://openepcis.io/docs/connectors/odoo/visibility-events"
last_updated: "2026-10-09T10:21:42.909Z"
---

Master data says what a product *is*. An EPCIS event says what *became* of one of them — received, packed, shipped, sold, returned, turned into something else — and together they are what a scanned serial number can answer. This addon reports Odoo's warehouse operations as those events, and reads back what partners report about the same goods.

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <strong>
        Module
      </strong>
    </td>
    
    <td>
      <code>
        openepcis_connector_events
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Depends on
      </strong>
    </td>
    
    <td>
      <code>
        openepcis_connector_stock
      </code>
      
      ; the Python package <code>
        epcis_event_hash_generator
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Installs
      </strong>
    </td>
    
    <td>
      By hand — reporting movements to a shared repository is a decision, not a default
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Talks to
      </strong>
    </td>
    
    <td>
      The EPCIS 2.0 repository — a different service from the resolver, usually a different host
    </td>
  </tr>
  
  <tr>
    <td>
      <strong>
        Source
      </strong>
    </td>
    
    <td>
      <a href="https://github.com/openepcis/openepcis-odoo/tree/18.0/openepcis_connector_events" rel="nofollow">
        openepcis-odoo
      </a>
      
       · LGPL-3
    </td>
  </tr>
</tbody>
</table>

## What is reported

<table>
<thead>
  <tr>
    <th>
      What happened in Odoo
    </th>
    
    <th>
      Event
    </th>
    
    <th>
      Business step
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      A transfer is validated
    </td>
    
    <td>
      <code>
        ObjectEvent
      </code>
      
      , <code>
        OBSERVE
      </code>
    </td>
    
    <td>
      From the operation type: <code>
        receiving
      </code>
      
      , <code>
        shipping
      </code>
      
      , <code>
        storing
      </code>
      
      , …
    </td>
  </tr>
  
  <tr>
    <td>
      Goods are put into a package
    </td>
    
    <td>
      <code>
        AggregationEvent
      </code>
      
      , <code>
        ADD
      </code>
      
      , parent = the package's SSCC
    </td>
    
    <td>
      <code>
        packing
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      A package is emptied — by picking from it or by <em>
        Unpack
      </em>
    </td>
    
    <td>
      <code>
        AggregationEvent
      </code>
      
      , <code>
        DELETE
      </code>
    </td>
    
    <td>
      <code>
        unpacking
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Cases go onto a pallet (Odoo 19, nested packages)
    </td>
    
    <td>
      <code>
        AggregationEvent
      </code>
      
      , children = SSCCs
    </td>
    
    <td>
      <code>
        packing
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Goods come back on a return
    </td>
    
    <td>
      <code>
        TransactionEvent
      </code>
      
      , <code>
        DELETE
      </code>
      
       — releases the shipment they went out on
    </td>
    
    <td>
      <code>
        receiving
      </code>
      
      , disposition <code>
        returned
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      A manufacturing order is finished
    </td>
    
    <td>
      <code>
        TransformationEvent
      </code>
      
       — see <a href="/docs/connectors/odoo/manufacturing-events">
        manufacturing events
      </a>
    </td>
    
    <td>
      <code>
        commissioning
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      A report was wrong
    </td>
    
    <td>
      The same event again, with an <code>
        errorDeclaration
      </code>
      
       of <code>
        did_not_occur
      </code>
    </td>
    
    <td>
      —
    </td>
  </tr>
</tbody>
</table>

Four decisions inside every event are worth knowing, because they are decisions rather than transcription:

- **A lot is not a serial number.** A serial identifies one unit and goes into the `epcList` as an SGTIN. A lot identifies a *set* and goes into the `quantityList` as an LGTIN. Untracked goods are a set too — the trade item itself — so a warehouse that tracks nothing still produces useful events.
- **Only published products appear.** An identifier that resolves nowhere is not worth putting into a repository: a scan would find an event and no product. What is not published is left out, and the event still describes what did move.
- **The read point is not optional**, although EPCIS allows it to be. An event that says something happened without saying where is half an answer. A transfer whose locations carry no GLN is reported in its chatter instead, where somebody can fix the location.
- **The Origin field becomes the business transaction**, so downstream readers match on what the warehouse actually typed. A delivery with no reference is named after the transfer.

## Three settings, each where the fact lives

**The company switch.** *Settings → General Settings → OpenEPCIS → Report visibility events*, with the repository's address and your GS1 company prefix. *Test capture* checks that the offline token the connector already holds is also allowed to capture — publishing master data and capturing events are two rights, and a token can hold one without the other.

![The visibility-event settings | Figure 1: The switch, the repository address, and the company prefix — which is used for SSCCs and nothing else](/img/16.Connectors/odoo-settings-events.png)

The prefix is what SSCCs are minted from. Unlike a GTIN, an SSCC is not drawn from a registry: a company allocates its own and only has to make sure it never repeats one, so the addon mints them locally from a sequence, at the moment a package comes into being.

**The operation type carries the why.** Odoo knows a transfer is "incoming"; it does not know GS1 calls that `receiving` with the goods `in_progress`. Two fields on every operation type hold the business step and the disposition, pre-filled from Odoo's own codes and the sequence prefix, and never overwritten once somebody has chosen otherwise. A third flag, *Report as EPCIS events*, lets you leave an operation type out.

![The EPCIS block on an operation type | Figure 2: Business step and disposition, seeded from the operation's codes](/img/16.Connectors/odoo-operation-type.png)

<table>
<thead>
  <tr>
    <th>
      Operation type
    </th>
    
    <th>
      Odoo code
    </th>
    
    <th>
      Business step
    </th>
    
    <th>
      Disposition
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Receipts
    </td>
    
    <td>
      <code>
        incoming
      </code>
    </td>
    
    <td>
      <code>
        receiving
      </code>
    </td>
    
    <td>
      <code>
        in_progress
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Pick / Pack / Quality Control
    </td>
    
    <td>
      <code>
        internal
      </code>
    </td>
    
    <td>
      <code>
        picking
      </code>
      
       / <code>
        packing
      </code>
      
       / <code>
        inspecting
      </code>
    </td>
    
    <td>
      <code>
        in_progress
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Storage
    </td>
    
    <td>
      <code>
        internal
      </code>
    </td>
    
    <td>
      <code>
        storing
      </code>
    </td>
    
    <td>
      <code>
        sellable_accessible
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Delivery Orders
    </td>
    
    <td>
      <code>
        outgoing
      </code>
    </td>
    
    <td>
      <code>
        shipping
      </code>
    </td>
    
    <td>
      <code>
        in_transit
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Manufacturing
    </td>
    
    <td>
      <code>
        mrp_operation
      </code>
    </td>
    
    <td>
      <code>
        commissioning
      </code>
    </td>
    
    <td>
      <code>
        active
      </code>
    </td>
  </tr>
</tbody>
</table>

**The location carries the where.** EPCIS wants an SGLN, so a location needs a GLN, and Odoo's locations have none. They do have a tree: set the GLN on the warehouse, the loading bay or the shop floor, and everything underneath inherits it by walking up. One number makes a whole site answerable, and because Odoo's own GS1 barcode nomenclature binds AI 414 to a location, the GLN written here is also what a scanner reads off a GS1 location label.

## The outbox

Validating a transfer must never wait for a network. Pressing *Validate* writes rows into an outbox and returns; a scheduled action delivers them every minute.

![The EPCIS events outbox | Figure 3: One warehouse day as twelve rows](/img/16.Connectors/odoo-outbox.png)

Delivery is two answers, kept apart. *Accepted* means the repository took the document; *Captured* means it stored it. Capture is asynchronous — the repository validates afterwards and may still refuse — so a queue that deleted its row on the `202` would report success for events thrown away minutes later. A row carries its capture job and is asked again on the next run. After five attempts it stops asking and says it cannot be confirmed.

Each row shows two identifiers that do different jobs. The **expected event ID** is the canonical CBV event hash over everything the event asserts; anyone holding the event can recompute it, which is what lets two companies name the same observation without agreeing on anything. Odoo computes it only to recognise its own events coming back and sends the document *without* it — there must be exactly one canonicalisation, and it is the repository's. The **idempotency key** is private bookkeeping, derived from the database, the transfer and the identifiers, so the same movement cannot enter the outbox twice. It never leaves Odoo.

## Taking a report back

An event cannot be pulled out of a repository; EPCIS withdraws it with an error declaration. The addon sends the original event again with `errorDeclaration.reason = did_not_occur`. Declaration fields are excluded from the hash, so the correction carries the same identity and the repository knows which event is meant without Odoo ever learning its name. The original stays on the row, greyed out and marked as corrected.

## The inbox

Events also come *in*: what partners' systems have said about our lots and packages. A scheduled action reads the repository every five minutes from a watermark — the repository's own write clock, so an outage costs one run rather than a reconciliation — and shows what it finds on the lot, on the package, and where Odoo's own traceability report stops at the company boundary.

A partner's event is an observation, not a document. Odoo's stock is a valued figure that ends up in the books, and posting an observation into it would make the closing depend on a third party's data quality. So **the inbox never moves stock.** Three settings under *What we read back* keep it manageable: which events to keep (everything the credential may see, only identifiers under our own prefix, or only what can be placed in this database), events per run, and pages per run — the brake that stops a day of catch-up from becoming one unbounded transaction.

### The one exception

An incoming event may advance a transfer that is **already open, reserved and waiting for exactly this confirmation** — a supplier confirming his own despatch, a customer booking a receipt. It never creates a document, never creates stock, never reopens something closed, never touches quantities. The worst a wrong event can do is validate a transfer a day early: visible, attributable, reversible like any other mistaken validation.

Two settings have to agree before even that happens, so that neither is a single point of trust:

- The **operation type** says what an incoming event may do at all — *Ignore*, *Show only* (the default), *Propose a posting*, or *Post automatically*.
- The **partner** says for whom. *May move our transfers* on the contact, next to the GLN that identifies him in the events, off by default.

And before any of it runs for real, it runs as a rehearsal: while the operation type's *Observe only* flag is set, the whole decision is made and written to the log and the last step is skipped. Somebody reads a week of that log, then clears the flag.

## See it end to end

[A warehouse day](/docs/connectors/odoo/walkthrough) walks through a receipt, a pallet, a delivery, a return, a manufacturing order and a withdrawal with the event each one produces, copied from the outbox of the instance in the screenshots.
