Files
enterprise-addons/l10n_co_dian/tests/test_dian_moves.py
T

711 lines
32 KiB
Python

from datetime import datetime
from lxml import etree
from odoo import Command
from odoo.exceptions import UserError
from odoo.tests import tagged, freeze_time
from .common import TestCoDianCommon
from odoo.addons.l10n_co_edi.models.account_invoice import L10N_CO_EDI_TYPE
from odoo.addons.l10n_co_dian import xml_utils
@freeze_time('2024-01-30')
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestDianMoves(TestCoDianCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
# Alcohol Taxes
cls.alcohol_tax_1 = cls.env['account.tax'].create({
'name': "ICL >20%",
'amount_type': 'fixed',
'amount': 30,
'l10n_co_edi_type': cls.env.ref('l10n_co_edi.tax_type_18').id, # ICL
})
cls.alcohol_tax_2 = cls.alcohol_tax_1.copy({
'name': "ICL <=20%",
'amount': 15,
})
# Alcohol Products
cls.product_alcohol_1 = cls._create_product(
name="Wine 50% ABV",
l10n_co_edi_ref_nominal_tax=50,
default_code='VINO1',
)
cls.product_alcohol_2 = cls._create_product(
name="Beer 3% ABV",
l10n_co_edi_ref_nominal_tax=3,
default_code='BEER1',
)
# Plastic Bag Taxes (INC Bolsas)
cls.plastic_bag_tax = cls.env['account.tax'].create({
'name': "INC Plastic Bags",
'amount_type': 'fixed',
'amount': 70, # Fixed rate per bag
'type_tax_use': 'sale',
'invoice_label': 'INC Plastic Bags',
'description': 'INC Plastic Bags',
'tax_group_id': cls.env['account.chart.template'].ref('l10n_co_tax_group_inc_bolsas').id, # INC Bolsas
'l10n_co_edi_type': cls.env.ref('l10n_co_edi.tax_type_9').id, # INC Bolsas
})
# Plastic Bag Product
cls.product_plastic_bag = cls._create_product(
name="Bolsa Plastica",
default_code='BOLSA1',
)
# 1 USD ~= 3919 COP
usd = cls.env.ref('base.USD')
cls.env['res.currency.rate'].create({
'name': '2024-01-30',
'inverse_company_rate': 3919.109578,
'currency_id': usd.id,
'company_id': cls.env.company.id,
})
def test_invoice_sugar(self):
invoice = self._create_move(invoice_line_ids=[
# Sugar taxes should not be grouped together in xml since they have different rates
Command.create({
'product_id': self.product_sugar_1.id,
'quantity': 3,
'price_unit': 100,
'tax_ids': [Command.set([self.tax_iva_5.id, self.sugar_tax_1.id])],
}),
Command.create({
'product_id': self.product_sugar_2.id,
'quantity': 2,
'price_unit': 200,
'tax_ids': [Command.set([self.tax_iva_5.id, self.sugar_tax_2.id])],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 10,
'price_unit': 100,
'tax_ids': [Command.set([self.tax_iva_5.id])],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 5,
'price_unit': 100,
'tax_ids': [Command.set([self.tax_iva_19.id])],
}),
])
xml = self._generate_xml(invoice)
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/invoice_sugar.xml")
def test_invoice_alcohol(self):
invoice = self._create_move(invoice_line_ids=[
Command.create({
'product_id': self.product_alcohol_1.id,
'quantity': 3,
'price_unit': 100,
'tax_ids': [Command.set([self.tax_iva_5.id, self.alcohol_tax_1.id])],
}),
Command.create({
'product_id': self.product_alcohol_2.id,
'quantity': 2,
'price_unit': 200,
'tax_ids': [Command.set([self.tax_iva_5.id, self.alcohol_tax_2.id])],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 10,
'price_unit': 100,
'tax_ids': [Command.set([self.tax_iva_5.id])],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 5,
'price_unit': 100,
'tax_ids': [Command.set([self.tax_iva_19.id])],
}),
])
xml = self._generate_xml(invoice)
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/invoice_alcohol.xml")
def test_invoice_service_aiu(self):
product_service = self.env['product.product'].create({
'name': 'Limpieza Instrumento',
'type': 'service',
'default_code': 'LU0000001'
})
exempt_tax = self.env['account.chart.template'].ref('l10n_co_tax_10')
invoice = self._create_move(
invoice_line_ids=[
Command.create({
'product_id': product_service.id,
'quantity': 1,
'price_unit': 1500.00,
'tax_ids': [Command.set([exempt_tax.id])],
}),
Command.create({
'product_id': self.env.ref('l10n_co_dian.product_product_administracion').id,
'quantity': 1,
'price_unit': 450.00,
'tax_ids': [Command.set([exempt_tax.id])],
}),
Command.create({
'product_id': self.env.ref('l10n_co_dian.product_product_imprevistos').id,
'quantity': 1,
'price_unit': 300.00,
'tax_ids': [Command.set([exempt_tax.id])],
}),
Command.create({
'product_id': self.env.ref('l10n_co_dian.product_product_utilidad').id,
'quantity': 1,
'price_unit': 750.00,
'tax_ids': [Command.set([self.tax_iva_19.id])],
}),
])
invoice.l10n_co_edi_operation_type = '09' # "AIU"
xml = self.env['account.edi.xml.ubl_dian']._export_invoice(invoice)[0]
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/invoice_aiu.xml")
def test_invoice_plastic_bags(self):
invoice = self._create_move(invoice_line_ids=[
Command.create({
'product_id': self.product_plastic_bag.id,
'quantity': 2,
'price_unit': 100,
'tax_ids': [Command.set([self.tax_iva_19.id, self.plastic_bag_tax.id])],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 5,
'price_unit': 100,
'tax_ids': [Command.set([self.plastic_bag_tax.id])],
}),
])
xml = self._generate_xml(invoice)
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/invoice_plastic_bags.xml")
def test_invoice_plastic_bags_new(self):
self.test_invoice_plastic_bags()
def test_multicurrency(self):
"""
In the xml, all labels should be expressed in COP, not in the document's currency.
Suppose we have the rate: 1 USD = 3919.109578 Pesos
Using round per line, DIAN tests will fail and we are forced to used round globally
## Rule
DIAN checks that the base amount * the percentage is roughly (a tolerance of +5 or -5 pesos is allowed)
equal to the tax amount
In the example, 226.98 USD = 889559.49 COP -> tax amount should roughly be 169016,30
## Round per Line:
Base amount = 889559.49
Tax amount = 169031.20 (rejected because not in [169011,30; 169021,30])
Total = 1058590.69
## Round globally:
Base amount = 889559.49
Tax amount = 169016.30 (accepted)
Total = 1058575.79
"""
self.env.company.tax_calculation_rounding_method = 'round_globally'
invoice = self._create_move(
currency_id=self.env.ref('base.USD').id,
invoice_line_ids=[
Command.create({
'product_id': self.product_a.id,
'price_unit': 226.98,
'tax_ids': [Command.set([self.tax_iva_19.id])],
})
]
)
xml = self._generate_xml(invoice)
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/invoice_multicurrency.xml")
def test_credit_note_20(self):
""" Credit note referencing an invoice """
invoice = self._create_move()
xml = self._generate_xml(invoice)
self.env['l10n_co_dian.document']._create_document(xml, invoice, state='invoice_accepted')
credit_note = self._create_move(
move_type='out_refund',
reversed_entry_id=invoice.id,
ref="broken product",
l10n_co_edi_type=L10N_CO_EDI_TYPE['Credit Note'],
l10n_co_edi_operation_type='20', # "Nota Crédito que referencia una factura electrónica"
l10n_co_edi_description_code_credit='1', # "Devolución parcial de los bienes"
)
xml = self._generate_xml(credit_note)
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/credit_note_20.xml")
self.assertFalse('xmlns:sts="http://www.dian.gov.co/contratos/facturaelectronica/v1/Structures"' in xml.decode())
self.assertTrue('xmlns:sts="dian:gov:co:facturaelectronica:Structures-2-1"' in xml.decode())
def test_credit_note_22(self):
""" Credit note not referencing an invoice """
credit_note = self._create_move(
move_type='out_refund',
ref="broken product",
l10n_co_edi_type=L10N_CO_EDI_TYPE['Credit Note'],
l10n_co_edi_operation_type='22', # "Nota Crédito sin referencia a facturas"
l10n_co_edi_description_code_credit='6', # "Descuento comercial por volumen de ventas"
)
xml = self._generate_xml(credit_note)
self.env['l10n_co_dian.document']._create_document(xml, credit_note, state='invoice_accepted')
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/credit_note_22.xml")
self.assertFalse('xmlns:sts="http://www.dian.gov.co/contratos/facturaelectronica/v1/Structures"' in xml.decode())
self.assertTrue('xmlns:sts="dian:gov:co:facturaelectronica:Structures-2-1"' in xml.decode())
def test_invoice_exportation(self):
""" Invoice to a non-Colombian customer. Also checks the rounding of the tax amounts. """
self.partner_a.write({
'country_id': self.env.ref('base.us').id,
'vat': 'US12345673',
'l10n_co_edi_obligation_type_ids': [Command.set(self.env.ref('l10n_co_edi.obligation_type_5').ids)], # R-99-PN
'l10n_latam_identification_type_id': self.env.ref('l10n_co.external_id').id, # "Nit de otro país"
})
self.product_a.write({
'l10n_co_edi_customs_code': '0123456789',
'l10n_co_edi_brand': 'BRAND',
})
invoice = self._create_move(
partner_id=self.partner_a.id,
l10n_co_edi_type=L10N_CO_EDI_TYPE['Export Invoice'],
invoice_line_ids=[
Command.create({
'product_id': self.product_a.id,
'price_unit': 750,
'tax_ids': [Command.set([self.tax_iva_19.id, self.tax_ret_ica_0414.id])],
}),
]
)
xml = self._generate_xml(invoice)
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/invoice_exportation.xml")
def test_invoice_with_stmt_lines_and_exchange_diff(self):
""" Partially paid invoice in USD with 2 statement lines, one in COP and the second in USD """
# Invoice: 1050 USD (=3919109.58 COP)
usd = self.env.ref('base.USD')
invoice = self._create_move(currency_id=usd.id)
self.assertEqual(invoice.amount_residual, 1050.00)
# Create a 1st bank statement line (in COP) and reconcile with the invoice
bank_stmt_line = self.env['account.bank.statement.line'].create({
'payment_ref': 'Payment ref 1',
'journal_id': self.company_data['default_journal_bank'].id,
'amount': 1000000,
'date': '2024-01-30',
})
suspense = bank_stmt_line.line_ids.filtered(lambda l: l.account_type == 'asset_current')
line = invoice.line_ids.filtered(lambda line: line.account_type == 'asset_receivable')
suspense.account_id = line.account_id
(suspense + line).reconcile()
self.assertEqual(invoice.amount_residual, 794.84)
# Create a 2nd bank statement line (in USD) with an exchange difference and reconcile with the invoice
bank_stmt_line = self.env['account.bank.statement.line'].create({
'payment_ref': 'Payment ref 2 (!= rate)',
'journal_id': self.company_data['default_journal_bank'].id,
'date': '2024-01-30',
'amount': 390000,
'amount_currency': 100, # bank rate: 1 USD = 3900 COP, odoo rate: 1 USD = ~3919.10 COP
'foreign_currency_id': usd.id,
})
suspense = bank_stmt_line.line_ids.filtered(lambda l: l.account_type == 'asset_current')
line = invoice.line_ids.filtered(lambda line: line.account_type == 'asset_receivable')
suspense.account_id = line.account_id
(suspense + line).reconcile()
self.assertEqual(invoice.amount_residual, 694.84)
# The xml should include 2 'PrepaidPayment'
xml = self._generate_xml(invoice)
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/invoice_prepaid_payment_1.xml")
def test_invoice_with_payment(self):
""" Partially paid invoice (payment registered then reconciled with a statement line) """
invoice = self._create_move()
self.assertEqual(invoice.amount_residual, 4115065.06)
# Payment
payment = self.env['account.payment'].create({
'date': invoice.date,
'amount': 500,
'partner_id': self.partner_a.id,
})
payment.action_post()
lines = (invoice + payment.move_id).line_ids.filtered(lambda x: x.account_type == 'asset_receivable')
lines.reconcile()
self.assertEqual(invoice.amount_residual, 4114565.06)
# The xml should already include 1 'PrepaidPayment'
xml = self._generate_xml(invoice)
root = etree.fromstring(xml)
self.assertEqual(len(root.findall('.//{*}PrepaidPayment')), 1)
self.assertEqual(root.findtext('.//{*}PrepaidPayment/{*}PaidAmount'), "500.00")
self.assertEqual(root.findtext('.//{*}PrepaidPayment/{*}ID'), "Manual Payment")
# Bank Statement line
bank_stmt_line = self.env['account.bank.statement.line'].create({
'payment_ref': 'Payment ref',
'journal_id': self.company_data['default_journal_bank'].id,
'amount': 500,
'date': '2024-01-30',
})
outstanding = payment.move_id.line_ids.filtered(lambda x: x.account_type == 'asset_current')
suspense = bank_stmt_line.line_ids.filtered(lambda x: x.account_type == 'asset_current')
suspense.account_id = outstanding.account_id
(outstanding + suspense).reconcile()
self.assertEqual(invoice.amount_residual, 4114565.06)
# The xml should still include 1 'PrepaidPayment'
xml = self._generate_xml(invoice)
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/invoice_prepaid_payment_2.xml")
def test_invoice_with_line_discount(self):
invoice = self._create_move(
invoice_line_ids=[
Command.create({
'product_id': self.product_a.id,
'price_unit': 100,
'discount': 10,
}),
],
)
xml = self.env['account.edi.xml.ubl_dian']._export_invoice(invoice)[0]
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/invoice_with_line_discount.xml")
def test_invoice_withholding(self):
""" Invoice containing retention taxes (should fill the 'WithholdingTaxTotal' nodes) """
invoice = self._create_move(
invoice_line_ids=[
Command.create({
'product_id': self.product_a.id,
'price_unit': 100,
'tax_ids': [Command.set([
self.tax_iva_19.id,
self.env["account.chart.template"].ref('l10n_co_tax_56').id,
self.tax_ret_ica_0414.id,
])],
}),
Command.create({
'product_id': self.product_b.id,
'price_unit': 100,
'tax_ids': [Command.set([
self.tax_iva_5.id,
self.env["account.chart.template"].ref('l10n_co_tax_55').id,
])],
}),
],
)
xml = self._generate_xml(invoice)
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/invoice_withholding.xml")
def test_credit_note_withholding(self):
""" Credit note containing retention taxes (should NOT fill the 'WithholdingTaxTotal' nodes) """
credit_note = self._create_move(
move_type='out_refund',
l10n_co_edi_type=L10N_CO_EDI_TYPE['Credit Note'],
invoice_line_ids=[
Command.create({
'product_id': self.product_a.id,
'price_unit': 100,
'tax_ids': [Command.set([
self.tax_iva_19.id,
self.env["account.chart.template"].ref('l10n_co_tax_56').id,
self.tax_ret_ica_0414.id,
])],
}),
Command.create({
'product_id': self.product_b.id,
'price_unit': 100,
'tax_ids': [Command.set([
self.tax_iva_5.id,
self.env["account.chart.template"].ref('l10n_co_tax_55').id,
])],
}),
],
)
xml = self.env['account.edi.xml.ubl_dian']._export_invoice(credit_note)[0]
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/credit_note_withholding.xml")
def test_debit_note_30(self):
""" Debit note referencing an invoice """
invoice = self._create_move()
xml = self._generate_xml(invoice)
self.env['l10n_co_dian.document']._create_document(xml, invoice, state='invoice_accepted')
wizard = self.env['account.debit.note']\
.with_context(active_model="account.move", active_ids=invoice.ids)\
.create({
'l10n_co_edi_description_code_debit': '1',
'copy_lines': True,
'reason': 'The value of the product just went up',
'journal_id': self.debit_note_journal.id,
})
wizard.create_debit()
debit_note = invoice.debit_note_ids
debit_note.l10n_co_edi_description_code_debit = '3' # "Cambio del valor"
debit_note.action_post()
self.assertEqual(debit_note.l10n_co_edi_operation_type, '30') # "Nota Débito que referencia una factura electrónica"
xml = self._generate_xml(debit_note)
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/debit_note_30.xml")
def test_debit_note_32(self):
""" Debit note not referencing an invoice """
debit_note = self._create_move(
journal_id=self.debit_note_journal.id,
l10n_co_edi_description_code_debit='4', # Otros
)
self.assertFalse(debit_note.debit_origin_id, False)
self.assertEqual(debit_note.l10n_co_edi_operation_type, '32')
xml = self._generate_xml(debit_note)
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/debit_note_32.xml")
def test_support_document(self):
"""
- RetICA taxes should not be reported in Support Documents
- RetIVA taxes for Support Documents should be reported ONLY with percentage 15.00 or 100.00
In addition, the taxable amount of these taxes should be the tax amount of the taxes with type '01'
"""
bill = self._create_move(
move_type="in_invoice",
invoice_date=datetime.today(),
journal_id=self.support_document_journal.id,
invoice_line_ids=[
Command.create({
'product_id': self.product_a.id,
'tax_ids': [Command.set(self.tax_iva_19.ids + self.tax_ret_ica_1_104.ids)],
}),
Command.create({
'product_id': self.product_a.id,
'tax_ids': [Command.set(self.tax_iva_19.ids + self.tax_ret_iva_2_85.ids)],
}),
],
)
bill.partner_id.country_id = self.env.ref('base.us')
xml = self._generate_xml(bill)
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/support_document.xml")
def test_support_document_credit_note(self):
""" Support Document credit note referencing another support document """
bill = self._create_move(
move_type='in_invoice',
invoice_date=datetime.today(),
journal_id=self.support_document_journal.id,
invoice_line_ids=[
Command.create({
'product_id': self.product_a.id,
'tax_ids': [Command.set(self.tax_iva_19.ids + self.tax_ret_ica_1_104.ids)],
}),
],
)
xml = self._generate_xml(bill)
self.env['l10n_co_dian.document']._create_document(xml, bill, state='invoice_accepted')
credit_note = self._create_move(
move_type='in_refund',
invoice_date=datetime.today(),
journal_id=self.support_document_journal.id,
reversed_entry_id=bill.id,
l10n_co_edi_operation_type='10', # "Estandar"
l10n_co_edi_description_code_credit='1', # "Devolución parcial de los bienes"
)
xml = self._generate_xml(credit_note)
self._assert_document_dian(xml, 'l10n_co_dian/tests/attachments/support_document_credit_note.xml')
def test_dian_import_vendor_xml(self):
kwargs = {
'subfolder': 'tests/attachments',
'invoice_vals': {
'currency_id': self.currency.id,
'amount_total': 224.00,
'amount_tax': 24.00,
'l10n_co_edi_cufe_cude_ref': '8007424c5ee187a2aa3bb99fdbfaee9354c0b2a355ce9654fcd97eae289ad827e19460b71e4390b3e9b1cc6c293fb247',
'l10n_co_edi_type': '03',
'invoice_lines': [
{'price_subtotal': 100.00, 'price_unit': 100.00},
{'price_subtotal': 100.00, 'price_unit': 100.00},
],
},
}
# We are testing variation of the ProfileID
self._assert_imported_invoice_from_file(filename='import_attached_document.xml', **kwargs) # DIAN 2.1
self._assert_imported_invoice_from_file(filename='import_attached_document_2.xml', **kwargs) # DIAN 2.1: Factura Electrónica de Venta
self._assert_imported_invoice_from_file(filename='import_attached_document_3.xml', **kwargs) # Factura Electrónica de Venta
def test_dian_import_vendor_xml_base_quantity(self):
""" Test to ensure that PriceAmount is imported as the exact price unit for Colombia."""
line_xml = b"""
<cac:InvoiceLine
xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="94">3.0</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="COP">30.00</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Description>product_a</cbc:Description>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="COP">10.0</cbc:PriceAmount>
<cbc:BaseQuantity unitCode="94">3.0</cbc:BaseQuantity>
</cac:Price>
</cac:InvoiceLine>
"""
line_tree = etree.fromstring(line_xml)
# DIAN parser: BaseQuantity is ignored as a divisor -> exact unit price, no discount.
dian_vals = self.env['account.edi.xml.ubl_dian']._retrieve_line_vals(line_tree, 'in_invoice')
self.assertEqual(dian_vals['quantity'], 3.0)
self.assertEqual(dian_vals['price_unit'], 10.0)
def test_dian_invoicing_access_rights(self):
self.user.group_ids = [Command.unlink(self.env.ref('base.group_system').id)]
invoice = self._create_move()
xml = self._generate_xml(invoice)
self.env.invalidate_all() # certificates are available in cache, we still need to test the access through `_build_and_send_request`
self.env['l10n_co_dian.document']._create_document(xml, invoice, state='invoice_accepted')
self._mock_get_status_zip(invoice, 'GetStatusZip_pending.xml')
self.assertEqual(invoice.l10n_co_dian_state, 'invoice_pending')
def test_credit_note_foreign_currency(self):
self.env['res.currency.rate'].create({
'name': '2025-01-01',
'rate': 1.5,
'currency_id': self.env.ref('base.USD').id,
'company_id': self.company.id
})
credit_note = self._create_move(
move_type='out_refund',
ref="broken product",
l10n_co_edi_type=L10N_CO_EDI_TYPE['Credit Note'],
l10n_co_edi_operation_type='22',
l10n_co_edi_description_code_credit='6',
currency_id=self.env.ref('base.USD').id,
)
xml = self._generate_xml(credit_note)
self.env['l10n_co_dian.document']._create_document(xml, credit_note, state='invoice_accepted')
self._assert_document_dian(xml, "l10n_co_dian/tests/attachments/credit_note_foreign_currency.xml")
def test_send_confirmed_move(self):
""" Test that sending a confirmed invoice with operation type '30' and no debit origin raises error without crashing. """
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': self.partner_a.id,
'invoice_line_ids': [
Command.create({
'product_id': self.product_a.id,
'price_unit': 100,
'tax_ids': [Command.set([self.tax_iva_19.id])],
}),
],
})
invoice.l10n_co_edi_operation_type = '30'
invoice.action_post()
self.assertEqual(invoice.state, 'posted')
self.assertEqual(invoice.l10n_co_edi_operation_type, '30')
self.assertFalse(invoice.debit_origin_id)
with self.assertRaisesRegex(UserError, "There is no original debited invoice but the operation type is '30'."):
self._mock_send_and_print(move=invoice, response_file='SendTestSetAsync.xml')
def test_support_documents_report_name(self):
""" Test that we get the right report for support documents """
bill = self._create_move(
move_type='in_invoice',
invoice_date=datetime.today(),
journal_id=self.support_document_journal.id,
)
self.assertEqual(bill._get_name_invoice_report(), 'l10n_co_dian.report_vendor_document')
xml = self._generate_xml(bill)
self.env['l10n_co_dian.document']._create_document(xml, bill, state='invoice_accepted')
self.assertEqual(bill._get_name_invoice_report(), 'l10n_co_dian.report_vendor_document')
credit_note = self._create_move(
move_type='in_refund',
invoice_date=datetime.today(),
journal_id=self.support_document_journal.id,
reversed_entry_id=bill.id,
l10n_co_edi_operation_type='10', # "Estandar"
l10n_co_edi_description_code_credit='1', # "Devolución parcial de los bienes"
)
self.assertEqual(credit_note._get_name_invoice_report(), 'l10n_co_dian.report_vendor_document')
xml = self._generate_xml(credit_note)
self.env['l10n_co_dian.document']._create_document(xml, credit_note, state='invoice_accepted')
self.assertEqual(credit_note._get_name_invoice_report(), 'l10n_co_dian.report_vendor_document')
invoice = self._create_move(
move_type='out_invoice',
invoice_date=datetime.today(),
journal_id=self.company_data['default_journal_sale'].id,
)
self.assertEqual(invoice._get_name_invoice_report(), 'account.report_invoice_document')
xml = self._generate_xml(invoice)
self.env['l10n_co_dian.document']._create_document(xml, invoice, state='invoice_accepted')
self.assertEqual(invoice._get_name_invoice_report(), 'l10n_co_dian.report_invoice_document')
credit_note = self._create_move(
move_type='out_refund',
invoice_date=datetime.today(),
journal_id=self.company_data['default_journal_sale'].id,
reversed_entry_id=invoice.id,
l10n_co_edi_operation_type='10', # "Estandar"
l10n_co_edi_description_code_credit='1', # "Devolución parcial de los bienes"
)
self.assertEqual(credit_note._get_name_invoice_report(), 'account.report_invoice_document')
xml = self._generate_xml(credit_note)
self.env['l10n_co_dian.document']._create_document(xml, credit_note, state='invoice_accepted')
self.assertEqual(credit_note._get_name_invoice_report(), 'l10n_co_dian.report_invoice_document')
def test_invoice_narration_in_note(self):
""" Test that invoice narration (Terms and Conditions) appears in first cbc:Note tag """
invoice = self._create_move()
invoice.narration = '<p>Payment due in 30 days. Bank account: 123456789.</p>'
xml = self._generate_xml(invoice)
root = etree.fromstring(xml)
notes = root.findall('.//{*}Note')
self.assertEqual(len(notes), 2, "Should have 2 Note tags")
self.assertEqual(notes[0].text, 'Payment due in 30 days. Bank account: 123456789.', "First Note should contain Terms and Conditions")
self.assertTrue(notes[1].text.startswith('SETP'), "Second Note should contain CUFE calculation data")
def test_embedded_xml_encoding(self):
self.partner_co.email = 'test@test.com'
invoice = self._create_move()
invoice.company_id.l10n_co_dian_demo_mode = True
# response_file doesn't matter here
self._mock_send_and_print(move=invoice, response_file='SendTestSetAsync.xml')
zip_file = invoice.attachment_ids.filtered(lambda att: att.mimetype == 'application/zip')
raw = xml_utils._unzip(zip_file.raw)
namespaces = {
'cac': "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2",
'cbc': "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2",
}
tree = etree.fromstring(raw)
# Invoice is embedded as text in Description tag
description_str = tree.xpath('//cbc:Description/text()', namespaces=namespaces)[0]
description_tree = etree.fromstring(description_str.encode('utf-8'))
result = description_tree.find('.//cac:AccountingSupplierParty/cac:Party/cac:PhysicalLocation/cac:Address/cbc:CountrySubentity', namespaces)
self.assertEqual(result.text, 'Bogotá')
def test_debit_note_with_buyer_reference(self):
""" Test that generating a UBL document for a debit note with a buyer reference does
not crash due to the lack of a BuyerReference node in the debit note XML template. """
partner = self.partner_a
partner.ref = '123456'
debit_note = self._create_move(
move_type='out_invoice',
journal_id=self.debit_note_journal.id,
partner_id=partner.id
)
# Should not raise a ValueError
self._generate_xml(debit_note)