Odoo Community is often described as unsuitable for RFID because it lacks the Enterprise IoT Box and the barcode app. In practice the IoT Box is a convenience, not a capability: it wraps a set of device drivers in a paid container. A gateway that already speaks MQTT or HTTP does not need it.
What Community does require is discipline. The temptation is to patch the stock module directly, and that temptation is the reason many Odoo projects cannot upgrade. A separate module of a few hundred lines will outlive three migrations; a fork will not survive one.
Why Community edition is enough
The gaps are narrower than they look, and each has a clean workaround.
- The core strength is already there: stock.quant, stock.move and the inventory adjustment model are in the free edition.
- Odoo's public API and ORM give full access to models, so a custom module can write stock exactly as the Enterprise app does.
- Controllers let you expose a small inbound endpoint without installing anything extra.
- The cron scheduler is included, which is all a batch writer needs.
- What you give up is pre-built device drivers and a support contract for them; both are replaceable by your own gateway.
Module skeleton: what lives where
A predictable layout is worth more than clever code, because the next developer will read it first.
- The module root holds the manifest, which declares dependencies on stock and, if used, on the job queue.
- models contains three files: the reader registry, the raw read event, and the mapping from EPC to product.
- views holds the list, form and search views plus the menu definitions, and nothing else.
- security holds the group definitions, the access rights CSV and the record rules.
- controllers holds the inbound endpoint; data holds the cron definition; tests holds the transaction tests.
Data models: reader, event, mapping
Three models are enough for a first version, and adding a fourth should require an argument.
- Reader: identifier, site, zone, protocol, status, last heartbeat and the antenna configuration.
- Read event: reader, antenna, EPC, signal strength, device timestamp, receive timestamp, sequence and state.
- Mapping: EPC pattern, product, lot, quantity and the rule version used to resolve it.
- State on the event is a small enum — received, resolved, applied, rejected — and it is the backbone of the audit trail.
- Keep the raw event immutable after it reaches a terminal state; corrections belong in a new event, not an edit.
Views and menus without touching core
Inheritance is for adding, not for rewriting, and that distinction decides whether upgrades hurt.
- Add a menu under Inventory, not a new top-level app, unless the module genuinely serves several modules.
- Use inherited views to add a smart button on the product form that opens its tag history.
- Give the event list a default filter for today and a group-by on zone, because that is how operators actually look at it.
- Show the mapping rule version on the event form; when a discrepancy appears, this is the first field anyone checks.
- Never override a core view's architecture wholesale; add the specific node you need by xpath.
Security: ACLs, record rules and the API user
Security is the part that is skipped in prototypes and audited in production.
- Two groups are usually right: an operator who can read events and confirm mappings, and a manager who can change readers and rules.
- Grant no write access on the stock models beyond what the standard inventory groups already have.
- Use a dedicated integration user with a narrow set of rights, and give the gateway its own API key rather than a human login.
- Add a record rule so that a user in one company cannot see another company's readers and events.
- Log who changed a mapping rule, because a wrong rule silently corrupts weeks of inventory.
stock.quant integration without forking stock
The write path is the only genuinely dangerous part of the module.
- Call the existing ORM helper that updates available quantity atomically, and never assign the quantity field directly.
- Wrap the apply in a single transaction per location, so a failure rolls back a coherent unit rather than half an aisle.
- Record the inventory adjustment reason and the originating event id in the reference field, so the audit trail reaches back to the radio.
- Do not create stock moves for a count correction; use the inventory adjustment model, which is what it exists for.
- Keep the gateway's deduplication and aggregation outside Odoo; the module should receive deltas, not a stream of raw reads.
The inbound endpoint: controller, cron and batching
The endpoint should be boring: authenticate, validate, enqueue, return.
- Accept a JSON body with a batch of deltas and an idempotency key, and reject anything that fails schema validation with a specific error.
- Write the delta to the event table and return immediately; never do the Odoo apply inside the HTTP request.
- Let the cron drain the queue every minute, or every few seconds under the job queue, in a single writer.
- Make the apply idempotent on the idempotency key, so a network retry cannot double the quantity.
- Cap the batch size and the request body size, because an unbounded endpoint is an invitation.
Testing and upgrade discipline
The module will be upgraded, and the tests are what make that a routine rather than a project.
- Write transaction tests that create a reader, ingest an event, apply it and assert the quant, then roll back.
- Test the duplicate case explicitly: send the same idempotency key twice and assert one application.
- Test the rejection path: an unknown EPC must land in quarantine, not in an exception traceback.
- Keep a migration script per version that touches data, and test it against a copy of production.
- Pin the module's dependencies and avoid private API calls, because those are what break on the next major version.






