Stock Lot Production Date with Aging Analysis
by Osool Consulting https://www.osoolconsulting.com| Availability |
Odoo Online
Odoo.sh
On Premise
|
| Odoo Apps Dependencies |
•
Barcode (stock_barcode)
• Inventory (stock) • Discuss (mail) |
| Lines of code | 1530 |
| Technical Name |
stock_lot_barcode_production |
| License | OPL-1 |
| Website | https://www.osoolconsulting.com |
Lot Production Date & Aging Analysis
What this module does
This module extends stock.lot with a first-class Production Date field, then uses it to compute aging metrics that feed a professional reporting suite — including executive dashboards, category KPIs, product-level aging, and a slow-moving inventory view. Production dates are also exposed in detailed operations, the Barcode app, and lot labels.
Who this is for
Manufacturing
Track production batches and enforce FIFO/FEFO rotation.
Finance & CFO
Quantify aging exposure on working capital and identify inventory impairment candidates.
Quality & compliance
Full traceability from scan to label, including manufacturing date on every printed lot.
Inventory managers
Catch obsolete inventory before it becomes a write-off with the Slow Moving dashboard.
Core features
Production Date on every lot
Indexed production_date field on stock.lot
with automatic propagation to related move lines, quants, and
lot labels.
Shelf life & expiry
product.template.shelf_life_days drives a stored,
indexed stock.lot.expiry_date — ready to query,
sort and FEFO-pick against.
FEFO reservation helper
stock.lot.find_fefo_lots() returns lots ordered by
oldest production/expiry, optionally filtered by on-hand
quantity at a location.
Barcode & detailed ops
Production date is writable in stock.picking detailed operations and the Barcode app (picking and inventory adjustments).
Lot-name barcode scanning
The barcode scanner can resolve a scanned lot name, attach it to the right move line, and pre-fill its production date — including stripping GS1 AI prefixes (AI 10 / AI 21).
Enterprise-aware costing
Unit cost is derived from stock_valuation_layer when
available, falling back to purchase price and standard price so
totals stay accurate on Community installs.
Outbound velocity & days-of-stock
Ship-out volume over a configurable window (default 90 days) feeds a proper days-of-stock estimate — on-hand ÷ average daily outbound.
NUL-byte safe search
Lot/product/picking searches scrub NUL (0x00) characters from domains — the classic PostgreSQL invalid UTF-8 crash when noisy barcodes reach the database.
Lot label with Mfg date
The default report_lot_label template is extended
with a Manufacturing Date line and dynamic product-name sizing.
Reports & dashboards
- Aging Dashboard (Category) Executive kanban + list with value/percentage breakdowns by age band (0–6M, 6–12M, 1–2Y, 2Y+), a weighted aging risk score and a color-coded risk level (Low / Medium / High / Critical).
- Aging KPI by Category Pivot, graph and list views — measures value, quantity, lot count, product count, and average/min/max age per category × age range.
- Product Aging Analysis One row per product with lot count, total value, average age, the oldest lot (and its value), outbound velocity, and a real days-of-stock estimate.
- Slow Moving Inventory Identifies obsolete stock by days since last movement: Active (<90d), Slow (90–180d), Very Slow (180–365d), Obsolete (>365d).
- Lot Production Report Per-lot × location detail with quantity, unit cost, total value, first-receipt and last-movement dates, production date, age in years, and outbound-window quantity.
Age bands used across reports
Technical notes
| Target Odoo version | 19.0 Enterprise (Community also supported with reduced costing accuracy) |
|---|---|
| Dependencies | stock, stock_barcode — purchase is
detected at install time and wired in only if present. |
| Models added |
lot.production.report,
lot.aging.kpi,
category.aging.summary,
product.aging.analysis,
slow.moving.inventory (all _auto = False SQL views)
|
| Models extended |
stock.lot, stock.move.line,
stock.move, stock.picking,
stock.quant, stock.location,
product.product, product.template,
uom.uom, product.uom,
barcode.nomenclature,
res.config.settings
|
| Hooks | post_init_hook rebuilds the 5 SQL views in dependency
order; uninstall_hook drops them. |
| Security |
Read-only access to all report views for
stock.group_stock_user and
stock.group_stock_manager.
|
| License | OPL-1 |
Installation
- Copy this module into your Odoo addons path.
- Activate developer mode and install Stock Lot Production Date with Aging Analysis from Apps.
- Open Inventory > Reporting > Inventory Aging to find the new dashboards.
- Tune the outbound-velocity window under Inventory > Configuration > Settings > Lot Aging & Velocity.
Changelog
-
19.0.4.0.1
2026-04-24
-
fix
stock.lot.expiry_datenow depends onproduct_id.product_tmpl_id.shelf_life_daysdirectly (was a non-stored related field) so template edits reliably invalidate existing lots. -
chore
find_fefo_lotsmigrated from the deprecatedread_groupto the Odoo 19_read_groupsignature.
-
fix
-
19.0.4.0.0
2026-04-24
-
fix
Correctness: removed duplicate XML IDs across
stock_lot_views.xml/barcode.xml; correctedage_yearsSQL math to a straight(CURRENT_DATE - production_date) / 365.25(the previous formula over-counted viaAGE()part sums); fixedstock.move.lot_production_datecompute to pick the oldest lot date deterministically instead of whichever move line was iterated last. -
fix
Dropped the deprecated
read_groupoverride and moved aggregation toaggregator='sum'on the field definitions — this eliminates the N-extra-searches-per-grouped-row path. -
fix
Rewrote the
stock_valuation_layerCTE to aggregate directly bysvl.lot_id(positive rows only), so unit cost no longer mixes inbound and outbound valuations or double-counts quantities across joined move lines. -
chore
Added
post_init_hookanduninstall_hookso the 5 SQL views are rebuilt in dependency order on install and dropped cleanly on uninstall. -
chore
Factored
_has_nested_relational_domainintobarcode_utils; introducedSanitizedSearchMixinso every_searchoverride shares one implementation. Removed redundantsearch_readoverrides (search_read→search→_searchalready flows through sanitization). -
chore
Centralised age-band thresholds, risk weights, and risk-level
cutoffs into a single
models/constants.pymodule; SQL CASE expressions are generated from it viaage_band_case_sql/risk_weight_case_sql/risk_level_case_sql. -
chore
Dropped the hard
purchasedependency — the purchase-price CTE is now added only whenpurchase_order_lineandstock_move.purchase_line_idare present at install time. -
feat
Shelf life & expiry:
product.template.shelf_life_daysand a stored, indexedstock.lot.expiry_datecomputed from production date + shelf life. Exposed in the product form and lot views. -
feat
Outbound velocity: new
outbound_qty_windowcolumn inlot.production.reportaggregates ship-out quantity from internal to non-internal locations over a configurable window (default 90 days). Propagated to the aging KPI and product-aging views. -
feat
Real days-of-stock:
product.aging.analysis.days_of_stockis now on-hand ÷ (outbound_qty_window ÷ window_days) instead ofavg_age_years × 365(which was a misnamed average-age). The original metric is still available underavg_age_days. -
feat
FEFO helper:
stock.lot.find_fefo_lots(product_id, …)returns lots ordered by oldest production date / expiry, optionally filtered by on-hand quantity at a location. Ready for use from the barcode app or custom picking flows. -
feat
Configurable velocity window: new
Inventory → Settings → Lot Aging & Velocity
section exposes
stock_aging_velocity_window_days. Saving rebuilds the dependent SQL views so the new window takes effect immediately.
-
fix
Correctness: removed duplicate XML IDs across
-
19.0.3.8.0
2026-04-24
- chore Added module icon, store thumbnail and full HTML description page.
- chore Manifest cleaned up — verbose RST description moved into
static/description/index.html.
-
19.0.3.7.7
2026-04-24
-
fix
Inverted filter on Location Report (and any list with a contains filter on a Product many2one).
sanitize_domainwas normalising the internalany!/not any!operators as if!meant negation; it actually means "same semantic, bypass record rules on the comodel".any!is now mapped toanyandnot any!tonot any, preserving the original direction of the filter.
-
fix
Inverted filter on Location Report (and any list with a contains filter on a Product many2one).
-
19.0.3.7.6
baseline
-
feat
Production date on
stock.lotwith indexed field, propagation to move lines / quants / lot labels, GS1 AI-prefix aware barcode scanning and NUL-byte sanitization on domain searches. - feat Reporting suite: Lot Production Report, Aging KPI by Category, Category Aging Dashboard, Product Aging Analysis, and Slow Moving Inventory — all SQL-view backed.
-
feat
Production date on
Odoo Proprietary License v1.0 This software and associated files (the "Software") may only be used (executed, modified, executed after modifications) if you have purchased a valid license from the authors, typically via Odoo Apps, or if you have received a written agreement from the authors of the Software (see the COPYRIGHT file). You may develop Odoo modules that use the Software as a library (typically by depending on it, importing it and using its resources), but without copying any source code or material from the Software. You may distribute those modules under the license of your choice, provided that this license is compatible with the terms of the Odoo Proprietary License (For example: LGPL, MIT, or proprietary licenses similar to this one). It is forbidden to publish, distribute, sublicense, or sell copies of the Software or modified copies of the Software. The above copyright notice and this permission notice must be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Please log in to comment on this module
There are no ratings yet!
It´s ok now
Thanks
I sent a screenshot by email.
can you try it now?
Hi, We've identified errors after installing your module when filtering/grouping by in the inventory module. Could you please check this? What is happening is that the filter logic applied to the Product field together with the "contains" operator is being inverted, which causes Odoo not to correctly filter the selected product in the Location Report.
Hi, We've identified errors after installing your module when filtering/grouping by in the inventory module. Could you please check this? What is happening is that the filter logic applied to the Product field together with the "contains" operator is being inverted, which causes Odoo not to correctly filter the selected product in the Location Report.
dear, thanks for the report, but can you mail me screen shot or record for the error, so i can under more about how it happened?