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:
@@ -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': [
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
24
fusion_plating/scripts/fp_print_order.py
Normal file
24
fusion_plating/scripts/fp_print_order.py
Normal 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"]}')
|
||||||
Reference in New Issue
Block a user