122 lines
4.8 KiB
Python
122 lines
4.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2026 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
"""Two orthogonal jobs:
|
|
|
|
1. Sequence the Print-menu report bindings so Fusion reports float to the
|
|
top — Odoo 19 returns `ir.actions.report` bindings in raw insertion
|
|
order, which buries third-party reports.
|
|
|
|
2. Bridge `sale_pdf_quote_builder` to our Fusion sale-order reports.
|
|
Upstream gates its header/footer PDF merge on
|
|
`report_name == 'sale.report_saleorder'`, so the merge silently no-ops
|
|
when the user prints with our Portrait/Landscape variants. We replay
|
|
the same merge here for those report names.
|
|
"""
|
|
import io
|
|
|
|
from odoo import api, fields, models
|
|
from odoo.tools import frozendict, pdf, str2bool
|
|
|
|
|
|
_FUSION_SALE_REPORTS = frozenset({
|
|
'fusion_reports_templates.report_fr_sale_portrait',
|
|
'fusion_reports_templates.report_fr_sale_landscape',
|
|
})
|
|
|
|
|
|
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).',
|
|
)
|
|
|
|
def _render_qweb_pdf_prepare_streams(self, report_ref, data, res_ids=None):
|
|
result = super()._render_qweb_pdf_prepare_streams(report_ref, data, res_ids=res_ids)
|
|
if not res_ids:
|
|
return result
|
|
report = self._get_report(report_ref)
|
|
if report.report_name not in _FUSION_SALE_REPORTS:
|
|
return result
|
|
# quotation_document_ids is added by sale_pdf_quote_builder; bail if
|
|
# that module isn't installed (nothing to merge).
|
|
if 'quotation_document_ids' not in self.env['sale.order']._fields:
|
|
return result
|
|
try:
|
|
from odoo.tools.pdf import PdfFileWriter
|
|
except ImportError:
|
|
return result
|
|
|
|
always_include = str2bool(
|
|
self.env['ir.config_parameter'].sudo().get_param(
|
|
'sale.always_include_selected_documents'
|
|
)
|
|
)
|
|
orders = self.env['sale.order'].browse(res_ids)
|
|
for order in orders:
|
|
initial_stream = result.get(order.id, {}).get('stream')
|
|
if not initial_stream:
|
|
continue
|
|
if order.state == 'sale' and not always_include:
|
|
continue
|
|
quotation_documents = order.quotation_document_ids
|
|
headers = quotation_documents.filtered(lambda d: d.document_type == 'header')
|
|
footers = quotation_documents - headers
|
|
has_product_document = any(
|
|
line.product_document_ids for line in order.order_line
|
|
)
|
|
if not headers and not has_product_document and not footers:
|
|
continue
|
|
|
|
form_fields_values_mapping = {}
|
|
writer = PdfFileWriter()
|
|
self_with_order_context = self.with_context(
|
|
use_babel=True, lang=order._get_lang() or self.env.user.lang
|
|
)
|
|
for header in headers:
|
|
prefix = f'quotation_document_id_{header.id}__'
|
|
self_with_order_context._update_mapping_and_add_pages_to_writer(
|
|
writer, header, form_fields_values_mapping, prefix, order
|
|
)
|
|
if has_product_document:
|
|
for line in order.order_line:
|
|
for doc in line.product_document_ids:
|
|
prefix = f'sol_id_{line.id}_product_document_id_{doc.id}__'
|
|
self_with_order_context._update_mapping_and_add_pages_to_writer(
|
|
writer, doc, form_fields_values_mapping, prefix, order, line
|
|
)
|
|
self._add_pages_to_writer(writer, initial_stream.getvalue())
|
|
for footer in footers:
|
|
prefix = f'quotation_document_id_{footer.id}__'
|
|
self_with_order_context._update_mapping_and_add_pages_to_writer(
|
|
writer, footer, form_fields_values_mapping, prefix, order
|
|
)
|
|
pdf.fill_form_fields_pdf(writer, form_fields=form_fields_values_mapping)
|
|
buffer = io.BytesIO()
|
|
writer.write(buffer)
|
|
result[order.id]['stream'] = io.BytesIO(buffer.getvalue())
|
|
return result
|
|
|
|
|
|
class IrActionsActions(models.Model):
|
|
_inherit = 'ir.actions.actions'
|
|
|
|
@api.model
|
|
def _get_bindings(self, model_name):
|
|
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(),
|
|
),
|
|
))
|
|
new_result = dict(result)
|
|
new_result['report'] = sorted_reports
|
|
return frozendict(new_result)
|