feat(reports): sequence-sort the Print dropdown so FP reports are #1

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) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-19 09:05:29 -04:00
parent 9a1ee4b369
commit fa82ce17dd
5 changed files with 165 additions and 1 deletions

View File

@@ -3,7 +3,7 @@
# License OPL-1 (Odoo Proprietary License v1.0) # License OPL-1 (Odoo Proprietary License v1.0)
{ {
'name': 'Fusion Plating — Reports', 'name': 'Fusion Plating — Reports',
'version': '19.0.4.8.0', 'version': '19.0.4.9.0',
'category': 'Manufacturing/Plating', 'category': 'Manufacturing/Plating',
'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.', 'summary': 'PDF reports for Fusion Plating: quote, SO, WO, packing, BoL, CoC, invoice, receipt, quality + compliance.',
'depends': [ 'depends': [

View File

@@ -82,4 +82,86 @@
<field name="binding_type">action</field> <field name="binding_type">action</field>
</record> </record>
<!-- ================================================================
Print-menu sequencing — pin FP reports to the TOP of each
dropdown so customer-facing reports appear before internal
Odoo defaults (timesheets, picking ops, finished-product
labels, etc.) which now sit at sequence 100 by default.
Convention: Portrait = primary (10) → Landscape = secondary (15)
================================================================ -->
<!-- sale.order: Quotation/Sales Order is the primary -->
<record id="fusion_plating_reports.action_report_fp_sale_portrait" model="ir.actions.report">
<field name="sequence" eval="10"/>
</record>
<record id="fusion_plating_reports.action_report_fp_sale_landscape" model="ir.actions.report">
<field name="sequence" eval="15"/>
</record>
<record id="fusion_plating_reports.action_report_fp_job_traveller_so_portrait" model="ir.actions.report">
<field name="sequence" eval="20"/>
</record>
<record id="fusion_plating_reports.action_report_fp_job_traveller_so_landscape" model="ir.actions.report">
<field name="sequence" eval="25"/>
</record>
<!-- account.move: Invoice — Plating is the primary -->
<record id="fusion_plating_reports.action_report_fp_invoice_portrait" model="ir.actions.report">
<field name="sequence" eval="10"/>
</record>
<record id="fusion_plating_reports.action_report_fp_invoice_landscape" model="ir.actions.report">
<field name="sequence" eval="15"/>
</record>
<!-- stock.picking: Packing Slip is the primary -->
<record id="fusion_plating_reports.action_report_fp_packing_slip_portrait" model="ir.actions.report">
<field name="sequence" eval="10"/>
</record>
<record id="fusion_plating_reports.action_report_fp_packing_slip_landscape" model="ir.actions.report">
<field name="sequence" eval="15"/>
</record>
<!-- mrp.production: Job Traveller is the primary -->
<record id="fusion_plating_reports.action_report_fp_job_traveller_mo_portrait" model="ir.actions.report">
<field name="sequence" eval="10"/>
</record>
<record id="fusion_plating_reports.action_report_fp_job_traveller_mo_landscape" model="ir.actions.report">
<field name="sequence" eval="15"/>
</record>
<record id="fusion_plating_reports.action_report_wo_margin" model="ir.actions.report">
<field name="sequence" eval="20"/>
</record>
<!-- account.payment: Receipt — primary -->
<record id="fusion_plating_reports.action_report_fp_receipt_portrait" model="ir.actions.report">
<field name="sequence" eval="10"/>
</record>
<record id="fusion_plating_reports.action_report_fp_receipt_landscape" model="ir.actions.report">
<field name="sequence" eval="15"/>
</record>
<!-- fusion.plating.delivery: Bill of Lading -->
<record id="fusion_plating_reports.action_report_fp_bol_portrait" model="ir.actions.report">
<field name="sequence" eval="10"/>
</record>
<record id="fusion_plating_reports.action_report_fp_bol_landscape" model="ir.actions.report">
<field name="sequence" eval="15"/>
</record>
<!-- fp.certificate: English-first by default -->
<record id="fusion_plating_reports.action_report_coc_en" model="ir.actions.report">
<field name="sequence" eval="10"/>
</record>
<record id="fusion_plating_reports.action_report_coc_fr" model="ir.actions.report">
<field name="sequence" eval="15"/>
</record>
<!-- portal job CoC -->
<record id="fusion_plating_reports.action_report_coc_portrait" model="ir.actions.report">
<field name="sequence" eval="10"/>
</record>
<record id="fusion_plating_reports.action_report_coc" model="ir.actions.report">
<field name="sequence" eval="15"/>
</record>
</odoo> </odoo>

View File

@@ -3,4 +3,5 @@
# License OPL-1 (Odoo Proprietary License v1.0) # License OPL-1 (Odoo Proprietary License v1.0)
# Part of the Fusion Plating product family. # Part of the Fusion Plating product family.
from . import ir_actions_report
from . import report_wo_margin from . import report_wo_margin

View File

@@ -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)

View File

@@ -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"]}')