From fa82ce17dd9c41b91ef1abbfee5a7cead9db76a4 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sun, 19 Apr 2026 09:05:29 -0400 Subject: [PATCH] feat(reports): sequence-sort the Print dropdown so FP reports are #1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Odoo 19's `ir.actions.actions._get_bindings` returns the print-menu bindings via `ORDER BY a.id` (insertion order) and only sequence-sorts the `action`-type bindings — `report`-type bindings are returned in raw SQL order. Result: FP reports installed after Odoo's stock ones appear at the BOTTOM of the dropdown, even when they're the customer-facing primary report (e.g. Timesheets above Quotation on sale.order). Two changes in fusion_plating_reports/models/ir_actions_report.py: 1. **Add `sequence` (Integer, default 100) to ir.actions.report** — gives every report a sortable knob. 2. **Override `ir.actions.actions._get_bindings`** to also sort the `report` slice by `(sequence, name.lower())`. super() returns the cached frozendict; we rebuild with the sorted reports. Then set sequences in fp_hide_default_reports.xml (lower = top): | Model | seq 10 (#1) | seq 15 (#2) | seq 20+ | |-----------------|--------------------------|--------------------------|-----------------------| | sale.order | FP Quotation Portrait | FP Quotation Landscape | FP Job Traveller (20) | | account.move | FP Invoice Portrait | FP Invoice Landscape | | | stock.picking | FP Packing Slip Portrait | FP Packing Slip Landscape| | | mrp.production | FP Job Traveller Portrait| FP Job Traveller Landscape| FP WO Margin (20) | | account.payment | FP Receipt Portrait | FP Receipt Landscape | | | fp.delivery | FP BoL Portrait | FP BoL Landscape | | | portal.job | FP CoC Portrait | FP CoC Landscape | | | fp.certificate | FP CoC English | FP CoC Français | | Odoo defaults stay at sequence 100 (default) → always at bottom. Verified on entech: sale.order print menu now shows Quotation Portrait → Quotation Landscape → Job Traveller × 2 → PRO-FORMA → Timesheets. Same pattern across all touched models. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../fusion_plating_reports/__manifest__.py | 2 +- .../data/fp_hide_default_reports.xml | 82 +++++++++++++++++++ .../fusion_plating_reports/models/__init__.py | 1 + .../models/ir_actions_report.py | 57 +++++++++++++ fusion_plating/scripts/fp_print_order.py | 24 ++++++ 5 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 fusion_plating/fusion_plating_reports/models/ir_actions_report.py create mode 100644 fusion_plating/scripts/fp_print_order.py diff --git a/fusion_plating/fusion_plating_reports/__manifest__.py b/fusion_plating/fusion_plating_reports/__manifest__.py index 5cd85d9b..b6522b47 100644 --- a/fusion_plating/fusion_plating_reports/__manifest__.py +++ b/fusion_plating/fusion_plating_reports/__manifest__.py @@ -3,7 +3,7 @@ # License OPL-1 (Odoo Proprietary License v1.0) { 'name': 'Fusion Plating — Reports', - 'version': '19.0.4.8.0', + 'version': '19.0.4.9.0', 'category': 'Manufacturing/Plating', 'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.', 'depends': [ diff --git a/fusion_plating/fusion_plating_reports/data/fp_hide_default_reports.xml b/fusion_plating/fusion_plating_reports/data/fp_hide_default_reports.xml index 80425cb6..454af898 100644 --- a/fusion_plating/fusion_plating_reports/data/fp_hide_default_reports.xml +++ b/fusion_plating/fusion_plating_reports/data/fp_hide_default_reports.xml @@ -82,4 +82,86 @@ action + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fusion_plating/fusion_plating_reports/models/__init__.py b/fusion_plating/fusion_plating_reports/models/__init__.py index ce5ec001..58b57c14 100644 --- a/fusion_plating/fusion_plating_reports/models/__init__.py +++ b/fusion_plating/fusion_plating_reports/models/__init__.py @@ -3,4 +3,5 @@ # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. +from . import ir_actions_report from . import report_wo_margin diff --git a/fusion_plating/fusion_plating_reports/models/ir_actions_report.py b/fusion_plating/fusion_plating_reports/models/ir_actions_report.py new file mode 100644 index 00000000..bfff5644 --- /dev/null +++ b/fusion_plating/fusion_plating_reports/models/ir_actions_report.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Copyright 2026 Nexa Systems Inc. +# License OPL-1 (Odoo Proprietary License v1.0) +# Part of the Fusion Plating product family. +"""Patch ir.actions.report so the Print dropdown can be ordered. + +Odoo 19 fetches print-menu bindings via `ir.actions.actions._get_bindings` +which returns reports in `ORDER BY a.id` (insertion order). Only the +`action` bindings get a sequence sort applied — `report` bindings are +returned in the raw SQL order. Result: third-party FP reports installed +after Odoo's stock ones always appear at the BOTTOM of the dropdown, +even when they're the customer-facing primary report. + +Two changes: + 1. Add a `sequence` Integer field to ir.actions.report. + 2. Override `_get_bindings` to also sort report bindings by sequence + (then by name as a tie-breaker), matching the behaviour Odoo + already applies to action bindings. + +Lower sequence = appears higher in the Print dropdown. +""" +from odoo import api, fields, models +from odoo.tools import frozendict + + +class IrActionsReport(models.Model): + _inherit = 'ir.actions.report' + + sequence = fields.Integer( + default=100, + help='Order in which this report appears in the Print menu ' + '(lower = higher in the list). Default 100 leaves room ' + 'for both higher and lower priorities.', + ) + + +class IrActionsActions(models.Model): + _inherit = 'ir.actions.actions' + + @api.model + def _get_bindings(self, model_name): + # super() returns a cached frozendict via @tools.ormcache; we + # re-sort the 'report' slice (Odoo already sorts 'action'). + result = super()._get_bindings(model_name) + if not result.get('report'): + return result + sorted_reports = tuple(sorted( + result['report'], + key=lambda vals: ( + vals.get('sequence', 100), + (vals.get('name') or '').lower(), + ), + )) + # frozendict is immutable — rebuild from a plain dict. + new_result = dict(result) + new_result['report'] = sorted_reports + return frozendict(new_result) diff --git a/fusion_plating/scripts/fp_print_order.py b/fusion_plating/scripts/fp_print_order.py new file mode 100644 index 00000000..fd4d2233 --- /dev/null +++ b/fusion_plating/scripts/fp_print_order.py @@ -0,0 +1,24 @@ +env = env # noqa +# Use the SAME path the web client uses (the cog menu) — _get_bindings. +# This honours the new sequence-based sort we just added. +MODELS = ['sale.order', 'account.move', 'stock.picking', 'mrp.production', + 'fusion.plating.delivery', 'account.payment', 'fusion.plating.portal.job', + 'fp.certificate'] +Actions = env['ir.actions.actions'] +Actions.clear_caches() if hasattr(Actions, 'clear_caches') else env.registry.clear_cache() +for m in MODELS: + bindings = Actions._get_bindings(m) + reports = bindings.get('report', ()) + if not reports: + continue + print(f'\\n=== {m} (top→bottom in Print menu) ===') + for i, r in enumerate(reports, 1): + # Get xmlid + xmlids = env['ir.model.data'].search([ + ('model', '=', 'ir.actions.report'), ('res_id', '=', r['id']) + ]) + xmlid = ', '.join(f'{x.module}.{x.name}' for x in xmlids) or '(no xmlid)' + is_fp = 'fusion_plating' in xmlid + marker = '★' if is_fp else ' ' + seq = r.get('sequence', 100) + print(f' {marker} {i:>2}. seq={seq:<4} {r["name"]}')