From 5ccc32fcf72cbfb6bb077d99a7657416c502aac1 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Tue, 14 Jun 2022 14:09:07 +0000 Subject: [PATCH] [IMP] tests: common.Form, can't write on invisible fields In the web client, in a real use case, it's not possible to write on fields which are invisible, as it's not possible to write on fields which are readonly. This is a first step in the goal to change the behavior of the `groups=` attribute in the back-end views, to remove them for the view instead of making them invisible. This is mainly to reduce the diff of the revision that will introduce the mentioned above behavior change. As nodes with `groups=` will be removed from the view when the user doesn't have the group, it's no longer possible to set a value on a field having a `groups=` the user doesn't have in the `Form` test class, as the field will no longer be at all in the view. However, these unit tests shouldn't have been able to set values on invisible fields in the first place. This revision therefore aims to correct the unit tests setting value on fields which were invisible because the user executing the test was not part of the required group(s) for these fields to be visible in the view. closes odoo/odoo#94337 Related: odoo/enterprise#28936 Signed-off-by: Denis Ledoux (dle) --- addons/account/tests/common.py | 9 +- .../tests/test_account_move_in_invoice.py | 8 + .../tests/test_account_move_in_refund.py | 18 ++ .../tests/test_account_move_out_invoice.py | 7 +- .../tests/test_account_move_out_refund.py | 2 + .../tests/test_account_move_reconcile.py | 2 + addons/account/tests/test_account_payment.py | 14 +- addons/account/tests/test_payment_term.py | 1 - addons/crm/tests/test_crm_lead.py | 304 +++++++++--------- addons/crm/tests/test_res_partner.py | 14 + .../tests/test_automatic_leave_dates.py | 2 + .../hr_holidays/tests/test_leave_requests.py | 12 + addons/hr_holidays/tests/test_stress_days.py | 6 +- .../tests/common.py | 3 + .../mass_mailing/tests/test_mailing_list.py | 4 +- addons/mrp/tests/common.py | 7 +- addons/mrp/tests/test_backorder.py | 4 + addons/mrp/tests/test_bom.py | 4 + addons/mrp/tests/test_multicompany.py | 5 + addons/mrp/tests/test_order.py | 36 +++ addons/mrp/tests/test_procurement.py | 2 + .../test_warehouse_multistep_manufacturing.py | 4 + .../tests/test_analytic_account.py | 4 + addons/mrp_account/tests/test_bom_price.py | 2 + .../tests/test_stock_landed_costs_mrp.py | 3 + .../tests/test_subcontracting.py | 2 + addons/product/tests/test_variants.py | 2 + addons/project/tests/test_multicompany.py | 46 ++- .../project/tests/test_project_recurrence.py | 6 + addons/project/tests/test_project_sharing.py | 4 + addons/project/tests/test_project_subtasks.py | 44 +-- addons/purchase/tests/test_purchase.py | 2 + .../purchase/tests/test_purchase_invoice.py | 2 + .../tests/test_purchase_order_report.py | 34 +- ...st_anglo_saxon_valuation_reconciliation.py | 2 +- .../tests/test_create_picking.py | 2 + .../tests/test_onchange_product.py | 2 + .../tests/test_purchase_order.py | 11 +- .../tests/test_reordering_rule.py | 6 + .../tests/test_stockvaluation.py | 20 +- addons/repair/tests/test_repair.py | 2 + addons/sale/tests/test_reinvoice.py | 6 + addons/sale/tests/test_sale_order.py | 4 + addons/sale/tests/test_sale_to_invoice.py | 2 + .../tests/test_multistep_manufacturing.py | 5 + addons/sale_mrp/tests/test_sale_mrp_flow.py | 4 +- .../sale_mrp/tests/test_sale_mrp_lead_time.py | 8 +- .../tests/test_sale_mrp_procurement.py | 6 + .../tests/test_project_billing.py | 6 + addons/sale_timesheet/tests/test_reinvoice.py | 6 + addons/stock/tests/test_move.py | 8 + addons/stock/tests/test_move2.py | 4 +- addons/stock/tests/test_multicompany.py | 14 +- addons/stock/tests/test_packing.py | 46 ++- addons/stock/tests/test_proc_rule.py | 5 + addons/stock/tests/test_report.py | 6 +- addons/stock/tests/test_warehouse.py | 2 + .../tests/test_stockvaluationlayer.py | 2 + .../stock_dropshipping/tests/test_dropship.py | 2 + .../tests/test_lifo_price.py | 27 +- .../tests/test_procurement_exception.py | 17 +- .../tests/test_stockvaluation.py | 2 +- .../tests/test_stock_landed_costs_purchase.py | 2 +- addons/survey/tests/test_survey_invite.py | 11 + .../test_crm_full/tests/test_performance.py | 9 +- .../test_event_full/tests/test_performance.py | 55 ++-- addons/test_mail/tests/test_mail_activity.py | 7 +- odoo/addons/base/tests/test_form_create.py | 2 + odoo/addons/base/tests/test_res_partner.py | 7 +- odoo/addons/base/tests/test_res_users.py | 4 +- odoo/tests/common.py | 58 +++- 71 files changed, 749 insertions(+), 252 deletions(-) diff --git a/addons/account/tests/common.py b/addons/account/tests/common.py index 7b0515bf16c..6ae2a249630 100644 --- a/addons/account/tests/common.py +++ b/addons/account/tests/common.py @@ -385,7 +385,14 @@ class AccountTestInvoicingCommon(TransactionCase): .with_company(company or cls.env.company) \ .with_context(default_move_type=move_type, account_predictive_bills_disable_prediction=True)) move_form.invoice_date = invoice_date or fields.Date.from_string('2019-01-01') - move_form.date = move_form.invoice_date + # According to the state or type of the invoice, the date field is sometimes visible or not + # Besides, the date field can be put multiple times in the view + # "invisible": "['|', ('state', '!=', 'draft'), ('auto_post', '!=', 'at_date')]" + # "invisible": ['|', '|', ('state', '!=', 'draft'), ('auto_post', '=', 'no'), ('auto_post', '=', 'at_date')] + # "invisible": "['&', ('move_type', 'in', ['out_invoice', 'out_refund', 'out_receipt']), ('quick_edit_mode', '=', False)]" + # :TestAccountMoveOutInvoiceOnchanges, :TestAccountMoveOutRefundOnchanges, .test_00_debit_note_out_invoice, :TestAccountEdi + if not move_form._get_modifier('date', 'invisible'): + move_form.date = move_form.invoice_date move_form.partner_id = partner or cls.partner_a for product in (products or []): diff --git a/addons/account/tests/test_account_move_in_invoice.py b/addons/account/tests/test_account_move_in_invoice.py index 8060e11f694..897e7635c46 100644 --- a/addons/account/tests/test_account_move_in_invoice.py +++ b/addons/account/tests/test_account_move_in_invoice.py @@ -849,6 +849,8 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon): }) def test_in_invoice_line_onchange_cash_rounding_1(self): + # Required for `invoice_cash_rounding_id` to be visible in the view + self.env.user.groups_id += self.env.ref('account.group_cash_rounding') # Test 'add_invoice_line' rounding move_form = Form(self.invoice) # Add a cash rounding having 'add_invoice_line'. @@ -1193,6 +1195,12 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon): }) def test_in_invoice_onchange_past_invoice_1(self): + if self.env.ref('purchase.group_purchase_manager', raise_if_not_found=False): + # `purchase` adds a view which makes `invoice_vendor_bill_id` invisible + # for purchase users + # https://github.com/odoo/odoo/blob/385884afd31f25d61e99d139ecd4c574d99a1863/addons/purchase/views/account_move_views.xml#L26 + self.env.user.groups_id -= self.env.ref('purchase.group_purchase_manager') + self.env.user.groups_id -= self.env.ref('purchase.group_purchase_user') copy_invoice = self.invoice.copy() move_form = Form(self.invoice) diff --git a/addons/account/tests/test_account_move_in_refund.py b/addons/account/tests/test_account_move_in_refund.py index c44b32a3f7a..50bd082f28a 100644 --- a/addons/account/tests/test_account_move_in_refund.py +++ b/addons/account/tests/test_account_move_in_refund.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- # pylint: disable=bad-whitespace +from lxml import etree + from odoo.addons.account.tests.common import AccountTestInvoicingCommon from odoo.tests.common import Form from odoo.tests import tagged @@ -533,6 +535,8 @@ class TestAccountMoveInRefundOnchanges(AccountTestInvoicingCommon): }) def test_in_refund_line_onchange_cash_rounding_1(self): + # Required for `invoice_cash_rounding_id` to be visible in the view + self.env.user.groups_id += self.env.ref('account.group_cash_rounding') # Test 'add_invoice_line' rounding move_form = Form(self.invoice) # Add a cash rounding having 'add_invoice_line'. @@ -878,6 +882,20 @@ class TestAccountMoveInRefundOnchanges(AccountTestInvoicingCommon): def test_in_refund_onchange_past_invoice_1(self): copy_invoice = self.invoice.copy() + if self.env.ref('purchase.group_purchase_manager', raise_if_not_found=False): + # `purchase` adds a view which makes `invoice_vendor_bill_id` invisible + # for purchase users + # https://github.com/odoo/odoo/blob/385884afd31f25d61e99d139ecd4c574d99a1863/addons/purchase/views/account_move_views.xml#L26 + self.env.user.groups_id -= self.env.ref('purchase.group_purchase_manager') + self.env.user.groups_id -= self.env.ref('purchase.group_purchase_user') + # 'invisible': ['|', ('state', '!=', 'draft'), ('move_type', '!=', 'in_invoice')] + # This is an in_refund invoice, `invoice_vendor_bill_id` is not supposed to be visible + # and therefore not supposed to be changed. + view = self.env.ref('account.view_move_form') + tree = etree.fromstring(view.arch) + for node in tree.xpath('//field[@name="invoice_vendor_bill_id"]'): + del node.attrib['attrs'] + view.arch = etree.tostring(tree) move_form = Form(self.invoice) move_form.invoice_line_ids.remove(0) diff --git a/addons/account/tests/test_account_move_out_invoice.py b/addons/account/tests/test_account_move_out_invoice.py index 7cb6bb9b560..497b3860abd 100644 --- a/addons/account/tests/test_account_move_out_invoice.py +++ b/addons/account/tests/test_account_move_out_invoice.py @@ -1453,6 +1453,8 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon): ]) def test_out_invoice_line_onchange_cash_rounding_1(self): + # Required for `invoice_cash_rounding_id` to be visible in the view + self.env.user.groups_id += self.env.ref('account.group_cash_rounding') # Test 'add_invoice_line' rounding move_form = Form(self.invoice) # Add a cash rounding having 'add_invoice_line'. @@ -1977,7 +1979,8 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon): a custom reversal date. ''' move_form = Form(self.invoice) - move_form.date = '2016-01-01' + # `date` is invisible in the view, the date of the invoice should be set using `invoice_date` instead + move_form.invoice_date = '2016-01-01' move_form.currency_id = self.currency_data['currency'] move_form.save() @@ -3459,6 +3462,8 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon): move_form.partner_id = self.partner_a # Quick edit total amount not activated yet + # As quick edit total is not yet activated, it's invisible by default in the view + move_form._view['modifiers']['quick_edit_total_amount']['invisible'] = False move_form.quick_edit_total_amount = 100.0 invoice = move_form.save() self.assertEqual(invoice.amount_total, 0.0) diff --git a/addons/account/tests/test_account_move_out_refund.py b/addons/account/tests/test_account_move_out_refund.py index 9cdd3a6b81a..95735bc92b2 100644 --- a/addons/account/tests/test_account_move_out_refund.py +++ b/addons/account/tests/test_account_move_out_refund.py @@ -532,6 +532,8 @@ class TestAccountMoveOutRefundOnchanges(AccountTestInvoicingCommon): }) def test_out_refund_line_onchange_cash_rounding_1(self): + # Required for `invoice_cash_rounding_id` to be visible in the view + self.env.user.groups_id += self.env.ref('account.group_cash_rounding') # Test 'add_invoice_line' rounding move_form = Form(self.invoice) # Add a cash rounding having 'add_invoice_line'. diff --git a/addons/account/tests/test_account_move_reconcile.py b/addons/account/tests/test_account_move_reconcile.py index 9209a44a8f8..20a82816ba2 100644 --- a/addons/account/tests/test_account_move_reconcile.py +++ b/addons/account/tests/test_account_move_reconcile.py @@ -3536,6 +3536,8 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon): """ Test the CABA entries generated from an invoice with almost equal lines, different only on analytic accounting """ + # Required for `analytic_account_id` to be visible in the view + self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting') # Make the tax account reconcilable self.tax_account_1.reconcile = True diff --git a/addons/account/tests/test_account_payment.py b/addons/account/tests/test_account_payment.py index bd5bebe0b99..115627f4277 100644 --- a/addons/account/tests/test_account_payment.py +++ b/addons/account/tests/test_account_payment.py @@ -200,10 +200,14 @@ class TestAccountPayment(AccountTestInvoicingCommon): def test_payment_move_sync_onchange(self): - pay_form = Form(self.env['account.payment'].with_context(default_journal_id=self.company_data['default_journal_bank'].id)) + pay_form = Form(self.env['account.payment'].with_context( + default_journal_id=self.company_data['default_journal_bank'].id, + # The `partner_type` is set through the window action context in the web client + # the field is otherwise invisible in the form. + default_partner_type='customer', + )) pay_form.amount = 50.0 pay_form.payment_type = 'inbound' - pay_form.partner_type = 'customer' pay_form.partner_id = self.partner_a payment = pay_form.save() @@ -246,8 +250,12 @@ class TestAccountPayment(AccountTestInvoicingCommon): # ==== Check editing the account.payment ==== + # `partner_type` on payment is always invisible. It's supposed to be set through a context `default_` key + # In this case the goal of the test is to take an existing customer payment and change it to a supplier payment, + # which is not supposed to be possible through the web interface. + # So, change the payment partner_type beforehand rather than in the form view. + payment.partner_type = 'supplier' pay_form = Form(payment) - pay_form.partner_type = 'supplier' pay_form.currency_id = self.currency_data['currency'] pay_form.partner_id = self.partner_a payment = pay_form.save() diff --git a/addons/account/tests/test_payment_term.py b/addons/account/tests/test_payment_term.py index fb40d38c126..a3133247f6c 100644 --- a/addons/account/tests/test_payment_term.py +++ b/addons/account/tests/test_payment_term.py @@ -73,7 +73,6 @@ class TestAccountInvoiceRounding(AccountTestInvoicingCommon): with Form(self.invoice) as move_form: move_form.invoice_payment_term_id = pay_term move_form.invoice_date = invoice_date - move_form.date = invoice_date self.assertEqual( self.invoice.line_ids.filtered( lambda l: l.account_id == self.company_data['default_account_receivable'] diff --git a/addons/crm/tests/test_crm_lead.py b/addons/crm/tests/test_crm_lead.py index e1bd9854275..34acfcad160 100644 --- a/addons/crm/tests/test_crm_lead.py +++ b/addons/crm/tests/test_crm_lead.py @@ -287,105 +287,110 @@ class TestCRMLead(TestCrmCommon): phone and email fields. Phone especially has some corner cases due to automatic formatting (notably with onchange in form view). """ lead, partner = self.lead_1.with_user(self.env.user), self.contact_2 - lead_form = Form(lead) + # This is a type == 'lead', not a type == 'opportunity' + # {'invisible': ['|', ('type', '=', 'opportunity'), ('is_partner_visible', '=', False)]} + # lead.is_partner_visible = bool(lead.type == 'opportunity' or lead.partner_id or is_debug_mode) + # Hence, debug mode required for `partner_id` to be visible + with self.debug_mode(): + lead_form = Form(lead) - # reset partner phone to a local number and prepare formatted / sanitized values - partner_phone, partner_mobile = self.test_phone_data[2], self.test_phone_data[1] - partner_phone_formatted = phone_format(partner_phone, 'US', '1') - partner_phone_sanitized = phone_format(partner_phone, 'US', '1', force_format='E164') - partner_mobile_formatted = phone_format(partner_mobile, 'US', '1') - partner_mobile_sanitized = phone_format(partner_mobile, 'US', '1', force_format='E164') - partner_email, partner_email_normalized = self.test_email_data[2], self.test_email_data_normalized[2] - self.assertEqual(partner_phone_formatted, '+1 202-555-0888') - self.assertEqual(partner_phone_sanitized, self.test_phone_data_sanitized[2]) - self.assertEqual(partner_mobile_formatted, '+1 202-555-0999') - self.assertEqual(partner_mobile_sanitized, self.test_phone_data_sanitized[1]) - # ensure initial data - self.assertEqual(partner.phone, partner_phone) - self.assertEqual(partner.mobile, partner_mobile) - self.assertEqual(partner.email, partner_email) + # reset partner phone to a local number and prepare formatted / sanitized values + partner_phone, partner_mobile = self.test_phone_data[2], self.test_phone_data[1] + partner_phone_formatted = phone_format(partner_phone, 'US', '1') + partner_phone_sanitized = phone_format(partner_phone, 'US', '1', force_format='E164') + partner_mobile_formatted = phone_format(partner_mobile, 'US', '1') + partner_mobile_sanitized = phone_format(partner_mobile, 'US', '1', force_format='E164') + partner_email, partner_email_normalized = self.test_email_data[2], self.test_email_data_normalized[2] + self.assertEqual(partner_phone_formatted, '+1 202-555-0888') + self.assertEqual(partner_phone_sanitized, self.test_phone_data_sanitized[2]) + self.assertEqual(partner_mobile_formatted, '+1 202-555-0999') + self.assertEqual(partner_mobile_sanitized, self.test_phone_data_sanitized[1]) + # ensure initial data + self.assertEqual(partner.phone, partner_phone) + self.assertEqual(partner.mobile, partner_mobile) + self.assertEqual(partner.email, partner_email) - # LEAD/PARTNER SYNC: email and phone are propagated to lead - # as well as mobile (who does not trigger the reverse sync) - lead_form.partner_id = partner - self.assertEqual(lead_form.email_from, partner_email) - self.assertEqual(lead_form.phone, partner_phone_formatted, - 'Lead: form automatically formats numbers') - self.assertEqual(lead_form.mobile, partner_mobile_formatted, - 'Lead: form automatically formats numbers') - self.assertFalse(lead_form.partner_email_update) - self.assertFalse(lead_form.partner_phone_update) + # LEAD/PARTNER SYNC: email and phone are propagated to lead + # as well as mobile (who does not trigger the reverse sync) + lead_form.partner_id = partner + self.assertEqual(lead_form.email_from, partner_email) + self.assertEqual(lead_form.phone, partner_phone_formatted, + 'Lead: form automatically formats numbers') + self.assertEqual(lead_form.mobile, partner_mobile_formatted, + 'Lead: form automatically formats numbers') + self.assertFalse(lead_form.partner_email_update) + self.assertFalse(lead_form.partner_phone_update) - lead_form.save() - self.assertEqual(partner.phone, partner_phone, - 'Lead / Partner: partner values sent to lead') - self.assertEqual(lead.email_from, partner_email, - 'Lead / Partner: partner values sent to lead') - self.assertEqual(lead.email_normalized, partner_email_normalized, - 'Lead / Partner: equal emails should lead to equal normalized emails') - self.assertEqual(lead.phone, partner_phone_formatted, - 'Lead / Partner: partner values (formatted) sent to lead') - self.assertEqual(lead.mobile, partner_mobile_formatted, - 'Lead / Partner: partner values (formatted) sent to lead') - self.assertEqual(lead.phone_sanitized, partner_mobile_sanitized, - 'Lead: phone_sanitized computed field on mobile') + lead_form.save() + self.assertEqual(partner.phone, partner_phone, + 'Lead / Partner: partner values sent to lead') + self.assertEqual(lead.email_from, partner_email, + 'Lead / Partner: partner values sent to lead') + self.assertEqual(lead.email_normalized, partner_email_normalized, + 'Lead / Partner: equal emails should lead to equal normalized emails') + self.assertEqual(lead.phone, partner_phone_formatted, + 'Lead / Partner: partner values (formatted) sent to lead') + self.assertEqual(lead.mobile, partner_mobile_formatted, + 'Lead / Partner: partner values (formatted) sent to lead') + self.assertEqual(lead.phone_sanitized, partner_mobile_sanitized, + 'Lead: phone_sanitized computed field on mobile') - # for email_from, if only formatting differs, warning should not appear and - # email on partner should not be updated - lead_form.email_from = '"Hermes Conrad" <%s>' % partner_email_normalized - self.assertFalse(lead_form.partner_email_update) - lead_form.save() - self.assertEqual(partner.email, partner_email) + # for email_from, if only formatting differs, warning should not appear and + # email on partner should not be updated + lead_form.email_from = '"Hermes Conrad" <%s>' % partner_email_normalized + self.assertFalse(lead_form.partner_email_update) + lead_form.save() + self.assertEqual(partner.email, partner_email) - # for phone, if only formatting differs, warning should not appear and - # phone on partner should not be updated - lead_form.phone = partner_phone_sanitized - self.assertFalse(lead_form.partner_phone_update) - lead_form.save() - self.assertEqual(partner.phone, partner_phone) + # for phone, if only formatting differs, warning should not appear and + # phone on partner should not be updated + lead_form.phone = partner_phone_sanitized + self.assertFalse(lead_form.partner_phone_update) + lead_form.save() + self.assertEqual(partner.phone, partner_phone) - # LEAD/PARTNER SYNC: lead updates partner - new_email = '"John Zoidberg" ' - new_email_normalized = 'john.zoidberg@test.example.com' - lead_form.email_from = new_email - self.assertTrue(lead_form.partner_email_update) - new_phone = '+1 202 555 7799' - new_phone_formatted = phone_format(new_phone, 'US', '1') - lead_form.phone = new_phone - self.assertEqual(lead_form.phone, new_phone_formatted) - self.assertTrue(lead_form.partner_email_update) - self.assertTrue(lead_form.partner_phone_update) + # LEAD/PARTNER SYNC: lead updates partner + new_email = '"John Zoidberg" ' + new_email_normalized = 'john.zoidberg@test.example.com' + lead_form.email_from = new_email + self.assertTrue(lead_form.partner_email_update) + new_phone = '+1 202 555 7799' + new_phone_formatted = phone_format(new_phone, 'US', '1') + lead_form.phone = new_phone + self.assertEqual(lead_form.phone, new_phone_formatted) + self.assertTrue(lead_form.partner_email_update) + self.assertTrue(lead_form.partner_phone_update) - lead_form.save() - self.assertEqual(partner.email, new_email) - self.assertEqual(partner.email_normalized, new_email_normalized) - self.assertEqual(partner.phone, new_phone_formatted) + lead_form.save() + self.assertEqual(partner.email, new_email) + self.assertEqual(partner.email_normalized, new_email_normalized) + self.assertEqual(partner.phone, new_phone_formatted) - # LEAD/PARTNER SYNC: mobile does not update partner - new_mobile = '+1 202 555 6543' - new_mobile_formatted = phone_format(new_mobile, 'US', '1') - lead_form.mobile = new_mobile - lead_form.save() - self.assertEqual(lead.mobile, new_mobile_formatted) - self.assertEqual(partner.mobile, partner_mobile) + # LEAD/PARTNER SYNC: mobile does not update partner + new_mobile = '+1 202 555 6543' + new_mobile_formatted = phone_format(new_mobile, 'US', '1') + lead_form.mobile = new_mobile + lead_form.save() + self.assertEqual(lead.mobile, new_mobile_formatted) + self.assertEqual(partner.mobile, partner_mobile) - # LEAD/PARTNER SYNC: reseting lead values also resets partner for email - # and phone, but not for mobile - lead_form.email_from, lead_form.phone, lead.mobile = False, False, False - self.assertTrue(lead_form.partner_email_update) - self.assertTrue(lead_form.partner_phone_update) - lead_form.save() - self.assertFalse(partner.email) - self.assertFalse(partner.email_normalized) - self.assertFalse(partner.phone) - self.assertFalse(lead.phone) - self.assertFalse(lead.mobile) - self.assertFalse(lead.phone_sanitized) - self.assertEqual(partner.mobile, partner_mobile) - # if SMS is uninstalled, phone_sanitized is not available on partner - if 'phone_sanitized' in partner: - self.assertEqual(partner.phone_sanitized, partner_mobile_sanitized, - 'Partner sanitized should be computed on mobile') + # LEAD/PARTNER SYNC: reseting lead values also resets partner for email + # and phone, but not for mobile + lead_form.email_from, lead_form.phone, lead.mobile = False, False, False + self.assertTrue(lead_form.partner_email_update) + self.assertTrue(lead_form.partner_phone_update) + lead_form.save() + self.assertFalse(partner.email) + self.assertFalse(partner.email_normalized) + self.assertFalse(partner.phone) + self.assertFalse(lead.phone) + self.assertFalse(lead.mobile) + self.assertFalse(lead.phone_sanitized) + self.assertEqual(partner.mobile, partner_mobile) + # if SMS is uninstalled, phone_sanitized is not available on partner + if 'phone_sanitized' in partner: + self.assertEqual(partner.phone_sanitized, partner_mobile_sanitized, + 'Partner sanitized should be computed on mobile') @users('user_sales_manager') def test_crm_lead_partner_sync_email_phone_corner_cases(self): @@ -401,69 +406,74 @@ class TestCRMLead(TestCrmCommon): 'mobile': '', }) - lead_form = Form(lead) - self.assertEqual(lead_form.email_from, test_email) - self.assertFalse(lead_form.partner_email_update) - self.assertFalse(lead_form.partner_phone_update) + # This is a type == 'lead', not a type == 'opportunity' + # {'invisible': ['|', ('type', '=', 'opportunity'), ('is_partner_visible', '=', False)]} + # lead.is_partner_visible = bool(lead.type == 'opportunity' or lead.partner_id or is_debug_mode) + # Hence, debug mode required for `partner_id` to be visible + with self.debug_mode(): + lead_form = Form(lead) + self.assertEqual(lead_form.email_from, test_email) + self.assertFalse(lead_form.partner_email_update) + self.assertFalse(lead_form.partner_phone_update) - # email: False versus empty string - lead_form.partner_id = contact - self.assertTrue(lead_form.partner_email_update) - self.assertFalse(lead_form.partner_phone_update) - lead_form.email_from = '' - self.assertFalse(lead_form.partner_email_update) - lead_form.email_from = False - self.assertFalse(lead_form.partner_email_update) + # email: False versus empty string + lead_form.partner_id = contact + self.assertTrue(lead_form.partner_email_update) + self.assertFalse(lead_form.partner_phone_update) + lead_form.email_from = '' + self.assertFalse(lead_form.partner_email_update) + lead_form.email_from = False + self.assertFalse(lead_form.partner_email_update) - # phone: False versus empty string - lead_form.phone = '+1 202-555-0888' - self.assertFalse(lead_form.partner_email_update) - self.assertTrue(lead_form.partner_phone_update) - lead_form.phone = '' - self.assertFalse(lead_form.partner_phone_update) - lead_form.phone = False - self.assertFalse(lead_form.partner_phone_update) + # phone: False versus empty string + lead_form.phone = '+1 202-555-0888' + self.assertFalse(lead_form.partner_email_update) + self.assertTrue(lead_form.partner_phone_update) + lead_form.phone = '' + self.assertFalse(lead_form.partner_phone_update) + lead_form.phone = False + self.assertFalse(lead_form.partner_phone_update) - # email/phone: formatting should not trigger ribbon - lead.write({ - 'email_from': '"My Name" <%s>' % test_email, - 'phone': '+1 202-555-0888', - }) - contact.write({ - 'email': '"My Name" <%s>' % test_email, - 'phone': '+1 202-555-0888', - }) + # email/phone: formatting should not trigger ribbon + lead.write({ + 'email_from': '"My Name" <%s>' % test_email, + 'phone': '+1 202-555-0888', + }) + contact.write({ + 'email': '"My Name" <%s>' % test_email, + 'phone': '+1 202-555-0888', + }) - lead_form = Form(lead) - self.assertFalse(lead_form.partner_email_update) - self.assertFalse(lead_form.partner_phone_update) - lead_form.partner_id = contact - self.assertFalse(lead_form.partner_email_update) - self.assertFalse(lead_form.partner_phone_update) - lead_form.email_from = '"Another Name" <%s>' % test_email # same email normalized - self.assertFalse(lead_form.partner_email_update, 'Formatting-only change should not trigger write') - self.assertFalse(lead_form.partner_phone_update, 'Formatting-only change should not trigger write') - lead_form.phone = '2025550888' # same number but another format - self.assertFalse(lead_form.partner_email_update, 'Formatting-only change should not trigger write') - self.assertFalse(lead_form.partner_phone_update, 'Formatting-only change should not trigger write') + lead_form = Form(lead) + self.assertFalse(lead_form.partner_email_update) + self.assertFalse(lead_form.partner_phone_update) + lead_form.partner_id = contact + self.assertFalse(lead_form.partner_email_update) + self.assertFalse(lead_form.partner_phone_update) + lead_form.email_from = '"Another Name" <%s>' % test_email # same email normalized + self.assertFalse(lead_form.partner_email_update, 'Formatting-only change should not trigger write') + self.assertFalse(lead_form.partner_phone_update, 'Formatting-only change should not trigger write') + lead_form.phone = '2025550888' # same number but another format + self.assertFalse(lead_form.partner_email_update, 'Formatting-only change should not trigger write') + self.assertFalse(lead_form.partner_phone_update, 'Formatting-only change should not trigger write') - # wrong value are also propagated - lead_form.phone = '666 789456789456789456' - self.assertTrue(lead_form.partner_phone_update) + # wrong value are also propagated + lead_form.phone = '666 789456789456789456' + self.assertTrue(lead_form.partner_phone_update) - # test country propagation allowing to correctly compute sanitized numbers - # by adding missing relevant information from contact - be_country = self.env.ref('base.be') - contact.write({ - 'country_id': be_country.id, - 'phone': '+32456001122', - }) - lead.write({'country_id': False}) - lead_form = Form(lead) - lead_form.partner_id = contact - lead_form.phone = '0456 00 11 22' - self.assertFalse(lead_form.partner_phone_update) - self.assertEqual(lead_form.country_id, be_country) + # test country propagation allowing to correctly compute sanitized numbers + # by adding missing relevant information from contact + be_country = self.env.ref('base.be') + contact.write({ + 'country_id': be_country.id, + 'phone': '+32456001122', + }) + lead.write({'country_id': False}) + lead_form = Form(lead) + lead_form.partner_id = contact + lead_form.phone = '0456 00 11 22' + self.assertFalse(lead_form.partner_phone_update) + self.assertEqual(lead_form.country_id, be_country) @users('user_sales_manager') diff --git a/addons/crm/tests/test_res_partner.py b/addons/crm/tests/test_res_partner.py index e8b5c295496..3525801f5a6 100644 --- a/addons/crm/tests/test_res_partner.py +++ b/addons/crm/tests/test_res_partner.py @@ -46,6 +46,20 @@ class TestPartner(TestCrmCommon): # test form tool partner_form = Form(self.env['res.partner'], 'base.view_partner_form') + # `parent_id` is invisible when `is_company` is True (`company_type == 'company'`) + # and parent_id is not set + # So, set a temporary `parent_id` before setting the contact as company + # to make `parent_id` visible in the interface while being a company + # + partner_form.parent_id = contact_company_1 partner_form.company_type = 'company' partner_form.parent_id = contact_company partner_form.name = 'Mom Corp' diff --git a/addons/hr_holidays/tests/test_automatic_leave_dates.py b/addons/hr_holidays/tests/test_automatic_leave_dates.py index 87f554b7b3a..2c47d3d95a6 100644 --- a/addons/hr_holidays/tests/test_automatic_leave_dates.py +++ b/addons/hr_holidays/tests/test_automatic_leave_dates.py @@ -16,6 +16,8 @@ class TestAutomaticLeaveDates(TestHrHolidaysCommon): 'name': 'Automatic Test', 'time_type': 'leave', 'requires_allocation': 'no', + # Required for `request_unit_half` to be visible in the view + 'request_unit': 'half_day', }) def test_no_attendances(self): diff --git a/addons/hr_holidays/tests/test_leave_requests.py b/addons/hr_holidays/tests/test_leave_requests.py index fdc161cc131..c70efec9464 100644 --- a/addons/hr_holidays/tests/test_leave_requests.py +++ b/addons/hr_holidays/tests/test_leave_requests.py @@ -271,6 +271,18 @@ class TestLeaveRequests(TestHrHolidaysCommon): leave_form.holiday_status_id = self.holidays_type_1 leave_form.request_date_from = date(2019, 5, 6) leave_form.request_date_to = date(2019, 5, 6) + # TODO: The test is wrong by modifying `date_from` and `date_to`, which are invisible + # It should edit only `request_date_from` and `request_date_to` instead + # And there is really a bug. Using the web client, when you put your PC in Auckland timezone, + # and the admin preferences in Auckland Timezone + # and create a time off for the current day, the computation is completely wrong + # and compute the date to before the date from *-) + # For instance, for a time-off from 06/16/2022 to 06/16/2022 (1 day) it computes + # 06/16/2022 08:00:00 as date_from and 06/15/2022 17:00:00 as date_to + # Bug reported to the rd-fun-vidange channel to the dev who introduced the bug + # https://discord.com/channels/678381219515465750/687337760452902925/986918361768263710 + leave_form._view['modifiers']['date_from']['invisible'] = False + leave_form._view['modifiers']['date_to']['invisible'] = False leave_form.date_from = datetime(2019, 5, 6, 0, 0, 0) leave_form.date_to = datetime(2019, 5, 6, 23, 59, 59) leave = leave_form.save() diff --git a/addons/hr_holidays/tests/test_stress_days.py b/addons/hr_holidays/tests/test_stress_days.py index 4408d83d9b4..1b09f824d4a 100644 --- a/addons/hr_holidays/tests/test_stress_days.py +++ b/addons/hr_holidays/tests/test_stress_days.py @@ -111,9 +111,9 @@ class TestHrLeaveStressDays(TransactionCase): with self.assertRaises(ValidationError), Form(self.env['hr.leave'].with_user(self.employee_user.id).with_context(default_employee_id=self.employee_emp.id)) as leave_form: leave_form.holiday_status_id = self.leave_type - leave_form.date_from = datetime(2021, 11, 1) - leave_form.date_to = datetime(2021, 11, 1) + leave_form.request_date_from = datetime(2021, 11, 1) + leave_form.request_date_to = datetime(2021, 11, 1) self.assertFalse(leave_form.has_stress_day) - leave_form.date_to = datetime(2021, 11, 5) + leave_form.request_date_to = datetime(2021, 11, 5) self.assertTrue(leave_form.has_stress_day) diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/common.py b/addons/l10n_account_edi_ubl_cii_tests/tests/common.py index 311cf1819f1..bca15a54a45 100644 --- a/addons/l10n_account_edi_ubl_cii_tests/tests/common.py +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/common.py @@ -17,6 +17,9 @@ class TestUBLCommon(AccountEdiTestCommon): def setUpClass(cls, chart_template_ref=None, edi_format_ref=None): super().setUpClass(chart_template_ref=chart_template_ref, edi_format_ref=edi_format_ref) + # Required for `product_uom_id` to be visible in the form views + cls.env.user.groups_id += cls.env.ref('uom.group_uom') + # Ensure the testing currency is using a valid ISO code. real_usd = cls.env.ref('base.USD') real_usd.name = 'FUSD' diff --git a/addons/mass_mailing/tests/test_mailing_list.py b/addons/mass_mailing/tests/test_mailing_list.py index 38aaba6b1f1..6fa18139357 100644 --- a/addons/mass_mailing/tests/test_mailing_list.py +++ b/addons/mass_mailing/tests/test_mailing_list.py @@ -122,8 +122,10 @@ class TestMailingListMerge(MassMailCommon): active_model='mailing.list' )) merge_form.new_list_name = False - merge_form.dest_list_id = self.mailing_list_3 merge_form.merge_options = 'existing' + # Need to set `merge_options` before `dest_lid_id` so `dest_list_id` is visible + # `'invisible': [('merge_options', '=', 'new')]` + merge_form.dest_list_id = self.mailing_list_3 merge_form.archive_src_lists = False result_list = merge_form.save().action_mailing_lists_merge() diff --git a/addons/mrp/tests/common.py b/addons/mrp/tests/common.py index 3a837202bca..0237d71022c 100644 --- a/addons/mrp/tests/common.py +++ b/addons/mrp/tests/common.py @@ -66,7 +66,7 @@ class TestMrpCommon(common2.TestStockCommon): login='hilda', email='h.h@example.com', notification_type='inbox', - groups='mrp.group_mrp_user, stock.group_stock_user, mrp.group_mrp_byproducts', + groups='mrp.group_mrp_user, stock.group_stock_user, mrp.group_mrp_byproducts, uom.group_uom', ) cls.user_mrp_manager = mail_new_test_user( cls.env, @@ -74,8 +74,11 @@ class TestMrpCommon(common2.TestStockCommon): login='gary', email='g.g@example.com', notification_type='inbox', - groups='mrp.group_mrp_manager, stock.group_stock_user, mrp.group_mrp_byproducts', + groups='mrp.group_mrp_manager, stock.group_stock_user, mrp.group_mrp_byproducts, uom.group_uom', ) + # Required for `product_uom_id` to be visible in the view + # This class is used by a lot of tests which sets `product_uom_id` on `mrp.production` + cls.env.user.groups_id += cls.env.ref('uom.group_uom') cls.workcenter_1 = cls.env['mrp.workcenter'].create({ 'name': 'Nuclear Workcenter', diff --git a/addons/mrp/tests/test_backorder.py b/addons/mrp/tests/test_backorder.py index 6dd33195823..543aafdc67e 100644 --- a/addons/mrp/tests/test_backorder.py +++ b/addons/mrp/tests/test_backorder.py @@ -85,6 +85,8 @@ class TestMrpProductionBackorder(TestMrpCommon): should be MO/001-02. Check that all MO are reachable through the procurement group. """ + # Required for `manufacture_steps` to be visible in the view + self.env.user.groups_id += self.env.ref("stock.group_adv_location") with Form(self.warehouse) as warehouse: warehouse.manufacture_steps = 'pbm' @@ -128,6 +130,8 @@ class TestMrpProductionBackorder(TestMrpCommon): should be MO/001-02. Check that all MO are reachable through the procurement group. """ + # Required for `manufacture_steps` to be visible in the view + self.env.user.groups_id += self.env.ref("stock.group_adv_location") with Form(self.warehouse) as warehouse: warehouse.manufacture_steps = 'pbm_sam' production, _, product_to_build, product_to_use_1, product_to_use_2 = self.generate_mo(qty_base_1=4, qty_final=4, picking_type_id=self.warehouse.manu_type_id) diff --git a/addons/mrp/tests/test_bom.py b/addons/mrp/tests/test_bom.py index 5afb46081c5..2fac4c75609 100644 --- a/addons/mrp/tests/test_bom.py +++ b/addons/mrp/tests/test_bom.py @@ -496,6 +496,8 @@ class TestBoM(TestMrpCommon): 'name': 'Deserts Table' }) + # Required to display `operation_ids` in the form view + self.env.user.groups_id += self.env.ref("mrp.group_mrp_routings") with Form(bom_crumble) as bom: with bom.bom_line_ids.new() as line: line.product_id = butter @@ -660,6 +662,8 @@ class TestBoM(TestMrpCommon): 'name': 'Deserts Table' }) + # Required to display `operation_ids` in the form view + self.env.user.groups_id += self.env.ref("mrp.group_mrp_routings") with Form(bom_drawer) as bom: with bom.bom_line_ids.new() as line: line.product_id = screw diff --git a/addons/mrp/tests/test_multicompany.py b/addons/mrp/tests/test_multicompany.py index 83e4d3fe051..aeca55a0560 100644 --- a/addons/mrp/tests/test_multicompany.py +++ b/addons/mrp/tests/test_multicompany.py @@ -134,6 +134,11 @@ class TestMrpMulticompany(common.TransactionCase): }) mo_form = Form(self.env['mrp.production'].with_user(self.user_a)) mo_form.product_id = product + # The mo must be confirmed, no longer in draft, in order for `lot_producing_id` to be visible in the view + #
+ mo = mo_form.save() + mo.action_confirm() + mo_form = Form(mo) mo_form.lot_producing_id = lot_b mo = mo_form.save() with self.assertRaises(UserError): diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py index c71dc488935..56746fd6b1f 100644 --- a/addons/mrp/tests/test_order.py +++ b/addons/mrp/tests/test_order.py @@ -339,6 +339,8 @@ class TestMrpOrder(TestMrpCommon): def test_update_quantity_4(self): """ Workcenter 1 has 10' start time and 5' stop time """ + # Required for `workerorder_ids` to be visible in the view + self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings') bom = self.env['mrp.bom'].create({ 'product_id': self.product_6.id, 'product_tmpl_id': self.product_6.product_tmpl_id.id, @@ -517,6 +519,11 @@ class TestMrpOrder(TestMrpCommon): self.assertEqual(production.move_raw_ids.mapped('manual_consumption'), [False, True, True]) + # + production.action_confirm() + production.action_assign() + production.is_locked = False + # test no updating production_form = Form(production) production_form.qty_producing = 5 @@ -689,6 +696,11 @@ class TestMrpOrder(TestMrpCommon): self.stock_shelf_2 = self.stock_location_14 mo, _, p_final, p1, p2 = self.generate_mo(tracking_base_1='lot', qty_base_1=10, qty_final=1) + + # Required for `lot_producing_id` to be visible in the view + # + p_final.tracking = 'lot' + self.assertEqual(len(mo), 1, 'MO should have been created') first_lot_for_p1 = self.env['stock.lot'].create({ @@ -999,6 +1011,8 @@ class TestMrpOrder(TestMrpCommon): byproduct3 none 1.0 dozen Check qty producing update and moves finished values. """ + # Required for `byproduct_ids` to be visible in the view + self.env.user.groups_id += self.env.ref('mrp.group_mrp_byproducts') dozen = self.env.ref('uom.product_uom_dozen') self.byproduct1 = self.env['product.product'].create({ 'name': 'Byproduct 1', @@ -2368,6 +2382,8 @@ class TestMrpOrder(TestMrpCommon): Create a second one in 10 minutes (expected should NOT go from 15 to 12.5, it should go from 15 to 10) """ # First production, the default is 60 and there is 0 productions of that operation + # Required for `workorder_ids` to be visible in the view + self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings') production_form = Form(self.env['mrp.production']) production_form.bom_id = self.bom_4 production = production_form.save() @@ -2375,6 +2391,8 @@ class TestMrpOrder(TestMrpCommon): production.action_confirm() production.button_plan() # Production planned, time to start, I produce all the 1 product + # 'invisible': [('state', '=', 'draft')] + production_form = Form(production) production_form.qty_producing = 1 with production_form.workorder_ids.edit(0) as wo: wo.duration = 15 # in 15 minutes @@ -2390,6 +2408,8 @@ class TestMrpOrder(TestMrpCommon): production.action_confirm() production.button_plan() # Production planned, time to start, I produce all the 1 product + # 'invisible': [('state', '=', 'draft')] + production_form = Form(production) production_form.qty_producing = 1 with production_form.workorder_ids.edit(0) as wo: wo.duration = 10 # In 10 minutes this time @@ -2410,6 +2430,8 @@ class TestMrpOrder(TestMrpCommon): Test that when tracking the 2 last production, if we make one with under capacity, and one with normal capacity, the two are equivalent (1 done with capacity 2 in 10mn = 2 done with capacity 2 in 10mn) """ + # Required for `workorder_ids` to be visible in the view + self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings') production_form = Form(self.env['mrp.production']) production_form.bom_id = self.bom_5 production = production_form.save() @@ -2417,6 +2439,8 @@ class TestMrpOrder(TestMrpCommon): production.button_plan() # Production planned, time to start, I produce all the 1 product + # 'invisible': [('state', '=', 'draft')] + production_form = Form(production) production_form.qty_producing = 1 with production_form.workorder_ids.edit(0) as wo: wo.duration = 10 # in 10 minutes @@ -2434,6 +2458,8 @@ class TestMrpOrder(TestMrpCommon): production.action_confirm() production.button_plan() # Production planned, time to start, I produce all the 2 product + # 'invisible': [('state', '=', 'draft')] + production_form = Form(production) production_form.qty_producing = 2 with production_form.workorder_ids.edit(0) as wo: wo.duration = 10 # In 10 minutes this time @@ -2457,6 +2483,8 @@ class TestMrpOrder(TestMrpCommon): 5 -> 30mn ... """ + # Required for `workorder_ids` to be visible in the view + self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings') production_form = Form(self.env['mrp.production']) production_form.bom_id = self.bom_6 production = production_form.save() @@ -2464,6 +2492,8 @@ class TestMrpOrder(TestMrpCommon): production.button_plan() # Production planned, time to start, I produce all the 1 product + # 'invisible': [('state', '=', 'draft')] + production_form = Form(production) production_form.qty_producing = 1 with production_form.workorder_ids.edit(0) as wo: wo.duration = 10 # in 10 minutes @@ -2760,6 +2790,8 @@ class TestMrpOrder(TestMrpCommon): """ Check that the work order is started only once when clicking the start button several times. """ + # Required for `workorder_ids` to be visible in the view + self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings') production_form = Form(self.env['mrp.production']) production_form.bom_id = self.bom_2 production_form.product_qty = 1 @@ -2869,6 +2901,8 @@ class TestMrpOrder(TestMrpCommon): -> The user replans one of the WO: the warnings should disappear and the WO should be postponed. """ + # Required for `workorder_ids` to be visible in the view + self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings') mos = self.env['mrp.production'] for _ in range(2): mo_form = Form(self.env['mrp.production']) @@ -2899,6 +2933,8 @@ class TestMrpOrder(TestMrpCommon): -> The user replans one of the WO: the warnings should disappear and the WO should be postponed. """ + # Required for `workorder_ids` to be visible in the view + self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings') mos = self.env['mrp.production'] for _ in range(2): mo_form = Form(self.env['mrp.production']) diff --git a/addons/mrp/tests/test_procurement.py b/addons/mrp/tests/test_procurement.py index d2f4743f2b5..e37580d67d9 100644 --- a/addons/mrp/tests/test_procurement.py +++ b/addons/mrp/tests/test_procurement.py @@ -681,6 +681,8 @@ class TestProcurement(TestMrpCommon): This test ensures that, when running the scheduler, the generated MOs are based on the correct BoMs """ + # Required for `picking_type_id` to be visible in the view + self.env.user.groups_id += self.env.ref('stock.group_adv_location') warehouse = self.env.ref('stock.warehouse0') stock_location01 = warehouse.lot_stock_id diff --git a/addons/mrp/tests/test_warehouse_multistep_manufacturing.py b/addons/mrp/tests/test_warehouse_multistep_manufacturing.py index 9bce94adc2f..952510439da 100644 --- a/addons/mrp/tests/test_warehouse_multistep_manufacturing.py +++ b/addons/mrp/tests/test_warehouse_multistep_manufacturing.py @@ -11,6 +11,10 @@ class TestMultistepManufacturingWarehouse(TestMrpCommon): @classmethod def setUpClass(cls): super().setUpClass() + # Required for `uom_id` to be visible in the view + cls.env.user.groups_id += cls.env.ref('uom.group_uom') + # Required for `manufacture_steps` to be visible in the view + cls.env.user.groups_id += cls.env.ref('stock.group_adv_location') # Create warehouse cls.customer_location = cls.env['ir.model.data']._xmlid_to_res_id('stock.stock_location_customers') warehouse_form = Form(cls.env['stock.warehouse']) diff --git a/addons/mrp_account/tests/test_analytic_account.py b/addons/mrp_account/tests/test_analytic_account.py index 6f4c25ae654..becba02cb9d 100644 --- a/addons/mrp_account/tests/test_analytic_account.py +++ b/addons/mrp_account/tests/test_analytic_account.py @@ -107,6 +107,8 @@ class TestAnalyticAccount(TransactionCase): """Test when workcenter and MO are using the same analytic account, no duplicated lines will be post. """ + # Required for `workorder_ids` to be visible in the view + self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings') # set wc analytic account to be the same of the one on the bom self.workcenter.costs_hour_account_id = self.analytic_account @@ -147,6 +149,8 @@ class TestAnalyticAccount(TransactionCase): """Test when workcenter and MO are using the same analytic account, no duplicated lines will be post. """ + # Required for `workorder_ids` to be visible in the view + self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings') # set wc analytic account to be different from the one on the bom wc_analytic_account = self.env['account.analytic.account'].create({'name': 'wc_analytic_account'}) self.workcenter.costs_hour_account_id = wc_analytic_account diff --git a/addons/mrp_account/tests/test_bom_price.py b/addons/mrp_account/tests/test_bom_price.py index 355b39496ce..dff1f023b75 100644 --- a/addons/mrp_account/tests/test_bom_price.py +++ b/addons/mrp_account/tests/test_bom_price.py @@ -19,6 +19,8 @@ class TestBomPrice(common.TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() + # Required for `product_uom_id ` to be visible in the view + cls.env.user.groups_id += cls.env.ref('uom.group_uom') cls.Product = cls.env['product.product'] cls.Bom = cls.env['mrp.bom'] diff --git a/addons/mrp_landed_costs/tests/test_stock_landed_costs_mrp.py b/addons/mrp_landed_costs/tests/test_stock_landed_costs_mrp.py index 62c3fcf0ef1..e2b75cd779c 100644 --- a/addons/mrp_landed_costs/tests/test_stock_landed_costs_mrp.py +++ b/addons/mrp_landed_costs/tests/test_stock_landed_costs_mrp.py @@ -160,6 +160,9 @@ class TestStockLandedCostsMrp(ValuationReconciliationTestCommon): man_order = man_order_form.save() man_order.action_confirm() # produce product + # To edit `qty_producing`, the mo must no be draft. It's not thanks to the above `action_confirm()` + # but the values of the form do not update automatically, it must be reloaded. + man_order_form = Form(man_order) man_order_form.qty_producing = 1 man_order_form.save() man_order.button_mark_done() diff --git a/addons/mrp_subcontracting/tests/test_subcontracting.py b/addons/mrp_subcontracting/tests/test_subcontracting.py index f4d43680e02..99b369fde7a 100644 --- a/addons/mrp_subcontracting/tests/test_subcontracting.py +++ b/addons/mrp_subcontracting/tests/test_subcontracting.py @@ -237,6 +237,8 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon): picking. Checks that the delivery and MO for its components are automatically created. """ + # Required for `location_id` to be visible in the view + self.env.user.groups_id += self.env.ref('stock.group_stock_multi_locations') # Tick "manufacture" and MTO on self.comp2 mto_route = self.env.ref('stock.route_warehouse0_mto') mto_route.active = True diff --git a/addons/product/tests/test_variants.py b/addons/product/tests/test_variants.py index 4176aea3487..fe6272c04f3 100644 --- a/addons/product/tests/test_variants.py +++ b/addons/product/tests/test_variants.py @@ -1008,6 +1008,8 @@ class TestVariantsArchive(common.TestProductCommon): def test_uom_update_variant(self): """ Changing the uom on the template do not behave the same as changing on the product product.""" + # Required for `uom_id` to be visible in the view + self.env.user.groups_id += self.env.ref("uom.group_uom") units = self.env.ref('uom.product_uom_unit') cm = self.env.ref('uom.product_uom_cm') template = self.env['product.template'].create({ diff --git a/addons/project/tests/test_multicompany.py b/addons/project/tests/test_multicompany.py index 85703c442e7..0236d01e79e 100644 --- a/addons/project/tests/test_multicompany.py +++ b/addons/project/tests/test_multicompany.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from contextlib import contextmanager +from lxml import etree from odoo.tests.common import TransactionCase, Form from odoo.exceptions import AccessError, UserError @@ -257,10 +258,24 @@ class TestMultiCompanyProject(TestMultiCompanyCommon): # set parent on existing orphan task; the onchange will set the correct company and subtask project self.task_2.write({'project_id': False}) - with Form(self.task_2) as task_form: - task_form.name = 'Test Task 2 becomes child of Task 1 (other company)' - task_form.parent_id = self.task_1 - task = task_form.save() + # For `parent_id` to be visible in the view, you need + # 1. The debug mode + # 2. `allow_subtasks` to be true + # + # `allow_subtasks` is a related to `allow_subtasks` on the project + # as the point of the test is to test the behavior of the task `_compute_project_id` when there is no project, + # `allow_subtasks` is by default invisible, and you shouldn't therefore be able to change it. + # So, to make it visible, temporary modify the view to make it visible even when `allow_subtasks` is `False`. + view = self.env.ref('project.view_task_form2').sudo() + tree = etree.fromstring(view.arch) + for node in tree.xpath('//field[@name="parent_id"][@attrs]'): + node.attrib.pop('attrs') + view.arch = etree.tostring(tree) + with self.debug_mode(): + with Form(self.task_2) as task_form: + task_form.name = 'Test Task 2 becomes child of Task 1 (other company)' + task_form.parent_id = self.task_1 + task = task_form.save() self.assertEqual(task.company_id, task.project_id.company_id, "The company of the orphan subtask should be the one from its project.") @@ -268,13 +283,28 @@ class TestMultiCompanyProject(TestMultiCompanyCommon): # set up default subtask project self.project_company_a.write({'allow_subtasks': True}) + # For `parent_id` to be visible in the view, you need + # 1. The debug mode + # 2. `allow_subtasks` to be true + # + # `allow_subtasks` is a related to `allow_subtasks` on the project + # as the point of the test is to test the behavior of the task `_compute_project_id` when there is no project, + # `allow_subtasks` is by default invisible, and you shouldn't therefore be able to change it. + # So, to make it visible, temporary modify the view to make it visible even when `allow_subtasks` is `False`. + view = self.env.ref('project.view_task_form2').sudo() + tree = etree.fromstring(view.arch) + for node in tree.xpath('//field[@name="parent_id"][@attrs]'): + node.attrib.pop('attrs') + view.arch = etree.tostring(tree) + with self.sudo('employee-a'): with self.allow_companies([self.company_a.id, self.company_b.id]): - with Form(self.env['project.task'].with_context({'tracking_disable': True})) as task_form: - task_form.name = 'Test Subtask in company B' - task_form.parent_id = self.task_1 + with self.debug_mode(): + with Form(self.env['project.task'].with_context({'tracking_disable': True})) as task_form: + task_form.name = 'Test Subtask in company B' + task_form.parent_id = self.task_1 - task = task_form.save() + task = task_form.save() self.assertEqual(task.project_id, self.task_1.project_id, "The default project of a subtask should be the default subtask project of the project from the mother task") self.assertEqual(task.company_id, task.project_id.company_id, "The company of the orphan subtask should be the one from its project.") diff --git a/addons/project/tests/test_project_recurrence.py b/addons/project/tests/test_project_recurrence.py index f68aa81a5c1..7f3d68e86ae 100644 --- a/addons/project/tests/test_project_recurrence.py +++ b/addons/project/tests/test_project_recurrence.py @@ -465,6 +465,9 @@ class TestProjectrecurrence(TransactionCase): tasks = self.env['project.task'].search(domain) return tasks, len(tasks), len(tasks.filtered('parent_id')) + # Required for `child_ids` to be visible in the view + # {'invisible': [('allow_subtasks', '=', False)]} + self.project_recurring.allow_subtasks = True parent_task = self.env['project.task'].create({ 'name': 'Parent Task', 'project_id': self.project_recurring.id @@ -558,6 +561,9 @@ class TestProjectrecurrence(TransactionCase): return len(tasks), len(tasks.filtered('parent_id')) # Phase 0 : Initialize test case + # Required for `child_ids` to be visible in the view + # {'invisible': [('allow_subtasks', '=', False)]} + self.project_recurring.allow_subtasks = True parent_task = self.env['project.task'].create({ 'name': 'Parent Task', 'project_id': self.project_recurring.id diff --git a/addons/project/tests/test_project_sharing.py b/addons/project/tests/test_project_sharing.py index a88756641db..7829069233f 100644 --- a/addons/project/tests/test_project_sharing.py +++ b/addons/project/tests/test_project_sharing.py @@ -144,6 +144,10 @@ class TestProjectSharing(TestProjectSharingCommon): 3.2) Create a sub-task 3.3) Create a second sub-task """ + # 0) Allow to create subtasks in the project tasks + # Required for `child_ids` to be visible in the view + # {'invisible': [('allow_subtasks', '=', False)]} + self.project_cows.allow_subtasks = True # 1) Give the 'read' access mode to a portal user in a project and try to create task with this user. with self.assertRaises(AccessError, msg="Should not accept the portal user create a task in the project when he has not the edit access right."): with self.get_project_sharing_form_view(self.task_cow.with_context({'tracking_disable': True, 'default_project_id': self.project_cows.id}), self.user_portal) as form: diff --git a/addons/project/tests/test_project_subtasks.py b/addons/project/tests/test_project_subtasks.py index 5e1bf1aeaa3..66f134417fa 100644 --- a/addons/project/tests/test_project_subtasks.py +++ b/addons/project/tests/test_project_subtasks.py @@ -72,8 +72,10 @@ class TestProjectSubtasks(TestProjectCommon): """ form_views = self.env['ir.ui.view'].search([('model', '=', 'project.task'), ('type', '=', 'form')]) for form_view in form_views: - with Form(self.env['project.task'].with_context({'tracking_disable': True, 'default_project_id': self.project_pigs.id}), view=form_view) as task_form: - task_form.name = 'Test Task 1' + task_form = Form(self.env['project.task'].with_context({'tracking_disable': True, 'default_project_id': self.project_pigs.id, 'default_name': 'Test Task 1'}), view=form_view) + # Some views have the `name` field invisible + # As the goal is simply to test the default project field and not the name, we can skip setting the name + # in the view and set it using `default_name` instead task = task_form.save() self.assertEqual(task.project_id, self.project_pigs, "The project should be assigned from the default project, form_view name : %s." % form_view.name) @@ -148,27 +150,29 @@ class TestProjectSubtasks(TestProjectCommon): self.assertEqual(self.task_1.child_ids.display_project_id, self.project_goats, "Display Project of the task should be well assigned") self.assertEqual(self.task_1.child_ids.project_id, self.project_goats, "Changing display project id on a subtask should change project id") - # 6) - with Form(self.task_1.child_ids.with_context({'tracking_disable': True})) as subtask_form: - subtask_form.parent_id = self.env['project.task'] - orphan_subtask = subtask_form.save() + # Debug mode required for `parent_id` to be visible in the view + with self.debug_mode(): + # 6) + with Form(self.task_1.child_ids.with_context({'tracking_disable': True})) as subtask_form: + subtask_form.parent_id = self.env['project.task'] + orphan_subtask = subtask_form.save() - self.assertEqual(orphan_subtask.display_project_id, self.project_goats, "Display Project of the task should be well assigned") - self.assertEqual(orphan_subtask.project_id, self.project_goats, "Changing display project id on a subtask should change project id") - self.assertFalse(orphan_subtask.parent_id, "Parent should be false") + self.assertEqual(orphan_subtask.display_project_id, self.project_goats, "Display Project of the task should be well assigned") + self.assertEqual(orphan_subtask.project_id, self.project_goats, "Changing display project id on a subtask should change project id") + self.assertFalse(orphan_subtask.parent_id, "Parent should be false") - # 7) - with Form(self.task_1.with_context({'tracking_disable': True})) as task_form: - with task_form.child_ids.new() as subtask_form: - subtask_form.name = 'Test Subtask 1' - subtask_form.display_project_id = self.project_goats - with Form(self.task_1.child_ids.with_context({'tracking_disable': True})) as subtask_form: - subtask_form.display_project_id = self.env['project.project'] - subtask_form.parent_id = self.env['project.task'] - orphan_subtask = subtask_form.save() + # 7) + with Form(self.task_1.with_context({'tracking_disable': True})) as task_form: + with task_form.child_ids.new() as subtask_form: + subtask_form.name = 'Test Subtask 1' + subtask_form.display_project_id = self.project_goats + with Form(self.task_1.child_ids.with_context({'tracking_disable': True})) as subtask_form: + subtask_form.display_project_id = self.env['project.project'] + subtask_form.parent_id = self.env['project.task'] + orphan_subtask = subtask_form.save() - self.assertEqual(orphan_subtask.project_id, self.project_pigs, "Removing parent should not change project") - self.assertEqual(orphan_subtask.display_project_id, self.project_pigs, "Removing parent should make the display project set as project.") + self.assertEqual(orphan_subtask.project_id, self.project_pigs, "Removing parent should not change project") + self.assertEqual(orphan_subtask.display_project_id, self.project_pigs, "Removing parent should make the display project set as project.") def test_subtask_stage(self): """ diff --git a/addons/purchase/tests/test_purchase.py b/addons/purchase/tests/test_purchase.py index 3df561ddd1d..c585a61e0c4 100644 --- a/addons/purchase/tests/test_purchase.py +++ b/addons/purchase/tests/test_purchase.py @@ -179,6 +179,8 @@ class TestPurchase(AccountTestInvoicingCommon): according to the product_qty. Also check product_qty or product_packaging are correctly calculated when one of them changed. """ + # Required for `product_packaging_qty` to be visible in the view + self.env.user.groups_id += self.env.ref('product.group_stock_packaging') packaging_single = self.env['product.packaging'].create({ 'name': "I'm a packaging", 'product_id': self.product_a.id, diff --git a/addons/purchase/tests/test_purchase_invoice.py b/addons/purchase/tests/test_purchase_invoice.py index 630397b7d7a..4c91da3ddb9 100644 --- a/addons/purchase/tests/test_purchase_invoice.py +++ b/addons/purchase/tests/test_purchase_invoice.py @@ -304,6 +304,8 @@ class TestPurchaseToInvoice(AccountTestInvoicingCommon): """ Tests whether, when an analytic account rule is set, and user changes manually the analytic account on the po, it is the same that is mentioned in the bill. """ + # Required for `analytic.group_analytic_accounting` to be visible in the view + self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting') analytic_account_default = self.env['account.analytic.account'].create({'name': 'default'}) analytic_account_manual = self.env['account.analytic.account'].create({'name': 'manual'}) diff --git a/addons/purchase/tests/test_purchase_order_report.py b/addons/purchase/tests/test_purchase_order_report.py index 0df4857a6c4..53e0ce954f3 100644 --- a/addons/purchase/tests/test_purchase_order_report.py +++ b/addons/purchase/tests/test_purchase_order_report.py @@ -41,7 +41,39 @@ class TestPurchaseOrderReport(AccountTestInvoicingCommon): f = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) f.invoice_date = f.date f.partner_id = po.partner_id - f.purchase_id = po + # + # + #