# Fusion Accounting - HTTP Controllers # Provides web endpoints for report file generation and attachment downloads import json from werkzeug.exceptions import InternalServerError from odoo import http from odoo.addons.fusion_accounting.models.account_report import AccountReportFileDownloadException from odoo.addons.account.controllers.download_docs import _get_headers from odoo.http import content_disposition, request from odoo.models import check_method_name from odoo.tools.misc import html_escape class AccountReportController(http.Controller): """Handles HTTP requests for generating and downloading accounting report files in various formats.""" @http.route('/fusion_accounting', type='http', auth='user', methods=['POST'], csrf=False) def get_report(self, options, file_generator, **kwargs): """Generate a report file based on the provided options and generator method. :param options: JSON-encoded report configuration options :param file_generator: name of the method that produces the file :returns: HTTP response with the generated file content """ current_uid = request.uid parsed_options = json.loads(options) # Determine which companies are in scope for this report company_ids = request.env['account.report'].get_report_company_ids(parsed_options) if not company_ids: cookie_cids = request.cookies.get('cids', str(request.env.user.company_id.id)) company_ids = [int(cid) for cid in cookie_cids.split('-')] target_report = ( request.env['account.report'] .with_user(current_uid) .with_context(allowed_company_ids=company_ids) .browse(parsed_options['report_id']) ) try: check_method_name(file_generator) file_data = target_report.dispatch_report_action(parsed_options, file_generator) raw_content = file_data['file_content'] output_type = file_data['file_type'] resp_headers = self._build_response_headers( output_type, file_data['file_name'], raw_content, ) if output_type == 'xlsx': # Stream binary spreadsheet data http_response = request.make_response(None, headers=resp_headers) http_response.stream.write(raw_content) else: http_response = request.make_response(raw_content, headers=resp_headers) if output_type in ('zip', 'xaf'): # Enable streaming for large archive files to avoid # loading the entire content into memory at once http_response.direct_passthrough = True return http_response except AccountReportFileDownloadException as exc: if exc.content: exc.content['file_content'] = exc.content['file_content'].decode() error_payload = { 'name': type(exc).__name__, 'arguments': [exc.errors, exc.content], } raise InternalServerError( response=self._format_error_response(error_payload) ) from exc except Exception as exc: # noqa: BLE001 error_payload = http.serialize_exception(exc) raise InternalServerError( response=self._format_error_response(error_payload) ) from exc def _format_error_response(self, error_data): """Wrap error details into a JSON response matching the Odoo RPC error format.""" envelope = { 'code': 200, 'message': 'Odoo Server Error', 'data': error_data, } return request.make_response(html_escape(json.dumps(envelope))) def _build_response_headers(self, file_type, file_name, raw_content): """Construct HTTP response headers appropriate for the given file type.""" mime_type = request.env['account.report'].get_export_mime_type(file_type) header_list = [ ('Content-Type', mime_type), ('Content-Disposition', content_disposition(file_name)), ] # Include Content-Length for text-based formats if file_type in ('xml', 'txt', 'csv', 'kvr'): header_list.append(('Content-Length', len(raw_content))) return header_list @http.route( '/fusion_accounting/download_attachments/', type='http', auth='user', ) def download_report_attachments(self, attachments): """Download one or more report attachments, packaging them into a zip archive when multiple files are requested.""" attachments.check_access('read') assert all( att.res_id and att.res_model == 'res.partner' for att in attachments ) if len(attachments) == 1: single = attachments resp_headers = _get_headers(single.name, single.mimetype, single.raw) return request.make_response(single.raw, resp_headers) else: zip_data = attachments._build_zip_from_attachments() resp_headers = _get_headers('attachments.zip', 'zip', zip_data) return request.make_response(zip_data, resp_headers)