From 095e59c8b71d5a417bf2e22f251b4af5b6c18148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Hennecart?= Date: Thu, 19 Nov 2020 15:21:56 +0000 Subject: [PATCH 1/2] [IMP] crm: add tracking on recurring_revenue field Add tracking on the recurring_revenue field like the expected revenue. odoo/odoo#61999 odoo/upgrade#2060 task-2387268 --- addons/crm/models/crm_lead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index deb0df7cdee..c03ebc3f9ec 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -147,7 +147,7 @@ class Lead(models.Model): # Revenues expected_revenue = fields.Monetary('Expected Revenue', currency_field='company_currency', tracking=True) prorated_revenue = fields.Monetary('Prorated Revenue', currency_field='company_currency', store=True, compute="_compute_prorated_revenue") - recurring_revenue = fields.Monetary('Recurring Revenues', currency_field='company_currency', groups="crm.group_use_recurring_revenues") + recurring_revenue = fields.Monetary('Recurring Revenues', currency_field='company_currency', groups="crm.group_use_recurring_revenues", tracking=True) recurring_plan = fields.Many2one('crm.recurring.plan', string="Recurring Plan", groups="crm.group_use_recurring_revenues") recurring_revenue_monthly = fields.Monetary('Expected MRR', currency_field='company_currency', store=True, compute="_compute_recurring_revenue_monthly", From 3a398dc9d905d57828b7c8ded83768b996606bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Hennecart?= Date: Wed, 18 Nov 2020 10:01:47 +0000 Subject: [PATCH 2/2] [IMP] mail: improve monetary tracking field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the currency symbol for the monetary tracking field to better represent the change of a monetary field. The currency will be fetch from the currency defined in the monetary field or on the record's company in case there is not. For example, if we have a record with a monetary field displaying "$ 500". If we modify the currency and the value to have "450 €", the message containing the tracking values will display: "500 € -> 450 €". We only use one field to track the currency of a monetary field. Indeed, in the case where the currency is changed with the value of a monetary field, only the new currency is tracked. We focus on the fact that the more important thing is the new value. Furthermore, when modifying a currency of a monetary field, the user can already see the new currency before saving the changes. This allows him to adapt the value of the field if he needs it. (N.B. we assume that this case will happen very rarely) Using only one field takes also into account that there are millions of record for this model and adding a new field would take a lot of memory. odoo/odoo#61999 odoo/upgrade#2060 task-2387268 --- addons/mail/models/mail_message.py | 1 + addons/mail/models/mail_tracking_value.py | 3 + addons/mail/models/models.py | 2 + .../static/src/components/message/message.js | 9 +++ .../src/components/message/message_tests.js | 65 +++++++++++++++++++ addons/mail/tests/common.py | 3 + .../models/test_mail_corner_case_models.py | 9 +++ addons/test_mail/security/ir.model.access.csv | 1 + addons/test_mail/tests/common.py | 1 + addons/test_mail/tests/test_message_track.py | 40 +++++++++++- 10 files changed, 133 insertions(+), 1 deletion(-) diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 3477881ebe1..6dd9c5edb6f 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -985,6 +985,7 @@ class Message(models.Model): 'old_value': tracking.get_old_display_value()[0], 'new_value': tracking.get_new_display_value()[0], 'field_type': tracking.field_type, + 'currency_id': tracking.currency_id.id, }) vals.update({ diff --git a/addons/mail/models/mail_tracking_value.py b/addons/mail/models/mail_tracking_value.py index 472555a53ba..2edfab5abfc 100644 --- a/addons/mail/models/mail_tracking_value.py +++ b/addons/mail/models/mail_tracking_value.py @@ -31,6 +31,9 @@ class MailTracking(models.Model): new_value_text = fields.Text('New Value Text', readonly=1) new_value_datetime = fields.Datetime('New Value Datetime', readonly=1) + currency_id = fields.Many2one('res.currency', 'Currency', readonly=True, ondelete='set null', + help="Used to display the currency when tracking monetary values") + mail_message_id = fields.Many2one('mail.message', 'Message ID', required=True, index=True, ondelete='cascade') tracking_sequence = fields.Integer('Tracking field sequence', readonly=1, default=100) diff --git a/addons/mail/models/models.py b/addons/mail/models/models.py index f68cf42ed5f..947100de01a 100644 --- a/addons/mail/models/models.py +++ b/addons/mail/models/models.py @@ -46,6 +46,8 @@ class BaseModel(models.AbstractModel): tracking_sequence = 100 tracking = self.env['mail.tracking.value'].create_tracking_values(initial_value, new_value, col_name, col_info, tracking_sequence, self._name) if tracking: + if tracking['field_type'] == 'monetary': + tracking['currency_id'] = getattr(self, col_info.get('currency_field', ''), self.company_id.currency_id).id tracking_value_ids.append([0, 0, tracking]) changes.add(col_name) diff --git a/addons/mail/static/src/components/message/message.js b/addons/mail/static/src/components/message/message.js index 6d6e64e2b18..52333f2cd5b 100644 --- a/addons/mail/static/src/components/message/message.js +++ b/addons/mail/static/src/components/message/message.js @@ -15,6 +15,7 @@ const useStore = require('mail/static/src/component_hooks/use_store/use_store.js const useUpdate = require('mail/static/src/component_hooks/use_update/use_update.js'); const { _lt } = require('web.core'); +const { format } = require('web.field_utils'); const { getLangDatetimeFormat } = require('web.time'); const { Component, useState } = owl; @@ -310,6 +311,14 @@ class Message extends Component { if (value.new_value !== undefined){ value.new_value = value.new_value ? this.env._t("True") : this.env._t("False"); } + } else if (value.field_type === 'monetary' && value.currency_id) { + const currency_id = this.env.session.currencies[value.currency_id] + if (value.old_value !== undefined) { + value.old_value = format.monetary(value.old_value, null, { currency: currency_id, forceString: true }); + } + if (value.new_value !== undefined) { + value.new_value = format.monetary(value.new_value, null, { currency: currency_id, forceString: true }); + } } return value; }); diff --git a/addons/mail/static/src/components/message/message_tests.js b/addons/mail/static/src/components/message/message_tests.js index 8aaf1d2a268..99f7bf24aae 100644 --- a/addons/mail/static/src/components/message/message_tests.js +++ b/addons/mail/static/src/components/message/message_tests.js @@ -1212,6 +1212,71 @@ QUnit.test('rendering of tracked field with change of value from empty to string ); }); +QUnit.test('basic rendering of tracking value (monetary type)', async function (assert) { + assert.expect(8); + + await this.start({ + env: { + session: { + currencies: { 1: { symbol: '$', position: 'before' } }, + }, + }, + }); + const message = this.env.models['mail.message'].create({ + id: 11, + tracking_value_ids: [{ + changed_field: "Revenue", + currency_id: 1, + field_type: "monetary", + id: 6, + new_value: 500, + old_value: 1000, + }], + }); + + await this.createMessageComponent(message); + assert.containsOnce( + document.body, + '.o_Message_trackingValue', + "should display a tracking value" + ); + assert.containsOnce( + document.body, + '.o_Message_trackingValueFieldName', + "should display the name of the tracked field" + ); + assert.strictEqual( + document.querySelector('.o_Message_trackingValueFieldName').textContent, + "Revenue:", + "should display the correct tracked field name (Revenue)", + ); + assert.containsOnce( + document.body, + '.o_Message_trackingValueOldValue', + "should display the old value" + ); + assert.strictEqual( + document.querySelector('.o_Message_trackingValueOldValue').innerHTML, + "$ 1000.00", + "should display the correct old value with the currency symbol ($ 1000.00)", + ); + assert.containsOnce( + document.body, + '.o_Message_trackingValueSeparator', + "should display the separator" + ); + assert.containsOnce( + document.body, + '.o_Message_trackingValueNewValue', + "should display the new value" + ); + assert.strictEqual( + document.querySelector('.o_Message_trackingValueNewValue').innerHTML, + "$ 500.00", + "should display the correct new value with the currency symbol ($ 500.00)", + ); +}); + }); }); }); diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py index 1d74e915bc8..7f9485d59a5 100644 --- a/addons/mail/tests/common.py +++ b/addons/mail/tests/common.py @@ -662,6 +662,9 @@ class MailCase(MockEmail): self.assertEqual(tracking.new_value_integer, new_value and new_value.id or False) self.assertEqual(tracking.old_value_char, old_value and old_value.display_name or '') self.assertEqual(tracking.new_value_char, new_value and new_value.display_name or '') + elif value_type in ('monetary'): + self.assertEqual(tracking.old_value_monetary, old_value) + self.assertEqual(tracking.new_value_monetary, new_value) else: self.assertEqual(1, 0) diff --git a/addons/test_mail/models/test_mail_corner_case_models.py b/addons/test_mail/models/test_mail_corner_case_models.py index 6ede8e2f735..b6547223468 100644 --- a/addons/test_mail/models/test_mail_corner_case_models.py +++ b/addons/test_mail/models/test_mail_corner_case_models.py @@ -42,6 +42,15 @@ class MailTestTrackCompute(models.Model): partner_email = fields.Char(related='partner_id.email', store=True, tracking=True) partner_phone = fields.Char(related='partner_id.phone', tracking=True) +class MailTestTrackMonetary(models.Model): + _name = 'mail.test.track.monetary' + _description = 'Test tracking monetary field' + _inherit = ['mail.thread'] + + company_id = fields.Many2one('res.company') + company_currency = fields.Many2one("res.currency", string='Currency', related='company_id.currency_id', readonly=True, tracking=True) + revenue = fields.Monetary('Revenue', currency_field='company_currency', tracking=True) + class MailTestMultiCompany(models.Model): """ This model can be used in multi company tests""" diff --git a/addons/test_mail/security/ir.model.access.csv b/addons/test_mail/security/ir.model.access.csv index 46aa2a2f50e..2f2fe28732d 100644 --- a/addons/test_mail/security/ir.model.access.csv +++ b/addons/test_mail/security/ir.model.access.csv @@ -18,3 +18,4 @@ access_mail_test_cc_user,mail.test.cc.user,model_mail_test_cc,base.group_user,1, access_mail_test_multi_company_user,mail.test.multi.company.user,model_mail_test_multi_company,base.group_user,1,1,1,1 access_mail_test_multi_company_portal,mail.test.multi.company.portal,model_mail_test_multi_company,base.group_portal,1,0,0,0 access_mail_test_track_compute,mail.test.track.compute,model_mail_test_track_compute,base.group_user,1,1,1,1 +access_mail_test_track_monetary,mail.test.track.monetary,model_mail_test_track_monetary,base.group_user,1,1,1,1 diff --git a/addons/test_mail/tests/common.py b/addons/test_mail/tests/common.py index 0d1f3beb6f4..5ac033e76d8 100644 --- a/addons/test_mail/tests/common.py +++ b/addons/test_mail/tests/common.py @@ -43,6 +43,7 @@ class TestMailMultiCompanyCommon(MailCommon): super(TestMailMultiCompanyCommon, cls).setUpClass() cls.company_2 = cls.env['res.company'].create({ 'name': 'Second Test Company', + 'currency_id': 2, }) diff --git a/addons/test_mail/tests/test_message_track.py b/addons/test_mail/tests/test_message_track.py index df024c1e094..924691dc9df 100644 --- a/addons/test_mail/tests/test_message_track.py +++ b/addons/test_mail/tests/test_message_track.py @@ -3,7 +3,7 @@ from unittest.mock import patch -from odoo.addons.test_mail.tests.common import TestMailCommon +from odoo.addons.test_mail.tests.common import TestMailCommon, TestMailMultiCompanyCommon from odoo.tests.common import tagged from odoo.tests import Form @@ -283,6 +283,44 @@ class TestTracking(TestMailCommon): ('partner_email', 'char', 'foo@example.com', 'bar@example.com'), ]) +@tagged('mail_track') +class TestTrackingMonetary(TestMailMultiCompanyCommon): + + def setUp(self): + super(TestTrackingMonetary, self).setUp() + + record = self.env['mail.test.track.monetary'].with_user(self.user_employee).with_context(self._test_context).create({ + 'company_id': self.user_employee.company_id.id, + }) + self.flush_tracking() + self.record = record.with_context(mail_notrack=False) + + def test_message_track_monetary(self): + """ Update a record with a tracked monetary field """ + + # Check if the tracking value have the correct currency and values + self.record.write({ + 'revenue': 100, + }) + self.flush_tracking() + self.assertEqual(len(self.record.message_ids), 1) + + self.assertTracking(self.record.message_ids[0], [ + ('revenue', 'monetary', 0, 100), + ]) + + # Check if the tracking value have the correct currency and values after changing the value and the company + self.record.write({ + 'revenue': 200, + 'company_id': self.company_2.id, + }) + self.flush_tracking() + self.assertEqual(len(self.record.message_ids), 2) + + self.assertTracking(self.record.message_ids[0], [ + ('revenue', 'monetary', 100, 200), + ('company_currency', 'many2one', self.user_employee.company_id.currency_id, self.company_2.currency_id) + ]) @tagged('mail_track') class TestTrackingInternals(TestMailCommon):