# -*- 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 from odoo import api, models, _ class AccountGeneralLedger(models.AbstractModel): _inherit = 'account.general.ledger.report.handler' def _custom_options_initializer(self, report, options, previous_options=None): 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') }) @api.model def _fill_l10n_lu_saft_report_invoices_values(self, options, values): def _get_product_action(message, product_ids, critical=False): return { 'message': message, 'action_text': _('View Products'), 'action_name': 'action_open_products', 'action_params': product_ids, 'critical': critical, } def _get_product_vals_list(values, encountered_product_ids): lang = self.env.user.lang or get_lang(self.env).code product_template_name = f"COALESCE(product_template.name->>'{lang}', product_template.name->>'en_US')" uom_name = f"COALESCE(uom.name->>'{lang}', uom.name->>'en_US')" base_uom_name = f"COALESCE(base_uom.name->>'{lang}', base_uom.name->>'en_US')" self._cr.execute(f''' SELECT product.id, product.barcode, {product_template_name} AS name, product.product_tmpl_id, product.default_code, product_category.name AS product_category, {uom_name} AS standard_uom, uom.uom_type AS uom_type, TRUNC(uom.factor, 8) AS uom_ratio, CASE WHEN uom.factor != 0 THEN TRUNC((1.0 / uom.factor), 8) ELSE 0 END AS ratio, {base_uom_name} AS base_uom 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 LEFT JOIN uom_uom base_uom ON base_uom.category_id = uom.category_id AND base_uom.uom_type='reference' WHERE product.id in %s ORDER BY default_code ''', [tuple(encountered_product_ids)]) product_vals_list = self._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.add(product_list[0]['id']) elif len(product_list) > 1: for product in product_list: duplicate_product_ids.add(product['id']) if duplicate_product_ids: values['errors'].append(_get_product_action( _("Some products have duplicate 'Internal Reference', please make them unique."), list(duplicate_product_ids), critical=True )) if empty_product_ids: values['errors'].append(_get_product_action( _("Some products are missing `Internal Reference`, please define them."), list(empty_product_ids), critical=True )) return product_vals_list res = { 'total_invoices_debit': 0.0, 'total_invoices_credit': 0.0, 'invoice_vals_list': [], 'uoms': [], 'product_vals_list': [], } # Fill 'total_invoices_debit', 'total_invoices_credit', 'invoice_vals_list'. encountered_product_ids = set() encountered_product_uom_ids = set() for move_vals in values['move_vals_list']: if 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'], 'tax_id': line_vals['tax_line_id'], 'tax_name': line_vals['tax_name'], 'tax_amount': line_vals['tax_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: res['total_invoices_debit'] += line_vals['balance'] else: res['total_invoices_credit'] -= 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)) non_ref_uoms = uoms.filtered(lambda uom: uom.uom_type != 'reference') if non_ref_uoms: # search base UoM for UoM master table uoms |= self.env['uom.uom'].search([('category_id', 'in', non_ref_uoms.category_id.ids), ('uom_type', '=', 'reference')]) 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 = self._saft_generate_file_data_with_error_check( report, options, template_vals, 'l10n_lu_reports.saft_template_inherit_l10n_lu_saft' ) self.env['ir.attachment'].l10n_lu_reports_validate_xml_from_attachment(file_data['file_content'], 'saft') return file_data