# -*- coding: utf-8 -*- # Copyright 2021 Joan Marín # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import pytz from dateutil import tz from datetime import datetime, timedelta from uuid import uuid4 from odoo import api, models, fields, SUPERUSER_ID, _ from odoo.exceptions import UserError DIAN_TYPES = ('e-support_document', 'e-invoicing', 'e-credit_note', 'e-debit_note') class AccountMove(models.Model): _inherit = "account.move" @api.model def _default_operation_type(self): user = self.env['res.users'].search([('id', '=', self.env.user.id)]) view_operation_type_field = False if (user.has_group('l10n_co_account_e_invoicing.group_view_operation_type_field') and self.env.user.id != SUPERUSER_ID): view_operation_type_field = True if 'move_type' in self._context.keys(): if self._context['move_type'] == 'out_invoice' and not view_operation_type_field: return '10' elif self._context['move_type'] == 'in_invoice': return '10' else: return False elif not view_operation_type_field: return '10' else: return False @api.model def _default_invoice_type_code(self): user = self.env['res.users'].search([('id', '=', self.env.user.id)]) view_invoice_type_field = False if (user.has_group('l10n_co_account_e_invoicing.group_view_invoice_type_field') and self.env.user.id != SUPERUSER_ID): view_invoice_type_field = True if 'move_type' in self._context.keys(): if self._context['move_type'] == 'out_invoice' and not view_invoice_type_field: return '01' elif self._context['move_type'] == 'in_invoice': return '05' else: return False elif not view_invoice_type_field: return '01' else: return False @api.model def _default_send_invoice_to_dian(self): return self.env.user.company_id.send_invoice_to_dian or '0' def _compute_warn_certificate(self): warn_remaining_certificate = False warn_inactive_certificate = False if self.company_id.einvoicing_enabled: warn_inactive_certificate = True if (self.company_id.certificate_file and self.company_id.certificate_password and self.company_id.certificate_date): remaining_days = self.company_id.certificate_remaining_days or 0 today = fields.Date.context_today(self) date_to = self.company_id.certificate_date days = (date_to - today).days warn_inactive_certificate = False if days < remaining_days: if days < 0: warn_inactive_certificate = True else: warn_remaining_certificate = True self.warn_inactive_certificate = warn_inactive_certificate self.warn_remaining_certificate = warn_remaining_certificate def _compute_sequence_resolution_id(self): for invoice_id in self: sequence_resolution = False if (invoice_id.move_type == "out_invoice" or (invoice_id.journal_id.sequence_id.dian_type == "e-support_document" and invoice_id.move_type == "in_invoice")): sequence_resolution_ids = self.env['ir.sequence.date_range'].search([ ('sequence_id', '=', invoice_id.journal_id.sequence_id.id)]) for sequence_resolution_id in sequence_resolution_ids: move_name = invoice_id.move_name or '' number = move_name.replace(sequence_resolution_id.prefix or '', '') if sequence_resolution_id.active_resolution: sequence_resolution = sequence_resolution_id if (number.isnumeric() and sequence_resolution_id.number_from <= int(number) and int(number) <= sequence_resolution_id.number_to): sequence_resolution = sequence_resolution_id invoice_id.sequence_resolution_id = sequence_resolution def _compute_dbname(self): self.dbname = self._cr.dbname warn_remaining_certificate = fields.Boolean( string="Warn About Remainings?", compute="_compute_warn_certificate", store=False) warn_inactive_certificate = fields.Boolean( string="Warn About Inactive Certificate?", compute="_compute_warn_certificate", store=False) sequence_resolution_id = fields.Many2one( comodel_name='ir.sequence.date_range', string='Sequence Resolution', compute='_compute_sequence_resolution_id', store=False) dbname = fields.Char(string="DB Name", compute="_compute_dbname", store=False) invoice_datetime = fields.Datetime(string='Invoice Datetime', copy=False) delivery_datetime = fields.Datetime(string='Delivery Datetime', copy=False) operation_type = fields.Selection( selection=[ ('09', 'AIU'), ('10', 'Standard'), ('20', 'Credit note that references an e-invoice'), ('22', 'Credit note without reference to invoices *'), ('30', 'Debit note that references an e-invoice'), ('32', 'Debit note without reference to invoices *')], string='Operation Type', default=_default_operation_type, copy=False) invoice_type_code = fields.Selection( selection=[ ('01', 'E-invoice of sale'), ('03', 'E-document of transmission - type 03'), ('04', 'E-invoice of sale - type 04'), ('05', 'E-Support Document')], string='Invoice Type', default=_default_invoice_type_code, copy=False) send_invoice_to_dian = fields.Selection( selection=[('0', 'Immediately'), ('1', 'After 1 Day'), ('2', 'After 2 Days')], string='Send Invoice to DIAN?', default=_default_send_invoice_to_dian, copy=False) access_token = fields.Char(string='Access Token', copy=False) is_accepted_rejected = fields.Boolean(string='Is Accepted/Rejected?', copy=False) accepted_rejected_datetime = fields.Datetime( string='Datetime of Accepted/Rejected', copy=False) receipt_document_reference = fields.Char( string='Merchandise / Service Receipt Document', copy=False) dian_document_state = fields.Selection( selection=[ ('pending', 'No Transferido'), ('done', 'Emitido'), ('exception', 'Excepción de Envio'), ('dian_reject', 'Rechazado DIAN'), ('dian_accept', 'Aceptado DIAN'), ('customer_reject', 'Rechazado Cliente'), ('customer_accept', 'Aceptado Cliente')], string='DIAN Document State', default='pending', copy=False, readonly=True) sale_order_id = fields.Many2one( comodel_name='sale.order', string='Sale Order', copy=False, readonly=True, states={'draft': [('readonly', False)]}) merge_with_sale_order = fields.Boolean( string='Merge With Sale Order?', copy=False, readonly=True, states={'draft': [('readonly', False)]}) summary_line_ids = fields.One2many( comodel_name='account.move.summary.line', inverse_name='move_id', string='Summary of Invoice Lines', copy=False, readonly=True, states={'draft': [('readonly', False)]}) summary_amount_untaxed = fields.Monetary( string='Untaxed Amount', readonly=True, copy=False) summary_amount_tax = fields.Monetary(string='Tax', readonly=True, copy=False) summary_amount_total = fields.Monetary(string='Total', readonly=True, copy=False) dian_document_ids = fields.One2many( comodel_name='account.move.dian.document', inverse_name='invoice_id', string='DIAN Documents', copy=False, readonly=True, states={'draft': [('readonly', False)]}) def _create_token(self): self.access_token = self.access_token or uuid4().hex def _get_active_dian_resolution(self): msg = _("You do not have an active dian resolution, contact with your administrator.") resolution_number = False if self.sequence_resolution_id: resolution_number = self.sequence_resolution_id.resolution_number date_from = self.sequence_resolution_id.date_from date_to = self.sequence_resolution_id.date_to_resolution prefix = self.sequence_resolution_id.prefix number_from = self.sequence_resolution_id.number_from number_to = self.sequence_resolution_id.number_to technical_key = self.sequence_resolution_id.technical_key if not resolution_number: raise UserError(msg) return { 'InvoiceAuthorization': resolution_number, 'StartDate': date_from, 'EndDate': date_to, 'Prefix': prefix, 'From': number_from, 'To': number_to, 'technical_key': technical_key } def _get_billing_reference(self): msg1 = _("You can not make a refund invoice of an invoice with state different " "to 'Posted'.") msg2 = _("You can not make a refund invoice of an invoice with DIAN documents " "with state 'Draft', 'Sent' or 'Cancelled'.") billing_reference = {} refund_invoice_id = self.reversed_entry_id or self.debit_origin_id if refund_invoice_id: if refund_invoice_id.state != 'posted': raise UserError(msg1) if refund_invoice_id.state == 'posted': dian_document_state_done = False dian_document_state_cancel = False dian_document_state_sent = False dian_document_state_draft = False for dian_document_id in refund_invoice_id.dian_document_ids: if dian_document_id.state == 'done': dian_document_state_done = True billing_reference['ID'] = refund_invoice_id.name billing_reference['UUID'] = dian_document_id.cufe_cude billing_reference[ 'IssueDate'] = refund_invoice_id.invoice_date billing_reference[ 'CustomizationID'] = refund_invoice_id.operation_type if dian_document_id.state == 'cancel': dian_document_state_cancel = True if dian_document_id.state == 'draft': dian_document_state_draft = True if dian_document_id.state == 'sent': dian_document_state_sent = True if ((not dian_document_state_done and dian_document_state_cancel) or dian_document_state_draft or dian_document_state_sent): raise UserError(msg2) return billing_reference def _get_payment_exchange_rate(self): rate = 1 if self.currency_id != self.company_id.currency_id: rate = self.currency_id._convert( rate, self.company_id.currency_id, self.company_id, self.date) return { 'SourceCurrencyCode': self.currency_id.name, 'TargetCurrencyCode': self.company_id.currency_id.name, 'CalculationRate': rate, 'Date': self.date } def _get_einvoicing_taxes(self): msg1 = _("Your tax: '%s', has no e-invoicing tax group type, contact with your " "administrator.") msg2 = _("Your withholding tax: '%s', has amount equal to zero (0), the " "withholding taxes must have amount different to zero (0), contact with " "your administrator.") msg3 = _("Your tax: '%s', has negative amount or an amount equal to zero (0), " "the taxes must have an amount greater than zero (0), contact with your " "administrator.") taxes = {} tax_total_base = 0 withholding_taxes = {} currency_rate = self._get_payment_exchange_rate() for move_line in self.line_ids.filtered(lambda l: l.tax_line_id): if move_line.tax_line_id.tax_group_id.is_einvoicing: if not move_line.tax_line_id.tax_group_id.tax_group_type_id: raise UserError(msg1 % move_line.name) tax_code = move_line.tax_line_id.tax_group_id.tax_group_type_id.code tax_name = move_line.tax_line_id.tax_group_id.tax_group_type_id.name tax_type = move_line.tax_line_id.tax_group_id.tax_group_type_id.type tax_percent = '{:.2f}'.format(move_line.tax_line_id.amount) tax_amount = move_line.price_total tax_base = move_line.tax_base_amount / currency_rate['CalculationRate'] if tax_type == 'withholding_tax' and move_line.tax_line_id.amount == 0: raise UserError(msg2 % move_line.name) if tax_type == 'tax' and move_line.tax_line_id.amount <= 0: raise UserError(msg3 % move_line.name) if tax_amount != (tax_base * move_line.tax_line_id.amount / 100): tax_amount = tax_base * move_line.tax_line_id.amount / 100 if tax_type == 'withholding_tax' and move_line.tax_line_id.amount > 0: if tax_code not in withholding_taxes: withholding_taxes[tax_code] = {} withholding_taxes[tax_code]['total'] = 0 withholding_taxes[tax_code]['name'] = tax_name withholding_taxes[tax_code]['taxes'] = {} if tax_percent not in withholding_taxes[tax_code]['taxes']: withholding_taxes[tax_code]['taxes'][tax_percent] = {} withholding_taxes[tax_code]['taxes'][tax_percent][ 'base'] = 0 withholding_taxes[tax_code]['taxes'][tax_percent][ 'amount'] = 0 withholding_taxes[tax_code]['total'] += tax_amount * (-1) withholding_taxes[tax_code]['taxes'][tax_percent]['base'] += tax_base tax_total_base += tax_base withholding_taxes[tax_code]['taxes'][tax_percent][ 'amount'] += tax_amount * (-1) elif tax_type == 'withholding_tax' and move_line.tax_line_id.amount < 0: # TODO 3.0 Las retenciones se recomienda no enviarlas a la DIAN # Solo las positivas que indicarian una autorretencion, Si la DIAN # pide que se envien las retenciones, seria quitar o comentar este if pass else: if tax_code not in taxes: taxes[tax_code] = {} taxes[tax_code]['total'] = 0 taxes[tax_code]['name'] = tax_name taxes[tax_code]['taxes'] = {} if tax_percent not in taxes[tax_code]['taxes']: taxes[tax_code]['taxes'][tax_percent] = {} taxes[tax_code]['taxes'][tax_percent]['base'] = 0 taxes[tax_code]['taxes'][tax_percent]['amount'] = 0 taxes[tax_code]['total'] += tax_amount taxes[tax_code]['taxes'][tax_percent]['base'] += tax_base tax_total_base += tax_base taxes[tax_code]['taxes'][tax_percent]['amount'] += tax_amount if '01' not in taxes: taxes['01'] = {} taxes['01']['total'] = 0 taxes['01']['name'] = 'IVA' taxes['01']['taxes'] = {} taxes['01']['taxes']['0.00'] = {} taxes['01']['taxes']['0.00']['base'] = 0 taxes['01']['taxes']['0.00']['amount'] = 0 if '04' not in taxes: taxes['04'] = {} taxes['04']['total'] = 0 taxes['04']['name'] = 'ICA' taxes['04']['taxes'] = {} taxes['04']['taxes']['0.00'] = {} taxes['04']['taxes']['0.00']['base'] = 0 taxes['04']['taxes']['0.00']['amount'] = 0 if '03' not in taxes: taxes['03'] = {} taxes['03']['total'] = 0 taxes['03']['name'] = 'INC' taxes['03']['taxes'] = {} taxes['03']['taxes']['0.00'] = {} taxes['03']['taxes']['0.00']['base'] = 0 taxes['03']['taxes']['0.00']['amount'] = 0 return { 'TaxesTotal': taxes, 'TaxesTotalBase': tax_total_base, 'WithholdingTaxesTotal': withholding_taxes} def _get_invoice_lines(self): msg1 = _("Your Unit of Measure: '%s', has no Unit of Measure Code, contact " + "with your administrator.") msg2 = _("The invoice line %s has no reference") msg3 = _("Your product: '%s', has no reference price, contact with your " + "administrator.") msg4 = _("Your tax: '%s', has no e-invoicing tax group type, contact with " + "your administrator.") msg5 = _("Your product: '%s', cannot have two VAT type taxes.") msg6 = _("Your withholding tax: '%s', has amount equal to zero (0), the " + "withholding taxes must have amount different to zero (0), contact " + "with your administrator.") msg7 = _("Your tax: '%s', has negative amount or an amount equal to zero " + "(0), the taxes must have an amount greater than zero (0), contact " + "with your administrator.") invoice_lines = {} count = 1 exception = False for invoice_line in self.invoice_line_ids.filtered( lambda line: not line.display_type): if not invoice_line.product_uom_id.product_uom_code_id: raise UserError(msg1 % invoice_line.product_uom_id.name) disc_amount = 0 total_wo_disc = 0 brand_name = False model_name = invoice_line.product_id.default_code if invoice_line.price_unit != 0 and invoice_line.quantity != 0: total_wo_disc = invoice_line.price_unit * invoice_line.quantity if total_wo_disc != 0 and invoice_line.discount != 0: disc_amount = (total_wo_disc * invoice_line.discount) / 100 if not invoice_line.product_id or not invoice_line.product_id.default_code: raise UserError(msg2 % invoice_line.name) if invoice_line.price_subtotal <= 0 and invoice_line.reference_price <= 0: raise UserError(msg3 % invoice_line.product_id.default_code) if self.invoice_type_code == '02': if invoice_line.product_id.product_brand_id: brand_name = invoice_line.product_id.product_brand_id.name if invoice_line.product_id.manufacturer_pref: model_name = invoice_line.product_id.manufacturer_pref invoice_lines[count] = {} invoice_lines[count][ 'unitCode'] = invoice_line.product_uom_id.product_uom_code_id.code invoice_lines[count]['Quantity'] = '{:.2f}'.format( invoice_line.quantity) invoice_lines[count][ 'PricingReferencePriceAmount'] = '{:.2f}'.format( invoice_line.reference_price) invoice_lines[count]['LineExtensionAmount'] = '{:.2f}'.format( invoice_line.price_subtotal) invoice_lines[count]['MultiplierFactorNumeric'] = '{:.2f}'.format( invoice_line.discount) invoice_lines[count]['AllowanceChargeAmount'] = '{:.2f}'.format( disc_amount) invoice_lines[count][ 'AllowanceChargeBaseAmount'] = '{:.2f}'.format(total_wo_disc) invoice_lines[count]['TaxesTotal'] = {} invoice_lines[count]['WithholdingTaxesTotal'] = {} invoice_lines[count][ 'StandardItemIdentification'] = invoice_line.product_id.default_code iva_count = 0 for tax in invoice_line.tax_ids: if tax.amount_type == 'group': tax_ids = tax.children_tax_ids else: tax_ids = tax for tax_id in tax_ids: if tax_id.tax_group_id.is_einvoicing: if not tax_id.tax_group_id.tax_group_type_id: raise UserError(msg4 % tax.name) tax_type = tax_id.tax_group_id.tax_group_type_id.type if tax_id.tax_group_id.tax_group_type_id.name == 'IVA': iva_count += 1 if iva_count > 1: dian_document_id = self.dian_document_ids.filtered( lambda d: d.state != 'cancel') exception = True msg = msg5 % invoice_line.product_id.default_code dian_document_id.write({'get_status_zip_response': msg}) if tax_type == 'withholding_tax' and tax_id.amount == 0: raise UserError(msg6 % tax_id.name) if tax_type == 'tax' and tax_id.amount <= 0: raise UserError(msg7 % tax_id.name) if tax_type == 'withholding_tax' and tax_id.amount > 0: invoice_lines[count]['WithholdingTaxesTotal'] = ( invoice_line._get_invoice_lines_taxes( tax_id, tax_id.amount, invoice_lines[count]['WithholdingTaxesTotal'])) elif tax_type == 'withholding_tax' and tax_id.amount < 0: # TODO 3.0 Las retenciones se recomienda no enviarlas a la DIAN. # Solo la parte positiva que indicaria una autoretencion, Si la DIAN # pide que se envie la parte negativa, seria quitar o comentar este if pass else: invoice_lines[count]['TaxesTotal'] = ( invoice_line._get_invoice_lines_taxes( tax_id, tax_id.amount, invoice_lines[count]['TaxesTotal'])) if '01' not in invoice_lines[count]['TaxesTotal']: invoice_lines[count]['TaxesTotal']['01'] = {} invoice_lines[count]['TaxesTotal']['01']['total'] = 0 invoice_lines[count]['TaxesTotal']['01']['name'] = 'IVA' invoice_lines[count]['TaxesTotal']['01']['taxes'] = {} invoice_lines[count]['TaxesTotal']['01']['taxes']['0.00'] = {} invoice_lines[count]['TaxesTotal']['01']['taxes']['0.00'][ 'base'] = invoice_line.price_subtotal invoice_lines[count]['TaxesTotal']['01']['taxes']['0.00'][ 'amount'] = 0 if '04' not in invoice_lines[count]['TaxesTotal']: invoice_lines[count]['TaxesTotal']['04'] = {} invoice_lines[count]['TaxesTotal']['04']['total'] = 0 invoice_lines[count]['TaxesTotal']['04']['name'] = 'ICA' invoice_lines[count]['TaxesTotal']['04']['taxes'] = {} invoice_lines[count]['TaxesTotal']['04']['taxes']['0.00'] = {} invoice_lines[count]['TaxesTotal']['04']['taxes']['0.00'][ 'base'] = invoice_line.price_subtotal invoice_lines[count]['TaxesTotal']['04']['taxes']['0.00'][ 'amount'] = 0 if '03' not in invoice_lines[count]['TaxesTotal']: invoice_lines[count]['TaxesTotal']['03'] = {} invoice_lines[count]['TaxesTotal']['03']['total'] = 0 invoice_lines[count]['TaxesTotal']['03']['name'] = 'INC' invoice_lines[count]['TaxesTotal']['03']['taxes'] = {} invoice_lines[count]['TaxesTotal']['03']['taxes']['0.00'] = {} invoice_lines[count]['TaxesTotal']['03']['taxes']['0.00'][ 'base'] = invoice_line.price_subtotal invoice_lines[count]['TaxesTotal']['03']['taxes']['0.00'][ 'amount'] = 0 invoice_lines[count]['BrandName'] = brand_name invoice_lines[count]['ModelName'] = model_name invoice_lines[count]['ItemDescription'] = invoice_line.name invoice_lines[count]['InformationContentProviderParty'] = ( invoice_line._get_information_content_provider_party_values()) invoice_lines[count]['PriceAmount'] = '{:.2f}'.format( invoice_line.price_unit) count += 1 if exception: self.write({'dian_document_state': 'exception'}) else: self.write({'dian_document_state': 'pending'}) return invoice_lines def _set_invoice_lines_price_reference(self): for invoice_line in self.invoice_line_ids.filtered( lambda line: not line.display_type): percentage = 100 margin_percentage = invoice_line.product_id.margin_percentage if invoice_line.product_id.reference_price > 0: reference_price = invoice_line.product_id.reference_price elif 0 < margin_percentage < 100: percentage = (percentage - margin_percentage) / 100 reference_price = invoice_line.product_id.standard_price / percentage else: reference_price = 0 invoice_line.write({ 'cost_price': invoice_line.product_id.standard_price, 'reference_price': reference_price }) return True def update(self, values): res = super(AccountMove, self).update(values) for invoice_id in self: if values.get('refund_type') == "credit": invoice_id.operation_type = '20' elif values.get('refund_type') == "debit": invoice_id.operation_type = '30' return res def action_set_dian_document(self): msg = _("The 'delivery date' must be equal or greater per maximum 10 days to " "the 'invoice date'.") timezone = pytz.timezone(self.env.user.tz or 'America/Bogota') from_zone = tz.gettz('UTC') to_zone = tz.gettz(timezone.zone) for invoice_id in self: if not invoice_id.company_id.einvoicing_enabled: return True if invoice_id.journal_id.sequence_id.dian_type not in DIAN_TYPES: return True if invoice_id.dian_document_ids.filtered(lambda d: d.state != 'cancel'): return True if not invoice_id.invoice_datetime: invoice_datetime = datetime.strptime( str(invoice_id.invoice_date) + ' 13:00:00', '%Y-%m-%d %H:%M:%S').replace(tzinfo=from_zone) invoice_datetime = invoice_datetime.astimezone( to_zone).strftime('%Y-%m-%d %H:%M:%S') invoice_id.invoice_datetime = invoice_datetime if (invoice_id.company_id.automatic_delivery_datetime and not invoice_id.delivery_datetime): invoice_datetime = invoice_id.invoice_datetime hours_added = timedelta( hours=invoice_id.company_id.additional_hours_delivery_datetime) invoice_id.delivery_datetime = invoice_datetime + hours_added if not invoice_id.delivery_datetime: raise UserError(msg) invoice_date = invoice_id.invoice_date delivery_date = datetime.strftime(invoice_id.delivery_datetime, '%Y-%m-%d') delivery_date = datetime.strptime(delivery_date, '%Y-%m-%d').date() days = (delivery_date - invoice_date).days if days < 0 or days > 10: raise UserError(msg) invoice_id._set_invoice_lines_price_reference() xml_filename = False zipped_filename = False ar_xml_filename = False ad_zipped_filename = False for dian_document_id in invoice_id.dian_document_ids: xml_filename = dian_document_id.xml_filename zipped_filename = dian_document_id.zipped_filename ar_xml_filename = dian_document_id.ar_xml_filename ad_zipped_filename = dian_document_id.ad_zipped_filename break dian_document_id = self.env['account.move.dian.document'].create({ 'invoice_id': invoice_id.id, 'company_id': invoice_id.company_id.id, 'xml_filename': xml_filename, 'zipped_filename': zipped_filename, 'ar_xml_filename': ar_xml_filename, 'ad_zipped_filename': ad_zipped_filename}) set_files = dian_document_id.action_set_files() if (not invoice_id.send_invoice_to_dian and invoice_id.company_id.send_invoice_to_dian): invoice_id.send_invoice_to_dian = invoice_id.company_id.send_invoice_to_dian if (not invoice_id.invoice_type_code and invoice_id.company_id.invoice_type_code): invoice_id.invoice_type_code = invoice_id.company_id.invoice_type_code if invoice_id.send_invoice_to_dian == '0': if set_files: if invoice_id.invoice_type_code in ('01', '02', '05'): if dian_document_id.zip_key: to_return = dian_document_id.action_GetStatusZip() else: to_return = dian_document_id._get_GetStatus(True) if not to_return: dian_document_id.action_send_zipped_file() elif invoice_id.invoice_type_code == '04': dian_document_id.action_send_mail() else: dian_document_id.send_failure_mail() return True def action_process_dian_document(self): for invoice_id in self: dian_document_id = invoice_id.dian_document_ids.filtered( lambda d: d.state not in ('cancel', 'done')) if dian_document_id: dian_document_id.action_process() return True def action_merge_with_sale_order(self): for invoice_id in self: sale_order_id = invoice_id.sale_order_id if sale_order_id: move_id = sale_order_id._create_invoices() move_line_data = [] for invoice_line_id in invoice_id.invoice_line_ids: move_line_data.append({ 'move_id': move_id.id, 'currency_id': invoice_line_id.currency_id.id, 'product_id': invoice_line_id.product_id.id, 'name': invoice_line_id.name, 'quantity': invoice_line_id.quantity, 'product_uom_id': invoice_line_id.product_uom_id.id, 'price_unit': invoice_line_id.price_unit, 'discount': invoice_line_id.discount, 'tax_ids': invoice_line_id.tax_ids, 'price_subtotal': invoice_line_id.price_subtotal, 'price_total': invoice_line_id.price_total, 'cost_price': invoice_line_id.cost_price, 'reference_price': invoice_line_id.reference_price}) self.env['account.move.summary.line'].create(move_line_data) move_id.write({ 'name': invoice_id.name, 'move_name': invoice_id.move_name, 'invoice_date': invoice_id.invoice_date, 'summary_amount_untaxed': invoice_id.amount_untaxed, 'summary_amount_tax': invoice_id.amount_tax, 'summary_amount_total': invoice_id.amount_total}) mail_message_ids = self.env['mail.message'].search( [('model', '=', 'account.move'), ('res_id', '=', invoice_id.id)]) if mail_message_ids: mail_message_ids.write({'res_id': move_id.id}) if invoice_id.dian_document_ids: invoice_id.dian_document_ids.write({'invoice_id': move_id.id}) move_id.write({ 'invoice_datetime': invoice_id.invoice_datetime, 'delivery_datetime': invoice_id.delivery_datetime, 'operation_type': invoice_id.operation_type, 'invoice_type_code': invoice_id.invoice_type_code, 'send_invoice_to_dian': invoice_id.send_invoice_to_dian, 'access_token': invoice_id.access_token, 'is_accepted_rejected': invoice_id.is_accepted_rejected, 'accepted_rejected_datetime': invoice_id.accepted_rejected_datetime, 'receipt_document_reference': invoice_id.receipt_document_reference, 'dian_document_state': invoice_id.dian_document_state, 'send_invoice_to_dian': invoice_id.send_invoice_to_dian}) if not invoice_id.dian_document_ids.filtered(lambda d: d.state == 'done'): move_id.write({'merge_with_sale_order': True}) invoice_id.write({'move_name': False}) invoice_id.with_context(force_delete=True).unlink() return sale_order_id.action_view_invoice() return True def action_post(self): msg = _('Invoice totals do not match summary totals.') res = super(AccountMove, self).action_post() for invoice_id in self: invoice_id.action_set_dian_document() if invoice_id.merge_with_sale_order: if not invoice_id.summary_line_ids: invoice_id.button_draft() else: if (invoice_id.summary_amount_untaxed != invoice_id.amount_untaxed or invoice_id.summary_amount_tax != invoice_id.amount_tax or invoice_id.summary_amount_total != invoice_id.amount_total): raise UserError(msg) return res def button_draft(self): msg = _('You cannot cancel a invoice sent to the DIAN and that was approved.') user = self.env['res.users'].search([('id', '=', self.env.user.id)]) allow_cancel_invoice_dian_document_done = False if user.has_group( 'l10n_co_account_e_invoicing.group_allow_cancel_invoice_dian_document_done'): allow_cancel_invoice_dian_document_done = True for invoice_id in self: if invoice_id.merge_with_sale_order and not invoice_id.summary_line_ids: continue for dian_document_id in invoice_id.dian_document_ids: if dian_document_id.state == 'done': if not allow_cancel_invoice_dian_document_done: raise UserError(msg) else: dian_document_id.state = 'cancel' self.write({'dian_document_state': 'pending'}) return super(AccountMove, self).button_draft()