204 lines
7.9 KiB
Python
204 lines
7.9 KiB
Python
# Fusion Accounting - Report Export Wizard
|
|
# Provides multi-format export capabilities for accounting reports
|
|
|
|
import base64
|
|
import json
|
|
import types
|
|
from urllib.parse import urlparse, parse_qs
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import UserError
|
|
from odoo.models import check_method_name
|
|
|
|
|
|
class ReportExportWizard(models.TransientModel):
|
|
"""Transient wizard that enables batch export of accounting reports
|
|
into various file formats, stored as downloadable attachments."""
|
|
|
|
_name = 'account_reports.export.wizard'
|
|
_description = "Fusion Accounting Report Export Wizard"
|
|
|
|
export_format_ids = fields.Many2many(
|
|
string="Target Formats",
|
|
comodel_name='account_reports.export.wizard.format',
|
|
relation="dms_acc_rep_export_wizard_format_rel",
|
|
)
|
|
report_id = fields.Many2one(
|
|
string="Source Report",
|
|
comodel_name='account.report',
|
|
required=True,
|
|
)
|
|
doc_name = fields.Char(
|
|
string="Output Filename",
|
|
help="Base name applied to all generated output files.",
|
|
)
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
"""Override creation to auto-populate available export formats
|
|
from the report's configured action buttons."""
|
|
new_wizards = super().create(vals_list)
|
|
|
|
for wiz in new_wizards:
|
|
wiz.doc_name = wiz.report_id.name
|
|
|
|
# Build format entries from the report's generation options
|
|
generation_opts = self.env.context.get('account_report_generation_options', {})
|
|
available_buttons = generation_opts.get('buttons', [])
|
|
|
|
for btn_config in available_buttons:
|
|
export_type = btn_config.get('file_export_type')
|
|
if export_type:
|
|
self.env['account_reports.export.wizard.format'].create({
|
|
'name': export_type,
|
|
'fun_to_call': btn_config['action'],
|
|
'fun_param': btn_config.get('action_param'),
|
|
'export_wizard_id': wiz.id,
|
|
})
|
|
|
|
return new_wizards
|
|
|
|
def export_report(self):
|
|
"""Execute the export process: generate files in each selected format
|
|
and return a window action displaying the resulting attachments."""
|
|
self.ensure_one()
|
|
|
|
saved_attachments = self.env['ir.attachment']
|
|
for attachment_data in self._build_attachment_values():
|
|
saved_attachments |= self.env['ir.attachment'].create(attachment_data)
|
|
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': _('Generated Documents'),
|
|
'view_mode': 'kanban,form',
|
|
'res_model': 'ir.attachment',
|
|
'domain': [('id', 'in', saved_attachments.ids)],
|
|
}
|
|
|
|
def _build_attachment_values(self):
|
|
"""Iterate over selected formats, invoke the corresponding report
|
|
generator, and collect attachment value dictionaries."""
|
|
self.ensure_one()
|
|
|
|
attachment_vals_list = []
|
|
current_options = self.env.context['account_report_generation_options']
|
|
|
|
for fmt in self.export_format_ids:
|
|
# Validate the callable name is a safe public method
|
|
method_name = fmt.fun_to_call
|
|
check_method_name(method_name)
|
|
|
|
# Resolve whether the custom handler or base report owns the method
|
|
target_report = self.report_id
|
|
if target_report.custom_handler_model_id:
|
|
handler_obj = self.env[target_report.custom_handler_model_name]
|
|
if hasattr(handler_obj, method_name):
|
|
callable_fn = getattr(handler_obj, method_name)
|
|
else:
|
|
callable_fn = getattr(target_report, method_name)
|
|
else:
|
|
callable_fn = getattr(target_report, method_name)
|
|
|
|
extra_args = [fmt.fun_param] if fmt.fun_param else []
|
|
action_result = callable_fn(current_options, *extra_args)
|
|
|
|
attachment_vals_list.append(fmt.apply_export(action_result))
|
|
|
|
return attachment_vals_list
|
|
|
|
|
|
class ReportExportFormatOption(models.TransientModel):
|
|
"""Represents a single selectable export format within the export wizard,
|
|
linking a display label to the callable that produces the file."""
|
|
|
|
_name = 'account_reports.export.wizard.format'
|
|
_description = "Fusion Accounting Report Export Format"
|
|
|
|
name = fields.Char(string="Format Label", required=True)
|
|
fun_to_call = fields.Char(string="Generator Method", required=True)
|
|
fun_param = fields.Char(string="Method Argument")
|
|
export_wizard_id = fields.Many2one(
|
|
string="Owning Wizard",
|
|
comodel_name='account_reports.export.wizard',
|
|
required=True,
|
|
ondelete='cascade',
|
|
)
|
|
|
|
def apply_export(self, action_payload):
|
|
"""Convert a report action response into attachment-ready values.
|
|
|
|
Handles two action types:
|
|
- ir_actions_account_report_download: direct file generation
|
|
- ir.actions.act_url: fetch file from a wizard model via URL params
|
|
"""
|
|
self.ensure_one()
|
|
|
|
if action_payload['type'] == 'ir_actions_account_report_download':
|
|
opts_dict = json.loads(action_payload['data']['options'])
|
|
|
|
# Resolve and invoke the file generator function
|
|
generator_name = action_payload['data']['file_generator']
|
|
check_method_name(generator_name)
|
|
|
|
source_report = self.export_wizard_id.report_id
|
|
if source_report.custom_handler_model_id:
|
|
handler_env = self.env[source_report.custom_handler_model_name]
|
|
if hasattr(handler_env, generator_name):
|
|
gen_callable = getattr(handler_env, generator_name)
|
|
else:
|
|
gen_callable = getattr(source_report, generator_name)
|
|
else:
|
|
gen_callable = getattr(source_report, generator_name)
|
|
|
|
output = gen_callable(opts_dict)
|
|
|
|
# Encode raw bytes content to base64; handle generator objects
|
|
raw_content = output['file_content']
|
|
if isinstance(raw_content, bytes):
|
|
encoded_data = base64.encodebytes(raw_content)
|
|
elif isinstance(raw_content, types.GeneratorType):
|
|
encoded_data = base64.encodebytes(b''.join(raw_content))
|
|
else:
|
|
encoded_data = raw_content
|
|
|
|
output_name = (
|
|
f"{self.export_wizard_id.doc_name or self.export_wizard_id.report_id.name}"
|
|
f".{output['file_type']}"
|
|
)
|
|
content_mime = self.export_wizard_id.report_id.get_export_mime_type(
|
|
output['file_type']
|
|
)
|
|
|
|
elif action_payload['type'] == 'ir.actions.act_url':
|
|
# Extract file data from a URL-based wizard action
|
|
url_parts = urlparse(action_payload['url'])
|
|
params = parse_qs(url_parts.query)
|
|
|
|
target_model = params['model'][0]
|
|
record_id = int(params['id'][0])
|
|
source_wizard = self.env[target_model].browse(record_id)
|
|
|
|
output_name = source_wizard[params['filename_field'][0]]
|
|
encoded_data = source_wizard[params['field'][0]]
|
|
extension = output_name.split('.')[-1]
|
|
content_mime = self.env['account.report'].get_export_mime_type(extension)
|
|
opts_dict = {}
|
|
|
|
else:
|
|
raise UserError(
|
|
_("The selected format cannot be exported as a document attachment.")
|
|
)
|
|
|
|
return self._prepare_attachment_dict(output_name, encoded_data, content_mime, opts_dict)
|
|
|
|
def _prepare_attachment_dict(self, filename, binary_data, mime_type, options_log):
|
|
"""Build the dictionary of values for creating an ir.attachment record."""
|
|
self.ensure_one()
|
|
return {
|
|
'name': filename,
|
|
'company_id': self.env.company.id,
|
|
'datas': binary_data,
|
|
'mimetype': mime_type,
|
|
'description': json.dumps(options_log),
|
|
}
|