From 7c28c97724d34f961c2cf271783fbca2fe485fc4 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Thu, 5 Sep 2019 09:22:20 +0000 Subject: [PATCH 1/3] [FIX] hr_expense: sheet multi expense partial payment Create two expense and put them into one sheet Post the sheet Make One partial payment with the wizard (Register Payment) Do it again for the residual amount Before this commit, it crashed because the second payment tried to reconcile itself with an already reconciled line After this commit, all the lines that need to be reconciled actually are without crash OPW 2065501 closes odoo/odoo#36454 Signed-off-by: Lucas Perais (lpe) --- addons/hr_expense/tests/test_expenses.py | 50 +++++++++++++++++++ .../hr_expense_sheet_register_payment.py | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/addons/hr_expense/tests/test_expenses.py b/addons/hr_expense/tests/test_expenses.py index d789f6e92be..e9cc5bf366d 100644 --- a/addons/hr_expense/tests/test_expenses.py +++ b/addons/hr_expense/tests/test_expenses.py @@ -53,6 +53,19 @@ class TestCheckJournalEntry(TransactionCase): 'company_id': company.id }) + self.bank_journal = self.env['account.journal'].create({ + 'name': 'Payment Journal', + 'code': 'PAY', + 'type': 'bank', + 'company_id': company.id + }) + + self.outbound_pay_method = self.env['account.payment.method'].create({ + 'name': 'outbound', + 'code': 'out', + 'payment_type': 'outbound', + }) + self.expense = self.env['hr.expense.sheet'].create({ 'name': 'Expense for John Smith', 'employee_id': self.employee.id, @@ -107,3 +120,40 @@ class TestCheckJournalEntry(TransactionCase): self.assertEquals(expense.tax_ids.ids, [self.tax.id]) self.assertEquals(expense.total_amount, 10863.60) self.assertTrue(expense.employee_id in user_demo.employee_ids) + + def test_partial_payment_multiexpense(self): + self.expense_line.unit_amount = 200 + expense_line2 = self.expense_line.copy({ + 'sheet_id': self.expense.id + }) + self.expense.approve_expense_sheets() + self.expense.action_sheet_move_create() + exp_move_lines = self.expense.account_move_id.line_ids + payable_move_lines = exp_move_lines.filtered(lambda l: l.account_id.internal_type == 'payable') + self.assertEquals(len(payable_move_lines), 2) + + WizardRegister = self.env['hr.expense.sheet.register.payment.wizard'].with_context(active_ids=self.expense.ids) + + register_pay1 = WizardRegister.create({ + 'journal_id': self.bank_journal.id, + 'payment_method_id': self.outbound_pay_method.id, + 'amount': 300, + }) + register_pay1.expense_post_payment() + + exp_move_lines = self.expense.account_move_id.line_ids + payable_move_lines = exp_move_lines.filtered(lambda l: l.account_id.internal_type == 'payable') + self.assertEquals(len(payable_move_lines.filtered(lambda l: l.reconciled)), 1) + + register_pay2 = WizardRegister.create({ + 'journal_id': self.bank_journal.id, + 'payment_method_id': self.outbound_pay_method.id, + 'amount': 100, + }) + register_pay2.expense_post_payment() + exp_move_lines = self.expense.account_move_id.line_ids + payable_move_lines = exp_move_lines.filtered(lambda l: l.account_id.internal_type == 'payable') + self.assertEquals(len(payable_move_lines.filtered(lambda l: l.reconciled)), 2) + + full_reconcile = payable_move_lines.mapped('full_reconcile_id') + self.assertEquals(len(full_reconcile), 1) diff --git a/addons/hr_expense/wizard/hr_expense_sheet_register_payment.py b/addons/hr_expense/wizard/hr_expense_sheet_register_payment.py index f378f15a471..54b8d816d14 100644 --- a/addons/hr_expense/wizard/hr_expense_sheet_register_payment.py +++ b/addons/hr_expense/wizard/hr_expense_sheet_register_payment.py @@ -86,7 +86,7 @@ class HrExpenseSheetRegisterPaymentWizard(models.TransientModel): # Reconcile the payment and the expense, i.e. lookup on the payable account move lines account_move_lines_to_reconcile = self.env['account.move.line'] for line in payment.move_line_ids + expense_sheet.account_move_id.line_ids: - if line.account_id.internal_type == 'payable': + if line.account_id.internal_type == 'payable' and not line.reconciled: account_move_lines_to_reconcile |= line account_move_lines_to_reconcile.reconcile() From 96266bfe547834e98f933e156c7e96a66b182095 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 5 Sep 2019 08:54:51 +0000 Subject: [PATCH 2/3] [FIX] models: callable in api.depends() sees valid _rec_name Determine `model._rec_name` before the dependencies of the fields. The compute method of the automatic field `display_name` uses a callable depends that retrieves `model._rec_name`. closes odoo/odoo#36480 Signed-off-by: Raphael Collet (rco) --- .../test_new_api/tests/test_new_fields.py | 7 +++++++ odoo/fields.py | 12 ++++++------ odoo/models.py | 18 +++++++++--------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index 5c568f873c6..2306b834571 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -74,6 +74,13 @@ class TestFields(common.TransactionCase): field = self.env['test_new_api.message']._fields['x_bool_false_computed'] self.assertFalse(field.depends) + def test_10_display_name(self): + """ test definition of automatic field 'display_name' """ + field = type(self.env['test_new_api.discussion']).display_name + self.assertTrue(field.automatic) + self.assertTrue(field.compute) + self.assertEqual(field.depends, ('name',)) + def test_10_non_stored(self): """ test non-stored fields """ # a field declared with store=False should not have a column diff --git a/odoo/fields.py b/odoo/fields.py index 70466bf3c61..e5590df9ccb 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -485,6 +485,10 @@ class Field(MetaField('DummyField', (object,), {})): def _setup_regular_base(self, model): """ Setup the attributes of a non-related field. """ + pass + + def _setup_regular_full(self, model): + """ Determine the dependencies and inverse field(s) of ``self``. """ if self.depends is not None: return @@ -502,10 +506,6 @@ class Field(MetaField('DummyField', (object,), {})): else: self.depends = tuple(get_depends(self.compute)) - def _setup_regular_full(self, model): - """ Setup the inverse field(s) of ``self``. """ - pass - # # Setup of related fields # @@ -2213,8 +2213,8 @@ class _RelationalMulti(_Relational): for record in records: record[self.name] = record[self.name].filtered(accessible) - def _setup_regular_base(self, model): - super(_RelationalMulti, self)._setup_regular_base(model) + def _setup_regular_full(self, model): + super(_RelationalMulti, self)._setup_regular_full(model) if isinstance(self.domain, list): self.depends += tuple( self.name + '.' + arg[0] diff --git a/odoo/models.py b/odoo/models.py index 7a22538e338..5298c2ffbf8 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -2436,6 +2436,15 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): cls._setup_done = True + # 5. determine and validate rec_name + if cls._rec_name: + assert cls._rec_name in cls._fields, \ + "Invalid rec_name %s for model %s" % (cls._rec_name, cls._name) + elif 'name' in cls._fields: + cls._rec_name = 'name' + elif 'x_name' in cls._fields: + cls._rec_name = 'x_name' + @api.model def _setup_fields(self): """ Setup the fields, except for recomputation triggers. """ @@ -2488,15 +2497,6 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): # register constraints and onchange methods cls._init_constraints_onchanges() - # validate rec_name - if cls._rec_name: - assert cls._rec_name in cls._fields, \ - "Invalid rec_name %s for model %s" % (cls._rec_name, cls._name) - elif 'name' in cls._fields: - cls._rec_name = 'name' - elif 'x_name' in cls._fields: - cls._rec_name = 'x_name' - @api.model def fields_get(self, allfields=None, attributes=None): """ fields_get([fields][, attributes]) From f048652ca7ef761d69a4b1fce2c9db812380b0c0 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 5 Sep 2019 08:54:51 +0000 Subject: [PATCH 3/3] [FIX] models: callable in api.depends() sees valid _rec_name Determine `model._rec_name` before the dependencies of the fields. The compute method of the automatic field `display_name` uses a callable depends that retrieves `model._rec_name`. closes odoo/odoo#36479 Signed-off-by: Raphael Collet (rco) --- .../test_new_api/tests/test_new_fields.py | 7 +++++++ odoo/fields.py | 12 ++++++------ odoo/models.py | 18 +++++++++--------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index 343e184e5ca..afdb8cf6d92 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -74,6 +74,13 @@ class TestFields(common.TransactionCase): field = self.env['test_new_api.message']._fields['x_bool_false_computed'] self.assertFalse(field.depends) + def test_10_display_name(self): + """ test definition of automatic field 'display_name' """ + field = type(self.env['test_new_api.discussion']).display_name + self.assertTrue(field.automatic) + self.assertTrue(field.compute) + self.assertEqual(field.depends, ('name',)) + def test_10_non_stored(self): """ test non-stored fields """ # a field declared with store=False should not have a column diff --git a/odoo/fields.py b/odoo/fields.py index f86bf938d33..a84dfa92e62 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -478,6 +478,10 @@ class Field(MetaField('DummyField', (object,), {})): def _setup_regular_base(self, model): """ Setup the attributes of a non-related field. """ + pass + + def _setup_regular_full(self, model): + """ Determine the dependencies and inverse field(s) of ``self``. """ def make_depends(deps): return tuple(deps(model) if callable(deps) else deps) @@ -489,10 +493,6 @@ class Field(MetaField('DummyField', (object,), {})): else: self.depends = make_depends(getattr(self.compute, '_depends', ())) - def _setup_regular_full(self, model): - """ Setup the inverse field(s) of ``self``. """ - pass - # # Setup of related fields # @@ -2182,8 +2182,8 @@ class _RelationalMulti(_Relational): for record in records: record[self.name] = record[self.name].filtered(accessible) - def _setup_regular_base(self, model): - super(_RelationalMulti, self)._setup_regular_base(model) + def _setup_regular_full(self, model): + super(_RelationalMulti, self)._setup_regular_full(model) if isinstance(self.domain, list): self.depends += tuple( self.name + '.' + arg[0] diff --git a/odoo/models.py b/odoo/models.py index 7707ffd2228..4eea8e8343d 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -2414,6 +2414,15 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): cls._setup_done = True + # 5. determine and validate rec_name + if cls._rec_name: + assert cls._rec_name in cls._fields, \ + "Invalid rec_name %s for model %s" % (cls._rec_name, cls._name) + elif 'name' in cls._fields: + cls._rec_name = 'name' + elif 'x_name' in cls._fields: + cls._rec_name = 'x_name' + @api.model def _setup_fields(self): """ Setup the fields, except for recomputation triggers. """ @@ -2466,15 +2475,6 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): # register constraints and onchange methods cls._init_constraints_onchanges() - # validate rec_name - if cls._rec_name: - assert cls._rec_name in cls._fields, \ - "Invalid rec_name %s for model %s" % (cls._rec_name, cls._name) - elif 'name' in cls._fields: - cls._rec_name = 'name' - elif 'x_name' in cls._fields: - cls._rec_name = 'x_name' - # make sure parent_order is set when necessary if cls._parent_store and not cls._parent_order: cls._parent_order = cls._order