diff --git a/addons/onboarding/__manifest__.py b/addons/onboarding/__manifest__.py
index 759d6af3e0f..6ec2a67c99a 100644
--- a/addons/onboarding/__manifest__.py
+++ b/addons/onboarding/__manifest__.py
@@ -3,7 +3,7 @@
{
'name': 'Onboarding Toolbox',
- 'version': '1.1',
+ 'version': '1.2',
'category': 'Hidden',
'sequence': 9001,
'description': """
@@ -15,8 +15,14 @@ This module allows to manage onboardings and their progress
'data': [
'data/onboarding_data.xml',
'views/onboarding_views.xml',
+ 'views/onboarding_templates.xml',
'views/onboarding_menus.xml',
'security/ir.model.access.csv',
],
+ 'assets': {
+ 'web.assets_backend': [
+ 'onboarding/static/src/**/*',
+ ],
+ },
'license': 'LGPL-3',
}
diff --git a/addons/onboarding/models/__init__.py b/addons/onboarding/models/__init__.py
index 8fd1fc0e1d6..0e27595dad9 100644
--- a/addons/onboarding/models/__init__.py
+++ b/addons/onboarding/models/__init__.py
@@ -2,6 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import onboarding_onboarding
-from . import onboarding_step
+from . import onboarding_onboarding_step
from . import onboarding_progress
from . import onboarding_progress_step
diff --git a/addons/onboarding/models/onboarding_onboarding.py b/addons/onboarding/models/onboarding_onboarding.py
index 8eef10a1ac6..7acba62d45e 100644
--- a/addons/onboarding/models/onboarding_onboarding.py
+++ b/addons/onboarding/models/onboarding_onboarding.py
@@ -13,10 +13,11 @@ class Onboarding(models.Model):
name = fields.Char('Name of the onboarding', translate=True)
# One word identifier used to define the onboarding panel's route: `/onboarding/{route_name}`.
route_name = fields.Char('One word name', required=True)
- step_ids = fields.One2many('onboarding.onboarding.step', 'onboarding_id', 'Onboarding steps')
-
- is_per_company = fields.Boolean('Should be done per company?', default=True)
+ step_ids = fields.Many2many('onboarding.onboarding.step', string='Onboarding steps')
+ is_per_company = fields.Boolean(
+ 'Should be done per company?', compute='_compute_is_per_company', readonly=True, store=False,
+ )
panel_close_action_name = fields.Char(
'Closing action', help='Name of the onboarding model action to execute when closing the panel.')
@@ -36,8 +37,19 @@ class Onboarding(models.Model):
('route_name_uniq', 'UNIQUE (route_name)', 'Onboarding alias must be unique.'),
]
+ @api.depends('progress_ids', 'progress_ids.company_id', 'step_ids', 'step_ids.is_per_company')
+ def _compute_is_per_company(self):
+ # Once an onboarding is made "per-company", there is no drawback to simply still consider
+ # it per-company even when if its last per-company step is unlinked. This allows to avoid
+ # handling the merging of existing progress (step) records.
+
+ onboardings_with_per_company_steps_or_progress = self.filtered(
+ lambda o: o.progress_ids.company_id or (True in o.step_ids.mapped('is_per_company')))
+ onboardings_with_per_company_steps_or_progress.is_per_company = True
+ (self - onboardings_with_per_company_steps_or_progress).is_per_company = False
+
@api.depends_context('company')
- @api.depends('progress_ids', 'progress_ids.is_onboarding_closed', 'progress_ids.onboarding_state')
+ @api.depends('progress_ids', 'progress_ids.is_onboarding_closed', 'progress_ids.onboarding_state', 'progress_ids.company_id')
def _compute_current_progress(self):
for onboarding in self:
current_progress_id = onboarding.progress_ids.filtered(
@@ -51,28 +63,44 @@ class Onboarding(models.Model):
onboarding.current_progress_id = False
onboarding.is_onboarding_closed = False
+ def write(self, vals):
+ """Recompute progress step ids if new steps are added/removed."""
+ already_linked_steps = self.step_ids
+ res = super().write(vals)
+ if self.step_ids != already_linked_steps:
+ self.progress_ids._recompute_progress_step_ids()
+ return res
+
def action_close(self):
"""Close the onboarding panel."""
self.current_progress_id.action_close()
+ @api.model
+ def action_close_panel(self, xmlid):
+ """Close the onboarding panel identified by its `xmlid`.
+
+ If not found, quietly do nothing.
+ """
+ if onboarding := self.env.ref(xmlid, raise_if_not_found=False):
+ onboarding.action_close()
+
+ def action_refresh_progress_ids(self):
+ """Re-initialize onboarding progress records (after step is_per_company change).
+
+ Meant to be called when `is_per_company` of linked steps is modified (or per-company
+ steps are added to an onboarding).
+ """
+ onboardings_to_refresh_progress = self.filtered(
+ lambda o: o.is_per_company and o.progress_ids and not o.progress_ids.company_id
+ )
+ onboardings_to_refresh_progress.progress_ids.unlink()
+ onboardings_to_refresh_progress._create_progress()
+
def action_toggle_visibility(self):
self.current_progress_id.action_toggle_visibility()
- def write(self, values):
- if 'is_per_company' in values:
- onboardings_per_company_update = self.filtered(
- lambda onboarding: onboarding.is_per_company != values['is_per_company'])
-
- res = super().write(values)
-
- if 'is_per_company' in values:
- # When changing this parameter, all progress (onboarding and steps) is reset.
- onboardings_per_company_update.progress_ids.unlink()
- return res
-
def _search_or_create_progress(self):
- """Create Progress record(s) as necessary for the context.
- """
+ """Create Progress record(s) as necessary for the context."""
onboardings_without_progress = self.filtered(lambda onboarding: not onboarding.current_progress_id)
onboardings_without_progress._create_progress()
return self.current_progress_id
@@ -81,8 +109,12 @@ class Onboarding(models.Model):
return self.env['onboarding.progress'].create([
{
'company_id': self.env.company.id if onboarding.is_per_company else False,
- 'onboarding_id': onboarding.id
- } for onboarding in self
+ 'onboarding_id': onboarding.id,
+ 'progress_step_ids': onboarding.step_ids.progress_ids.filtered(
+ lambda p: p.company_id.id in [False, self.env.company.id]
+ ),
+ }
+ for onboarding in self
])
def _prepare_rendering_values(self):
diff --git a/addons/onboarding/models/onboarding_step.py b/addons/onboarding/models/onboarding_onboarding_step.py
similarity index 60%
rename from addons/onboarding/models/onboarding_step.py
rename to addons/onboarding/models/onboarding_onboarding_step.py
index 7f2d83c26e4..52b5eb56770 100644
--- a/addons/onboarding/models/onboarding_step.py
+++ b/addons/onboarding/models/onboarding_onboarding_step.py
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import _, api, fields, models
+from odoo import _, api, Command, fields, models
from odoo.addons.onboarding.models.onboarding_progress import ONBOARDING_PROGRESS_STATES
+from odoo.exceptions import ValidationError
class OnboardingStep(models.Model):
@@ -11,8 +12,7 @@ class OnboardingStep(models.Model):
_order = 'sequence asc, id asc'
_rec_name = 'title'
- onboarding_id = fields.Many2one(
- 'onboarding.onboarding', string='Onboarding', readonly=True, required=True, ondelete='cascade')
+ onboarding_ids = fields.Many2many('onboarding.onboarding', string='Onboardings')
title = fields.Char('Title', translate=True)
description = fields.Char('Description', translate=True)
@@ -21,14 +21,14 @@ class OnboardingStep(models.Model):
help="Text on the panel's button to start this step")
done_icon = fields.Char('Font Awesome Icon when completed', default='fa-star')
done_text = fields.Char(
- 'Text to show when step is completed', default=_('Step Completed! - Click to review'), translate=True)
+ 'Text to show when step is completed', default=_('Step Completed!'), translate=True)
step_image = fields.Binary("Step Image")
step_image_filename = fields.Char("Step Image Filename")
step_image_alt = fields.Char(
'Alt Text for the Step Image', default='Onboarding Step Image', translate=True,
help='Show when impossible to load the image')
panel_step_open_action_name = fields.Char(
- string='Opening action', required=True,
+ string='Opening action', required=False,
help='Name of the onboarding step model action to execute when opening the step, '
'e.g. action_open_onboarding_1_step_1')
@@ -41,14 +41,16 @@ class OnboardingStep(models.Model):
'onboarding.progress.step', 'step_id', string='Onboarding Progress Step Records', readonly=True,
help='All related Onboarding Progress Step Records (across companies)')
+ is_per_company = fields.Boolean('Is per company', default=True)
sequence = fields.Integer(default=10)
@api.depends_context('company')
@api.depends('progress_ids', 'progress_ids.step_state')
def _compute_current_progress(self):
+ # When `is_per_company` is changed, `progress_ids` is updated (see `write`) which triggers this `_compute`.
existing_progress_steps = self.progress_ids.filtered_domain([
('step_id', 'in', self.ids),
- ('progress_id.company_id', 'in', [False, self.env.company.id]),
+ ('company_id', 'in', [False, self.env.company.id]),
])
for step in self:
if step in existing_progress_steps.step_id:
@@ -60,6 +62,35 @@ class OnboardingStep(models.Model):
step.current_progress_step_id = False
step.current_step_state = 'not_done'
+ @api.constrains('onboarding_ids')
+ def check_step_on_onboarding_has_action(self):
+ if steps_without_action := self.filtered(lambda step: step.onboarding_ids and not step.panel_step_open_action_name):
+ raise ValidationError(_(
+ 'An "Opening Action" is required for the following steps to be '
+ 'linked to an onboarding panel: %(step_titles)s',
+ step_titles=steps_without_action.mapped('title'),
+ ))
+
+ def write(self, vals):
+ new_is_per_company = vals.get('is_per_company')
+ steps_changing_is_per_company = (
+ self.browse() if new_is_per_company is None
+ else self.filtered(lambda step: step.is_per_company != new_is_per_company)
+ )
+ already_linked_onboardings = self.onboarding_ids
+
+ res = super().write(vals)
+
+ # Progress is reset (to be done per-company or, for steps, to have a single record)
+ if steps_changing_is_per_company:
+ steps_changing_is_per_company.progress_ids.unlink()
+ self.onboarding_ids.action_refresh_progress_ids()
+
+ if self.onboarding_ids - already_linked_onboardings:
+ self.onboarding_ids.progress_ids._recompute_progress_step_ids()
+
+ return res
+
def action_set_just_done(self):
# Make sure progress records exist for the current context (company)
steps_without_progress = self.filtered(lambda step: not step.current_progress_step_id)
@@ -80,21 +111,23 @@ class OnboardingStep(models.Model):
return super()._get_placeholder_filename(field)
def _create_progress_steps(self):
+ """Create progress step records as necessary to validate steps.
+
+ Only considers existing `onboarding.progress` records for the current
+ company or without company (depending on `is_per_company`).
+ """
onboarding_progress_records = self.env['onboarding.progress'].search([
- ('onboarding_id', 'in', self.onboarding_id.ids),
+ ('onboarding_id', 'in', self.onboarding_ids.ids),
('company_id', 'in', [False, self.env.company.id])
])
- progress_step_values = []
- for onboarding_progress_record in onboarding_progress_records:
- progress_step_values += [
- {
- 'onboarding_id': onboarding_progress_record.onboarding_id.id,
- 'progress_id': onboarding_progress_record.id,
- 'step_id': step_id.id,
- }
- for step_id
- in self.filtered(lambda step: self.onboarding_id == onboarding_progress_record.onboarding_id)
- if step_id not in onboarding_progress_record.progress_step_ids.step_id
- ]
-
+ progress_step_values = [
+ {
+ 'step_id': step_id.id,
+ 'progress_ids': [
+ Command.link(onboarding_progress_record.id)
+ for onboarding_progress_record
+ in onboarding_progress_records.filtered(lambda p: step_id in p.onboarding_id.step_ids)],
+ 'company_id': self.env.company.id if step_id.is_per_company else False,
+ } for step_id in self
+ ]
return self.env['onboarding.progress.step'].create(progress_step_values)
diff --git a/addons/onboarding/models/onboarding_progress.py b/addons/onboarding/models/onboarding_progress.py
index 11539d2d143..3bb15560b7e 100644
--- a/addons/onboarding/models/onboarding_progress.py
+++ b/addons/onboarding/models/onboarding_progress.py
@@ -22,9 +22,10 @@ class OnboardingProgress(models.Model):
company_id = fields.Many2one('res.company')
onboarding_id = fields.Many2one(
'onboarding.onboarding', 'Related onboarding tracked', required=True, ondelete='cascade')
- progress_step_ids = fields.One2many('onboarding.progress.step', 'progress_id', 'Progress Steps Trackers')
+ progress_step_ids = fields.Many2many('onboarding.progress.step', string='Progress Steps Trackers')
def init(self):
+ """Make sure there aren't multiple records for the same onboarding and company."""
# not in _sql_constraint because COALESCE is not supported for PostgreSQL constraint
self.env.cr.execute("""
CREATE UNIQUE INDEX IF NOT EXISTS onboarding_progress_onboarding_company_uniq
@@ -33,15 +34,19 @@ class OnboardingProgress(models.Model):
@api.depends('onboarding_id.step_ids', 'progress_step_ids', 'progress_step_ids.step_state')
def _compute_onboarding_state(self):
- progress_steps_data = self.env['onboarding.progress.step']._read_group(
- [('progress_id', 'in', self.ids), ('step_state', 'in', ['just_done', 'done'])],
- ['progress_id'], ['__count']
- )
- result = {progress.id: count for progress, count in progress_steps_data}
for progress in self:
progress.onboarding_state = (
- 'not_done' if result.get(progress.id, 0) != len(progress.onboarding_id.step_ids)
- else 'done')
+ 'not_done' if (
+ len(progress.progress_step_ids.filtered(lambda p: p.step_state in {'just_done', 'done'}))
+ != len(progress.onboarding_id.step_ids)
+ )
+ else 'done'
+ )
+
+ def _recompute_progress_step_ids(self):
+ """Update progress steps when a step (with existing progress) is added to an onboarding."""
+ for progress in self:
+ progress.progress_step_ids = progress.onboarding_id.step_ids.current_progress_step_id
def action_close(self):
self.is_onboarding_closed = True
@@ -51,9 +56,11 @@ class OnboardingProgress(models.Model):
progress.is_onboarding_closed = not progress.is_onboarding_closed
def _get_and_update_onboarding_state(self):
- """Used to fetch the progress of an onboarding for rendering its panel and is expected to
- be called by the onboarding controller. It also has the responsibility of updating the
- 'just_done' states into 'done' so that the 'just_done' states are only rendered once.
+ """Fetch the progress of an onboarding for rendering its panel.
+
+ This method is expected to only be called by the onboarding controller.
+ It also has the responsibility of updating the 'just_done' state into
+ 'done' so that the 'just_done' states are only rendered once.
"""
self.ensure_one()
onboarding_states_values = {}
diff --git a/addons/onboarding/models/onboarding_progress_step.py b/addons/onboarding/models/onboarding_progress_step.py
index 04c3538ff44..87e5e5b30f2 100644
--- a/addons/onboarding/models/onboarding_progress_step.py
+++ b/addons/onboarding/models/onboarding_progress_step.py
@@ -10,18 +10,21 @@ class OnboardingProgressStep(models.Model):
_description = 'Onboarding Progress Step Tracker'
_rec_name = 'step_id'
- progress_id = fields.Many2one(
- 'onboarding.progress', 'Related Onboarding Progress Tracker', required=True, ondelete='cascade')
+ progress_ids = fields.Many2many('onboarding.progress', string='Related Onboarding Progress Tracker')
step_state = fields.Selection(
ONBOARDING_PROGRESS_STATES, string='Onboarding Step Progress', default='not_done')
- onboarding_id = fields.Many2one(related='progress_id.onboarding_id', string='Onboarding')
step_id = fields.Many2one(
'onboarding.onboarding.step', string='Onboarding Step', required=True, ondelete='cascade')
- _sql_constraints = [
- ('progress_step_uniq', 'unique (progress_id, step_id)',
- 'There cannot be multiple records of the same onboarding step completion for the same Progress record.'),
- ]
+ company_id = fields.Many2one('res.company', ondelete='cascade')
+
+ def init(self):
+ """Make sure there aren't multiple records for the same onboarding step and company."""
+ # not in _sql_constraint because COALESCE is not supported for PostgreSQL constraint
+ self.env.cr.execute("""
+ CREATE UNIQUE INDEX IF NOT EXISTS onboarding_progress_step_company_uniq
+ ON onboarding_progress_step (step_id, COALESCE(company_id, 0))
+ """)
def action_consolidate_just_done(self):
was_just_done = self.filtered(lambda progress: progress.step_state == 'just_done')
diff --git a/addons/onboarding/static/src/views/form/onboarding_step_form_controller.js b/addons/onboarding/static/src/views/form/onboarding_step_form_controller.js
new file mode 100644
index 00000000000..5d276e6b915
--- /dev/null
+++ b/addons/onboarding/static/src/views/form/onboarding_step_form_controller.js
@@ -0,0 +1,49 @@
+/** @odoo-module **/
+
+import { FormController } from "@web/views/form/form_controller";
+import { useService } from "@web/core/utils/hooks";
+
+/**
+ * Controller to use for an onboarding step dialog, not the
+ * onboarding.onboarding.step form view itself.
+ */
+export default class OnboardingStepFormController extends FormController {
+ setup() {
+ super.setup();
+ this.action = useService('action');
+ this.orm = useService('orm');
+ }
+ /**
+ * If necessary, mark the step as done and reload the main view.
+ * @override
+ */
+ async onRecordSaved(record) {
+ const { reloadOnFirstValidation, reloadAlways } = this.stepConfig;
+ const validationResponse = await this.orm.call(
+ 'onboarding.onboarding.step',
+ 'action_validate_step',
+ [this.stepName],
+ );
+ if (reloadAlways || (reloadOnFirstValidation && validationResponse === "JUST_DONE")) {
+ this.action.restore(this.action.currentController.jsId);
+ }
+ }
+ /**
+ * Returns the name of the onboarding step to validate after the dialog
+ * record is saved
+ *
+ * @return {string}
+ */
+ get stepName() {
+ return ''
+ }
+ /**
+ * Returns whether to reload the page (useful if the current
+ * view needs to be updated).
+ *
+ * @returns {{reloadAlways: boolean, reloadOnFirstValidation: boolean}}
+ */
+ get stepConfig() {
+ return { reloadAlways: false, reloadOnFirstValidation: false };
+ }
+}
diff --git a/addons/onboarding/tests/common.py b/addons/onboarding/tests/common.py
index 9f4dba3f271..50a08c2a81a 100644
--- a/addons/onboarding/tests/common.py
+++ b/addons/onboarding/tests/common.py
@@ -18,12 +18,12 @@ class TestOnboardingCommon(TransactionCase):
})
cls.user_admin.company_ids |= cls.company_2
- cls.onboarding_1 = cls.env['onboarding.onboarding'].create([
+ cls.onboarding_1, cls.onboarding_2 = cls.env['onboarding.onboarding'].create([
{
- 'name': 'Test Onboarding 1',
+ 'name': f'Test Onboarding {onboarding_id}',
+ 'route_name': f'onboarding{onboarding_id}',
'is_per_company': False,
- 'route_name': 'onboarding1'
- }
+ } for onboarding_id in range(2)
])
# create a fake action for step opening
@@ -36,14 +36,22 @@ class TestOnboardingCommon(TransactionCase):
cls.onboarding_1_step_1, cls.onboarding_1_step_2 = cls.env['onboarding.onboarding.step'].create([
{
'title': f'Test Onboarding 1 - Step {step_n}',
- 'onboarding_id': cls.onboarding_1.id,
+ 'onboarding_ids': [cls.onboarding_1.id],
+ 'is_per_company': False,
'panel_step_open_action_name': 'action_fake_open_onboarding_step',
}
for step_n in range(1, 3)
])
-
+ # Add one of these in onboarding_2, and an "original" one
+ cls.onboarding_2.step_ids = [cls.onboarding_1_step_1.id]
+ cls.onboarding_2_step_2 = cls.env['onboarding.onboarding.step'].create([{
+ 'title': 'Test Onboarding 2 - Step 2',
+ 'onboarding_ids': [cls.onboarding_2.id],
+ 'is_per_company': False,
+ 'panel_step_open_action_name': 'action_fake_open_onboarding_step',
+ }])
# Create progress records as would happen through the controller
- cls.onboarding_1.with_company(cls.company_1)._search_or_create_progress()
+ (cls.onboarding_1 + cls.onboarding_2).with_company(cls.company_1)._search_or_create_progress()
def assert_step_is_done(self, step, also_with_company=None):
self.assertIn(
@@ -72,9 +80,9 @@ class TestOnboardingCommon(TransactionCase):
self.assert_onboarding_is_done(onboarding.with_company(also_with_company))
def assert_onboarding_is_not_done(self, onboarding, also_with_company=None):
- self.assertEqual(
- onboarding.current_progress_id.onboarding_state, 'not_done',
- f'Expected "not_done" current state for onboarding {onboarding.name} '
+ self.assertIn(
+ onboarding.current_progress_id.onboarding_state, {'not_done', False},
+ f'Expected `"not_done"` or `False` current state for onboarding {onboarding.name} '
f'for {onboarding.env.company}')
self.assertEqual(onboarding.current_onboarding_state, 'not_done')
if also_with_company:
diff --git a/addons/onboarding/tests/test_onboarding.py b/addons/onboarding/tests/test_onboarding.py
index f3c08ea76a3..f5dab985eca 100644
--- a/addons/onboarding/tests/test_onboarding.py
+++ b/addons/onboarding/tests/test_onboarding.py
@@ -3,7 +3,9 @@
from psycopg2 import IntegrityError
+from odoo import Command
from odoo.addons.onboarding.tests.common import TestOnboardingCommon
+from odoo.exceptions import ValidationError
from odoo.tools import mute_logger
@@ -48,7 +50,8 @@ class TestOnboarding(TestOnboardingCommon):
# Adding new step resets onboarding state to 'not_done' even if closed
onboarding_1_step_3 = self.env['onboarding.onboarding.step'].create({
'title': 'Test Onboarding 1 - Step 3',
- 'onboarding_id': self.onboarding_1.id,
+ 'onboarding_ids': [self.onboarding_1.id],
+ 'is_per_company': False,
'panel_step_open_action_name': 'action_fake_open_onboarding_step',
})
self.assert_step_is_not_done(onboarding_1_step_3)
@@ -68,7 +71,8 @@ class TestOnboarding(TestOnboardingCommon):
# Adding new step resets onboarding state to 'not_done'
self.env['onboarding.onboarding.step'].create({
'title': 'Test Onboarding 1 - Step 4',
- 'onboarding_id': self.onboarding_1.id,
+ 'onboarding_ids': [self.onboarding_1.id],
+ 'is_per_company': False,
'panel_step_open_action_name': 'action_fake_open_onboarding_step',
})
@@ -85,14 +89,16 @@ class TestOnboarding(TestOnboardingCommon):
# Completing onboarding as company_1
self.assertEqual(self.env.company, self.company_1)
- # Updating onboarding to per-company
- self.onboarding_1.is_per_company = True
+ # Updating onboarding (and steps) to per-company
+ self.onboarding_1_step_1.is_per_company = True
+
# Required after progress reset (simulate role of controller)
self.onboarding_1._search_or_create_progress()
self.onboarding_1_step_1.action_set_just_done()
self.assert_step_is_done(self.onboarding_1_step_1)
+ self.assertFalse(self.onboarding_1_step_2.is_per_company)
self.onboarding_1_step_2.action_set_just_done()
self.assert_onboarding_is_done(self.onboarding_1)
@@ -101,19 +107,14 @@ class TestOnboarding(TestOnboardingCommon):
# First access from company_2
self.onboarding_1._search_or_create_progress()
- # Blank state for company 2
+ # Blank state for company 2 for step 1
self.assert_step_is_not_done(self.onboarding_1_step_1)
+ # But step 2 is done
+ self.assert_step_is_done(self.onboarding_1_step_2)
self.assert_onboarding_is_not_done(self.onboarding_1)
- # But no change for company 1
- self.assert_step_is_done(self.onboarding_1_step_1.with_company(self.company_1))
- self.assert_onboarding_is_done(self.onboarding_1.with_company(self.company_1))
-
self.onboarding_1_step_1.action_set_just_done()
self.assert_step_is_done(self.onboarding_1_step_1)
- self.assert_onboarding_is_not_done(self.onboarding_1)
- self.onboarding_1_step_2.with_company(self.company_2).action_set_just_done()
- self.assert_step_is_done(self.onboarding_1_step_2)
self.assert_onboarding_is_done(self.onboarding_1)
# is_onboarding_closed status is also company-independent
@@ -122,21 +123,32 @@ class TestOnboarding(TestOnboardingCommon):
self.assertFalse(self.onboarding_1.with_company(self.company_1).current_progress_id.is_onboarding_closed)
def test_onboarding_to_company_change(self):
- """Checks that changing onboarding to per-company resets completions states.
- """
+ """ Checks that changing an onboarding step to per-company resets
+ completion states."""
# Completing onboarding as company_1
self.assertEqual(self.env.company, self.company_1)
self.onboarding_1_step_1.action_set_just_done()
self.onboarding_1_step_2.action_set_just_done()
self.assert_onboarding_is_done(self.onboarding_1)
- # Updating onboarding to per-company
- self.onboarding_1.is_per_company = True
+ # Updating onboarding step 1 to per-company
+ self.onboarding_1_step_1.is_per_company = True
+ self.assertTrue(self.onboarding_1.is_per_company)
# Required after progress reset (simulate role of controller)
self.onboarding_1._search_or_create_progress()
self.assert_onboarding_is_not_done(self.onboarding_1)
+ def test_onboarding_shared_steps(self):
+ self.onboarding_2_step_2.action_set_just_done()
+ self.assert_step_is_done(self.onboarding_2_step_2)
+ # Completing common step is also required to be "done"
+ self.assert_onboarding_is_not_done(self.onboarding_2)
+
+ self.onboarding_1_step_1.action_set_just_done()
+ self.assert_onboarding_is_not_done(self.onboarding_1)
+ self.assert_onboarding_is_done(self.onboarding_2)
+
@mute_logger('odoo.sql_db')
def test_progress_no_company_uniqueness(self):
"""Check that there cannot be two progress records created for
@@ -159,8 +171,8 @@ class TestOnboarding(TestOnboardingCommon):
See also ``test_progress_no_company_uniqueness``
"""
# Updating onboarding to per-company
- self.onboarding_1.is_per_company = True
- # Required after progress reset (simulate role of controller)
+ self.onboarding_1_step_1.is_per_company = True
+ # Create an onboarding_progress (simulate role of controller)
self.onboarding_1._search_or_create_progress()
with self.assertRaises(IntegrityError):
@@ -168,3 +180,38 @@ class TestOnboarding(TestOnboardingCommon):
'onboarding_id': self.onboarding_1.id,
'company_id': self.env.company.id
})
+
+ def test_onboarding_step_without_onboarding(self):
+ self.step_initially_w_o_onboarding = self.env['onboarding.onboarding.step'].create({
+ 'title': 'Step Initially Without Onboarding',
+ })
+ self.assertEqual(self.step_initially_w_o_onboarding.current_step_state, 'not_done')
+ self.step_initially_w_o_onboarding.action_set_just_done()
+
+ self.assert_step_is_done(self.step_initially_w_o_onboarding)
+
+ self.onboarding_3 = self.env['onboarding.onboarding'].create({
+ 'name': 'Test Onboarding 3',
+ 'route_name': 'onboarding3',
+ })
+ self.onboarding_3._search_or_create_progress()
+
+ with self.assertRaises(ValidationError):
+ self.step_initially_w_o_onboarding.onboarding_ids = [Command.link(self.onboarding_3.id)]
+
+ self.step_initially_w_o_onboarding.write({
+ 'panel_step_open_action_name': 'action_fake_open_onboarding_step'
+ })
+ self.step_initially_w_o_onboarding.onboarding_ids = [Command.link(self.onboarding_3.id)]
+
+ with self.subTest('Progress records are recreated for companies with completed steps'):
+ # Onboarding is done as only step was already done by company 1
+ self.assert_onboarding_is_done(self.onboarding_3)
+
+ # Not by company 2
+ self.onboarding_3.with_company(self.company_2)._search_or_create_progress()
+ self.assert_onboarding_is_not_done(self.onboarding_3.with_company(self.company_2))
+
+ # But it can
+ self.step_initially_w_o_onboarding.with_company(self.company_2).action_set_just_done()
+ self.assert_onboarding_is_done(self.onboarding_3.with_company(self.company_2))
diff --git a/addons/onboarding/views/onboarding_views.xml b/addons/onboarding/views/onboarding_views.xml
index 146546769d3..e0da601fbb5 100644
--- a/addons/onboarding/views/onboarding_views.xml
+++ b/addons/onboarding/views/onboarding_views.xml
@@ -36,7 +36,9 @@
-
+
+
+
@@ -51,8 +53,9 @@
-
+
+
@@ -65,10 +68,16 @@
-
+
+
+
+
+
+
+