[FIX] account: prevent wrong floating representation of currency
Steps to reproduce: - In Journals/Bills/Advanced activate "Lock post entries with hash" - Create a purchase tax of 17% - Create a bill with a product of a cost of 30 and apply the "17%" tax - Post it - Export the data inalterability check report Issue: "Corrupted data" Cause: In python we have a reprentation issue ``` >>> 30*0.17 5.1000000000000005 ``` We define the hash string during the `_compute_string_to_hash` at the move creation. The issue is, at that time, the move_line tax debit is not rounded. Therefore, when we print the report, we take the `move.line_id. debit` from the db which is rounded and equal to '5.10' and compare it to '5.1000000000000005' which gives a different hash Solution: Implementing a V3 version that uses `repr` for monetary fields. opw-3072693 closes odoo/odoo#114567 X-original-commit: cfd71892aabf1510273b28032ff8f2d4cfd4d1f7 Signed-off-by: William André (wan) <wan@odoo.com> Signed-off-by: Yolann Sabaux (yosa) <yosa@odoo.com>
This commit is contained in:
@@ -22,6 +22,7 @@ from odoo.tools import (
|
||||
email_split,
|
||||
float_compare,
|
||||
float_is_zero,
|
||||
float_repr,
|
||||
format_amount,
|
||||
format_date,
|
||||
formatLang,
|
||||
@@ -34,7 +35,7 @@ from odoo.tools import (
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
MAX_HASH_VERSION = 2
|
||||
MAX_HASH_VERSION = 3
|
||||
|
||||
TYPE_REVERSE_MAP = {
|
||||
'entry': 'entry',
|
||||
@@ -2690,7 +2691,7 @@ class AccountMove(models.Model):
|
||||
hash_version = self._context.get('hash_version', MAX_HASH_VERSION)
|
||||
if hash_version == 1:
|
||||
return ['date', 'journal_id', 'company_id']
|
||||
elif hash_version == MAX_HASH_VERSION:
|
||||
elif hash_version in (2, 3):
|
||||
return ['name', 'date', 'journal_id', 'company_id']
|
||||
raise NotImplementedError(f"hash_version={hash_version} doesn't exist")
|
||||
|
||||
@@ -2724,9 +2725,12 @@ class AccountMove(models.Model):
|
||||
@api.depends_context('hash_version')
|
||||
def _compute_string_to_hash(self):
|
||||
def _getattrstring(obj, field_str):
|
||||
hash_version = self._context.get('hash_version', MAX_HASH_VERSION)
|
||||
field_value = obj[field_str]
|
||||
if obj._fields[field_str].type == 'many2one':
|
||||
field_value = field_value.id
|
||||
if obj._fields[field_str].type == 'monetary' and hash_version >= 3:
|
||||
return float_repr(field_value, obj.currency_id.decimal_places)
|
||||
return str(field_value)
|
||||
|
||||
for move in self:
|
||||
|
||||
@@ -2766,7 +2766,7 @@ class AccountMoveLine(models.Model):
|
||||
hash_version = self._context.get('hash_version', MAX_HASH_VERSION)
|
||||
if hash_version == 1:
|
||||
return ['debit', 'credit', 'account_id', 'partner_id']
|
||||
elif hash_version == MAX_HASH_VERSION:
|
||||
elif hash_version in (2, 3):
|
||||
return ['name', 'debit', 'credit', 'account_id', 'partner_id']
|
||||
raise NotImplementedError(f"hash_version={hash_version} doesn't exist")
|
||||
|
||||
|
||||
@@ -188,3 +188,58 @@ class TestAccountMoveInalterableHash(AccountTestInvoicingCommon):
|
||||
moves_v1_bis.with_context(hash_version=1).action_post()
|
||||
integrity_check = moves.company_id._check_hash_integrity()['results'][0]
|
||||
self.assertEqual(integrity_check['msg_cover'], f'Corrupted data on journal entry with id {moves_v1_bis[0].id}.')
|
||||
|
||||
def test_account_move_hash_versioning_3(self):
|
||||
"""
|
||||
Version 2 does not take into account floating point representation issues.
|
||||
Test that version 3 covers correctly this case
|
||||
"""
|
||||
self.init_invoice("out_invoice", self.partner_a, "2023-01-01", amounts=[1000, 2000],
|
||||
post=True) # Not hashed
|
||||
self.company_data['default_journal_sale'].restrict_mode_hash_table = True
|
||||
moves_v3 = (
|
||||
self.init_invoice("out_invoice", self.partner_a, "2023-01-01", amounts=[30*0.17, 2000])
|
||||
| self.init_invoice("out_invoice", self.partner_b, "2023-01-02", amounts=[1000, 2000])
|
||||
| self.init_invoice("out_invoice", self.partner_b, "2023-01-03", amounts=[1000, 2000])
|
||||
)
|
||||
moves_v3.action_post()
|
||||
|
||||
# invalidate cache
|
||||
moves_v3[0].line_ids[0].invalidate_recordset()
|
||||
|
||||
integrity_check_v3 = moves_v3.company_id._check_hash_integrity()['results'][0]
|
||||
self.assertRegex(integrity_check_v3['msg_cover'], f'Entries are hashed from {moves_v3[0].name}.*')
|
||||
|
||||
def test_account_move_hash_versioning_v2_to_v3(self):
|
||||
"""
|
||||
We are updating the hash algorithm. We want to make sure that we do not break the integrity report.
|
||||
This test focuses on the case with version 2 and version 3.
|
||||
"""
|
||||
self.init_invoice("out_invoice", self.partner_a, "2023-01-01", amounts=[1000, 2000],
|
||||
post=True) # Not hashed
|
||||
self.company_data['default_journal_sale'].restrict_mode_hash_table = True
|
||||
moves_v2 = (
|
||||
self.init_invoice("out_invoice", self.partner_a, "2023-01-01", amounts=[1000, 2000])
|
||||
| self.init_invoice("out_invoice", self.partner_b, "2023-01-02", amounts=[1000, 2000])
|
||||
| self.init_invoice("out_invoice", self.partner_b, "2023-01-03", amounts=[1000, 2000])
|
||||
)
|
||||
moves_v2.with_context(hash_version=2).action_post()
|
||||
|
||||
moves_v3 = (
|
||||
self.init_invoice("out_invoice", self.partner_a, "2023-01-01", amounts=[1000, 2000])
|
||||
| self.init_invoice("out_invoice", self.partner_b, "2023-01-02", amounts=[1000, 2000])
|
||||
| self.init_invoice("out_invoice", self.partner_b, "2023-01-03", amounts=[1000, 2000])
|
||||
)
|
||||
moves_v3.with_context(hash_version=3).action_post()
|
||||
|
||||
moves = moves_v2 | moves_v3
|
||||
integrity_check = moves.company_id._check_hash_integrity()['results'][0]
|
||||
self.assertRegex(integrity_check['msg_cover'], f'Entries are hashed from {moves[0].name}.*')
|
||||
self.assertEqual(integrity_check['first_move_date'],
|
||||
format_date(self.env, fields.Date.to_string(moves[0].date)))
|
||||
self.assertEqual(integrity_check['last_move_date'],
|
||||
format_date(self.env, fields.Date.to_string(moves[-1].date)))
|
||||
|
||||
Model.write(moves[1], {'date': fields.Date.from_string('2023-01-07')})
|
||||
integrity_check = moves.company_id._check_hash_integrity()['results'][0]
|
||||
self.assertEqual(integrity_check['msg_cover'], f'Corrupted data on journal entry with id {moves[1].id}.')
|
||||
|
||||
Reference in New Issue
Block a user