Cet article est actuellement disponible en anglais uniquement. Traduction à venir.
A user opens a list view, looks at the aggregation row at the bottom, and the number is wrong. Sometimes it shows zero. Sometimes it shows the sum of one record instead of all visible records. Sometimes it adds USD and EUR amounts as if they were the same. There is no error in the log, just incorrect totals.
# What the UI shows:
Sales Order Total ... 12,000.00
# What SQL returns:
SELECT sum(amount_total) FROM sale_order WHERE ... = 47,235.50
This is one of the most user-confusing Odoo bugs because there is no error to point at. The fix depends on which of the four roots applies on Odoo 17.0/18.0/19.0.
Quick Fix
Verify the field has a correct aggregation operator declared:
class SaleOrder(models.Model):
_inherit = 'sale.order'
amount_total = fields.Monetary(
'Total',
currency_field='currency_id',
store=True,
aggregator='sum', # Odoo 18.0+
# or group_operator='sum' # Odoo 17.0
)
Without this, list view aggregation defaults to none for some field types and silently shows zero or empty.
Why This Happens
List view aggregation runs as a SQL aggregate over the visible records. The result depends on:
- The field's aggregation declaration.
MonetaryandFloatdefault to no aggregator unless you setaggregator='sum'or'avg'(Odoo 18.0+) orgroup_operator='sum'(17.0). Without it, the column gets no footer. - Whether the field is stored. Non-stored computed fields cannot be aggregated by SQL — Odoo fetches each record and would have to compute on the application side, which it does not for list footers.
- Currency mixing. A list with multi-currency monetary fields cannot be summed sensibly. Odoo 17.0+ tries to detect mixed currencies and shows zero or hides the footer.
groupbysemantics. When grouping by a field, the aggregation applies within each group, not across groups. The displayed group total is each group's subtotal — easy to misread as a global total.activefilter — the default filter excludes archived records. The user assumes "I see 100 records, so the footer sums them" but archived records may be silently filtered.
Step-by-Step Diagnosis
1. Compare to the SQL.
SELECT sum(amount_total)
FROM sale_order
WHERE state IN ('sale', 'done')
AND active = true;
If the SQL matches the UI, the user is misreading. If they differ, you have a real aggregation bug.
2. Check the field declaration.
field = env['sale.order']._fields['amount_total']
print(field.aggregator) # Odoo 18.0+
print(field.group_operator) # Odoo 17.0
print(field.store)
print(field.currency_field)
aggregator (or group_operator) must be set for the column to aggregate.
3. Test directly:
total = env['sale.order'].search([]).read_group(
[('state', '=', 'sale')],
fields=['amount_total:sum'],
groupby=[],
)
print(total)
If read_group returns the right total but the UI shows wrong, it is a frontend rendering issue (rare). If read_group returns the wrong total, it is a backend definition issue.
4. Check for multi-currency.
SELECT count(DISTINCT currency_id) FROM sale_order
WHERE state IN ('sale', 'done');
1 means mixed currencies — Odoo refuses to sum them.
5. Check archived records. Add active=False to the filter and re-check.
Permanent Fix
Add the aggregator to every monetary or numeric field that should aggregate:
amount_total = fields.Monetary(
'Total',
currency_field='currency_id',
store=True,
aggregator='sum', # Odoo 18.0+
)
margin = fields.Float(
'Margin',
store=True,
aggregator='avg',
)
quantity = fields.Float(
'Qty',
aggregator='sum',
)
For computed fields, set store=True so SQL can aggregate them:
amount_taxed = fields.Monetary(
compute='_compute_amount_taxed',
store=True, # critical
aggregator='sum',
currency_field='currency_id',
)
@api.depends('amount_untaxed', 'amount_tax')
def _compute_amount_taxed(self):
for rec in self:
rec.amount_taxed = rec.amount_untaxed + rec.amount_tax
For multi-currency aggregation, normalize first. Add a "company currency total" field:
amount_total_company = fields.Monetary(
'Total in Company Currency',
compute='_compute_amount_total_company',
store=True,
currency_field='company_currency_id',
aggregator='sum',
)
@api.depends('amount_total', 'currency_id', 'company_id', 'date_order')
def _compute_amount_total_company(self):
for rec in self:
rec.amount_total_company = rec.currency_id._convert(
rec.amount_total,
rec.company_id.currency_id,
rec.company_id,
rec.date_order or fields.Date.today(),
)
Then aggregate on amount_total_company for any list view that mixes currencies.
For the "I see 100 records but the footer disagrees" case, check the user's filters. The active record filter, archived filter, and any company filter all apply silently. Add visible filter chips so users can see what is actually applied.
How to Prevent It
- Always declare
aggregatoron numeric fields. Make this a code-review rule. Every newMonetary,Float,Integerfield gets an explicit aggregator declaration, even if it isaggregator=Noneto mean "do not aggregate". - Stored, not computed-on-the-fly, for aggregable fields. Computed fields that need aggregation must be
store=Trueso PostgreSQL can sum them. - Test list-view aggregation in QA. Check that the column footer matches a SQL
SUM(...)for at least one filter. Surprisingly, this is rarely tested explicitly. - For multi-currency, always normalize. Have a company-currency mirror of every customer-facing monetary field. Aggregate on the mirror.
- Don't hide the active filter. Many users do not realize archived records are excluded by default. Document this in the user training.
- Lint computed fields. A CI check that flags any
@api.dependson a non-stored field is helpful. Computed-on-computed without store breaks aggregation.
Related Errors
- Form view renders blank — sibling view bug.
- Search view domain ignored — what looks like a wrong total but is actually a wrong filter.
- Slow list view on > 1M rows — performance pair to this correctness issue.
- Stale related field — frequent cause of stored fields holding wrong values.
Frequently Asked Questions
Why does Odoo not aggregate Float fields by default?
Because not every Float should aggregate. A weight, a percentage, a ratio — summing them is meaningless. Forcing the developer to declare intent (aggregator='sum' or 'avg') prevents wrong defaults from creating wrong totals. The cost is one extra line per field.
What is the difference between group_operator and aggregator?
Naming evolution. Odoo 16.0 and 17.0 use group_operator='sum'. Odoo 18.0 added aggregator='sum' and deprecated group_operator. Both still work in 18.0; only aggregator works in 19.0. Update during migrations.
Can I show a footer that converts on the fly?
No, list view footers are SQL-aggregated. To show a converted total, store a converted column (the company-currency mirror pattern above) and aggregate on that. The conversion happens at write/compute time, not at list-render time.
Why does my list show "0.00" in the footer when records have non-zero values?
Three common causes: missing aggregator, the field is non-stored, or all the visible records have NULL for the field. Use the SQL probe in step 1 of the diagnosis to confirm, then fix the cause.
Can I aggregate by a custom function (not sum/avg/min/max)?
Not via the standard footer. You can override read_group on the model to inject a custom value, but this breaks across versions and is hard to maintain. A better pattern is a stored compute that runs your custom function at write time, then aggregate normally.
Why does the total change when I toggle the archived filter?
Because archived records have active=False and are excluded by default. The footer respects the active filter, which is one of the silent defaults. Showing the total of all records (active + archived) requires explicitly disabling the active filter via the search bar.
How do I add a row count to the list footer?
Odoo shows record count in the breadcrumb area, not the column footer. For a count column inside the list, add a virtual field with aggregator='count':
record_count = fields.Integer(default=1, aggregator='sum', readonly=True)
Each row contributes 1, the footer sums to the total visible count. Costs nothing in storage and gives users a footer count.
Need help with a tricky Odoo error? ECOSIRE's Odoo experts have shipped 215+ modules — get expert help.
Rédigé par
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Transformez votre entreprise avec Odoo ERP
Implémentation, personnalisation et assistance expertes d'Odoo pour rationaliser vos opérations.
Articles connexes
Comment ajouter un bouton personnalisé à une vue de formulaire Odoo (2026)
Ajoutez des boutons d'action personnalisés aux vues de formulaire Odoo 19 : méthode d'action Python, héritage des vues, visibilité conditionnelle, boîtes de dialogue de confirmation. Testé en production.
Comment ajouter un champ personnalisé dans Odoo sans Studio (2026)
Ajoutez des champs personnalisés via le module personnalisé dans Odoo 19 : héritage de modèle, extension de vue, champs calculés, décisions magasin/non-magasin. Code d'abord, contrôle de version.
Comment ajouter un rapport personnalisé dans Odoo à l'aide d'une mise en page externe
Créez un rapport PDF de marque dans Odoo 19 à l'aide de web.external_layout : modèle QWeb, format papier, liaison d'action. Avec logo imprimé + remplacements de pied de page.