v14-facturacionelectronica/l10n_co_account_e_invoicing/models/account_move.py

810 lines
36 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2021 Joan Marín <Github@JoanMarin>
# 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()