# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from itertools import groupby from odoo.tools import get_lang, SQL from odoo import api, models, _ class AccountGeneralLedgerReportHandler(models.AbstractModel): _inherit = 'account.general.ledger.report.handler' def _custom_options_initializer(self, report, options, previous_options): super()._custom_options_initializer(report, options, previous_options) if self.env.company.account_fiscal_country_id.code == 'LU': options.setdefault('buttons', []).append({ 'name': _('FAIA'), 'sequence': 50, 'action': 'export_file', 'action_param': 'l10n_lu_export_saft_to_xml', 'file_export_type': _('XML'), 'branch_allowed': True, }) @api.model def _fill_l10n_lu_saft_report_invoices_values(self, options, values): def get_product_action(message, product_ids, level='warning'): return { 'message': message, 'action_text': _('View Products'), 'action': self.env['product.product'].browse(product_ids)._get_records_action(name=_("Invalid Products")), 'level': level, } def _get_product_vals_list(values, encountered_product_ids): product_template_name = self.env['product.template']._field_to_sql('product_template', 'name') uom_name = self.env['uom.uom']._field_to_sql('uom', 'name') base_uom_name = self.env['uom.uom']._field_to_sql('base_uom', 'name') self.env.cr.execute(SQL( ''' SELECT product.id, product.barcode, %(product_template_name)s AS name, product.product_tmpl_id, product.default_code, product_category.name AS product_category, %(uom_name)s AS standard_uom, TRUNC(CAST(uom.factor AS NUMERIC), 8) AS uom_ratio, CASE WHEN uom.factor != 0 THEN TRUNC(CAST(1.0 / uom.factor AS NUMERIC), 8) ELSE 0 END AS ratio FROM product_product product LEFT JOIN product_template ON product_template.id = product.product_tmpl_id LEFT JOIN product_category ON product_category.id = product_template.categ_id LEFT JOIN uom_uom uom ON uom.id = product_template.uom_id WHERE product.id in %(encountered_product_ids)s ORDER BY default_code ''', product_template_name=product_template_name, uom_name=uom_name, base_uom_name=base_uom_name, encountered_product_ids=tuple(encountered_product_ids) )) product_vals_list = self.env.cr.dictfetchall() duplicate_product_ids = set() empty_product_ids = set() for product_code, grouped_products in groupby(product_vals_list, key=lambda product: product['default_code']): product_list = list(grouped_products) if not product_code: empty_product_ids.update(product_list_item['id'] for product_list_item in product_list) elif len(product_list) > 1: for product in product_list: duplicate_product_ids.add(product['id']) if duplicate_product_ids: values['errors']['product_duplicate_ref'] = get_product_action( _("Some products have duplicate 'Internal Reference', please make them unique."), list(duplicate_product_ids), level='danger' ) if empty_product_ids: values['errors']['product_missing_ref'] = get_product_action( _("Some products are missing `Internal Reference`, please define them."), list(empty_product_ids), level='danger' ) return product_vals_list res = { 'total_invoices_debit': 0.0, 'total_invoices_credit': 0.0, 'total_bills_debit': 0.0, 'total_bills_credit': 0.0, 'invoice_vals_list': [], 'uoms': [], 'product_vals_list': [], } # Fill 'total_invoices_debit', 'total_invoices_credit', 'total_bills_debit', 'total_bills_credit', 'invoice_vals_list'. encountered_product_ids = set() encountered_product_uom_ids = set() # TODO: to remove in master, do not include the in_invoice if the template was not updated template_tree = self.env['ir.qweb']._get_template('l10n_lu_reports.saft_template_inherit_l10n_lu_saft')[0] missing_tree_node = template_tree.findall('.//PurchaseInvoices') for move_vals in values['move_vals_list']: if move_vals['type'] not in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund'): continue # TODO: to remove in master, do not include the in_invoice if the template was not updated if not missing_tree_node and move_vals['type'] not in ('out_invoice', 'out_refund'): continue move_vals.update({ 'invoice_line_vals_list': [], 'tax_detail_vals_list': [], 'total_invoice_untaxed_balance': 0.0, 'total_invoice_tax_balance': 0.0, }) for line_vals in move_vals['line_vals_list']: if line_vals['tax_line_id']: move_vals['tax_detail_vals_list'].append({ 'currency_id': line_vals['currency_id'], 'currency_code': line_vals['currency_code'], 'tax_id': line_vals['tax_line_id'], 'tax_name': line_vals['tax_name'], 'tax_amount': line_vals['tax_amount'], 'tax_base_amount': line_vals['tax_base_amount'], 'tax_amount_type': line_vals['tax_amount_type'], 'amount': line_vals['balance'], 'amount_currency': line_vals['amount_currency'], 'rate': line_vals['rate'], }) move_vals['total_invoice_tax_balance'] -= line_vals['balance'] elif not line_vals['account_type'] in ('asset_receivable', 'liability_payable') and line_vals['display_type'] == 'product': move_vals['total_invoice_untaxed_balance'] -= line_vals['balance'] if line_vals['balance'] > 0.0: total_debit_key = 'total_invoices_debit' if move_vals['type'] in ('out_invoice', 'out_refund') else 'total_bills_debit' res[total_debit_key] += line_vals['balance'] else: total_credit_key = 'total_invoices_credit' if move_vals['type'] in ('out_invoice', 'out_refund') else 'total_bills_credit' res[total_credit_key] -= line_vals['balance'] if line_vals['product_id']: encountered_product_ids.add(line_vals['product_id']) if line_vals['product_uom_id']: encountered_product_uom_ids.add(line_vals['product_uom_id']) move_vals['invoice_line_vals_list'].append(line_vals) res['invoice_vals_list'].append(move_vals) move_vals['total_invoice_balance'] = move_vals['total_invoice_untaxed_balance'] + move_vals['total_invoice_tax_balance'] # Fill 'uoms'. uoms = self.env['uom.uom'].browse(list(encountered_product_uom_ids)) res['uoms'] = uoms # Fill 'product_vals_list'. if len(encountered_product_ids) > 0: res['product_vals_list'] = _get_product_vals_list(values, encountered_product_ids) values.update(res) @api.model def _l10n_lu_prepare_saft_report_values(self, report, options): template_vals = self._saft_prepare_report_values(report, options) template_vals.update({ 'xmlns': 'urn:OECD:StandardAuditFile-Taxation/2.00', 'file_version': '2.01', 'accounting_basis': 'Invoice Accounting', }) self._fill_l10n_lu_saft_report_invoices_values(options, template_vals) return template_vals @api.model def l10n_lu_export_saft_to_xml(self, options): report = self.env['account.report'].browse(options['report_id']) template_vals = self._l10n_lu_prepare_saft_report_values(report, options) file_data = report._generate_file_data_with_error_check( options, self.env['ir.qweb']._render, {'values': template_vals, 'template': 'l10n_lu_reports.saft_template_inherit_l10n_lu_saft', 'file_type': 'xml'}, template_vals['errors'], ) return file_data