New to Kestra?
Use blueprints to kickstart your first workflows.
Audit Odoo product stock via SEARCH_COUNT, alert Slack when items fall below a minimum threshold, read-only.
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.
periodic_stock_check (io.kestra.plugin.core.trigger.Schedule) runs every 6 hours. Shipped disabled so you can validate a manual run first.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.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.log_audit_complete always runs last, printing the headline numbers regardless of which branch fired.errors block alerts Slack separately if the flow itself fails outright - an unreachable Odoo instance or bad credentials must not read as "no risk".search_count XML-RPC call, not an estimate or a dashboard someone has to remember to check.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.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.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.
product.product records carrying real qty_available values.product.product for XML-RPC calls.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).
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.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.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.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.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).ODOO_USERNAME, ODOO_PASSWORD, and SLACK_WEBHOOK_URL secrets.alert_low_stock fires, naming the exact products you set low.10 and re-run to confirm log_stock_healthy fires instead.periodic_stock_check once you trust the check.["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.(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.stock.quant SEARCH_COUNT alongside product.product to catch quantity discrepancies at the warehouse-location level, not just the aggregate product total.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.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.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.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.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.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.