Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions addons/l10n_jo_edi/models/account_edi_xml_ubl_21_jo.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,23 @@ def _round_max_dp(self, value):

def _get_line_amount_before_discount_jod(self, line):
amount_after_discount = abs(line.balance)
return amount_after_discount / (1 - line.discount / 100)
return (amount_after_discount / (1 - line.discount / 100)) \
if line.discount < 100 else line.currency_id._convert(
from_amount=line.price_unit * line.quantity,
to_currency=self.env.ref('base.JOD'),
company=line.company_id,
date=line.date,
)

def _get_line_discount_jod(self, line):
return self._get_line_amount_before_discount_jod(line) * line.discount / 100

def _get_unit_price_jod(self, line):
return self._get_line_amount_before_discount_jod(line) / line.quantity

def _get_line_taxable_amount(self, line):
return self._round_max_dp(self._get_unit_price_jod(line)) * self._round_max_dp(line.quantity) - self._round_max_dp(self._get_line_discount_jod(line))

def _get_payment_method_code(self, invoice):
return PAYMENT_CODES_MAP[invoice.company_id.l10n_jo_edi_taxpayer_type]['receivable']

Expand All @@ -72,18 +81,13 @@ def _aggregate_totals(self, vals):
quantity = self._round_max_dp(line_val['line_quantity'])
discount = self._round_max_dp(line_val['price_vals']['allowance_charge_vals'][0]['amount'])

line_val['line_extension_amount'] = (price_unit * quantity) - discount

total_tax_amount = 0
if line_val['tax_total_vals']:
for subtotal in line_val['tax_total_vals'][0]['tax_subtotal_vals']:
subtotal['taxable_amount'] = line_val['line_extension_amount']

total_tax_amount = self._round_max_dp(sum(subtotal['tax_amount'] for subtotal in line_val['tax_total_vals'][0]['tax_subtotal_vals']))
line_val['tax_total_vals'][0]['rounding_amount'] = line_val['line_extension_amount'] + total_tax_amount
total_tax_amount = self._round_max_dp(sum(self._round_max_dp(subtotal['tax_amount']) for subtotal in line_val['tax_total_vals'][0]['tax_subtotal_vals']))
line_val['tax_total_vals'][0]['rounding_amount'] = self._round_max_dp(line_val['line_extension_amount']) + total_tax_amount

tax_exclusive_amount += (price_unit * quantity)
tax_inclusive_amount += ((price_unit * quantity) - discount + total_tax_amount)
tax_exclusive_amount += self._round_max_dp(price_unit * quantity)
tax_inclusive_amount += self._round_max_dp((price_unit * quantity) - discount + total_tax_amount)

vals['monetary_total_vals']['tax_inclusive_amount'] = vals['monetary_total_vals']['payable_amount'] = tax_inclusive_amount
vals['monetary_total_vals']['tax_exclusive_amount'] = tax_exclusive_amount
Expand Down Expand Up @@ -162,7 +166,7 @@ def _get_invoice_tax_totals_vals_helper(self, taxes_vals, special_tax_amount):
}
for grouping_key, vals in taxes_vals['tax_details'].items():
if grouping_key['tax_amount_type'] != 'fixed':
taxable_amount = sum(abs(line.balance) for line in vals['records']) + special_tax_amount
taxable_amount = sum(self._round_max_dp(self._get_line_taxable_amount(line)) for line in vals['records']) + special_tax_amount
subtotal = {
'currency': JO_CURRENCY,
'currency_dp': self._get_currency_decimal_places(),
Expand Down Expand Up @@ -203,7 +207,7 @@ def _get_document_allowance_charge_vals_list(self, invoice):
discount_amount = 0
invoice_lines = invoice.invoice_line_ids.filtered(lambda line: line.display_type not in ('line_note', 'line_section'))
for line in invoice_lines:
discount_amount += self._get_line_discount_jod(line)
discount_amount += self._round_max_dp(self._get_line_discount_jod(line))
return [{
'charge_indicator': 'false',
'allowance_charge_reason': 'discount',
Expand Down Expand Up @@ -242,7 +246,7 @@ def _get_invoice_line_tax_totals_vals_list(self, line, taxes_vals):
taxable_amount = 0
for grouping_key, tax_details_vals in taxes_vals['tax_details'].items():
if grouping_key['tax_amount_type'] == 'fixed':
taxable_amount = sum(abs(line.balance) for line in tax_details_vals['records'])
taxable_amount = sum(self._round_max_dp(self._get_line_taxable_amount(line)) for line in tax_details_vals['records'])
special_tax_amount = tax_details_vals['tax_amount']
special_tax_subtotal = {
'currency': JO_CURRENCY,
Expand All @@ -264,7 +268,7 @@ def _get_invoice_line_vals(self, line, line_id, taxes_vals):
'id': line_id + 1,
'line_quantity': line.quantity,
'line_quantity_attrs': {'unitCode': self._get_uom_unece_code()},
'line_extension_amount': abs(line.balance),
'line_extension_amount': self._get_line_taxable_amount(line),
'tax_total_vals': self._get_invoice_line_tax_totals_vals_list(line, taxes_vals),
'item_vals': self._get_invoice_line_item_vals(line, taxes_vals),
'price_vals': self._get_invoice_line_price_vals(line, taxes_vals),
Expand Down
3 changes: 2 additions & 1 deletion addons/l10n_jo_edi/tests/jo_edi_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ def _create_special_tax(amount):
cls.jo_general_tax_10 = _get_general_tax(10)
cls.jo_special_tax_10 = _create_special_tax(10)
cls.jo_special_tax_5 = _create_special_tax(5)
cls.jo_general_tax_16_included = _get_general_tax(16)
cls.jo_general_tax_16 = _get_general_tax(16)
cls.jo_general_tax_16_included = cls.jo_general_tax_16.copy({'name': 'Tax 16% included'})
cls.jo_general_tax_16_included.price_include = True

cls.partner_jo = cls.env['res.partner'].create({
Expand Down
144 changes: 134 additions & 10 deletions addons/l10n_jo_edi/tests/test_jo_edi_precision.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from odoo import Command
from odoo.tests import tagged
from odoo.tools.float_utils import float_is_zero
from odoo.tools.float_utils import float_compare, float_round
from odoo.addons.l10n_jo_edi.tests.jo_edi_common import JoEdiCommon
from odoo.addons.l10n_jo_edi.models.account_edi_xml_ubl_21_jo import JO_MAX_DP

Expand All @@ -12,7 +12,7 @@ def equal_strict(val1, val2):
return val1 == val2

def equal_jo_max_dp(val1, val2):
return float_is_zero(val1 - val2, JO_MAX_DP)
return float_compare(val1, val2, JO_MAX_DP) == 0

equals = equal_jo_max_dp if up_to_jo_max_dp else equal_strict
first_tuple = None
Expand Down Expand Up @@ -44,6 +44,9 @@ def _extract_vals_from_subtotals(self, subtotals, defaults):

return defaults

def _round_max_dp(self, value):
return float_round(value, JO_MAX_DP)

def _validate_jo_edi_numbers(self, xml_string):
"""
TLDR: This method checks that units sum up to total values.
Expand Down Expand Up @@ -132,8 +135,8 @@ def _validate_jo_edi_numbers(self, xml_string):
error_message += line_errors
error_message += "-------------------------------------------------------------------------\n"

aggregated_tax_exclusive_amount = sum(line['price_unit'] * line['quantity'] for line in lines)
aggregated_tax_inclusive_amount = sum(line['price_unit'] * line['quantity'] - line['discount'] + line['total_tax_amount'] for line in lines)
aggregated_tax_exclusive_amount = sum(self._round_max_dp(line['price_unit'] * line['quantity']) for line in lines)
aggregated_tax_inclusive_amount = sum(self._round_max_dp(line['price_unit'] * line['quantity'] - line['discount'] + line['total_tax_amount']) for line in lines)
aggregated_tax_amount = sum(line['tax_amount_general'] for line in lines)
aggregated_discount_amount = sum(line['discount'] for line in lines)

Expand All @@ -154,13 +157,20 @@ def _validate_jo_edi_numbers(self, xml_string):

return error_message

def _validate_invoice_vals_jo_edi_numbers(self, invoice_vals):
with self.subTest(sub_test_name=invoice_vals['name']):
invoice = self._l10n_jo_create_invoice(invoice_vals)
generated_file = self.env['account.edi.xml.ubl_21.jo']._export_invoice(invoice)[0]
errors = self._validate_jo_edi_numbers(generated_file)
self.assertFalse(errors, errors)

def test_jo_sales_invoice_precision(self):
eur = self.env.ref('base.EUR')
self.setup_currency_rate(eur, 1.41)
self.company.l10n_jo_edi_taxpayer_type = 'sales'
self.company.l10n_jo_edi_sequence_income_source = '16683693'

invoice_vals = {
self._validate_invoice_vals_jo_edi_numbers({
'name': 'TestEIN022',
'currency_id': eur.id,
'date': '2023-11-12',
Expand All @@ -180,9 +190,123 @@ def test_jo_sales_invoice_precision(self):
'tax_ids': [Command.set(self.jo_general_tax_16_included.ids)],
}),
],
}
invoice = self._l10n_jo_create_invoice(invoice_vals)
})

generated_file = self.env['account.edi.xml.ubl_21.jo']._export_invoice(invoice)[0]
errors = self._validate_jo_edi_numbers(generated_file)
self.assertFalse(errors, errors)
self._validate_invoice_vals_jo_edi_numbers({
'name': 'TestEIN023',
'date': '2023-11-12',
'invoice_line_ids': [
Command.create({
'product_id': self.product_a.id,
'quantity': 5,
'price_unit': 206.25,
'discount': 12.73,
'tax_ids': [Command.set(self.jo_general_tax_16.ids)],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 5,
'price_unit': 195,
'discount': 15.39,
'tax_ids': [Command.set(self.jo_general_tax_16.ids)],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 5,
'price_unit': 206.25,
'discount': 14.55,
'tax_ids': [Command.set(self.jo_general_tax_16.ids)],
}),
],
})

self._validate_invoice_vals_jo_edi_numbers({
'name': 'TestEIN024',
'date': '2023-11-12',
'invoice_line_ids': [
Command.create({
'product_id': self.product_a.id,
'quantity': 10,
'price_unit': 206.25,
'discount': 12.72,
'tax_ids': [Command.set(self.jo_general_tax_16.ids)],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 7,
'price_unit': 187.5,
'discount': 16,
'tax_ids': [Command.set(self.jo_general_tax_16.ids)],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 10,
'price_unit': 66.25,
'discount': 8.3,
'tax_ids': [Command.set(self.jo_general_tax_16.ids)],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 6,
'price_unit': 33,
'discount': 0,
'tax_ids': [Command.set(self.jo_general_tax_16.ids)],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 1,
'price_unit': 206.25,
'discount': 14.45,
'tax_ids': [Command.set(self.jo_general_tax_16.ids)],
}),
],
})

self._validate_invoice_vals_jo_edi_numbers({
'name': 'TestEIN025',
'date': '2023-11-12',
'invoice_line_ids': [
Command.create({
'product_id': self.product_a.id,
'quantity': 1,
'price_unit': 3.75,
'discount': 25,
'tax_ids': [Command.set(self.jo_general_tax_16.ids)],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 0.2,
'price_unit': 13.75,
'discount': 25,
'tax_ids': [Command.set(self.jo_general_tax_16.ids)],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 0.5,
'price_unit': 5.85,
'discount': 25,
'tax_ids': [Command.set(self.jo_general_tax_16.ids)],
}),
],
})

self._validate_invoice_vals_jo_edi_numbers({
'name': 'TestEIN026',
'date': '2023-11-12',
'invoice_line_ids': [
Command.create({
'product_id': self.product_a.id,
'quantity': 30,
'price_unit': 22.2,
'discount': 0,
'tax_ids': [Command.set(self.jo_general_tax_16.ids)],
}),
Command.create({
'product_id': self.product_a.id,
'quantity': 3,
'price_unit': 22.2,
'discount': 100,
'tax_ids': [Command.set(self.jo_general_tax_16.ids)],
}),
],
})