Initial commit
This commit is contained in:
203
Fusion Accounting/wizard/report_export_wizard.py
Normal file
203
Fusion Accounting/wizard/report_export_wizard.py
Normal file
@@ -0,0 +1,203 @@
|
||||
# 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),
|
||||
}
|
||||
Reference in New Issue
Block a user