[FIX] l10n_ke_edi_tremol: add export tests
Exporting Kenyan invoices in a format readable by the Kenyan fiscal device creates some complex output. The data is significantly modified in order to represent an odoo invoice in the native format of the communication with the device (for instance global discount lines must be distrubuted accross the existing positive lines of the invoice, since the device is incapable of representing negative lines). This commit adds a series of tests designed to ensure the format of the export is correct, and that the values of these invoices are represented in a way that best reflects the data on the invoice, whist being readable by the device. closes odoo/odoo#116615 X-original-commit: dec8e7e3fefc8e00b916f4be9227a789c2a40b65 Signed-off-by: Josse Colpaert <jco@odoo.com> Signed-off-by: Daniel Kosky (dako) <dako@odoo.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from .import test_move_export
|
||||
@@ -0,0 +1,183 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
|
||||
@tagged('post_install_l10n', 'post_install', '-at_install')
|
||||
class TestKeMoveExport(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref='ke'):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
cls.partner_a.write({
|
||||
'name': 'Sirius Cybernetics Corporation',
|
||||
'street': 'Test Street',
|
||||
'street2': 'Further Test Street',
|
||||
'city': 'Nairobi',
|
||||
'zip': '00500',
|
||||
'country_id': cls.env.ref('base.ke').id,
|
||||
'vat': 'A000123456F',
|
||||
})
|
||||
|
||||
cls.product_a.write({
|
||||
'name': 'Infinite Improbability Drive',
|
||||
'l10n_ke_hsn_code': '0039.11.53',
|
||||
'l10n_ke_hsn_name': 'Spacecraft including satellites and suborbital and spacecraft launch vehicles'
|
||||
})
|
||||
|
||||
cls.standard_rate_tax = cls.env['account.tax'].create({
|
||||
'name': '16% tax',
|
||||
'amount': 16.0,
|
||||
'amount_type': 'percent',
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def line_dict_to_bytes(cls, line_dict):
|
||||
""" Helper method for creating the expected lines """
|
||||
msg = b'1' + b';'.join([ # 0x31, command to add a line
|
||||
line_dict.get('name', b''.ljust(36)), # 36 characters for the name
|
||||
line_dict.get('vat_class', b'A'), # 1 symbol for vat class (a because the tax is 16.0%)
|
||||
line_dict.get('price', b'1'), # up to 13 symbols for the unit price, tax included
|
||||
line_dict.get('uom', b'Uni'), # 3 symbols for uom
|
||||
line_dict.get('item_code', b''.ljust(10)), # 10 symbols for item code (only reported when the tax is not 16.0%)
|
||||
line_dict.get('item_desc', b''.ljust(20)), # item description (only reported when the tex is not 16.0%)
|
||||
line_dict.get('vat_rate', b'16.0'), # vat rate
|
||||
])
|
||||
if line_dict.get('quantity'):
|
||||
msg += b'*' + line_dict.get('quantity') # 1 to 10 symbols for quantity
|
||||
if line_dict.get('discount'):
|
||||
msg += b',' + line_dict.get('discount') # 1 to 7 symbols for discount/addition
|
||||
return msg
|
||||
|
||||
def test_export_simple_invoice(self):
|
||||
""" The _l10n_ke_get_cu_messages function serialises the data from the invoice as a series
|
||||
of messages representing commands to the device. The proxy must only wrap these messages
|
||||
(with the checksum, etc) and send them to the device, and issue a response.
|
||||
"""
|
||||
simple_invoice = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {
|
||||
'product_id': self.product_a.id,
|
||||
'quantity': 10,
|
||||
'price_unit': 1234.56,
|
||||
'tax_ids': [(6, 0, [self.company_data['company'].account_sale_tax_id.id])],
|
||||
'discount': 25,
|
||||
}),
|
||||
],
|
||||
})
|
||||
simple_invoice.action_post()
|
||||
generated_messages = simple_invoice._l10n_ke_get_cu_messages()
|
||||
expected_sale_line = self.line_dict_to_bytes({
|
||||
'name': b'Infinite Improbability Drive ',
|
||||
'price': b'1432.09', # This is the unit price, tax included
|
||||
'quantity': b'10.0',
|
||||
'discount': b'-25.0%',
|
||||
})
|
||||
expected_messages = [
|
||||
# open invoice
|
||||
b'01; 0;0;1;Sirius Cybernetics Corporation;A000123456F ;Test StreetFurther Test Street;Test StreetFurther Test Street;00500Nairobi ; ;INV202300001 ',
|
||||
# sale of article
|
||||
expected_sale_line,
|
||||
# close invoice
|
||||
b'8',
|
||||
# read date / time
|
||||
b'h',
|
||||
]
|
||||
self.assertEqual(generated_messages, expected_messages)
|
||||
|
||||
# Next assign the invoice a control unit number, and create a credit note from the invoice
|
||||
simple_invoice.l10n_ke_cu_invoice_number = '42424200420000004242'
|
||||
simple_credit_note = simple_invoice._reverse_moves()
|
||||
simple_credit_note.action_post()
|
||||
generated_messages = simple_credit_note._l10n_ke_get_cu_messages()
|
||||
|
||||
# The credit note of the simple invoice should have the same content, excepting that
|
||||
expected_credit_note_header = [b''.join([
|
||||
b'0', b'1;', b' 0;', b'0;',
|
||||
b'A;', # This reserved 'field' is a capital 'A' instead of a '1'
|
||||
b'Sirius Cybernetics Corporation;',
|
||||
b'A000123456F ;',
|
||||
b'Test StreetFurther Test Street;',
|
||||
b'Test StreetFurther Test Street;',
|
||||
b'00500Nairobi ;',
|
||||
b' ;',
|
||||
b'4242420042000000424;', # The 'Related invoice number' is the control unit number of the reversed invoice
|
||||
b'RINV202300001 ', # The invoice number is the number of the credit note
|
||||
])]
|
||||
expected_messages = expected_credit_note_header + expected_messages[1:]
|
||||
self.assertEqual(generated_messages, expected_messages)
|
||||
|
||||
def test_export_global_discount_invoice(self):
|
||||
""" Negative lines can be used as global discounts, the function that serialises the invoice
|
||||
should recognise these discount lines, and subtract them from positive lines,
|
||||
representing the subtraction as a discount. Existing discounts on lines should be
|
||||
handled correctly too.
|
||||
"""
|
||||
global_discount_invoice = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {
|
||||
'product_id': self.product_a.id,
|
||||
'quantity': 10,
|
||||
'price_unit': 10,
|
||||
'tax_ids': [(6, 0, [self.company_data['company'].account_sale_tax_id.id])],
|
||||
'discount': 10
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': "don't panic",
|
||||
'quantity': 1,
|
||||
'price_unit': -10,
|
||||
'tax_ids': [(6, 0, [self.company_data['company'].account_sale_tax_id.id])],
|
||||
}),
|
||||
],
|
||||
})
|
||||
global_discount_invoice.action_post()
|
||||
generated_messages = global_discount_invoice._l10n_ke_get_cu_messages()
|
||||
expected_discounted_line = self.line_dict_to_bytes({
|
||||
'name': b'Infinite Improbability Drive ',
|
||||
'price': b'11.6',
|
||||
'quantity': b'10.0',
|
||||
# The discount is -20%, because there is an existing discount on the line of 10%, and
|
||||
# another negative line with the amount -10 would be another -10% discount.
|
||||
'discount': b'-20.0%',
|
||||
})
|
||||
expected_messages = [
|
||||
b'01; 0;0;1;Sirius Cybernetics Corporation;A000123456F ;Test StreetFurther Test Street;Test StreetFurther Test Street;00500Nairobi ; ;INV202300001 ',
|
||||
expected_discounted_line,
|
||||
b'8',
|
||||
b'h'
|
||||
]
|
||||
self.assertEqual(generated_messages, expected_messages)
|
||||
|
||||
# A copy of the invoice where the positive line is the product of a double negative
|
||||
# (negative price and negative quantity) should yeild exactly the same representation.
|
||||
double_negative_invoice = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner_a.id,
|
||||
'invoice_line_ids': [
|
||||
(0, 0, {
|
||||
'product_id': self.product_a.id,
|
||||
'quantity': -10,
|
||||
'price_unit': -10,
|
||||
'tax_ids': [(6, 0, [self.company_data['company'].account_sale_tax_id.id])],
|
||||
'discount': 10
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': "don't panic",
|
||||
'quantity': 1,
|
||||
'price_unit': -10,
|
||||
'tax_ids': [(6, 0, [self.company_data['company'].account_sale_tax_id.id])],
|
||||
}),
|
||||
],
|
||||
})
|
||||
double_negative_invoice.action_post()
|
||||
generated_messages = double_negative_invoice._l10n_ke_get_cu_messages()
|
||||
# There representation is exactly the same, excepting that the name of the invoice is different
|
||||
expected_double_negative_header = [b'01; 0;0;1;Sirius Cybernetics Corporation;A000123456F ;Test StreetFurther Test Street;Test StreetFurther Test Street;00500Nairobi ; ;INV202300002 ']
|
||||
expected_messages = expected_double_negative_header + expected_messages[1:]
|
||||
self.assertEqual(generated_messages, expected_messages)
|
||||
Reference in New Issue
Block a user