from odoo import _, api, fields, models, Command class AccountMove(models.Model): _inherit = 'account.move' ubl_cii_xml_id = fields.Many2one( comodel_name='ir.attachment', string="Attachment", compute=lambda self: self._compute_linked_attachment_id('ubl_cii_xml_id', 'ubl_cii_xml_file'), depends=['ubl_cii_xml_id'] ) ubl_cii_xml_file = fields.Binary( attachment=True, string="UBL/CII File", copy=False, ) # ------------------------------------------------------------------------- # ACTIONS # ------------------------------------------------------------------------- def action_invoice_download_ubl(self): if invoices_with_ubl := self.filtered('ubl_cii_xml_id'): return { 'type': 'ir.actions.act_url', 'url': f'/account/download_invoice_documents/{",".join(map(str, invoices_with_ubl.ids))}/ubl', 'target': 'download', } return False # ------------------------------------------------------------------------- # BUSINESS # ------------------------------------------------------------------------- def _get_invoice_legal_documents(self, filetype, allow_fallback=False): # EXTENDS account if filetype == 'ubl': if ubl_attachment := self.ubl_cii_xml_id: return { 'filename': ubl_attachment.name, 'filetype': 'xml', 'content': ubl_attachment.raw, } return super()._get_invoice_legal_documents(filetype, allow_fallback=allow_fallback) def get_extra_print_items(self): print_items = super().get_extra_print_items() if self.ubl_cii_xml_id: print_items.append({ 'key': 'download_ubl', 'description': _('XML UBL'), **self.action_invoice_download_ubl(), }) return print_items # ------------------------------------------------------------------------- # EDI # ------------------------------------------------------------------------- @api.model def _get_ubl_cii_builder_from_xml_tree(self, tree): customization_id = tree.find('{*}CustomizationID') if tree.tag == '{urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100}CrossIndustryInvoice': return self.env['account.edi.xml.cii'] ubl_version = tree.find('{*}UBLVersionID') if ubl_version is not None: if ubl_version.text == '2.0': return self.env['account.edi.xml.ubl_20'] if ubl_version.text in ('2.1', '2.2', '2.3'): return self.env['account.edi.xml.ubl_21'] if customization_id is not None: if 'xrechnung' in customization_id.text: return self.env['account.edi.xml.ubl_de'] if customization_id.text == 'urn:cen.eu:en16931:2017#compliant#urn:fdc:nen.nl:nlcius:v1.0': return self.env['account.edi.xml.ubl_nl'] if customization_id.text == 'urn:cen.eu:en16931:2017#conformant#urn:fdc:peppol.eu:2017:poacc:billing:international:aunz:3.0': return self.env['account.edi.xml.ubl_a_nz'] if customization_id.text == 'urn:cen.eu:en16931:2017#conformant#urn:fdc:peppol.eu:2017:poacc:billing:international:sg:3.0': return self.env['account.edi.xml.ubl_sg'] if 'urn:cen.eu:en16931:2017' in customization_id.text: return self.env['account.edi.xml.ubl_bis3'] def _get_edi_decoder(self, file_data, new=False): # EXTENDS 'account' if file_data['type'] == 'xml': ubl_cii_xml_builder = self._get_ubl_cii_builder_from_xml_tree(file_data['xml_tree']) if ubl_cii_xml_builder is not None: return ubl_cii_xml_builder._import_invoice_ubl_cii return super()._get_edi_decoder(file_data, new=new) def _need_ubl_cii_xml(self, ubl_cii_format): self.ensure_one() return not self.ubl_cii_xml_id \ and self.is_sale_document() \ and ubl_cii_format in self.env['res.partner']._get_ubl_cii_formats() @api.model def _get_line_vals_list(self, lines_vals): """ Get invoice line values list. param list line_vals: List of values [name, qty, price, tax]. :return: List of invoice line values. """ return [{ 'sequence': 0, # be sure to put these lines above the 'real' invoice lines 'name': name, 'quantity': quantity, 'price_unit': price_unit, 'tax_ids': [Command.set(tax_ids)], } for name, quantity, price_unit, tax_ids in lines_vals] def _get_specific_tax(self, name, amount_type, amount, tax_type): AccountMoveLine = self.env['account.move.line'] if hasattr(AccountMoveLine, '_predict_specific_tax'): # company check is already done in the prediction query predicted_tax_id = AccountMoveLine._predict_specific_tax( self, name, self.partner_id, amount_type, amount, tax_type, ) return self.env['account.tax'].browse(predicted_tax_id) return self.env['account.tax']