Odoo computed field not updating when dependent field changes

Summary

A developer reports that a computed field in Odoo (total_discount on sale.order) fails to update when its dependencies (order_line.discount and order_line.price_total) change. The root cause is typically insufficient dependency specification regarding the relationship between the parent record and the child lines, or a lack of proper view configuration to trigger the computation on the client side. The fix involves ensuring the @api.depends decorator captures changes in the related One2many field’s sub-fields and verifying the view forces a re-evaluation.

Root Cause

The immediate cause is that the @api.depends definition is incomplete or the view does not signal the dependency change correctly to the Odoo ORM.

  • Missing Parent/Child Context: While @api.depends('order_line.discount') is technically valid for field-level triggers, it often fails to trigger the parent’s computed field recalculation when modifications happen inside an editable list view unless the framework sees a direct link. The robust dependency is @api.depends('order_line.discount') (already present), but the issue often lies in how the data is saved.
  • View Save Behavior: In Odoo, computed fields are recalculated on the fly by the server when data is saved or read. If the “Save” button isn’t clicked or the view doesn’t enforce a reload of the parent record, the client (JavaScript) holds a stale value.
  • Server-Side Logic: The formula sum(line.discount) assumes line.discount is the raw value. If discount is a stored/computed field itself (rare on standard sale.order.line, but possible in custom modules) and not correctly saved, it might not be available.

Why This Happens in Real Systems

Odoo’s ORM is event-driven but relies on explicit dependency chains.

  • Transient State: In an editable list view (inline editing), changing a value on a line doesn’t immediately trigger the parent record’s computed fields. The parent only recalculates when the record is explicitly saved (posted) to the database.
  • Computed Fields on Related Records: If order_line contains computed fields (like price_total used in the dependency), and those fields are not stored (store=True), they are calculated in memory. If the dependency chain breaks (e.g., the line is modified but the parent isn’t told to recalculate), the parent sees the old value.
  • Stale Cache: The browser might display the calculated value from the previous server interaction because the JSON-RPC call to update the line didn’t return the updated parent total.

Real-World Impact

  • Data Integrity: The total discount displayed on the Sales Order is incorrect, leading to confusion for sales representatives and customers.
  • Process Friction: Users must manually trigger a refresh (e.g., saving and reloading the whole form) to see correct totals, breaking the workflow flow.
  • Reporting Errors: If total_discount is used in financial reports, incorrect values lead to revenue recognition errors.

Example or Code

To fix the issue, we must ensure the dependency catches changes in the sub-records effectively. If the discount field is defined on the line, the dependency @api.depends('order_line.discount') is usually sufficient for the ORM, but explicit syntax @api.depends('order_line', 'order_line.discount') is safer to ensure the computation triggers when lines are added/removed and when their discounts change.

Here is the corrected implementation:

from odoo import models, fields, api

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    total_discount = fields.Float(compute='_compute_total_discount', store=True, readonly=True)

    @api.depends('order_line.discount')
    def _compute_total_discount(self):
        for order in self:
            order.total_discount = sum(line.discount for line in order.order_line)

How Senior Engineers Fix It

Senior engineers approach this by validating the data flow from the database up to the UI.

  1. Verify the Decorator: They ensure the @api.depends lists the specific fields that trigger the calculation. If the line discount field is modified via a button or code, the dependency must reflect that.
  2. Check View Architecture: They inspect the XML view. The order_line field must be an One2many widget (usually one2many or lines). If the view uses a specific widget that bypasses standard Odoo saving mechanisms, it must be adjusted to trigger the standard onchange or save events.
  3. Ensure Store=True (If needed): If the field needs to be searchable or sorted, store=True is required. However, for display only, store=False (default) works, but requires the client to request the value.
  4. Debugging: They use pdb or logs in the compute method to see if the method is being called at all when the line changes. If it’s not called, the dependency is wrong. If it’s called but returns the old value, the data hasn’t been committed to the transaction memory yet.
  5. Clear Cache: They force a cache clear (self.invalidate_cache()) if necessary, though Odoo usually handles this.

Why Juniors Miss It

  • Misunderstanding @api.depends: Juniors often think @api.depends('order_line.discount') is enough for any change on the line, but forget that Odoo only re-evaluates this when the parent record’s state changes or a specific onchange is triggered.
  • Ignoring the View: They focus entirely on the Python backend and forget that the order_line widget in the view needs to be configured to trigger the parent’s onchange events when a line is edited inline.
  • Assuming Immediate Recalculation: They expect the parent field to update instantly in the UI as they type in the child list, not realizing Odoo’s standard architecture requires a save/refresh cycle for 100% accuracy unless client-side logic (Odoo Studio/OWL) is used.
  • Tag Confusion: They often include irrelevant tags (like javascript or computer-vision in this specific input) indicating they are shooting in the dark rather than understanding the ORM’s event-driven nature.