Schedule icon
Query icon
If icon
Log icon
SlackIncomingWebhook icon

Odoo Inventory Stock Threshold Gate

Audit Odoo product stock via SEARCH_COUNT, alert Slack when items fall below a minimum threshold, read-only.

Categories
BusinessInfrastructure

Odoo's own low-stock indicators live inside the Inventory app, one product at a time - nothing pages a team when a dozen SKUs quietly cross a reorder line overnight. This blueprint runs Odoo's own XML-RPC search_count operation against product.product with a qty_available domain filter, counts how many products are below a threshold you set, and alerts Slack with the exact product names and quantities when the count is anything but zero.

How it works

  1. periodic_stock_check (io.kestra.plugin.core.trigger.Schedule) runs every 6 hours. Shipped disabled so you can validate a manual run first.
  2. check_low_stock_count (io.kestra.plugin.odoo.Query, operation: SEARCH_COUNT) counts product.product records matching qty_available < min_stock_threshold, landing the count in size.
  3. evaluate_stock_levels (io.kestra.plugin.core.flow.If) branches on size == 0.
    • then (HEALTHY): log_stock_healthy records a clean check.
    • else (AT_RISK): fetch_low_stock_products re-runs the same filter with operation: SEARCH_READ to name up to 20 affected products, then alert_low_stock lists them in the Slack message.
  4. log_audit_complete always runs last, printing the headline numbers regardless of which branch fired.
  5. The errors block alerts Slack separately if the flow itself fails outright - an unreachable Odoo instance or bad credentials must not read as "no risk".

What you get

  • A direct count from Odoo's own search_count XML-RPC call, not an estimate or a dashboard someone has to remember to check.
  • A Slack alert that names the actual products and quantities, not just a bare "something is low" count.
  • A read-only design: check_low_stock_count and fetch_low_stock_products only ever read - this flow cannot create a purchase order, write a quantity, or touch any Odoo record.
  • A final status log every run, breach or not, so the execution history doubles as a stock-level trend.

Who it's for

  • Operations and inventory teams running Odoo who want an early signal when stock crosses a reorder line, instead of discovering it at the next sales order that cannot be fulfilled.
  • Teams who already check Odoo's built-in low-stock views by hand and want the same signal pushed to them instead.
  • Anyone evaluating the Odoo plugin who wants a worked example of Query running SEARCH_COUNT for a threshold check and SEARCH_READ for the supporting detail, distinct from this repo's existing Odoo flows (a sales digest, a lead-creation webhook, an overdue scheduled-action sentinel), none of which touch inventory.

Why orchestrate this with Kestra

A search_count call run by hand answers one moment in time; it does not decide a cadence, compare the result against a threshold, or notify anyone. Kestra supplies the schedule, the threshold comparison as a first-class branch, and an execution history that shows exactly when stock started running low and for which products.

Prerequisites

  • A reachable Odoo instance with the Inventory/Sales apps installed and product.product records carrying real qty_available values.
  • A user with read access to product.product for XML-RPC calls.
  • A Slack incoming webhook for alerts.

Local testing: docker run -d --name odoo-stock-gate -p 8069:8069 --link odoo-stock-gate-db:db odoo:17.0 (Odoo's official image expects a linked PostgreSQL container; run docker run -d --name odoo-stock-gate-db -e POSTGRES_USER=odoo -e POSTGRES_PASSWORD=<your-local-password> -e POSTGRES_DB=postgres postgres:15 first, then the odoo container above, and create the demo database through Odoo's own first-run database-manager screen at http://localhost:8069.) docker network connect YOUR_KESTRA_NETWORK odoo-stock-gate (check existing networks first with docker network ls; only needed if the Kestra Worker runs in a separate Docker network than this container; set odoo_url to http://odoo-stock-gate:8069 from inside that network, or http://localhost:8069 from the host - 8069 is Odoo's own default port and does not collide with Kestra's UI on 8080).

Secrets

  • ODOO_USERNAME / ODOO_PASSWORD: credentials used by every io.kestra.plugin.odoo.Query task in this flow.
  • SLACK_WEBHOOK_URL: Slack incoming webhook used by alert_low_stock and the errors block.
  • In Kestra OSS (no Enterprise secrets backend), secrets are supplied as environment variables prefixed SECRET_, base64-encoded, and read back in flows with {{ secret('NAME') }} - for example SECRET_ODOO_PASSWORD=$(echo -n '<your-local-password>' | base64). This keeps credentials out of the flow YAML but, per Kestra's own documentation, offers no encryption at rest or access control beyond the host environment; use the Enterprise secrets backend for stronger guarantees.

Inputs

  • odoo_url (STRING, default http://localhost:8069): Odoo server URL.
  • odoo_db (STRING, default demo): Odoo database name.
  • min_stock_threshold (INT, default 10): quantity floor before a product counts as low stock.

Outputs

  • outputs.check_low_stock_count.size: the low-stock product count, every run.
  • outputs.fetch_low_stock_products.rows: up to 20 maps with name, default_code, qty_available, present only when size > 0.

Quick start

  1. Start Odoo locally with the commands above, create the demo database through the first-run screen, and set a handful of products' on-hand quantity below 10 (through the Inventory app's own adjustment screen).
  2. Add the ODOO_USERNAME, ODOO_PASSWORD, and SLACK_WEBHOOK_URL secrets.
  3. Run the flow manually with the defaults and confirm alert_low_stock fires, naming the exact products you set low.
  4. Raise every low product's quantity back above 10 and re-run to confirm log_stock_healthy fires instead.
  5. Enable periodic_stock_check once you trust the check.

How to extend

  • Add a ["type", "=", "..."] or ["categ_id", "=", category_id] condition to both filters lists to scope the gate to storable goods or a specific product category, rather than every product in the database - the exact selection value for type differs across Odoo versions, so check it against your own instance before adding it.
  • Loop over several (model, min_stock_threshold) pairs with io.kestra.plugin.core.flow.Loop to run the same gate across multiple warehouses if qty_available is scoped per-location in your setup.
  • Add a stock.quant SEARCH_COUNT alongside product.product to catch quantity discrepancies at the warehouse-location level, not just the aggregate product total.
  • Chain a downstream task that creates a draft purchase.order via operation: CREATE once vendor and pricing data is available, replacing the alert-only design with a gated, dry-run-protected remediation path.

Pitfalls

  • This flow applies no product type or category filter. Every product.product record with qty_available below the threshold counts, including services or non-stocked items if your catalog has qty_available populated on them - add your own domain conditions before relying on this in production.
  • This flow is read-only by design; there is no remediation action. CREATE and WRITE both exist on this plugin's Query task, but automatically raising a purchase order needs vendor, pricing, and lead-time data this flow does not have - inventing that logic risked creating bad purchase data, so the AT_RISK branch only ever alerts.
  • SEARCH_COUNT does not populate row, rows, or uri. Verified from the plugin's own source: only size is set for that operation. The Slack alert's product list comes from a second, separate SEARCH_READ call (fetch_low_stock_products), not from check_low_stock_count itself.
  • The fetch_low_stock_products filter is re-specified, not reused. Both check_low_stock_count and fetch_low_stock_products carry the same ["qty_available", "<", ...] domain independently; changing the threshold input updates both automatically, but any further filter you add per How to extend needs to be added to both tasks to stay consistent.
  • A limit: 20 cap means the Slack message never lists more than 20 products, even if size reports a much larger breach - the headline count in alert_low_stock and log_audit_complete is always accurate, the named list may be partial.
  • This blueprint is UNTESTED against a live Odoo instance. The Query task's properties (url, db, username, password, model, operation, filters, fields, limit, fetchType) and outputs (size, rows) are taken verbatim from the plugin's source on main - but no Docker container or Kestra engine was run to execute this flow end to end.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.