[FIX] web: reload on usable view when switch company does AccessError
Be logged in multiple companies (A and B). Be on a form view of some record which is visible only on company B via ir.rules. With the company switcher, unlog from company B. Before this commit, the user received an AccessError and arrived on a blank webclient. To say the least, it was rather inelegant. After this commit, the user ends up on the multi-record view of that model, provided that it is available for use. Task-3029616 closes odoo/odoo#141796 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -51,9 +51,39 @@ function getCompanyIdsFromBrowser(hash) {
|
||||
return cids || [];
|
||||
}
|
||||
|
||||
const errorHandlerRegistry = registry.category("error_handlers");
|
||||
function accessErrorHandler(env, error, originalError) {
|
||||
const router = env.services.router;
|
||||
const hash = router.current.hash;
|
||||
if (!hash._company_switching) {
|
||||
return false;
|
||||
}
|
||||
if (originalError?.exceptionName === "odoo.exceptions.AccessError") {
|
||||
const { model, id, view_type } = hash;
|
||||
if (!model || !id || view_type !== "form") {
|
||||
return false;
|
||||
}
|
||||
router.pushState({ view_type: undefined });
|
||||
|
||||
browser.setTimeout(() => {
|
||||
// Force the WebClient to reload the state contained in the hash.
|
||||
env.bus.trigger("ROUTE_CHANGE");
|
||||
});
|
||||
if (error.event) {
|
||||
error.event.preventDefault();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const companyService = {
|
||||
dependencies: ["user", "router", "action"],
|
||||
start(env, { user, router, action }) {
|
||||
// Push an error handler in the registry. It needs to be before "rpcErrorHandler", which
|
||||
// has a sequence of 97. The default sequence of registry is 50.
|
||||
errorHandlerRegistry.add("accessErrorHandlerCompanies", accessErrorHandler);
|
||||
|
||||
const allowedCompanies = session.user_companies.allowed_companies;
|
||||
const disallowedAncestorCompanies = session.user_companies.disallowed_ancestor_companies;
|
||||
const allowedCompaniesWithAncestors = {
|
||||
@@ -123,6 +153,7 @@ export const companyService = {
|
||||
|
||||
const cidsHash = formatCompanyIds(newCompanyIds, CIDS_HASH_SEPARATOR);
|
||||
router.pushState({ cids: cidsHash }, { lock: true });
|
||||
router.pushState({ _company_switching: true });
|
||||
cookie.set("cids", formatCompanyIds(newCompanyIds));
|
||||
browser.setTimeout(() => browser.location.reload()); // history.pushState is a little async
|
||||
},
|
||||
|
||||
@@ -86,6 +86,7 @@ QUnit.test("extract allowed company ids from url hash", async (assert) => {
|
||||
assert.strictEqual(env.services.company.currentCompany.id, 3);
|
||||
|
||||
// backward compatibility
|
||||
registry.category("error_handlers").remove("accessErrorHandlerCompanies");
|
||||
Object.assign(browser.location, { hash: "cids=3%2C1" });
|
||||
env = await makeTestEnv();
|
||||
assert.deepEqual(env.services.company.activeCompanyIds, [3, 1]);
|
||||
|
||||
@@ -119,7 +119,7 @@ QUnit.module("MobileSwitchCompanyMenu", (hooks) => {
|
||||
assert.containsN(scMenuEl, "[data-company-id] .fa-check-square", 2);
|
||||
assert.containsN(scMenuEl, "[data-company-id] .fa-square-o", 1);
|
||||
await prom;
|
||||
assert.verifySteps(["cids=1-2"]);
|
||||
assert.verifySteps(["cids=1-2&_company_switching=1"]);
|
||||
});
|
||||
|
||||
QUnit.test("can toggle multiple companies at once", async (assert) => {
|
||||
@@ -157,7 +157,7 @@ QUnit.module("MobileSwitchCompanyMenu", (hooks) => {
|
||||
|
||||
assert.verifySteps([]);
|
||||
await prom; // await toggle promise
|
||||
assert.verifySteps(["cids=2-3"]);
|
||||
assert.verifySteps(["cids=2-3&_company_switching=1"]);
|
||||
});
|
||||
|
||||
QUnit.test("single company selected: toggling it off will keep it", async (assert) => {
|
||||
@@ -189,7 +189,10 @@ QUnit.module("MobileSwitchCompanyMenu", (hooks) => {
|
||||
* [ ] Company 3
|
||||
*/
|
||||
await click(scMenuEl.querySelectorAll(".toggle_company")[0]);
|
||||
assert.deepEqual(scMenu.env.services.router.current.hash, { cids: 1 });
|
||||
assert.deepEqual(scMenu.env.services.router.current.hash, {
|
||||
cids: 1,
|
||||
_company_switching: 1,
|
||||
});
|
||||
assert.deepEqual(scMenu.env.services.company.activeCompanyIds, [1]);
|
||||
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 1);
|
||||
assert.containsN(scMenuEl, "[data-company-id] .fa-check-squarqe", 0);
|
||||
@@ -222,7 +225,7 @@ QUnit.module("MobileSwitchCompanyMenu", (hooks) => {
|
||||
* [ ] Company 3
|
||||
*/
|
||||
await click(scMenuEl.querySelectorAll(".log_into")[1]);
|
||||
assert.verifySteps(["cids=2"]);
|
||||
assert.verifySteps(["cids=2&_company_switching=1"]);
|
||||
});
|
||||
|
||||
QUnit.test("multi company mode: log into a non selected company", async (assert) => {
|
||||
@@ -252,7 +255,7 @@ QUnit.module("MobileSwitchCompanyMenu", (hooks) => {
|
||||
* [x] **Company 3**
|
||||
*/
|
||||
await click(scMenuEl.querySelectorAll(".log_into")[1]);
|
||||
assert.verifySteps(["cids=2"]);
|
||||
assert.verifySteps(["cids=2&_company_switching=1"]);
|
||||
});
|
||||
|
||||
QUnit.test("multi company mode: log into an already selected company", async (assert) => {
|
||||
@@ -282,7 +285,7 @@ QUnit.module("MobileSwitchCompanyMenu", (hooks) => {
|
||||
* [x] Company 3 -> log into
|
||||
*/
|
||||
await click(scMenuEl.querySelectorAll(".log_into")[2]);
|
||||
assert.verifySteps(["cids=3"]);
|
||||
assert.verifySteps(["cids=3&_company_switching=1"]);
|
||||
});
|
||||
|
||||
QUnit.test("companies can be logged in even if some toggled within delay", async (assert) => {
|
||||
@@ -313,6 +316,6 @@ QUnit.module("MobileSwitchCompanyMenu", (hooks) => {
|
||||
await click(scMenuEl.querySelectorAll(".toggle_company")[2]);
|
||||
await click(scMenuEl.querySelectorAll(".toggle_company")[0]);
|
||||
await click(scMenuEl.querySelectorAll(".log_into")[1]);
|
||||
assert.verifySteps(["cids=2"]);
|
||||
assert.verifySteps(["cids=2&_company_switching=1"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,7 +138,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
|
||||
["true", "false", "false", "false", "false"]
|
||||
);
|
||||
await prom;
|
||||
assert.verifySteps(["cids=3-2"]);
|
||||
assert.verifySteps(["cids=3-2&_company_switching=1"]);
|
||||
});
|
||||
|
||||
QUnit.test("can toggle multiple companies at once", async (assert) => {
|
||||
@@ -181,7 +181,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
|
||||
|
||||
assert.verifySteps([]);
|
||||
await prom; // await toggle promise
|
||||
assert.verifySteps(["cids=2-1-4-5"]);
|
||||
assert.verifySteps(["cids=2-1-4-5&_company_switching=1"]);
|
||||
});
|
||||
|
||||
QUnit.test("single company selected: toggling it off will keep it", async (assert) => {
|
||||
@@ -217,7 +217,10 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
|
||||
* [ ] Hulk
|
||||
*/
|
||||
await click(target.querySelectorAll(".toggle_company")[0]);
|
||||
assert.deepEqual(scMenu.env.services.router.current.hash, { cids: 3 });
|
||||
assert.deepEqual(scMenu.env.services.router.current.hash, {
|
||||
cids: 3,
|
||||
_company_switching: 1,
|
||||
});
|
||||
assert.deepEqual(scMenu.env.services.company.activeCompanyIds, [3]);
|
||||
assert.strictEqual(scMenu.env.services.company.currentCompany.id, 3);
|
||||
assert.containsOnce(target, ".dropdown-menu", "dropdown is still opened");
|
||||
@@ -256,7 +259,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
|
||||
*/
|
||||
await click(target.querySelectorAll(".log_into")[1]);
|
||||
assert.containsNone(target, ".dropdown-menu", "dropdown is directly closed");
|
||||
assert.verifySteps(["cids=2"]);
|
||||
assert.verifySteps(["cids=2&_company_switching=1"]);
|
||||
});
|
||||
|
||||
QUnit.test("multi company mode: log into a non selected company", async (assert) => {
|
||||
@@ -291,7 +294,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
|
||||
*/
|
||||
await click(target.querySelectorAll(".log_into")[1]);
|
||||
assert.containsNone(target, ".dropdown-menu", "dropdown is directly closed");
|
||||
assert.verifySteps(["cids=2"]);
|
||||
assert.verifySteps(["cids=2&_company_switching=1"]);
|
||||
});
|
||||
|
||||
QUnit.test("multi company mode: log into an already selected company", async (assert) => {
|
||||
@@ -326,7 +329,7 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
|
||||
*/
|
||||
await click(target.querySelectorAll(".log_into")[2]);
|
||||
assert.containsNone(target, ".dropdown-menu", "dropdown is directly closed");
|
||||
assert.verifySteps(["cids=1-4-5"]);
|
||||
assert.verifySteps(["cids=1-4-5&_company_switching=1"]);
|
||||
});
|
||||
|
||||
QUnit.test("companies can be logged in even if some toggled within delay", async (assert) => {
|
||||
@@ -362,6 +365,6 @@ QUnit.module("SwitchCompanyMenu", (hooks) => {
|
||||
await click(target.querySelectorAll(".toggle_company")[0]);
|
||||
await click(target.querySelectorAll(".log_into")[1]);
|
||||
assert.containsNone(target, ".dropdown-menu", "dropdown is directly closed");
|
||||
assert.verifySteps(["cids=2"]);
|
||||
assert.verifySteps(["cids=2&_company_switching=1"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from .models import model_multicompany
|
||||
|
||||
#
|
||||
# Conditional installation of enterprise modules.
|
||||
|
||||
@@ -11,10 +11,11 @@ It will install some main apps and will try to execute the most important action
|
||||
'depends': ['web_tour', 'crm', 'sale_timesheet', 'purchase_stock', 'mrp', 'account'],
|
||||
'installable': True,
|
||||
'post_init_hook': '_auto_install_enterprise_dependencies',
|
||||
'data': ['models/ir.model.access.csv'],
|
||||
'assets': {
|
||||
'web.assets_tests': [
|
||||
# inside .
|
||||
'test_main_flows/static/tests/tours/main_flow.js',
|
||||
'test_main_flows/static/tests/tours/*.js',
|
||||
],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
access_model_multicompany,access_model_multicompany,model_test_model_multicompany,base.group_user,1,0,0,0
|
||||
|
@@ -0,0 +1,8 @@
|
||||
from odoo import models, fields
|
||||
|
||||
class ModelMultiCompany(models.Model):
|
||||
_name = "test.model_multicompany"
|
||||
_description = "test multicompany model"
|
||||
|
||||
name = fields.Char()
|
||||
company_id = fields.Many2one("res.company")
|
||||
@@ -0,0 +1,71 @@
|
||||
/** @odoo-module **/
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const _console = window.console;
|
||||
function assertEqual(actual, expected, msg = "") {
|
||||
if (actual !== expected) {
|
||||
const description = msg ? ` ${msg}` : "";
|
||||
_console.error(`Assert failed: expected: ${expected} ; got: ${actual}.${description}`);
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("web_tour.tours").add("test_company_switch_access_error", {
|
||||
test: true,
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_list_view",
|
||||
run() {
|
||||
assertEqual(
|
||||
JSON.stringify(
|
||||
Array.from(this.$anchor[0].querySelectorAll(".o_data_cell")).map(
|
||||
(n) => n.innerText
|
||||
)
|
||||
),
|
||||
JSON.stringify(["p1", "p2"])
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_list_view .o_data_cell:contains(p2)",
|
||||
},
|
||||
{
|
||||
trigger: ".o_form_view .o_last_breadcrumb_item:contains(p2)",
|
||||
isCheck: true,
|
||||
},
|
||||
{
|
||||
trigger: ".o_switch_company_menu button",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".o_switch_company_menu .dropdown-item:contains(second company) .toggle_company",
|
||||
},
|
||||
{
|
||||
trigger: ".o_view_controller.o_list_view",
|
||||
},
|
||||
{
|
||||
trigger: ".o_view_controller.o_list_view",
|
||||
async run() {
|
||||
assertEqual(
|
||||
JSON.stringify(
|
||||
Array.from(this.$anchor[0].querySelectorAll(".o_data_cell")).map(
|
||||
(n) => n.innerText
|
||||
)
|
||||
),
|
||||
JSON.stringify(["p1"])
|
||||
);
|
||||
assertEqual(
|
||||
document.querySelector("header.o_navbar .o_menu_brand").innerText,
|
||||
"model_multicompany_menu"
|
||||
);
|
||||
const url = new URL(window.location);
|
||||
const hash = new URLSearchParams(url.hash.slice(1));
|
||||
assertEqual(hash.get("model"), "test.model_multicompany");
|
||||
assertEqual(hash.has("action"), true);
|
||||
assertEqual(hash.has("menu_id"), true);
|
||||
assertEqual(hash.get("view_type"), "list");
|
||||
assertEqual(hash.has("_company_switching"), false);
|
||||
},
|
||||
isCheck: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1,4 +1,6 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import Command
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
import odoo.tests
|
||||
|
||||
@@ -72,6 +74,38 @@ class TestUi(BaseTestUi):
|
||||
def test_01_main_flow_tour(self):
|
||||
self.main_flow_tour()
|
||||
|
||||
def test_company_switch_access_error(self):
|
||||
company1 = self.env.company
|
||||
company2 = self.env["res.company"].create({"name":"second company"})
|
||||
self.env["res.users"].browse(2).write({
|
||||
"company_ids": [Command.clear(), Command.link(company1.id), Command.link(company2.id)]
|
||||
})
|
||||
|
||||
self.env["ir.rule"].create({
|
||||
"name": "multiCompany rule",
|
||||
"domain_force": '["|", ("company_id", "=", False), ("company_id", "in", company_ids)]',
|
||||
"model_id": self.env["ir.model"]._get("test.model_multicompany").id
|
||||
})
|
||||
|
||||
self.env["test.model_multicompany"].create({"name": "p1"})
|
||||
self.env["test.model_multicompany"].create({"name": "p2", "company_id": company2.id})
|
||||
|
||||
act_window = self.env["ir.actions.act_window"].create({
|
||||
"name": "model_multicompany_action",
|
||||
"res_model": "test.model_multicompany",
|
||||
"view_ids": [Command.create({"view_mode": "tree"}), Command.create({"view_mode": "form"})]
|
||||
})
|
||||
|
||||
self.env["ir.ui.menu"].create({
|
||||
"name": "model_multicompany_menu",
|
||||
"action": f"ir.actions.act_window,{act_window.id}",
|
||||
})
|
||||
|
||||
current_companies = "%s-%s" % (company1.id, company2.id)
|
||||
with mute_logger("odoo.http"):
|
||||
self.start_tour(f"/web#action={act_window.id}&cids={current_companies}", "test_company_switch_access_error", login="admin")
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class TestUiMobile(BaseTestUi):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user